Skip to content

Added widgets/AppModel#92297

Merged
fluttergithubbot merged 20 commits intoflutter:masterfrom
HansMuller:app_model
Nov 4, 2021
Merged

Added widgets/AppModel#92297
fluttergithubbot merged 20 commits intoflutter:masterfrom
HansMuller:app_model

Conversation

@HansMuller
Copy link
Contributor

@HansMuller HansMuller commented Oct 21, 2021

Enables sharing key/value data with the all of the widgets below a child. Values must be immutable.

  • AppModel.setValue(context, key, value) changes the value of an entry in the shared data table and forces widgets that depend on that entry to be rebuilt.

  • AppModel.getValue(context, key, initCallback) creates a dependency on the key and returns the value for the key from the shared data table. If not value was defined for the key then the init callback is used to create one.

A widget whose build method uses AppModel.get(context, keyword, init) creates a dependency on the AppModel: when the value of keyword changes with AppModel.set(), the widget will be rebuilt.

An instance of this widget is created automatically by WidgetsApp.

AppModel is not intended to be a substitute for Provider or any of the other general purpose application state systems. AppModel is for situations where a package's custom widgets need to share one or a handful of immutable data objects, and where those objects can be lazily initialized. It exists so that packages like that can deliver custom widgets without requiring the developer to add an umbrella widget to their application.

@flutter-dashboard
Copy link

It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat.

If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix?

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Oct 21, 2021
@google-cla google-cla bot added the cla: yes label Oct 21, 2021
@charmoan
Copy link

Thanks

@cedvdb
Copy link
Contributor

cedvdb commented Oct 26, 2021

The Record/tuple proposal could help here

@HansMuller
Copy link
Contributor Author

@cedvdb - could you expand a little on what you mean by #92297 (comment)

@cedvdb
Copy link
Contributor

cedvdb commented Oct 26, 2021

@HansMuller I meant this dart-lang specification https://github.com/dart-lang/language/blob/master/working/0546-patterns/records-feature-specification.md

but looking at the commit again it does not necessarily applies here.

@flutter-dashboard flutter-dashboard bot added d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos documentation f: material design flutter/packages/flutter/material repository. c: contributor-productivity Team-specific productivity, code health, technical debt. labels Oct 27, 2021
@HansMuller HansMuller requested a review from goderbauer October 27, 2021 18:36
Copy link
Contributor

@craiglabenz craiglabenz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature seems well designed, and is definitely a common use-case for Flutter developers (as evidenced by the popularity of Provider, which, as far as I can tell, is the closest analog that already exists). I'll have to play with this a bit myself before I have more sophisticated thoughts, but at a glance, this seems to add directly to the framework some/most/all (not sure which of these yet) is currently available in Provider.

Which I suppose makes my first question - in a framework looking to shed responsibilities, do we want to absorb functionality currently well-implemented and understood (by the community) by a third-party package?

@HansMuller
Copy link
Contributor Author

HansMuller commented Oct 29, 2021

@craiglabenz - AppModel is not intended to be a substitute for Provider or any of the other general purpose application state systems. AppModel is for situations where a package's custom widgets need to share one or a handful of immutable data objects, and where those objects can be lazily initialized. It exists so that packages like that can deliver custom widgets without requiring the developer to add an umbrella widget to their application.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this shake out with state restoration? Does the AppModel need to serialize out the stored data somehow? Or is it reasonable to assume that the entities putting values into the AppModel are responsible for putting them there again on restore?

@rrousselGit
Copy link
Contributor

How would we remove/dispose a key?

Also what concrete use-case is it designed for? The issue mentions a problem, but in which situation is this problem encountered exactly?

@rrousselGit
Copy link
Contributor

About dispose, could we add a tear-off logic?

As in, a callback that is executed when all consumers are removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about keys that are removed/added?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually since we can't remove keys atm this probably wouldn't be an issue I think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this try to notify all consumers when a key changes, even when the key they are listening too didn't change?

This would make notification really expensive if there's a large number of keys

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the consumers that depend on a key (because they used that key to lookup a value) are rebuilt.

Copy link
Contributor

@rrousselGit rrousselGit Oct 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only the consumers where the value changed are indeed rebuilt.

But as far as I understand, the rebuild trigger would involve calling == on all values/keys.

In a Riverpod application, which solves a similar problem (no umbrella widget), the application could have thousands of such keys. Including values/keys which override == to have a deep collection comparison.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For comparison, Riverpod's notification mechanism will only call == on the changed key.

And it won't need to call == on values at all. So whether there's only one key or thousands of keys, the notification process takes the same amount of time.
Only adding consumers increase the time it takes to notify listeners

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you think that rebuilding an AppModel would cause calling == on all values/keys?

Copy link
Contributor

@rrousselGit rrousselGit Nov 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It creates a new Map, which will call updateShouldNotify followed by updateShouldNotifyDependents, which iterates over all the aspects that a consumer depends on

So my understanding is that this would end up calling == on all keys/values with active listeners (possibly multiple times if a key has multiple consumers)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true that calling setValue() once will cause operator== on the value twice: once at the call site and once in updateShouldNotifyDependents. Since setValue() could be called more than once before dependent widgets are rebuilt, the second == check covers the possibility that the new value is no longer different than the original one. It's also true that when multiple widgets depend on the same value, the == check is repeated unnecessarily. If these proves to be a problem in practice we could complicate the implementation a little.

@rrousselGit
Copy link
Contributor

rrousselGit commented Oct 29, 2021

What if we want to optionally locally override a key?

As in, there's no umbrella widget, but we optionally allow specifying such widget to change the behavior of one (but not all) key for a specific screen

Which means the key that is overridden is only for a subtree. But those that aren't use the ancestor's key value

@rrousselGit
Copy link
Contributor

Considering how the API seem to be focused on immutable objects and how expensive the notification process would be if there's a large number of keys, does exposing a "setValue" really make sense?

If we fused getValue/setValue/initValue into a "putIfAbsent" api, we would never have to notify listeners, while still allowing to implement a global immutable value

@HansMuller
Copy link
Contributor Author

HansMuller commented Nov 1, 2021

@goderbauer per #92297 (comment)

Although I was disappointed to add a required initialize callback to AppModel.get(), it eliminates the need for AppModel.init() and, as you pointed out, is less likely to lead to a use-before-init error. Since AppModel is likely to be wrapped up in a package or app-specific helpers anyway, this doesn't seem like too much of a compromise.

@HansMuller
Copy link
Contributor Author

@goderbauer per #92297 (review)

I've updated the API docs to emphasize the fact that AppModel values are expected to be immutable. State restoration of initial values will not require additional support. State restoration of values that have been updated with AppModel.set() are the app's responsibility.

@HansMuller
Copy link
Contributor Author

@rrousselGit

How would we remove/dispose a key?

There's no support for that although we could certainly add it.

Also what concrete use-case is it designed for? The issue mentions a problem, but in which situation is this problem encountered exactly?

The goal is to make it possible for a package to deliver custom widgets that depend on a shared value without requiring the developer to include an application scoped "umbrella" widget. The setValue method exists because the shared value might need to be updated; it was not included for a concrete use case.

What if we want to optionally locally override a key?

InheritedModel supports that and we could extend the support to AppModel. It seemed simpler to explain the semantics of AppModel without including this feature for now.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

/// or a handful of immutable data objects that can be lazily
/// initialized. It exists so that packages like that can deliver
/// custom widgets without requiring the developer to add a
/// package-specific umbrella widget to their application.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the sentence about the umbrella widget appears to be a repetition of the last sentence in the previous paragraph.

/// an immutable value because intrinsic changes to the value will
/// not cause dependent widgets to be rebuilt.
///
/// A Widget that depends on the app model's value for `key` should use
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Widget -> widget

@fluttergithubbot fluttergithubbot merged commit 389a12f into flutter:master Nov 4, 2021
@HansMuller HansMuller deleted the app_model branch November 5, 2021 00:20
HansMuller pushed a commit that referenced this pull request Nov 5, 2021
WizzXu pushed a commit to WizzXu/flutter that referenced this pull request Nov 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: contributor-productivity Team-specific productivity, code health, technical debt. d: api docs Issues with https://api.flutter.dev/ d: examples Sample code and demos f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants