-
Notifications
You must be signed in to change notification settings - Fork 309
Add personal user_id & full_name data, updating latter via events & in UI #25
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
Conversation
This can't be updated, so there's no need to add a matching event.
Neat, thanks! Taking a look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally this looks great. Various small things below. I'm particularly curious about that ListenableBuilder
.
lib/api/model/initial_snapshot.dart
Outdated
final int user_id; | ||
final String full_name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: in this API type, let's keep the properties in the same order as they appear in the docs, even when that's not the most logical order in itself.
That's helpful for comparing the type to the docs, as we fill in more and as we update it for changes in the API.
For these properties, it looks like that means they go at the end of the list.
lib/model/store.dart
Outdated
// The user's personal data | ||
final int user_id; | ||
String full_name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
// The user's personal data | |
final int user_id; | |
String full_name; | |
final int user_id; | |
String full_name; |
We can let the names speak for themselves :-)
When there's more here, we might add heading comments like these, but at that point it'll be easier to see what structure of headings we might want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do broadly agree, though these names are used in more complex structures, which is why I originally included the title.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see — to distinguish them from the user_id or full_name of other users.
Sure, reasonable to make that explicit. Perhaps like this:
// This user_id, etc., refer to the self user.
lib/api/model/events.dart
Outdated
/// A Zulip event of type `realm_user`. | ||
@JsonSerializable() | ||
class RealmUserEvent extends Event { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this type describes a realm_user
event with 'op': 'update'
:
https://zulip.com/api/get-events#realm_user-update
but not the other kinds:
https://zulip.com/api/get-events#realm_user-add
https://zulip.com/api/get-events#realm_user-remove
This will be the first event we handle that has the op
pattern. I think there is probably not a real benefit to having an intermediate base class for the three realm_user
events, so let's keep it simple:
- call this
RealmUserUpdateEvent
; update dartdoc to say it's for opupdate
- add an
op
getter, similar totype
, so thattoJson
works correctly - in
Event.fromJson
, write:
case 'realm_user':
switch (json['op'] as String) {
case 'update': return RealmUserUpdateEvent.fromJson(json);
// TODO add realm_user/add and realm_user/remove events
default: return UnexpectedEvent.fromJson(json);
}
Later we'll want to handle the add
and remove
variants too, but no need for that in this PR.
lib/api/model/model.dart
Outdated
|
||
final String? delivery_email; // TODO(server-7j | ||
|
||
// custom_profile_field has sub-parts to affect which id/value/rendered_value is updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// custom_profile_field has sub-parts to affect which id/value/rendered_value is updated | |
// final CustomProfileFieldValueUpdate? custom_profile_field; // TODO handle |
(following the format of others in this file like edit_history
, and making it a TODO comment so it's distinct from the "ignoring this because it's deprecated" comments)
lib/api/model/model.dart
Outdated
|
||
final int? role; | ||
|
||
final bool? is_billing_admin; // Can also be null? // TODO(server-5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it? The docs say this is never null if present:
is_billing_admin: boolean
I think this can also drop the TODO-server comment, because I think there isn't anything we'd actually change here when we make that update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my null comment was related to this being not as tidy as I'd have preferred, and representing the null vs not-present issue.
Re the TODO-server comment, my original understanding was that these were simply notes related to when a field was introduced, but this makes more sense 👍
lib/api/model/model.dart
Outdated
final String? avatar_url; | ||
final String? avatar_url_medium; | ||
final String? avatar_source; | ||
final String? avatar_version; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final String? avatar_url; | |
final String? avatar_url_medium; | |
final String? avatar_source; | |
final String? avatar_version; | |
// TODO express that all four avatar-related properties will be present if any of them is | |
final String? avatar_url; | |
final String? avatar_url_medium; | |
final String? avatar_source; | |
final String? avatar_version; |
lib/api/model/model.dart
Outdated
@JsonSerializable() | ||
class RealmUserEventPerson { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I think this feels to me pretty specific to the event, so let's put it in the events.dart file. Can go just below the event type itself, to match the "summary first, details after" pattern that file has so far.
lib/model/store.dart
Outdated
notifyListeners(); | ||
} | ||
} | ||
// TODO Update our other data, and that of other users |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// TODO Update our other data, and that of other users |
No need for a TODO in this spot at the end of the PR; once the event is being fully handled as to our existing data structures, there's nothing actionable to do here.
We'll have more to add here for sure, but that's a task to be done in tandem with adding the further data structures that need to be updated, so it can be driven as a required step in those changes.
(And that's covered by the admonition in this TODO comment above:
// TODO lots more data. When adding, be sure to update handleEvent too.
)
lib/model/store.dart
Outdated
String? new_full_name = event.person.full_name; | ||
if (new_full_name != null) { | ||
full_name = new_full_name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI there's an alternate way you can also write this:
String? new_full_name = event.person.full_name; | |
if (new_full_name != null) { | |
full_name = new_full_name; | |
if (event.person.full_name != null) { | |
full_name = event.person.full_name!; |
Both are idiomatic Dart, and have small pros and cons. I think both ways are fine here — just figured I'd mention it since I believe you're new to Dart (like all of us) and might not have seen it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did consider both approaches. I decided on the former based on not repeating the dotted event access (ie. DRY). I'm surprised the !
would be necessary given the null check in each case, but that seems a Dart design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. Yeah, the reason the !
is needed is that in Dart, every access of the form thing.property
is in principle an access through a getter method (and similarly thing.property = …
is through a setter method) — if the getter method happens to be implemented in a trivial way by a field on the object, that's an implementation detail of the object.
(The compiler will certainly look through that abstraction to know the difference when it's generating code, in order to make it efficient, but the language semantics maintain the abstraction.)
So from that perspective, there's no guarantee that just because event.person.full_name
returned something non-null once, it'll return something non-null the next time.
lib/widgets/app.dart
Outdated
ListenableBuilder( | ||
builder: (BuildContext context, Widget? child) { | ||
return Text.rich(TextSpan( | ||
text: 'You are: ', | ||
children: [bold(store.full_name)]));}, | ||
listenable: store, | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Neat!
Is this ListenableBuilder
necessary, though? This HomePage.build
method already calls PerAccountStoreWidget.of
, which in turn calls context.dependOnInheritedWidgetOfExactType<PerAccountStoreWidget>()
. That "dependOn…" method should cause HomePage
itself to get rebuilt after the store notifies its listeners. So if that wasn't working in your testing, we should investigate (perhaps in chat.)
Formatting nit: generally the "child", "children", "builder", or similar argument that provides the nested bit of UI goes last. So:
ListenableBuilder( | |
builder: (BuildContext context, Widget? child) { | |
return Text.rich(TextSpan( | |
text: 'You are: ', | |
children: [bold(store.full_name)]));}, | |
listenable: store, | |
), | |
ListenableBuilder( | |
listenable: store, | |
builder: (BuildContext context, Widget? child) { | |
return Text.rich(TextSpan( | |
text: 'You are: ', | |
children: [bold(store.full_name)])); | |
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I explored various ways of notifying the UI (eg. notifying on individual String
s), before noticing that the whole account was a notifier via inheritance, and that I needed to explicitly notifyListeners()
- which possibly is not necessary with the simple values eg. String
.
(I'd not reached the point of exploring trimming out the builder itself beyond that; it was more that things were working so wanted to push before spending too much extra time on it, in case going in completely the wrong direction)
I hadn't dug into that .of
call. That Widget is intended as a way to easily associate an account with another widget class, and auto-build on data-change?
Re the builder being necessary, apparently it works without it 👍
I'm not sure if they can be optimization to include it inline, if the base class widget always triggers a rebuild?
I can understand the nit of preferring the smaller listenable:
first. I was going from the flutter examples of this, which have them last.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re the builder being necessary, apparently it works without it +1
Great. :-) If it hadn't, that would have pointed to something we should definitely investigate to revise my understanding of how this works — and though I was pretty sure it should work from reading the docs, we hadn't really exercised it and so I wasn't sure if it would turn out I'd been missing something.
I explored various ways of notifying the UI (eg. notifying on individual
String
s), before noticing that the whole account was a notifier via inheritance, and that I needed to explicitlynotifyListeners()
-
Yeah, that's what I hoped had happened.
I'll start a chat thread for replying to the other questions. -> https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/Flutter.20live-updating.20UI/near/1524089
Initially only log the event. Event check added.
e780ad0
to
d23b5ed
Compare
@gnprice It's been a while since I've had a PR reviewed - and not in Python - so I tried using fixup-style commits except for conflicts or to update the main commit text. All points should be addressed. |
Thanks for the revision! For our review process, it's easiest to re-review if you go ahead and squash the fixups in, and otherwise revise the branch to the new version of the sequence of commits you propose be merged. (On our end, the key tool for this is |
@neiljp Do you still have plans to return to this PR? It will need some rebasing, because there have been changes in main. I just tried a quick The approach I'd recommend is:
A couple of the changes in main that will be relevant:
|
@gnprice I plan to return to this, but GSoC and ZT have been busy (I really wanted to start working more with flutter, but no-one else is supporting ZT). |
I managed to get the dev environment set up today, and this seemed to be a good starting point after digging around to wrap my head around how some of it works.
The commits as they stand should be fairly clean, but any feedback on Dart/Flutter style is appreciated.
In particular, there may well be code layout issues, since I'm not using an IDE right now, and tried a
dart format
at one point which may have made some unintentional changes - I'm not entirely sure if the linting is the same as in Flutter?