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

I find this UML graphs so confusing.

simple

Provider is the BurgerShop which has the employees (build) that will be building the Burgers (Object) also has thrash cans (dispose)

Consumer (us) have a way ( to eat the Burguer (build) ,we organize our table (widget)

rrousselGit commented 5 years ago

An alternative I was experimenting with lately, is to use flutter_web to make a dynamic graph, where we could see what life-cycles got called and which widgets have access to which value

jtlapp commented 5 years ago

@peekpt, did you mean to close an issue that I'm actively working on and am seeking help with, and for which Remi expressed a desire for assistance?

jtlapp commented 5 years ago

An alternative I was experimenting with lately, is to use flutter_web to make a dynamic graph, where we could see what life-cycles got called and which widgets have access to which value

Can you point me to an example of this elsewhere, so I might play with an alternate approach?

I realize only software engineers are going to understand UML, though it's hard for me to see how most of it wouldn't be intelligible to non-engineers.

jtlapp commented 5 years ago

In any case, we can formally express what we want in UML, and once we agree on the information that needs to be communicated, later translate that to a more intuitive diagram.

bradyt commented 5 years ago

Perhaps @peekpt just meant to unsubscribe. 😉

peekpt commented 5 years ago

@jtlapp No I'm sorry it was a mistake I'm so tired that I press the wrong button.

jtlapp commented 5 years ago

@peekpt, @bradyt, it's easy for me to imagine getting this onslaught of notifications and thinking, "Get me out of here!" :-)

jtlapp commented 5 years ago

Glancing through my design patterns books right now, they ALL use UML. It seems that anyone who is familiar with design patterns is going to have some familiarity with UML.

But I am open to creating diagrams that look less formal and more inviting.

peekpt commented 5 years ago

I'm still confused, I'm not used to UML and I know how Provider works, the thing is for young comers this could be a bit a pain to figure out.

peekpt commented 5 years ago

I think you should separate like this:

How to inject the value. How to access the value. How to make the all value classes ex: ChangeNotifier

Separately. If you are going to interconnect all that will be confusing

rrousselGit commented 5 years ago

Agreed.

Especially so that there's only one way to do these:

Anything else is just built around these two.

jtlapp commented 5 years ago

Okay, I'm looking to understand and describe the architecture. The how-tos are important, but I retain information by having a framework on which to hang the pieces.

I set out to solve the problem of not understanding how to use this package after reading the README. I had to read several other tutorial articles and examine the code before it finally clicked. This isn't a problem I expected to have with such a popular package.

Maybe that's just how my mind works. I encountered a few other things that might be important to add to the README as how-tos (e.g. the utility of ValueListenerProvider), but I was otherwise mainly looking to add a clear description of the architecture and explain things in terms of that.

If you're just looking for more how-tos, I'm not the person.

peekpt commented 5 years ago

You still can do diagrams like I said separately

jtlapp commented 5 years ago

You still can do diagrams like I said separately

I'm not sure how to separate them other than as I've already done.

Here is injection.

Here is access.

I think you're looking for a different kind of diagram.

peekpt commented 5 years ago

Yes, you are still mixing

We already know that consumer request and provider exposes.

A domingo, 1/09/2019, 02:02, Joe Lapp notifications@github.com escreveu:

You still can do diagrams like I said separately

I'm not sure how to separate them other than as I've already done.

Here is injection https://github.com/rrousselGit/provider/issues/83#issuecomment-526833084 .

Here is access https://github.com/rrousselGit/provider/issues/83#issuecomment-526831748 .

I think you're looking for a different kind of diagram.

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

jtlapp commented 5 years ago

Yes, you are still mixing

We already know that consumer request and provider exposes.

How do you separate them? I can drop the explicit request for the value from the diagram for the Provider<T> diagram that uses Consumer<T>, leaving just the incoming value data as I've done in other diagrams. But custom consumers are "injected" by explicitly asking the provider for the value.

In the case of ListenerProvider<T>, I already posted a version that doesn't show the underlying Consumer<T> request. It isn't all that different, but I was uncomfortable with it for not showing how changing the value induces a rebuild.

jtlapp commented 5 years ago

If you're worried about the apparent complexity of these diagrams, I assure you, this is an extremely simple architecture! Most architectures cannot be so succinctly represented! I think the generic binding diagram is beautiful and shows the brilliance of this pattern.

jtlapp commented 5 years ago

I have a guess about what the problem may be. Maybe you're expecting the diagram to mirror the code? I can do that! I'll revise to show you how that might look.

rhalff commented 5 years ago

@jtlapp if you're planning on taking up the tedious task of creating class diagrams note that there is a package to generate those: https://pub.dev/packages/dcdg

peekpt commented 5 years ago

I don't say to mirror code but to mirror the workflow as simple and direct possible, that's the intent. Abstraction.

jtlapp commented 5 years ago

@jtlapp if you're planning on taking up the tedious task of creating class diagrams note that there is a package to generate those: https://pub.dev/packages/dcdg

That's pretty cool, but that's the last thing I'd want to do. I'd rather look at code. The purpose of the diagrams is to convey how the package works in a glance. The UML I created does that for me. Now I'm trying to figure out how to do it for people who don't read UML.

jtlapp commented 5 years ago

Here's an attempt at displaying provider by mirroring the code. What would you like to do differently?

code provider

jtlapp commented 5 years ago

Hey, the ListenableProvider actually looks simpler in this diagram. By imitating the code, the reader already understands control flow, so I don't have to depict that. I only have to depict data flow. But I'm not sure how well this diagram succeeds.

code listenable1

EDIT: Here's an alternative approach.

code listenable2

I realize these actually depict ListenableProvider<T>.value.

peekpt commented 5 years ago

I mean like this:

jtlapp commented 5 years ago

I mean like this:

I think you have to know what all those pieces are before you can begin to make sense of that diagram. It's a puzzle to me. I'm staring at it trying to figure out how to interpret it.

jtlapp commented 5 years ago

Here's an attempt at StreamProvider<T>. I'm trying to figure out how to generalize all these async value providers in a single diagram.

code stream1

I realize this actually depicts StreamProvider<T>.value.

rrousselGit commented 5 years ago

was able to take a deeper look at everything Thanks for your hard work!

To be honest, I don't understand any of these graphs. I'm no expert on the topic, so I don't know what the issue is. But what's certain is that they are too complex for what provider is.

It's ultimately nothing but a setState on the top of InheritedProvider.

For example, we could say that StreamProvider used this way:

StreamProvider<T>.value(
  value: yourStream,
  child: child,
);

is similar to:

StreamBuilder<T>(
  stream: yourStream,
  builder: (_, snapshot) {
    return InheritedProvider<T>(
      value: snapshot.data,
     child: child,
    );
  }
)

I'm pretty sure that for most Flutter devs, this comparison is easier to understand than the current iteration of StreamProvider's graph

jtlapp commented 5 years ago

I'm pretty sure that for most Flutter devs, this comparison is easier to understand than the current iteration of StreamProvider's graph

So I'm off looking up how InheritedProvider works so I can understand your analogy.

I'm new to Flutter. I quickly discovered that I needed a state management tool. So I have a catch-22: become familiar with Flutter so I can pick a state management tool, but pick a state management tool so I can do things with Flutter.

I'm trying to appeal to Flutter newbies like myself.

jtlapp commented 5 years ago

I've stared at it for a while and don't know what to make of the analogy. The StreamBuilder rebuilds with each stream event, while the StreamProvider doesn't. Assuming there is a consumer below the StreamProvider, that consumer would rebuild, and the there is no difference between the two approaches except that the second rebuilds from the top with each new event.

I'm not sure where to take that.

rrousselGit commented 5 years ago

while the StreamProvider doesn't

It does. In fact, the current implementation of StreamProvider uses StreamBuilder internally. https://github.com/rrousselGit/provider/blob/master/packages/provider/lib/src/async_provider.dart#L144

jtlapp commented 5 years ago

while the StreamProvider doesn't

It does. In fact, the current implementation of StreamProvider uses StreamBuilder internally.

Oh! So StreamProvider is carrying forward a child that isn't rebuilt?

rrousselGit commented 5 years ago

Somehow, yes. It's "magical" thanks to how the inner of Flutter works. Basically, if the instance of a widget did not change, build is not called again.

So while StreamProvider internally rebuilds, the child parameter is still the same, and therefore child does not rebuild.

jtlapp commented 5 years ago

In using the provider package I found myself thinking that providers are not rebuilt with each change of value. That thinking suited me well for the simple experiments I was doing. Would you like the reader to think of providers as being rebuilt? If so, how does that help the reader?

Or is there some other takeaway I need to get here?

rrousselGit commented 5 years ago

With provider, it is not Consumer that listens to an object, but the provider. What Consumer listen is changes to InheritedProvider.

That's pretty important. It means that, for most use-cases, there's only one listener to an object and it persists for the whole life of the app.

On one hand:

On the other hand, it means that the listener is still there even if the class is not used at a given time (which can have some impacts with streams for example).

jtlapp commented 5 years ago

With provider, it is not Consumer that listens to an object, but the provider. What Consumer listen is changes to InheritedProvider.

That's pretty important. It means that, for most use-cases, there's only one listener to an object and it persists for the whole life of the app.

This is pretty awesome! I actually considered that possibility when creating the diagrams. I scanned through the code to find an answer, but apparently I came to the wrong conclusion. I saw listener.addListener(() => setState()) for ListenerProvider and assumed that meant each consumer subscribed.

Okay, then my UML diagrams are all wrong in this regard. And my code-like diagrams aren't wrong but mistakenly suggest that the consumers themselves subscribe. It sounds like you'd rather not create that impression? Maybe so that people recognize the benefits they're getting and not suffering the O(n) of ChangeNotifier?

On the other hand, it means that the listener is still there even if the class is not used at a given time (which can have some impacts with streams for example).

So when does the provider unsubscribe from the listener? I assume at least by the time the provider widget gets disposed?

I'll take a break to digest this and see what alternative representations come to mind. Thank you for explaining how things work!

rrousselGit commented 5 years ago

Maybe so that people recognize the benefits they're getting and not suffering the O(n) of ChangeNotifier?

Yes, and raise awareness that doings things like HTTP polling through a StreamProvider above MaterialApp may cause some unneeded requests.

So when does the provider unsubscribe from the listener? I assume at least by the time the provider widget gets disposed?

When the provider is removed from the widget tree, yes.

I have an hanging PR on Flutter repo that would allow removing the listeners when there's no more consumers listening to an InheritedProvider. But the Flutter team is skeptical sadly.

jtlapp commented 5 years ago

Okay, I'm going back to the drawing board. Thank you for your patience with me!

peekpt commented 5 years ago

Offtopic : you really must try the new BLoC flutter_bloc, it's very comprehensive, it uses provider and you don't need worry in using different types of providers.

Also built_value for immutability

jtlapp commented 5 years ago

you really must try the new BLoC flutter_bloc, it's very comprehensive,

I have. I created a little app that required some state management within the bloc. The resulting code was nearly identical to my implementation of the same app for provider and simpler than my implementation in any other state management package except for scoped_model.

The reason I came back to provider was first that it's less opinionated about how I receive messages from the notifier. I can use accessors on the notifier. Under flutter_bloc I had to subscribe to each data item individually; or if not that, create a new state object having all prior data plus the change and emit that. flutter_bloc can't emit the notifier itself.

Second, the flutter_bloc solution seems so heavy weight. Under provider I can choose my implementation and don't have to both message the notifier and receive message from the notifier via separate streams. I also don't have to translate each service call to an event.

I was anticipating my apps having a lot of unnecessarily complexity down the road, particularly upon learning that BLoC came out of the Angular work. I found Angular excessively complicated and am not fond of how they think about architecture.

Finally, Google seems to be making a push for provider, despite being the ones to introduce the BLoC approach. I figured they're seeing something I'm not.

it uses provider and you don't need worry in using different types of

I should look into that more. The two examples flutter_bloc solutions I investigated in the package provided horrible implementations. For example, the to-do list example rebuilt the entire list every time a to-do changed. My general sense was that efficiency was not a concern.

Thanks for the recommendation!

peekpt commented 5 years ago

The advantage I see in provider it's the possibility of rebuilding a list item instead of all the list, 100% agreement on that point.

I have app news app on the market that has infinite scroll and has to update the entire list everytime it grows and it works flawlessly I can scroll for minutes, no worries whatsoever.

All this is possible because you have a list builder that lazy loads and unloads the items of the list, so you don't need to be worried about this kind management.

A domingo, 1/09/2019, 17:31, Joe Lapp notifications@github.com escreveu:

you really must try the new BLoC flutter_bloc, it's very comprehensive,

I have. I created a little app that required some state management within the bloc. The resulting code was nearly identical to my implementation of the same app for provider and simpler than my implementation in any other state management package except for scoped_model.

The reason I came back to provider was first that it's less opinionated about how I receive messages from the notifier. I can use accessors on the notifier. Under flutter_bloc I had to subscribe to each data item individually; or if not that, create a new state object having all prior data plus the change and emit that. flutter_bloc can't emit the notifier itself.

Second, the flutter_bloc solution seems so heavy weight. Under provider I can choose my implementation and don't have to both message the notifier and receive message from the notifier via separate streams. I also don't have to translate each service call to an event.

I was anticipating my apps having a lot of unnecessarily complexity down the road, particularly upon learning that BLoC came out of the Angular work. I found Angular excessively complicated and am not fond of how they think about architecture.

Finally, Google seems to be making a push for provider, despite being the ones to introduce the BLoC approach. I figured they're seeing something I'm not.

it uses provider and you don't need worry in using different types of

I should look into that more. The two examples flutter_bloc solutions I investigated in the package provided horrible implementations. For example, the to-do list example rebuilt the entire list every time a to-do changed. My general sense was that efficiency was not a concern.

Thanks for the recommendation!

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

jtlapp commented 5 years ago

I think I've corrected the UML here for ListenableProvider. It's considerably more complicated. I needed to do UML first to accurately capture my understanding so I can think about other ways to represent this. Okay, now to see if I can find a simpler representation...

uml listenable1

jtlapp commented 5 years ago

Well, here's a first stab at a code-like version of ListenableProvider<T>. I'm struggling here.

code listenable3b

@peekpt, I wish I understood what you were trying to do, because I need a kick in a new direction. Were you drawing a dependency tree?

jtlapp commented 5 years ago

Here's an approach that suggests the provider is the subscriber. It requires explanatory text, though. I'm not sure how to do this with a Listenable though, because the Listenable is the value.

code stream2

jtlapp commented 5 years ago

This is about as abstract as I can make it. It leaves a lot unsaid.

pure provider2

Or maybe this, if you like:

pure provider2c

jtlapp commented 5 years ago

Or maybe the rebuild circle should go on each consumer, so it doesn't look like the whole thing is getting rebuilt?

rrousselGit commented 5 years ago

For rebuilds, we'll likely need an animated diagram that looks like a widget tree

I don't think UML can explain clearly rebuilds. It's more focused on relationships

jtlapp commented 5 years ago

I don't seem to be getting any traction here. Probably time to make this a side project.

jtlapp commented 5 years ago

Here's my latest UML that generically attempts to capture everything:

uml general1

EDIT: Revised to show value at notification.

jtlapp commented 5 years ago

Here I've also made the consumer generic:

uml generic2

EDIT: Revised to show value at notification.