jonataslaw / getx

Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with Get.
MIT License
10.43k stars 1.63k forks source link

Is there an equivalent of `context.watch<T>` in provider? #292

Closed esDotDev closed 4 years ago

esDotDev commented 4 years ago

With the latest Provider versions, there's a nice shorthand for when you just want to bind the entire Widget for a rebuild.

Can use

A a = context.watch();
B b = context.watch();
return Text(a.counterValue + b.counterValue);

Instead of:

Consumer<A>(
    builder: (_, a, __) {
      return Consumer<B>(
        builder: (_, b, __) {
          return Text(a.counterValue + b.counterValue);
        },
      );
    },
  )

Also works for selectors which is super handy for controllers/models that hold a bunch of different values:

var aValue = context.select<A>((a) => a.counterValue); 
var bValue = context.select<B>((b) => b.counterValue); 
return Text(aValue + bValue);

Would be awesome if we could have a similar alternative to GetBuilder()

jonataslaw commented 4 years ago

This approach is very naive (but useful for those using provider), however GetX is much more powerful. You don't need a Selector with GetX, it only reconstructs the view if the state changes ~ really ~ and another value is displayed on the screen.

You also only need to embed a widget with Obx(() => Text(controller.count)) to build it whenever a variable inside it changes, you don't need any Boilerplate, nor type the class if you don't want to.

Nothing from the provider applies, Provider does not use Streams, nor is it reactive, but it has functions that are in the middle between the Simple State Manager, and the powerful GetX, so I prefer not to add selector and these other functions, because people can use Provider if you want a middle ground. If you want something extremely simple, GetBuilder foresees it. If you want something powerful, the best choice is GetX. If you want something in between, use Provider.

esDotDev commented 4 years ago

Interesting. This distinction between Simple and Complex is not one that I think makes a ton of sense.

The difference between GetBuilder and GetX is more about a preference between a stream-based flow, or a setState/Update based flow. Both can get the job done, both can be equally 'powerful'. You can have a simplistic stream-based approach, you can have complex mapping-based approach.

My use case is basically that I am not a big fan of stream-based approach, because:

  1. As you pointed out iirc, there is a higher performance overhead to having a ton of streams, vs update mappings. I'm not sure how practical a concern this is.
  2. More importantly, I like to be able to manually update views sometimes. With streams you are blocked out of this, and actually need to modify a value in order to trigger a refresh. This tends to annoy me.

Also, I think I muddled things when I mentioned selector. just to clarify, I guess this is really 2 requests, I don't see any problems with the first, it's just syntactic sugar:

  1. Can we have a 1-liner that binds a widget to a controller update, as opposed to forced nesting? This is just a nice quality of life readability and scalability improvement. Anytime I need to reference > 1 controller, the current approach gets verbose. With the Get.BindState(c) or something, you can have any amount of controllers, and it scales fine.
  2. It would be nice if we could filter on GetBuilder somehow, this would put both approaches on even ground, and really let us choose whether we want reactive or event based.

It really has nothing to do with complexity or power, its scalability. Consider I have an aggregation app, it has various sources of data, ContactsModel, SocialFeedsModel, CalendarEventsModel, all of these are used in some DashboardView. This Dashboard view needs to do a full rebuild anytime any of these have changed (naive, maybe, but often it is not worth it in Flutter to micromanage the granular rebuilds). Now we need to nest 3 layers deep if it's going to use a GetBuilder for these. So we're forced into GetX, even though there is absolutely nothing complex or novel about this use case, simply for readability's sake. That doesn't seem right??

The selector is a nice to have, otherwise we'll similarly get forced into GetX, just because we want to only rebuild on ValueA and not ValueB, which again is not in my mind a complex use case.

jonataslaw commented 4 years ago

The difference between something based on Strem and something based on mechanical updating is precisely the possibility of merge flows, modifying them, filtering them, and if you compare the Workers functionalities for example, you can see that there is an abyss between an approach and another. One is much more powerful (in fact), and allows you to do a few dozen more things, and abstracts a lot that if done manually, it would take a long time. I usually say that anything is scalable, the difference is the way you take to make something scalable. You can use InheritedModel and scale, the difference is in the amount of time and human resources to do this. I understand that you want to put the two on an equal footing, but that would bring some little overhead to GetBuilder too (in fact I would have to create another widget for that), I do not rule out this possibility in the future, but I think that maybe that is not so priority for now. I don't know how many fans GetBuilder currently has, I will open communities in slack and others social medias to be more demicratic about creating new resources.

esDotDev commented 4 years ago

Ok, if it's near end of life or to be deprecated I understand. If no one is using it, then making it a tiny bit slower will not matter anyways :p Also maybe no-one is using it precisely because it's not really scalable in production? Or maybe I am just a weirdo and everyone but me loves the steam-based approach.

Also thx for the explanation on benefits of Streams, I need to learn more about that.

As a final note. just consider some code like this:

return GetBuilder<A>(
      init: Get.find(),
      builder: (a) => GetBuilder<B>(
        init: Get.find(),
        builder: (b) => GetBuilder<C>(
          init: Get.find(),
          builder: (c) => ShowStuff(a.list1, b.list2, c.list3),
        ),
      ),
    );

vs

A a = Get.bind(context);
B b = Get.bind(context);
C c = Get.bind(context);
return ShowStuff(a.list1, b.list2, c.list3);
jonataslaw commented 4 years ago
return GetBuilder<A>(
      init: Get.find(),
      builder: (a) => GetBuilder<B>(
        init: Get.find(),
        builder: (b) => GetBuilder<C>(
          init: Get.find(),
          builder: (c) => ShowStuff(a.list1, b.list2, c.list3),
        ),
      ),
    );

Have you taken a look at the Bindings api? I don't nest anything that way, I prefer to declare controllers in a Binding, and use them directly, and just type GetBuilder without init.

jonataslaw commented 4 years ago

Well, I think I made my position on using the library clear. There is simple and reactive management to be used together. If you need to reconstruct data only when the variable is changed, GetX does that. So there is no reason why I should inflate GetBuilder. You can use MixinBuilder to update this specific variable, and update everything else with update(), so this feature doesn't make any sense in the current scenario. context.watch also doesn't make much sense, it is an alias for Provider.of(context), Get doesn't use InheritedWidget for literally anything, and to find the data you have Get.find. There are still Bindings to initialize the dependencies, and to avoid multiple dazzles, in fact, with an Obx, you can use 300 Get.find pointing to 300 different controllers without needing a single pipeline, which is even more beneficial than the watch as it does not rebuild the entire parent widget. That way, I advise you to study the documentation to learn how to get the most out of state management, as this issue is basically a matter of adequacy. All Provider features are present, and a few dozen other features, Selector is not necessary, because Obx only rebuilds what is necessary, MixinBuilder too, so there is no single reason why you want to do this:

Selector<CounterProvider, int>(
builder: (context, data,child) {
return Text('$data');
}, selector: (buildContext , countPro)=>countPro.getCount,
),

Instead of doing just that:

Obx(()=>Text(controller.getCount));

This is a horrible approach.

return GetBuilder<A>(
      init: Get.find(),
      builder: (a) => GetBuilder<B>(
        init: Get.find(),
        builder: (b) => GetBuilder<C>(
          init: Get.find(),
          builder: (c) => ShowStuff(a.list1, b.list2, c.list3),
        ),
      ),
    );

It would be much better if you just do this:

A a = Get.find();
B b = Get.find();
C c = Get.find();

MixinBuilder<A>(builder:(a)=>ShowStuff(a.list1, b.list2, c.list3));

There is simple and reactive management to be used together. If you need to reconstruct data only when the variable is changed, GetX does that. So there is no reason why I should inflate GetBuilder with a selector, which is much more verbose, and an even visually ugly approach. You can use MixinBuilder to update this specific variable, and update everything else with update (), so this feature doesn't make any sense in the current scenario. context.watch also doesn't make sense, it is an alias for Provider.of (context), Get doesn't use InheritedWidget for literally anything, and to find the data you have Get.find . There are still Bindings to initialize the dependencies, and to avoid multiple dazzles, in fact, with an Obx, you can use 300 Get.find pointing to 300 different controllers without needing a single pipeline, which is even more beneficial than the watch as it does not rebuild the entire parent widget. That way, I advise you to study the documentation to learn how to get the most out of state management, as this issue is basically a matter of adequacy. All Provider features are present, and a few dozen other features, Selector is not necessary, because Obx only rebuilds what is necessary, MixinBuilder too, so there is no single reason why you want to do this:

If you need a widget to rebuild itself when N variables change, you should keep in mind that GetX is best suited for your use, and using any other approach is to reinvent the wheel. I added 2 types of state managers to the library, it was out of necessity, not a matter of "personal taste". I know you have your reservations for using reactive programming, so do I, but that is simply not justified, because MixinBuilder rebuilds itself with update (), and also when any .obs variable within it changes.

Having explained all this, I am closing this issue, since neither of the two resources mentioned are useful for this library, and it already has much better native approaches.

esDotDev commented 4 years ago

Neither of the 2 resources are useful for this library? I think you are a little too defensive about seeing the word Provider. It has absolutely nothing to do with the library in question, those are syntax examples.

If you just added:

  1. A 1-liner to bind a widget to controller update
  2. A selector

You could actually use GB for 99% of use cases, just like people do with a simple ChangeNotifier and Watch/Select. Yes I realize it would not be suitable for 300 controllers, in whatever universe that matters. Personally don't see much point in optimizing for benchmark queen use cases that never manifest in reality.

But it's your lib. You've obviously worked yourself into this corner where this weird rationale about simplicity and complexity in relation to Streams and Callbacks, which have basically nothing to do with eachother. And decided to completely neuter the non-stream based approach by making it verbose and useless for complex models.

I never thought for a second you would be resistant to eliminating the builder in favor of an optional 1-liner that could then enable composition w/o nesting. It's so obviously needed and so damn easy to add. Too bad.

esDotDev commented 4 years ago
A a = Get.find();
B b = Get.find();
C c = Get.find();

MixinBuilder<A>(builder:(a)=>ShowStuff(a.list1, b.list2, c.list3));

This does not rebuild if B or C list is changed. Not sure why you would suggest this as a functioning alternative.

jonataslaw commented 4 years ago
A a = Get.find();
B b = Get.find();
C c = Get.find();

MixinBuilder<A>(builder:(a)=>ShowStuff(a.list1, b.list2, c.list3));

This does not rebuild if B or C list is changed. Not sure why you would suggest this as a functioning alternative.

This will rebuild, as long as the list has a .obs at the end.

If you don't really like streams, I built something to be reactive, without streams, I don't use ChangeNotifier on it, but it has a very similar approach (VoidCallbacks HashSet). I actually consulted other users on the Facebook and Telegram Group, and none of them supported the idea of "tuning" the Simples State Manager. This was not an isolated decision of mine, and it is not protectionism, I am just not going to displease most users who want to have simple and light station management only when they need something really simple, where GetX would be overkill. This seems to be the general idea of the community, so as much as I fully understand your point, I cannot go in that direction. Well, I can go ahead with something separate, like this other project, and maybe in the future merge it in some way with GetX, but I can't change GetBuilder now, because I have another thousand users who would hate that change.

https://github.com/jonataslaw/easy

esDotDev commented 4 years ago

Ah, that is a neat feature with the sneaky rebuilds :)

I understand that you don't want to break existing API surface. I feel like you could easily use extensions or something to do this without a breaking change, but fair enough. It's not like making GetBuilder less clunky means they can not still do it the clunky way if they want. The 1-liner binding's really are nice in practice and reduce nesting a lot.

Obx is really nice of course, and there are many easy ways to bind to setState in flutter, this was more a philosophical argument.

esDotDev commented 4 years ago

https://github.com/jonataslaw/easy

Wow, thx for building this, very cool approach. Excited to dig in.