rrousselGit / provider

InheritedWidgets, but simple
https://pub.dev/packages/provider
MIT License
5.12k stars 512 forks source link

[REQ] Documented diagrams #83

Closed peekpt closed 4 years ago

peekpt commented 5 years ago

I did this simple sketch example, I'm no expert, but I think if the dev team adds diagrams to documentation will help a lot people that are starting to learn this Plugin. There's information missing like how the values behave, how they update, etc... With all this providers and ways to reach the provided values it is start to get confusing.

Provider

peekpt commented 5 years ago

Is that it? I'm still trying to figure out this plugin...

Provider

rrousselGit commented 5 years ago

To be completely honest with you, making diagrams is not my thing.

I can't really help you with that. But if others may want to help, I'll gladly answer all the questions you have.

jtlapp commented 5 years ago

We should use UML 2 for diagrams because they are well specified and universally understood.

The documentation for provider had me thinking it wasn't a well maintained or broadly adopted package, so I went looking elsewhere. Then I saw this Flutter team video saying how they use provider in house.

I'm excellent at technical documentation and may take this up, but I'm still in the stages of selecting my state mechanism. If I do draft a new README, what's the best way for us to collaborate on it? Is git really the best way for us to do this?

peekpt commented 5 years ago

I switched to the new Bloc 0.20 which now has the new Provider under the wood. The combination of both worlds is almost perfect for state management

rrousselGit commented 5 years ago

The documentation for provider had me thinking it wasn't a well maintained or broadly adopted package

Could you explain what you felt is lacking? It's not limited to the README, there's the dartdoc too.

If I do draft a new README, what's the best way for us to collaborate on it?

Make a pull request – everybody can see them and participate.

Any help is appreciated 😄

jtlapp commented 5 years ago

Could you explain what you felt is lacking? It's not limited to the README, there's the dartdoc too.

Yeah, I mean just the README. APIs are for reference once you have a basic understanding.

Most glaringly, it's unclear how to control whether a widget rebuilds. I'm having to play with it to figure this out. For example, the README doesn't even mention listen = false. I found this by browsing the dartdocs.

Make a pull request – everybody can see them and participate.

Okay, will do. I'll track my running understanding of things as I dive into provider.

jtlapp commented 5 years ago

The Flutter team is in several places promoting provider as the place to start, so I'm thinking we need a README that appeals to newbie flutterers, briefly explaining the problems provider solves. This should also help more experienced folks understand that provider isn't just training wheels, which is the impression I get reading posts about (other) BLoC solutions. (It seems to me that provider can be thought of as a BLoC solution, particularly with the support of StreamProvider.)

peekpt commented 5 years ago

Provider and Consumer is just an efficient way to deliver objects to the widget tree. BLoC is a pattern to organize your code with events, blocs and states. So the code flows only with one direction events->bloc->states->UI (and loops)

jtlapp commented 5 years ago

I implemented a simple app using provider, scoped_model, bloc_provider, bloc, and Didier Boelens' version of bloc. All but bloc produced largely identical code. Where provider and scoped_model have a "model," block solutions have a "BLoC." Models and blocs both manage state, they both contain "business logic," and they both have channels for receiving or delivering async messages.

bloc and one version of Didier Boelens' bloc asynchronously receive messages (events) and asynchronously deliver messages (states). Other versions of bloc synchronously receive state changes while asynchronously deliver state messages. Provider and scoped_model work similarly to these latter bloc implementations, but instead of delivering state they deliver change notifications, allowing the widgets to subsequently get state data synchronously.

I really don't see them as being all that different from one another. They all have a business logic object and it's just a matter of the channels that connect it to the UI. (There are tradeoffs of course, but I'm not seeing them as that significant.)

But my understanding may continue to evolve as I investigate further.

rrousselGit commented 5 years ago

You seem to fall for the common misconception that provider is an architecture (misconception caused by the IO talk). It's not.

I like to take food as an example.

State management is "Burgers". It comes in many, many different flavors and is very broad.

BLoC is the latest burger from McDonald. It's very specific, but you may not like it.

provider is a bag the most common ingredients to make a burger, already pre-assembled but without the meat that is the state implementation.

You can combine it with a steak to make a cheeseburger. But you don't have to. You can use chicken or a vegetarian ingredient too if you wanted to.

Basically, provider + ChangeNotifier = scoped_model But you can do provider + reducer, provider + mobx store, provider + bloc,...

rrousselGit commented 5 years ago

That's also exactly why you shouldn't expect the same level of documentation from provider than from alternatives.

An example doesn't make sense. You can't request a final product of something that is deliberately not complete.

I could make detailed examples of how to make a scoped-model architecture using provider, as it's what most peoples having watched the IO expects.

But i personally use my own steak, that is not ChangeNotifier as I'm allergic to mutability.

peekpt commented 5 years ago

I'm just not using Provider because it's already integrated in the BLoC plugging.

I'm using BLoC + BuiltValue + Dio for standard web apis. It's my secret sauce :-)

