Skip to content

Refactor store: RealmStore; proxy mixins; move more methods to UserStore etc #1736

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 34 commits into from
Jul 23, 2025

Conversation

gnprice
Copy link
Member

@gnprice gnprice commented Jul 23, 2025

This PR makes a series of refactors to how PerAccountStore and its substores are organized, which I hope will help keep things clean to understand as the app's model has continued to gain complexity to handle the features we've been adding.

Specifically:

  • There's a new RealmStore substore, for realm settings.
  • The proxy boilerplate which forwards method calls from PerAccountStore to the various substores has been pushed out of the central PerAccountStore definition, to each substore's respective file.
  • A new class HasRealmStore makes it easy for other substores to use data from RealmStore, much like PerAccountStoreBase does for CorePerAccountStore. Similarly a new HasUserStore for data from UserStore.
  • The definitions for methods like hasPassedWaitingPeriod, userDisplayEmail, and hasPostingPermission have been moved out of PerAccountStore to individual substores. The reason they'd been on PerAccountStore was because they need data from realm settings; now that that's available from substores, they can move to appropriate substores.
  • The logic that was in TopicName.processLikeServer has been moved to RealmStore. It needs data from realm settings (and from CorePerAccountStore) in order to do its work; we'd successfully handled that by having its caller look up that data in advance, but that solution required some contortions to the logic. Now its implementation can look up the particular details it needs directly.
  • There's now a crisp rule about what methods can live on substore interfaces like FooStore: their implementations on the substore FooStoreImpl and on the overall PerAccountStore must have identical behavior. (Where that's not the case, the method should instead live separately on FooStoreImpl and PerAccountStore, without claiming to implement a shared interface.) This rule had been very nearly true, with just two exceptions — setServerEmojiData and reconcileMessages — which risked being awfully confusing.

Selected commit messages

(Most of the most important commits are covered above, so omitted here.)

b30df8b realm [nfc]: Add Duration getters for durations

86ce025 dartdoc: Stop section-divider comments from getting absorbed into

docs

Before this change, if you hover in your IDE over an identifier whose
declaration happens to be the first one after one of these section
dividers, the doc shown would begin with a long string of slashes.
After all, the divider line did start with "///".

The choice of "|" as the substitute was fairly arbitrary. I tried a
space first, and felt it interrupted the divider too much and made it
look like just another line of comments.

Done with a bit of Perl and Git:

$ perl -i -0pe 's,^\s*//\K(?=//),|,gm' $(git grep -l //// '*.dart')

2b045d2 realm [nfc]: Organize realm settings, from current API doc

To identify which fields are realm settings in the API, so that #668
applies to them, I looked at the current API docs:
https://zulip.com/api/get-events#realm-update_dict

It turns out a couple of items that had been realm settings are now
something else; one that was a server setting (and even lacked "realm"
in the name: maxFileUploadSizeMib) is now a realm setting updated by
events. So the comments on the implementation fields need updating.
I'll do that in a separate commit, though, so that this one is
more purely moving code around.

db8809b emoji [nfc]: Drop setServerEmojiData from main interface

Instead, leave it as a method on EmojiStoreImpl and on the overall
PerAccountStore.

Each of these FooStore types, such as EmojiStore, is implemented by
both a FooStoreImpl class and the overall PerAccountStore. Nearly all
of these types' methods behave exactly the same on both those types;
the one implementation just forwards to the other.

But this method behaves slightly differently between them: the
PerAccountStore implementation will call notifyListeners, while the
EmojiStoreImpl implementation doesn't (because it can't, not being the
ChangeNotifier itself).

That mismatch, when nearly all the other similar methods have an
exact match, risks confusion. Mitigate that by removing the method
from the EmojiStore interface, so that the two implementations become
unrelated methods.

(The one other such mismatch is reconcileMessages. We'll deal
with that one too, in a later commit.)

f83c9c1 message test [nfc]: Move sendMessage smoke test to follow sendMessage

Ideally this would have been done when sendMessage itself was moved,
in 942aa87 (#1455), oops.

9499dbe message [nfc]: Consolidate "disposed" checks onto substore methods

For most of these methods, we check the store isn't disposed
within the substore's implementation; for a few, we check in
the proxy implementation. Simplify the proxies by always checking
in the substore's implementation.

gnprice added 30 commits July 22, 2025 18:21
…pass down params

This is a bit simpler in itself, and also closer to what we'll want
with the help of a substore.
This is the default when someone actually creates a custom profile
field, so the more realistic default for test data.
The other substores could always handle this themselves individually,
like Unreads does today with ChannelStore.  But this is one substore
that lots of others are going to want references to, so we might as
well make that a bit easier.
This will help us move the underlying logic to live on a substore,
which in turn will let us save these advance lookups and the annoying
zulipFeatureLevel conditional.
This way, the method's implementation can look up zulipFeatureLevel
and realmEmptyTopicDisplayName for itself, rather than have them
looked up in advance by the caller and passed in.

Then as a further consequence, the method only looks up
realmEmptyTopicDisplayName if and when it determines it actually needs
that information.  That makes the lookup compatible with the check we
have in the main realmEmptyTopicDisplayName getter to assert that it's
only consulted when it should in fact be needed, on old servers.
A hack in the message store, and a similar hack on the message list,
therefore become unnecessary.
Before this change, if you hover in your IDE over an identifier whose
declaration happens to be the first one after one of these section
dividers, the doc shown would begin with a long string of slashes.
After all, the divider line did start with "///".

The choice of "|" as the substitute was fairly arbitrary.  I tried a
space first, and felt it interrupted the divider too much and made it
look like just another line of comments.

Done with a bit of Perl and Git:

  $ perl -i -0pe 's,^\s*//\K(?=//),|,gm' $(git grep -l //// '*.dart')
To identify which fields are realm settings in the API, so that zulip#668
applies to them, I looked at the current API docs:
  https://zulip.com/api/get-events#realm-update_dict

It turns out a couple of items that had been realm settings are now
something else; one that was a server setting (and even lacked "realm"
in the name: maxFileUploadSizeMib) is now a realm setting updated by
events.  So the comments on the implementation fields need updating.
I'll do that in a separate commit, though, so that this one is
more purely moving code around.
The accurate version of these is now covered by a todo-comment up on
RealmStore.
Instead, leave it as a method on EmojiStoreImpl and on the overall
PerAccountStore.

Each of these FooStore types, such as EmojiStore, is implemented by
both a FooStoreImpl class and the overall PerAccountStore.  Nearly all
of these types' methods behave exactly the same on both those types;
the one implementation just forwards to the other.

But this method behaves slightly differently between them: the
PerAccountStore implementation will call notifyListeners, while the
EmojiStoreImpl implementation doesn't (because it can't, not being the
ChangeNotifier itself).

That mismatch, when nearly all the other similar methods have an
exact match, risks confusion.  Mitigate that by removing the method
from the EmojiStore interface, so that the two implementations become
unrelated methods.

(The one other such mismatch is `reconcileMessages`.  We'll deal
with that one too, in a later commit.)
This doesn't move any tests, because this method has no tests.

(Which at this point isn't a problem: for server versions we
actually support, this method's logic becomes trivial.)
Like HasRealmStore for data about the realm, this will help make it
convenient for other substores to refer to data about users.
This way, this substore becomes a valid home for methods that need
to refer to data about both channels and users.
gnprice added 4 commits July 22, 2025 19:45
Ideally this would have been done when sendMessage itself was moved,
in 942aa87 (zulip#1455), oops.
For most of these methods, we check the store isn't disposed
within the substore's implementation; for a few, we check in
the proxy implementation.  Simplify the proxies by always checking
in the substore's implementation.
Much like we did with setServerEmojiData in a recent commit,
and for the same reasons: after setServerEmojiData, this was
the only one of our numerous methods on the various FooStore
interfaces where the two different implementations of the method
actually had different behavior.  That's potentially misleading,
so make the two implementations be unrelated methods instead.
@gnprice gnprice requested a review from chrisbobbe July 23, 2025 03:02
@chrisbobbe
Copy link
Collaborator

Very helpful, thanks much!! LGTM; merging.

@chrisbobbe chrisbobbe merged commit 2b0944f into zulip:main Jul 23, 2025
1 check passed
@gnprice gnprice deleted the pr-substore branch July 23, 2025 18:56
@sm-sayedi
Copy link
Collaborator

CI is failing, and interestingly, the same problem occurred to me now when I fetched from upstream and then ran flutter doctor -v or flutter --version!

Downloading Darwin arm64 Dart SDK from Flutter engine 2d053c11840d49924aefe84dabb94545a3f9282e...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   258  100   258    0     0    451      0 --:--:-- --:--:-- --:--:--   451
[/Users/sm-sayedi/Library/Flutter/main/bin/cache/dart-sdk-darwin-arm64.zip]
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of /Users/sm-sayedi/Library/Flutter/main/bin/cache/dart-sdk-darwin-arm64.zip or
        /Users/sm-sayedi/Library/Flutter/main/bin/cache/dart-sdk-darwin-arm64.zip.zip, and cannot find /Users/sm-sayedi/Library/Flutter/main/bin/cache/dart-sdk-darwin-arm64.zip.ZIP, period.

It appears that the downloaded file is corrupt; please try again.
If this problem persists, please report the problem at:
  https://github.com/flutter/flutter/issues/new?template=01_activation.yml

@gnprice
Copy link
Member Author

gnprice commented Jul 23, 2025

Thanks — seems like definitely an upstream issue, then. Let's discuss in #mobile-team.

@sm-sayedi
Copy link
Collaborator

The problem seems to be solved. Here's the discussion link: #mobile-team > Flutter upgrade - downloding Dark SDK fails.

@gnprice gnprice mentioned this pull request Jul 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants