-
Notifications
You must be signed in to change notification settings - Fork 306
msglist [nfc]: Handle start markers within widget code, not model #1512
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 method is only called from callbacks which can only get invoked after the widget has built; and the build method already requires `model` to have been initialized. This is therefore NFC.
The view-model already exposes flags that express all the same information. By leaving these markers out of the list of items, we can save ourselves a bunch of stateful updates.
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.
Thanks! It's neat and I have rebased my outbox changes on top of this. Overall looks good to me. Left some comments.
_initModel(PerAccountStoreWidget.of(context)); | ||
} | ||
|
||
@override | ||
void dispose() { | ||
model?.dispose(); | ||
_model?.dispose(); |
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.
This makes me wonder if dispose
can ever get called before _initModel
.
I guess the answer is no. When dispose
is called, the State
should be in its "ready" state.
void dispose() {
assert(_debugLifecycleState == _StateLifecycle.ready);
assert(() {
_debugLifecycleState = _StateLifecycle.defunct;
return true;
}());
assert(debugMaybeDispatchDisposed(this));
}
and from the dart doc of _StateLifecycle
, "ready" is later than "created" and "initialized" in State
objects' lifecycle:
/// Tracks the lifecycle of [State] objects when asserts are enabled.
enum _StateLifecycle {
/// The [State] object has been created. [State.initState] is called at this
/// time.
created,
/// The [State.initState] method has been called but the [State] object is
/// not yet ready to build. [State.didChangeDependencies] is called at this time.
initialized,
/// The [State] object is ready to build and [State.dispose] has not yet been
/// called.
ready,
/// The [State.dispose] method has been called and the [State] object is
/// no longer able to build.
defunct,
}
so State.didChangeDependencies
(and then onNewStore
) should have been called already.
On a side-note, we have been cautious about this at other places. While the framework might maintain this guarantee that onNewStore
is called before dispose
is called, it's probably easier to rest assure that ?.dispose()
is always going to be safe. So I'm actually not sure if we want a null-assert here.
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, thanks for the investigation.
Given those results, I think there would be some value in systematically having our dispose
methods assume that didChangeDependencies
(and so onNewStore
where applicable) has been called. By visibly relying on that guarantee from the framework, it'd help teach the reader that that guarantee is there, and give confidence in relying on it in whatever new code they're writing.
That'd be an independent change from this one, though.
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.
Yeah, _EmojiPickerState
seems like a place where a similar refactor may apply. The teaching aspect of this is refreshing think about.
Widget _buildStartCap() { | ||
// These assertions are invariants of [MessageListView]. | ||
assert(!(model.fetchingOlder && model.fetchOlderCoolingDown)); | ||
final effectiveFetchingOlder = | ||
model.fetchingOlder || model.fetchOlderCoolingDown; | ||
assert(!(model.haveOldest && effectiveFetchingOlder)); |
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.
assert(fetched)
being absent seems like a notable change here.
I suppose _updateEndMarkers
has it because it nothing from items
matter when fetched
is false, because the widget code will just show the loading indicator and skip building the list view altogether.
I feel that it still applies here, right? _buildStartCap
shouldn't be called at all if nothing has been fetched yet.
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.
Yeah, the bulk of the code on _MessageListState
runs only when model.fetched
is true. When model.fetched
is false, the actual list doesn't get shown, so none of the "build" code runs beyond the first line of the build method.
In MessageListView
(or _MessageSequence
) where _updateEndMarkers
lives/lived, the control flow between different methods is a lot more complicated (going up and down in the file), and so that's less clear.
I think this method's logic also makes sense even without the assumption that model.fetched
is true. If we're fetching more, we say we're doing so; if we know we have the oldest messages, we say so; else say nothing. In _updateEndMarkers
, there's the additional complication that the method is mutating items
, so in order to think through the logic one wants to narrow down the possible states that items
could be in.
Thanks for the review! Replied above; merging. |
The view-model already exposes flags that express all the same information. By leaving these markers out of the list of items, we can save ourselves a bunch of stateful updates.
The main commit has a nice negative diffstat:
This comes up as part of my work toward #82: with this approach, we'll avoid needing to maintain marker items at the end of the list to depend on whether we've fetched the newest messages. Those markers would be more complex than the markers at the start, because they'd also include things like the typing-status line and the mark-as-read button.
@PIG208 I think this also makes for a small simplification in handling outbox-messages, in #1453.