A quinta, 29/08/2019, 08:22, Remi Rousselet notifications@github.com escreveu:

That's also exactly why you shouldn't expect the same level of documentation from provider than from alternatives.

An example doesn't make sense. You can't request a final product of something that is deliberately not complete.

I could make detailed examples of how to make a scoped-model architecture using provider, as it's what most peoples having watched the IO expects.

But i personally use my own steak, that is not ChangeNotifier as I'm allergic to mutability.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/rrousselGit/provider/issues/83?email_source=notifications&email_token=AABWJMOE3ZVQWBLZPB4XTZTQG52LJA5CNFSM4HPJG42KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5NQXLI#issuecomment-526060461, or mute the thread https://github.com/notifications/unsubscribe-auth/AABWJMNH2US6QJ7PY7POQMLQG52LJANCNFSM4HPJG42A .

jtlapp commented 5 years ago

You seem to fall for the common misconception that provider is an architecture (misconception caused by the IO talk). It's not.

I did the experiment and came to that conclusion prior to even discovering that talk. Instead, that talk is my reason for giving provider another look.

That said, I woke up this morning realizing what my misunderstanding was.

provider is a bag the most common ingredients to make a burger, already pre-assembled but without the meat that is the state implementation.

Gotcha. Provider is a way to lift state in an object tree.

rubensdemelo commented 5 years ago

But i personally use my own steak, that is not ChangeNotifier as I'm allergic to mutability.

Do you use provider to DI and mobx/streams ?

jtlapp commented 5 years ago

That's also exactly why you shouldn't expect the same level of documentation from provider than from alternatives.

I just want documentation to leave me understanding the tool. I still do not understand this tool and am working toward that. (My difficulty may be due to never having worked with the reactive pattern -- I don't know -- but it's not due to inexperience with software or software architecture. I'm 50 and have been a professional developer since age 15.)

An example doesn't make sense. You can't request a final product of something that is deliberately not complete.

The documentation just needs to state its scope and clearly explain its use. Last night I encountered another thing that confused me about the documentation. The docs distinguish between Provider<T>.value and Provider<T> by saying the former "exposes" a value, while the latter "creates and exposes" an object. Yet, I clearly could create and expose any object in either approach. By scanning through the old issues I found a case of of misuse, which you explained as Provider<T> disposing its object, while Provider<T>.value does not. And now I understand the difference. The difference between "exposes" and "creates and exposes" now makes sense to me -- it's good reminder language but not the best explanatory language.

I could make detailed examples of how to make a scoped-model architecture using provider, as it's what most peoples having watched the IO expects.

But i personally use my own steak, that is not ChangeNotifier as I'm allergic to mutability.

I just want to be able to read the docs to understand how to properly use provider with flutter.

I'm wondering if we could just focus on helping me understand and give me a chance to revise the README so you can evaluate what I'm trying to do after I've done it. If you don't like it, I can just make it a Medium post -- assuming the information is accurate.

rrousselGit commented 5 years ago

Gotcha. Provider is a way to lift state in an object tree.

And a data-binding. Otherwise, it'd be just like get_it.

Do you use provider to DI and mobx/streams ?

I don't have any professional Flutter project atm, so I can't answer that.

But here's a few gists of custom providers I made: https://gist.github.com/rrousselGit/b85ff28f3ee9509a97171f5b944890b1 https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91

It's basically like ChangeNotifier, but immutable.

rrousselGit commented 5 years ago

I just want to be able to read the docs to understand how to properly use provider with flutter.

I'm wondering if we could just focus on helping me understand and give me a chance to revise the README so you can evaluate what I'm trying to do after I've done it. If you don't like it, I can just make it a Medium post -- assuming the information is accurate.

Any help is greatly appreciated. Feel free to open a PR and ask questions.

I'm not very good at documentation, so it'll be very useful to get some external help.

jtlapp commented 5 years ago

I'm experimenting to see what's possible and am getting some awesomely helpful error messages. Nicely done!

jtlapp commented 5 years ago

I think I finally grok this package. It's actually beautifully simple! I think the README just needs to convey the overall abstraction and how it variously instantiates that abstraction.

I've hand drawn a number of UML diagrams, but I'm still refining them.

jtlapp commented 5 years ago

How exactly is the provider value preserved across rebuilds? I ran across the following code in InheritedProvider<T>:

  /// Mutating `value` should be avoided. Instead rebuild the widget tree
  /// and replace [InheritedProvider] with one that holds the new value.
  final T _value;

I know build() does not get called on the child of provider for each change in value, but I'm wondering if something gets rebuilt anyway.

I'm asking because I'm trying to work backwards from accurate UML diagrams to simplified diagrams suitable for the README. The accurate diagrams show how provider works to some degree, because they show how provider classes behave in response to flutter calls that we're otherwise used to handling. I need to show state being preserved across rebuilds. I was expecting the Provider class to delegate the value to an associated State class, but I'm not seeing that here. (I'm also trying to avoid reverse engineering the entire code base.)

jtlapp commented 5 years ago

Okay, as I continue to examine the code, it's looking like a new InheritedProvider<T> is only created upon rebuilding the Provider<T>, and at that time the value is copied from a state object that survives rebuilds.

rrousselGit commented 5 years ago

Yes. InheritedProvider is just an Inheritedwidget.

The state is maintained by another widget (although it's specific implementation do not matter)

jtlapp commented 5 years ago

The state is maintained by another widget (although it's specific implementation do not matter)

I'm working backward from knowing how things work to determining what matters and how to show it. The less I understand, the more correcting you'll need to do in the diagrams.

For example, I originally surmised that Provider created its value in response to createState(), but noticing that builder takes a context, I examined the code and found that the value is actually initialized on the first build. I could have handed you a diagram showing the value being created in response to createState(), and you would have had to correct that.

I'm finding it necessary to illustrate how this package works with respect to the normal sequence of flutter calls, so that people can see how a Provider<T> instance (for example), fits into the widget tree. Even so, I'm also looking for ways to remove (or creatively represent) the messaging that a user of the provider package shouldn't need to be aware of.

rrousselGit commented 5 years ago

I don't think the diagram should talk about createState/StatefulWidget, but only the builder callback.

It's worth noting that the builder will soon be lazy loaded

jtlapp commented 5 years ago

Thanks for the heads up about lazy loading builder, but I think you're missing my point about needing to understand how things work to get the diagrams right. I'll stop asking questions, get what I can from the code, and let you tell me what's wrong.

jtlapp commented 5 years ago

The diagram does show the order of events, so I'll depict prebuilding the value for now and we can change it later.

jtlapp commented 5 years ago

Okay, I'm sharing the most basic diagrams so you can see my approach. I'm also working on diagrams for listenables. I've been debating whether to show the return value from build() here, providing a Widget out to flutter (all the way on the left). I left it off for simplicity.

provider_value

jtlapp commented 5 years ago

One diagram per comment. Too squashed otherwise.

provider

EDIT: Something must be wrong because dispose() doesn't have the context here.

jtlapp commented 5 years ago

I'm not as confident about this one. I realize that some of the messaging does not directly indicate the actual senders and receivers, but I was trying to simplify things. It still looks too complex.

listenableprovider

I should probably hide the fact that setState is called, but I'm not sure how else to indicate that the listenable is inducing a rebuild.

UPDATE: Listenable doesn't have a dispose() method as shown, so I need to delete that.

jtlapp commented 5 years ago

I plan to explore representing StreamProvider and FutureProvider in hopes of find a simple abstraction that works for all the asynchronous rebuilders. But I'll wait for feedback on my understanding and this graphical approach before putting any more time into it.

jtlapp commented 5 years ago

FYI, I'm now experimenting with doing this in collaboration diagrams without showing flutter events.

jtlapp commented 5 years ago

And here's a really simplified collaboration diagram for ListenableProvider<T>. It makes more assumptions about the user's understanding, but the text could make sure there are no misunderstandings. I think this one would be easier to generalize into a diagram for introducing the provider pattern in use here.

listenableprovider_collab

EDIT: This might be more attractive using bubbles and arcs, but let's get the content right first.

jtlapp commented 5 years ago

Here's my first attempt at abstracting all providers into a common pattern.

abstract1

I see a number of tweaks that might make this clearer, but let's see if I've got it right first.

jtlapp commented 5 years ago

In particular, I'd like to make it clearer that the value has type T.

I may also need to better accommodate initialData.

And I think we can do away with the "request value" messages. Just show the value returning. (Or maybe not, because these message make it clear that the consumer relies on the provider for the values, and becaues the messages equally represent Provider.of<T>() calls.)

jtlapp commented 5 years ago

Here's an alternative abstraction to consider:

abstract2

EDIT: Oops, had to make a correction. I replaced the image.

jtlapp commented 5 years ago

I don't think either of these abstractions works for the case where a build() method calls Provider.of<T>().

It might be cool to swap the positions of Binding<T> and the provider's child Widget so that the new value drops in from the top, suggestive of a drip source.

jtlapp commented 5 years ago

I attempted to create analogous diagrams for the Consumer<T> and Provider.of<T>() cases. I'm not sure I'm happy with them.

abstract3 consumer

abstract3 provider_of

Any feedback on the direction this is taking? Or the accuracy?

EDIT: I've got the 0..* multiplicity on the wrong connection. It should be on the provider child's connection to its descendent widgets.

jtlapp commented 5 years ago

@rrousselGit, I'm hoping for your feedback before proceeding. We can continue to revise the diagrams, but I'd like to make sure my understanding is correct before working on the README.

rrousselGit commented 5 years ago

There's no difference between Consumer and Provider.of Consumer works by calling Provider.of in a new widget.

Similarly, all providers works by exposing and rebuilding an InheritedProvider (although the lazy loading branch refactored it to instead call InheritedElement.notifyClient)

jtlapp commented 5 years ago

There's no difference between Consumer and Provider.of Consumer works by calling Provider.of in a new widget.

Yeah, I knew that -- saw that in the code. But there is a difference in how client code (of this package) gets the value. I'm trying to show users how to use the package more than how the package works.

There are two ways to subscribe to rebuilds. We could chose to only diagram the Consumer<T> way, but it might be better to have diagrams for the major modes of using the package.

Similarly, all providers works by exposing and rebuilding an InheritedProvider (although the lazy loading branch refactored it to instead call InheritedElement.notifyClient)

Cool. But I'm not sure how this should affect the diagrams.

jtlapp commented 5 years ago

The sequence diagrams I posted earlier could show the call to Provider.of<T>() in both scenarios, clearly showing that Consumer<T> makes the call in one scenario, while the client code must do so in the other scenario. I'm not sure how to show that in these simpler collaboration diagrams.

jtlapp commented 5 years ago

Maybe this is a way to show both modes of behaving as a consumer, while still informing users of the library of what they're responsible for and what they aren't.

abstract4 consumer

abstract4 custom

jtlapp commented 5 years ago

And here's what analogous Provider<T>.value diagrams might look like

value1 consumer

value2 custom

jtlapp commented 5 years ago

@rrousselGit, you're quite except to correct a mistake. The vibe I'm getting is that you're either busy or not thrilled about this approach to diagramming.

rrousselGit commented 5 years ago

I'm indeed busy. I can't take a deeper look until tomorrow

jtlapp commented 5 years ago

I took your earlier suggestion to think of provider as both lifting state and providing data binding and incorporated that into the model. If I did the abstraction right, it suggests a way to explain the various kinds of providers in the README.

A provider is a widget that makes a value available to descendent widgets known as consumers, "exposing" the value to the consumers.

The value may have a data source and may change over time. The data source supplies the value and knows when the value changes.

When the value has a data source, the consumer may bind to the value via the provider. A consumer that is bound to a value rebuilds when the value changes.

Different kinds of bindings are available for the different kinds of data sources:

The wording needs some massage, but I think this would be the gist.

Notice that to say that a consumer is bound to value V is to say that the consumer receives each subsequent instance of V. Data sources and values aren't always the same, and this language makes it clear what exactly the consumer receives on change.

jtlapp commented 5 years ago

It may be possible to revise the diagrams to refer to publishers instead of bindings. That would be more consistent with the current documentation that refers to listeners (in the README) and subscription (in the dartdocs).

One way to do this is to replace the word "binding" in the diagrams with "pub type" and "data source" with "publisher," though I'd hope we could find a better term than "pub type."

rrousselGit commented 5 years ago
  • A Provider exposes a value without making it possible to bind to the value to listen for changes.

That's not true There's an example https://github.com/rrousselGit/provider#do-i-have-to-use-changenotifier-for-complex-states

jtlapp commented 5 years ago
  • A Provider exposes a value without making it possible to bind to the value to listen for changes.

That's not true There's an example https://github.com/rrousselGit/provider#do-i-have-to-use-changenotifier-for-complex-states

I just need to work on the wording. In your example, the provider takes no part in the binding. Clearly code elsewhere can wrap a value and implement notification.

jtlapp commented 5 years ago

Maybe: