Closed samuelstroschein closed 3 years ago
Yes, I agree the On.
can be more flexible, how about?
// In simple syntax
password.onData((state) => Text(state));
password.whenAll(onIdle: ..., onWaiting:)
password.whenAny(...)
// In widget-like syntax
On(
password,
child: When.all(
onIdle: null, // It'd be onData, then onWaiting if no data
onError: (err, refresh) => Text('$err'),
onData: () => Text('On Data'),
onWaiting: () => const CircularProgressIndicator(),
),
);
OnCombined(
[oldPassword, newPassword],
child: When.any(
onData: () => Text('On Data'),
onWaiting: () => const CircularProgressIndicator(),
),
);
On.future(()=> future(), child: When.onData(...) )
@amoslai5128 I assume you want to move the rebuild logic to the child parameter because an On widget can return multiple children?
// In widget-like syntax On( password, child: When.all( // child 1 onIdle: null, // It'd be onData, then onWaiting if no data // child 2 onError: (err, refresh) => Text('$err'), // child 3 onData: () => Text('On Data'), // child 4 onWaiting: () => const CircularProgressIndicator(), ), );
The more I look at the syntax the more I like it. However, the when syntax only shines for the all
use case right? Because for example When.data
only takes one child.
And I do like the On.all
/ On.future
syntax. Probably child
is only good for On
widgets that have one child widgets and all other On
widgets keep the current syntax like so:
// generic On widget without handling context
On(
password,
child: () => Text('On Data')
)
// generic On widget with handling context
// using it as an example because developers new to states rebuilder will
// replicate the FutureBuilde/ StreamBuilder sytax.
On(
password,
// probably pass the state as parameter in child?
child: () {
if (password.isWaiting){
return CircularProgressIndicator();
} else if (password.hasError){
return Text('something went wrong');
}
return Text('On Data')
}
)
// all widget
On.all(
password,
// onIdle: what is the use-case for idle in a widget tree?
onError: (err, refresh) => Text('$err'),
onWaiting: () => const CircularProgressIndicator(),
onData: Text('On Data')
)
And since we are at it, why is onIdle beneficial in a widget tree? Wouldn't the developer always like to show the onData widget in that case?
The more I look at the syntax the more I like it. However, the when syntax only shines for the
all
use case right? Because for exampleWhen.data
only takes one child. @samuelstroschein
Yes, the syntax looks lovely from your suggestions, making the child
generic makes sense.
On the other hand, I think most of the time, the default side effect could be defined into RM.Inject
, so in Widget
side should often use for listening to the state onData changes only.
Therefore, using On(foo, child: ()=>Text(foo.state.toString()));
, the On looks a little bit confusing when I got a common side effect object of On.all
passing it into multiple RM.injected, it may have a better naming?
How about On(foo, child: ()=> Text(...)).whenAny(waiting: ()=> const LinearProgressIndicator());
?
final commonOnSideEffect = On.any(
onIdle: () => const SizedBox(),
onWaiting: () => const CircularProgressIndicator(),
onError: (error) => showErrorDialog(error),
);
final Injected<Foo> foo = RM.inject<Foo>(
()=> Foo(),
onInitialized : (Foo state) => print('Initialized'),
// Default callbacks for side effects.
onSetState: commonOnSideEffect,
);
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// In your suggestion, If we only listen to onData
return On(foo, child: ()=> Text(foo.state.toString()));
// Not ideal if we want to override side effect , does it override the onWaiting at the parent?
return On(foo, child: ()=> foo.isWaiting ? const LinearProgressIndicator() : Text(foo.state.toString()));
// Better idea 1
return On(foo, child: ()=> Text(...)).whenAny(waiting: ()=> const LinearProgressIndicator(), error: ..., idle: ... );
// Better idea 2
return When.any(foo, waiting: ()=> const LinearProgressIndicator(), child: ()=> Text(...));
return WhenCombined.any([foo1, foo2], waiting: ()=> const LinearProgressIndicator(), child: ()=> Text(...));
// Sugar
return foo.whenAny(waiting: ()=> const LinearProgressIndicator(), child: ()=> .... );
return [foo1, foo2].whenAny(waiting: ()=> const LinearProgressIndicator(), child: ()=> .... );
}
}
I think onIdle
is somehow in a default condition when the state has not been initialized, if no onIdle
& no onData
it'd be showing onWaiting
widget. What do you think? @GIfatahTH
@samuelstroschein
Another day, another suggestion.
Your welcome; your day makes my day.
The On widget moves rebuilding logic at the end of the widget, opposite to the previous function syntax. That becomes problematic the longer a widget tree becomes:
Yes. I was aware of this downside when building the On API.
All your proposals were on my table and I see very well the validity of your arguments. I like them all but I don't like breaking changes.
// In simple syntax password.onData((state) => Text(state)); password.whenAll(onIdle: ..., onWaiting:) password.whenAny(...)
I see that the idea of @amoslai5128 is a good point to start with.
How about
On(foo, child: ()=> Text(...)).whenAny(waiting: ()=> const LinearProgressIndicator());
?
Fabulous but the syntax moves a lot of valuable information which is not related to the Text widget at the end of the widget tree again. Something that prevents me from using the On widget.
How about:
On(
foo,
shouldRebuild: (snapState) {
// rebuild logic
},
when: When.any(
waiting: () => const LinearProgressIndicator(),
),
child: () => Text('...'),
)
On(
foo,
when: When.waiting(
() => const LinearProgressIndicator(),
),
child: () => Text('...'),
)
Whereas the when parameter is optional. So one could write:
On(
foo,
child: () => Text('...'),
)
Another benefit that this syntax is more "explorable" compared to On().when()
because the IDE makes clear that a when parameter can be passed. While one would have to know by reading the docs that On().when()
is a possibility.
Offtopic
Shouldn't shouldRebuild()
yield the middleSnapState instead of the snapState? Reason being that the middleSnapState allows comparing the current to the next state state. Even if most devs don't make use of the middleSnapState, theMIddleSnapState contains the "snapState"/next state. Should we open an issue about that too?
How about
On(foo, child: ()=> Text(...)).whenAny(waiting: ()=> const LinearProgressIndicator());
?Fabulous but the syntax moves a lot of valuable information which is not related to the Text widget at the end of the widget tree again. Something that prevents me from using the On widget.
How about:
On( foo, shouldRebuild: (snapState) { // rebuild logic }, when: When.any( waiting: () => const LinearProgressIndicator(), ), child: () => Text('...'), )
On( foo, when: When.waiting( () => const LinearProgressIndicator(), ), child: () => Text('...'), )
Whereas the when parameter is optional. So one could write:
On( foo, child: () => Text('...'), )
Another benefit that this syntax is more "explorable" compared to
On().when()
because the IDE makes clear that a when parameter can be passed. While one would have to know by reading the docs thatOn().when()
is a possibility.Offtopic Shouldn't
shouldRebuild()
yield the middleSnapState instead of the snapState? Reason being that the middleSnapState allows comparing the current to the next state state. Even if most devs don't make use of the middleSnapState, theMIddleSnapState contains the "snapState"/next state. Should we open an issue about that too?
Beautiful, this is the syntax that I'm looking for. The When.any / When.all
just like a side effect module can be re-used for different injected states, making the code cleaner and easier maintenance.
Base On
a state, When
any situation (covering by side effect), finally, child
represents the body of expectation.
On
vs BaseOn
what do you think?
I like On
more because it's shorter. But besides, BaseOn
should be BasedOn
then it reads like "based on state do X" while "base on state" sounds like something is build on top of the state but not reacting on it.
On( foo, when: When.any( () => const LinearProgressIndicator(), ), child: () => Text('...'), )
What is the role of When
here, is it for side effects or for widget rebuild? If it is for widget rebuild, the code above is not clear on what will be rendered?
Please, zoom out for the big picture to see all use cases and their positive and negative sides.
The reason why I like the On
api, for me as a library maintainer, is its flexibility for adding dedicated rebuild widget for the custom Injected state: On.waiting
, On.all
, On.any
, On.auth
, On.crud
, On.animation
, On.form
, On.tab
(will be added soon for TabController),
If the only downside is moving the state at the end of the widget tree, I think that extension as proposed by @amoslai5128 is the way to go with.
On( foo, when: When.any( () => const LinearProgressIndicator(), ), child: () => Text('...'), )
What is the role of
When
here, is it for side effects or for widget rebuild? If it is for widget rebuild, the code above is not clear on what will be rendered?
True, When.any
is confusing/should not exist. It's for widget rebuilds. So When.waiting
, When.error
would have been more appropriate.
The reason why I like the On api, for me as a library maintainer, is its flexibility for adding dedicated rebuild widget for the custom Injected state: On.waiting, On.all, On.any, On.auth, On.crud, On.animation, On.form, On.tab (will be added soon for TabController),
I find On
better too. On.future(() -> myFuture)
reads intuitively. Probably On
could be On.state(() => injectedState)
.
If the only downside is moving the state at the end of the widget tree, I think that extension as proposed by @amoslai5128 is the way to go with.
Sure, that would improve the readability and would be good enough. I just observed that
However, the when syntax only shines for the all use case right? Because for example When.data only takes one child.
I was thinking a bit ahead here because in my mind When.data would have been a thing like so:
// That seemed redundant.
On(
fooState,
child: When.data(
() => Text('On Data'),
),
);
But of course, why would When.data
exist? It should be When.any(data: () => child)
and comparing @amoslai5128 proposal to mine e.g. On(state, child: When())
definitely reads better than On(state, when: When(), child: Widget)
. The latter one is indeed misleading what the actual child is.
I don't want to spam issues in this repo but what are your thoughts on:
Shouldn't shouldRebuild() yield the middleSnapState instead of the snapState? Reason being that the middleSnapState allows comparing the current to the next state state. Even if most devs don't make use of the middleSnapState, theMIddleSnapState contains the "snapState"/next state. Should I open another issue about that?
@ amoslai5128 @samuelstroschein from your fruitful discussion, I'm sure we will end with the best solution.
Offtopic
I don't want to spam issues in this repo but what are your thoughts on:
Shouldn't shouldRebuild() yield the middleSnapState instead of the snapState? Reason being that the middleSnapState allows comparing the current to the next state state. Even if most devs don't make use of the middleSnapState, theMIddleSnapState contains the "snapState"/next state. Should I open another issue about that?
I will check the feasibility.
@amoslai5128 @samuelstroschein from your fruitful discussion, I'm sure we will end with the best solution.
That's great!
All your proposals were on my table and I see very well the validity of your arguments. I like them all but I don't like breaking changes.
I agree with you that the library should try to be as consistent as possible, a smooth learning curve can persuade people to create tutorials on youtube or medium.
I come back after a period of vacation followed by another period of illness.
What do you think about this:
Alongside with:
On(
() => Text('Data'),
).listenTo(
fooState,
initState: () {
print('initState');
},
);
We can also use
fooState.on(
() => Text('Data'),
initState: () {
print('initState');
},
);
Instead of
On.all(
onIdle: () => Text('OnIdle'),
onWaiting: () => Text('onWaiting'),
onError: (e, refresh) => Text('onError'),
onData: () => Text('OnData'),
).listenTo(
fooState,
debugPrintWhenRebuild: '',
);
fooState.onAll(
onIdle: () => Text('OnIdle'),
onWaiting: () => Text('onWaiting'),
onError: (e, refresh) => Text('onError'),
onData: () => Text('OnData'),
debugPrintWhenRebuild: '',
);
SimilaryOn.or
, On.auth
, On.curd
, On.animation
, On.form
, ... will be fooState.OnOr
, fooState.onAuth
, fooState.onCrud
, fooState.onAnimation
, fooState.onForm
...
What do think? Do you have a better naming convention?
I come back after a period of vacation followed by another period of illness.
What do you think about this:
Alongside with:
On( () => Text('Data'), ).listenTo( fooState, initState: () { print('initState'); }, );
We can also use
fooState.on( () => Text('Data'), initState: () { print('initState'); }, );
Instead of
On.all( onIdle: () => Text('OnIdle'), onWaiting: () => Text('onWaiting'), onError: (e, refresh) => Text('onError'), onData: () => Text('OnData'), ).listenTo( fooState, debugPrintWhenRebuild: '', );
fooState.onAll( onIdle: () => Text('OnIdle'), onWaiting: () => Text('onWaiting'), onError: (e, refresh) => Text('onError'), onData: () => Text('OnData'), debugPrintWhenRebuild: '', );
Similary
On.or
,On.auth
,On.curd
,On.animation
,On.form
, ... will befooState.OnOr
,fooState.onAuth
,fooState.onCrud
,fooState.onAnimation
,fooState.onForm
...What do think? Do you have a better naming convention?
Yes, offering two approaches look lovely. However, the On
& state.on
may look confusing to the newcomers to understand on
is for rebuilding the side-effects or widget.
Yes, offering two approaches look lovely. However, the On & state.on may look confusing to the newcomers to understand on is for rebuilding the side-effects or widget.
That was my impression too. What you propose?
What about state.rebuild((){})
, state.rebuild.onData
, state.rebuild.onAll
, state.rebuild.onAnimation
, ...
Yes, offering two approaches look lovely. However, the On & state.on may look confusing to the newcomers to understand on is for rebuilding the side-effects or widget.
That was my impression too. What you propose?
What about
state.rebuild((){})
,state.rebuild.onData
,state.rebuild.onAll
,state.rebuild.onAnimation
, ...
I had a very similar thought today, if there is a panacea for saving maintenance cost from now on it'd be worth a breaking change. My thought was:
Widget builder only:
1. Rebuilder(state: stateA, widget: ()=> ....);
MultiRebuilder(states: [stateA, stateB], widget: ()=> ....);
2. state.rebuilds(widget: ()=> Text());
[stateA, stateB].rebuild(widget: ()=> ..., buildWhen: () => .... );
Side-Effect listener only:
1. StateListener(state: stateA, onState: (BuildContext context, On on) => ... )
MultiStateListener(states: [...], onState: (BuildContext context, On on) => ... )
2. state.listens() / [stateA, stateB].listens(onState: (context, On on) => )
Mixed (widget & side-effect in one):
1. state.consumes(onState: On.Waiting(()=> showIndicator()) , builder: (data) ... , buildWhen: )
The reason I put the On
inside the class constructor, it can be more flexible during the development, also benefits for those come from flutter_bloc
.
state.listens(
// Action for side-effects like navigation, snackbar
onState: (BuildContext context, On on) {
return on.Error((e) => showSnackbar(e));
}
);
UPDATE: It should be StateListener
@GIfatahTH @samuelstroschein The last shot of breaking change to win people back, what do you think?
What do you think about this:
Alongside with:
On( () => Text('Data'), ).listenTo( fooState, initState: () { print('initState'); }, );
We can also use
fooState.on( () => Text('Data'), initState: () { print('initState'); }, );
The "problem" with the
On
widget remains. In a longer widget tree, it's not clear whatOn
is reacting to (if buried somewhere a the end of the widget tree between endless parentheses and potentially other.listenTo
). I don't mind the current API in regards to naming or consistency.Widget builder only:
1. Rebuilder(state: stateA, widget: ()=> ....); MultiRebuilder(states: [stateA, stateB], widget: ()=> ....); 2. state.rebuilds(widget: ()=> Text()); [stateA, stateB].rebuild(widget: ()=> ..., buildWhen: () => .... );
Why would two widget builders be beneficial instead of just one?
The reason I put the On inside the class constructor, it can be more flexible during the development, also benefits for those > come from flutter_bloc.
That means that the widget
parameter takes the On class?
Rebuilder(state: stateA, widget: ()=> On.data(s) => ...);
The reason I put the On inside the class constructor, it can be more flexible during the development, also benefits for those > come from flutter_bloc.
That means that the
widget
parameter takes the On class?
Rebuilder(state: stateA, widget: ()=> On.data(s) => ...);
How about this?
StateConsumer(state: stateA ....)
// Or extension style
state.consumes(
// For doing some expensive side-effects like navigation, snackbar
onState: (BuildContext context, On on) {
return on.Error((e, refresh) => showSnackbar(e));
}
// For rebuilding widget only
widget: (On on)=> on.All(
waiting: ()=> IndictorWidget(),
error: (error, refresh)=> ErrorWidget(),
data: (data)=> ListView(...)
),
);
@amoslai5128
StateConsumer(state: stateA ....)
// Or extension style
state.consumes( // For doing some expensive side-effects like navigation, snackbar onState: (BuildContext context, On on) { return on.Error((e, refresh) => showSnackbar(e)); } // For rebuilding widget only widget: (On on)=> on.All( waiting: ()=> IndictorWidget(), error: (error, refresh)=> ErrorWidget(), data: (data)=> ListView(...) ), );
Without any breaking change, we can get the same thing using:
//Here it is cear that state triggers the rebuild process.
state.rebuild.onAll(
// For doing some expensive side-effects like navigation, snackbar
onSetState: () {
return on.Error((e, refresh) => showSnackbar(e));
}
// For rebuilding widget only
waiting: ()=> IndictorWidget(),
error: (error, refresh)=> ErrorWidget(),
data: (data)=> ListView(...)
);
@samuelstroschein
The "problem" with the On widget remains. In a longer widget tree, it's not clear what On is reacting to (if buried somewhere a the end of the widget tree between endless parentheses and potentially other .listenTo). I don't mind the current API in regards to naming or consistency.
I don't see that. The new proposed API is similar to the existing fooState.rebuilder method (Which will be depricated if the the new API is adapted).
Look at this:
password.rebuild(
() {
//No matter how the long the widget is, It's clear that fooState is trigiring the rebuild when it is notified.
}
);
It works for all On named constructor:
fooState.rebuild((){})
, fooState.rebuild.onData
,fooState.rebuild.onAll
,fooState.rebuild.onAnimation
, ...
I think it's necessary to separate into side-effect and widgets.
Some people may confuse state.rebuild.onAll(
with onSetState
.
@samuelstroschein
The "problem" with the On widget remains. In a longer widget tree, it's not clear what On is reacting to (if buried somewhere a the end of the widget tree between endless parentheses and potentially other .listenTo). I don't mind the current API in regards to naming or consistency.
I don't see that. The new proposed API is similar to the existing fooState.rebuilder method (Which will be depricated if the the new API is adapted).
Look at this:
password.rebuild( () { //No matter how the long the s widget is, It's clear that fooState is trigiring the rebuild when it is notified. } );
It works for all On named constructor:
fooState.rebuild((){})
,fooState.rebuild.onData
,fooState.rebuild.onAll
,fooState.rebuild.onAnimation
, ...
Sure, but that's not the On
widget. That's a function in a widget tree that returns a widget. By no means is that problematic but using the On
widget in a widget tree makes the syntax more "flutterish" (including the syntax highlighting). It's a minor argument really but I thought the On
widget is going to replace functions in widget trees. That does not seem to be the case which raises the question of why states rebuilder should provide two different ways to achieve the same thing (even though the API is similar)?
After taking the time to think about this question, I am still hesitant to move on to breaking changes without plausible benefits.
What I can do now is set for each On.foo(...).listen(state)
widget a state.rebuild.onFoo(...)
method and let everyone choose to use whatever they prefer.
Here's what we have:
//Widget-like
On(
() => Text('${counter.state}'),
).listenTo(counter),
//Method-like
counter.rebuild(
() => Text('{counter.state}'),
),
//
//Widget-like
On.data(
() => Text('${counter.state}'),
).listenTo(counter),
//Method-like
counter.rebuild.onData(
(data) => Text(data),
),
//Widget-like
On.all(
onIdle: () => Text('onIdle'),
onWaiting: () => Text('onWaiting'),
onError: (err, errorRefresh) => Text('onError'),
onData: () => Text('{counter.state}'),
).listenTo(counter),
//Method-like
counter.rebuild.onAll(
onIdle: () => Text('onIdle'),
onWaiting: () => Text('onWaiting'),
onError: (err, errorRefresh) => Text('onError'),
onData: () => Text('{counter.state}'),
),
//
//Widget-like
On.or(
onWaiting: () => Text('onWaiting'),
or: () => Text('{counter.state}'),
).listenTo(counter),
//Method-like
counter.rebuild.onOr(
onWaiting: () => Text('onWaiting'),
or: () => Text('{counter.state}'),
),
//Widget-like
On.auth(
onUnsigned: () => Text('Sign in/ up page'),
onSigned: () => Text('Home page'),
).listenTo(
user,
useRouteNavigation: true,
),
//Method-like
user.rebuild.onAuth(
onUnsigned: () => Text('Sign in/ up page'),
onSigned: () => Text('Home page'),
useRouteNavigation: true,
),
//Widget-like
On.crud(
onWaiting: () => Text('onWaiting'),
onError: (err, refreshError) => Text('onError'),
onResult: (result) => Text('onResult'),
).listenTo(products),
//Method-like
products.rebuild.onCRUD(
onWaiting: () => Text('onWaiting'),
onError: (err, refreshError) => Text('onError'),
onResult: (result) => Text('onResult'),
),
//Widget-like
On.animation((animate) {
return Container(
width: animate.fromTween((_) => Tween(begin: 0, end: 100)),
);
}).listenTo(myAnimation),
//Method-like
myAnimation.rebuild.onAnimation((animate) {
return Container(
width: animate.fromTween((_) => Tween(begin: 0, end: 100)),
);
}),
//Widget-like
On.form(
() => TextField(
controller: userName.controller,
),
).listenTo(form),
//Method-like
form.rebuild.onForm(
() => TextField(
controller: userName.controller,
),
),
//
//Widget-like
On.formSubmission(
onSubmitting: () => Text('onSubmitting'),
child: Text('child'),
).listenTo(form),
//Method-like
form.rebuild.onFormSubmission(
onSubmitting: () => Text('onSubmitting'),
child: Text('child'),
),
//Widget-like
On.scroll(
(scroll) => Text('onScroll'),
).listenTo(scroller),
//Method-like
scroller.rebuild.onScroll(
(scroll) => Text('onScroll'),
),
//Widget-like
Scaffold(
body: On.tab(
() {
return TabBarView(
controller: myTab.controller,
children: screens,
);
},
).listenTo(myTab),
bottomNavigationBar: On.tab(
() => TabBar(
controller: myTab.controller,
tabs: tabs,
),
).listenTo(myTab),
),
//Method-like
Scaffold(
body: myTab.rebuild.onTab(
() {
return TabBarView(
controller: myTab.controller,
children: screens,
);
},
),
bottomNavigationBar: myTab.rebuild.onTab(
() => TabBar(
controller: myTab.controller,
tabs: tabs,
),
),
),
If you don't see enough justification for a breaking change, don't do it. It's a minor syntax "issue" which I thought could be easily fixed by making listenTo
the first parameter instead of a suffix. Since the adoption of a state management solutions boils down to "how easy to use is it/how clean is the API?" I thought proposing the change might be helpful.
The solution you are proposing is not solving the issue I raised.
@samuelstroschein @amoslai5128
What about this:
final myState = 0.inj();
//In the widget tree
OnBuilder(
observe: myState,
child: On.all(
onIdle: () => Text('onIdle'),
onWaiting: () => Text('onWaiting'),
onError: (err, refreshError) => Text('onError'),
onData: () => Text(myState.state.toString()),
),
sideEffect: SideEffect(
initState: () => print('initState'),
onSetState: On(() => print('onSetState')),
onAfterBuild: On(() => print('onAfterBuild')),
dispose: () => print('dispose'),
),
shouldRebuild: (oldSnap, newSnap) {
return true;
},
debugPrintWhenRebuild: '',
),
If you want to observe many states, just use observeMany
parameter:
OnBuilder(
observeMany: [myState1, myState2],
child: On.all(
onWaiting: () => Text('onWaiting'), // Will be invoked if at least one state is waiting
onError: (err, refreshError) => Text('onError'), // Will be invoked if at least one state has error
onData: () => Text(myState.state.toString()), // Will be invoked if all states have data.
),
sideEffect: SideEffect(
initState: () => print('initState'),
onSetState: On(() => print('onSetState')),
onAfterBuild: On(() => print('onAfterBuild')),
dispose: () => print('dispose'),
),
/// shouldRebuild will take the oldSnap before mutation and the newSanp after mutation in the parameter
shouldRebuild: (oldSnap, newSnap) {
return true;
},
debugPrintWhenRebuild: '',
),
Notice that shouldRebuild
will take the oldSnap
before mutation and the newSanp
after mutation in the parameter as requested by @samuelstroschein
I think OnBuilder
and its friends OnFooBuilder
(OnAnimationBuilder
, OnAuthBuilder
, OnFormBuilder
, ...) satisfy what you have requested,Don't they?
OnBuilder(
listenTo: counter,
builder: On(() => Text('${counter.state}')),
sideEffect: SideEffect(
initState: () => print('initState'),
onSetState: On(() => print('onSetState')),
onAfterBuild: On(() => print('onAfterBuild')),
dispose: () => print('dispose'),
),
shouldRebuild: (oldSnap, newSnap) {
return true;
},
debugPrintWhenRebuild: 'counter',
),
OnBuilder(
listenTo: counter,
builder: On.data(
() => Text('${counter.state}'),
),
),
OnBuilder(
listenTo: counter,
builder: On.all(
onIdle: () => Text('onIdle'),
onWaiting: () => Text('onWaiting'),
onError: (err, errorRefresh) => Text('onError'),
onData: () => Text('{counter.state}'),
),
),
OnBuilder(
listenTo: counter,
builder: On.all(
onWaiting: () => Text('onWaiting'),
or: () => Text('{counter.state}'),
),
),
OnAuthBuilder(
listenTo: user,
onUnsigned: () => Text('Sign in/ up page'),
onSigned: () => Text('Home page'),
useRouteNavigation: true,
),
OnCRUDBuilder(
listenTo: products,
onWaiting: () => Text('onWaiting'),
onError: (err, refreshError) => Text('onError'),
onResult: (result) => Text('onResult'),
),
OnAnimationBuilder(
listenTo: myAnimation
builder: (animate) {
return Container(
width: animate.fromTween((_) => Tween(begin: 0, end: 100)),
);
}
),
OnFormBuilder(
listenTo: myForm
builder:() => TextField(
controller: userName.controller,
),
),
//
OnFormSubmissionBuilder(
listenTo: myForm
onSubmitting: () => Text('onSubmitting'),
child: Text('child'),
),
OnScrollBuilder(
listenTo: scroller,
builder: (scroll) => Text('onScroll'),
),
Scaffold(
body: OnTabBuilder(
listenTo: myTab,
builder : () {
return TabBarView(
controller: myTab.controller,
children: screens,
);
},
),
bottomNavigationBar:
OnTabBuilder(
listenTo: myTab,
builder :() => TabBar(
controller: myTab.controller,
tabs: tabs,
),
),
),
Yes, they do! Although the "builder" seems unnecessary. On
, OnScroll
, OnForm
reads nicer. But I see that dropping builder would conflict with On.data
etc. in the case of On
and potentially mixed up with OnForm
-> On.form
.
the "builder" seems unnecessary. On, OnScroll, OnForm reads nicer. But I see that dropping builder would conflict with On.data etc. in the case of On and potentially mixed up with OnForm -> On.form.
Yes, I add the Builder suffix, to avoid conflicts with already existing classes. On the other hand, adding Builder makes the API more Flutterish.
the "builder" seems unnecessary. On, OnScroll, OnForm reads nicer. But I see that dropping builder would conflict with On.data etc. in the case of On and potentially mixed up with OnForm -> On.form.
Yes, I add the Builder suffix, to avoid conflicts with already existing classes. On the other hand, adding Builder makes the API more Flutterish.
Yes, it looks Flutterish and friendly to a flutter developer. BTW, I'm thinking about On.
can be in another form.
// The extension after OnState is following the type of RM.inject.
final counterA = RM.inject<int?>(() => 0);
final counterB = RM.inject<int?>(() => 0);
final formState = RM.injectForm();
final scrollState = RM.injectScrolling();
....
// Widget Builder
OnState([counterA, counterB])
.builder(
idle: () => DefaultWidget(),
waiting: () => Indicator(),
error: (error, refresh) => ErrorWidger(),
data: () => Text(counter.state), // !! Only this is required
)
.settings( // (Override) Side-effects or State setting, sync to other states...
rebuildWhenAllOnData: false, // countA || counterB onData will trigger to rebuild.
initState: () => print('initState'),
onSetState: On(() => print('onSetState')),
),
// Widget Builder
OnState(formState).formBuilder(() => TextField(counter.state)),
// Scroll Builder
OnState(scrollState).scrollBuilder(
builder: (scroll) => ScrollBar(controller: ....),
)
Auto formated in VSCode:
So they won't make any breaking changes and are easy to wrap with other widgets what do you think? @GIfatahTH , @samuelstroschein
@amoslai5128 Did you try it? Or it is an imaginary scenario.
BTW, I successfully implemented OnObs
widget that is similar to Observer
in mobx and Obx
in getx packages where subscription to injected state is done implicitly.
final counter1 = RM.inject(() => 0);
final counter2 = 0.inj();
//Works with getter
int get counter3 => counter1.state + counter2.state;
// In the widget tree
OnObs(
() {
return Column(
children: [
Text('${counter1.state}'),
Builder(
builder: (_) {
// Even its deep inside the child widget tree, counter2 is
// subscribed to the OnObs. (Not possible with getX)
return Text('${counter2.state}');
},
),
// Captures the dependency of getters. When ever counter3 changes, the widget is rebuilt.
Text('$counter3'),
],
);
},
);
You can also use state status flags:
final counter = RM.inject(() => 0);
//In the widget tree
OnObs(
() {
if (counter.isWaiting) {
return Text('isWaiting');
}
if (counter.hasError) {
return Text('Error');
}
return Text('${counter.state}');
},
);
//Or you can use onAll or onOr methods:
OnObs(
() {
return counter.onAll(
onWaiting: () => Text('isWaiting'),
onError: (err, refreshErr) => Text('Error'),
onData: (data) => Text('$data'),
);
},
);
OnObs(
() {
return counter.onOr(
onWaiting: () => Text('isWaiting'),
or: (data) => Text('$data'),
);
},
);
What do you think?
So they won't make any breaking changes and are easy to wrap with other widgets what do you think? @GIfatahTH , @samuelstroschein
Hmmm, while it does have an appeal, it's yet another way to use states (rebuilder).
BTW, I successfully implemented OnObs widget that is similar to Observer in mobx and Obx in getx packages where subscription to injected state is done implicitly.
This is great! Are there any downsides? I would be keen to wrap every widget with OnObs
and drop On
and state.rebuilder()
entirely. A better name for OnObs
would probably be Reactive
.
@samuelstroschein
Why not OnReactive
orOnRx
, because I want the API to be more intuitive so that when the user taps On
, the IDE displays all the available widgets to the choice.
BTW, your issues always push me to think beyond the limit I set for myself.
This is great! Are there any downsides?
Until now I don't see any. Even the limitation of GetX to not use a HeavyWidget inside one Obx, I get rid of it.
@amoslai5128 Did you try it? Or it is an imaginary scenario.
BTW, I successfully implemented
OnObs
widget that is similar toObserver
in mobx andObx
in getx packages where subscription to injected state is done implicitly.final counter1 = RM.inject(() => 0); final counter2 = 0.inj(); //Works with getter int get counter3 => counter1.state + counter2.state; // In the widget tree OnObs( () { return Column( children: [ Text('${counter1.state}'), Builder( builder: (_) { // Even its deep inside the child widget tree, counter2 is // subscribed to the OnObs. (Not possible with getX) return Text('${counter2.state}'); }, ), // Captures the dependency of getters. When ever counter3 changes, the widget is rebuilt. Text('$counter3'), ], ); }, );
You can also use state status flags:
final counter = RM.inject(() => 0); //In the widget tree OnObs( () { if (counter.isWaiting) { return Text('isWaiting'); } if (counter.hasError) { return Text('Error'); } return Text('${counter.state}'); }, ); //Or you can use onAll or onOr methods: OnObs( () { return counter.onAll( onWaiting: () => Text('isWaiting'), onError: (err, refreshErr) => Text('Error'), onData: (data) => Text('$data'), ); }, ); OnObs( () { return counter.onOr( onWaiting: () => Text('isWaiting'), or: (data) => Text('$data'), ); }, );
What do you think?
The screenshots are just an imaginary scenario, I was wrapping the extension with it.
OnReactive
or OnRx
looks cool.
@amoslai5128 @samuelstroschein
OK, I choose OnReactive
. You can try the new dev version by using states_rebuilder: ^4.4.0-dev-1 in pubspec file.
The screenshots are just an imaginary scenario, I was wrapping the extension with it.
I had the same idea, and I was very tricky to implement. Once I get the right why to implement it we will discuss it again.
Here's an example that you can start with. This example if conveted to getX would throw
final items = List.generate(20, (index) => index).inj();
final filter = true.inj(); // true mean the number is even,
List<int> get filteredItems {
if (filter.state) {
return items.state.where((item) => item % 2 == 0).toList();
} else {
return items.state.where((item) => item % 2 == 1).toList();
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title'),
),
body: OnReactive(//Will capture all listener for the child widget tree
() => Column(
children: [
Row(
children: [
ToggleFilterWidget(),
AddItemsWidget(),
],
),
Expanded(
child: ListBuilderWidget(),
),
],
),
),
);
}
}
class ToggleFilterWidget extends StatelessWidget {
const ToggleFilterWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () => filter.toggle(),
icon: Icon(
filter.state ? Icons.toggle_off : Icons.toggle_on,
),
);
}
}
class AddItemsWidget extends StatelessWidget {
const AddItemsWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return OutlinedButton(
onPressed: () => items.state = List.generate(
items.state.length + 5,
(index) => index,
),
child: Text('Add items'),
);
}
}
class ListBuilderWidget extends StatelessWidget {
const ListBuilderWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: filteredItems.length,
itemBuilder: (ctx, index) {
return Text('Item : ${filteredItems[index]}');
},
);
}
}
body: OnReactive( //Will capture all listener for the child widget tree () => Column(
So, your example above means the OnReactive
can define on the root of that feature page, and each of inj() will hook up the corresponding widget to rebuild?
So, your example above means the OnReactive can define on the root of that feature page, and each of inj() will hook up the corresponding widget to rebuild?
Yes, but be aware that all the widget tree under OnReactive will rebuild.
@GIfatahTH
Thank you for giving the amazing upcoming updates. I want to know the or:
from myModel.onOr
meaning undefined status?
If so, does it run close in the default:
in Switch Case?
OnReactive(
()=>
//Or use on.Or method:
myModel.on(
onWaiting: ()=> WaitingWidget(),
onDefault: ()=> DataWidget(),
);
)
@amoslai5128
Thank you for giving the amazing upcoming updates. I want to know the or: from myModel.onOr meaning undefined status? If so, does it run close in the default: in Switch Case?
Yes, It means non-defined status. Similar to when and maybeWhen in other libraries.
//Example from freezed
//All callbacks are required
model.when(
waiting : ....,
error: ....,
data: ...
);
//Not all callbacks are required
model.maybeWhen(
waiting: ...,
orElse: ....,//fallback callback
);
In our case:
//All callbacks are required
model.onAll(
onWaiting : ....,
onError: ....,
onData: ...
);
//Not all callbacks are required
model.onOr(
onWaiting: ...,
or: ....,//fallback callback
);
To be more clearer I can rename or
to orElse
.
@amoslai5128
Thank you for giving the amazing upcoming updates. I want to know the or: from myModel.onOr meaning undefined status? If so, does it run close in the default: in Switch Case?
Yes, It means non-defined status. Similar to when and maybeWhen in other libraries.
//Example from freezed //All callbacks are required model.when( waiting : ...., error: ...., data: ... ); //Not all callbacks are required model.maybeWhen( waiting: ..., orElse: ....,//fallback callback );
In our case:
//All callbacks are required model.onAll( onWaiting : ...., onError: ...., onData: ... ); //Not all callbacks are required model.onOr( onWaiting: ..., or: ....,//fallback callback );
To be more clearer I can rename
or
toorElse
.
Do you think otherwise:
is a good idea too?
@amoslai5128 @samuelstroschein I am ready to publish the next update which is heavily influenced by this issue.
I want to wrap up.
I added the OnReactive
widget, which implicitly detects its listeners.
OnReactive(
(){
//Widget to rebuild
},
sideEffects: SideEffects(
initState: (){
// Side effect to call when the widget is first inserted into the widget tree
},
dispose: (){
// Side effect to call when the widget is removed from the widget tree
},
onSetState: (snapState){
// Side effect to call when is notified to rebuild
//if the OnReactive listens to many states, the exposed snapState is that of the state that emits the notification
},
)
shouldRebuild: (oldSnap, newSnap){
// return bool to whether rebuild the widget or not.
//if the OnReactive listens to many states, the exposed snapState is that of the state that emits the notification
},
//Debug prints an informative message when the widget is rebuilding with the name of the state that has emitted the notification.
debugPrintWhenRebuild: 'custom name',
//Debug prints an informative message when a state is added to the list of subscriptions.
debugPrintWhenObserverAdd: 'custom name,
);
I also added OnBuilder widget with its named constructor variants OnBuilder.data; OnBuilder.all and OnBuilder.orElse;
OnBuilder(
listenTo: myState,
//called whenever myState emits a notification
builder: () => Text('${counter.state}'),
sideEffect: SideEffect(
initState: () => print('initState'),
onSetState: On(() => print('onSetState')),
onAfterBuild: On(() => print('onAfterBuild')),
dispose: () => print('dispose'),
),
shouldRebuild: (oldSnap, newSnap) {
return true;
},
debugPrintWhenRebuild: 'myState',
),
//Rebuild only when the myState emits notification with isData equals to true
OnBuilder.data(
listenTo: myState,
builder: (data) => Text('$data'),
),
//Handle all possible state status
OnBuilder.all(
listenTo: myState,
onIdle: () => Text('onIdle'),
onWaiting: () => Text('onWaiting'),
onError: (err, errorRefresh) => Text('onError'),
onData: () => Text('{myState.state}'),
),
//Handle all possible state status with orElse fallback for the undefined status.
OnBuilder.orElse(
listenTo: myState,
onWaiting: () => Text('onWaiting'),
orElse: () => Text('{myState.state}'),
),
For each OnBuilder
widget flavor there is method like equivalent:
//Widget-like
OnBuilder(
listenTo: myState,
builder: () => Text('${myState.state}'),
),
//Method-like
myState.rebuild(
() => Text('{myState.state}'),
),
//
//Widget-like
OnBuilder.data(
listenTo: myState,
builder: (data) => Text('$data')),
),
//Method-like
myState.rebuild.onData(
(data) => Text(data),
),
//Widget-like
OnBuilder.all(
listenTo: myState,
onIdle: () => Text('onIdle'),
onWaiting: () => Text('onWaiting'),
onError: (err, errorRefresh) => Text('onError'),
onData: (data) => Text('{myState.state}'),
)
//Method-like
myState.rebuild.onAll(
onIdle: () => Text('onIdle'),
onWaiting: () => Text('onWaiting'),
onError: (err, errorRefresh) => Text('onError'),
onData: (data) => Text('{myState.state}'),
),
//
//Widget-like
OnBuilder.orElse(
listenTo: myState,
onWaiting: () => Text('onWaiting'),
or: (data) => Text('{myState.state}'),
),
//Method-like
myState.rebuild.onOrElse(
onWaiting: () => Text('onWaiting'),
orElse: (data) => Text('{myState.state}'),
),
//Widget-like
OnBuilder.orElse(
listenTo: [myState1 , myState2],
onWaiting: () => Text('onWaiting'),
orElse: (data) => Text('$data'),
),
//Method-like
[myState1 , myState2].rebuild.onOrElse(
onWaiting: () => Text('onWaiting'),
orElse: (data) => Text('$data'),
),
I would be very grateful if you could help me improve the readme and wiki docs.
@amoslai5128 @samuelstroschein I am ready to publish the next update which is heavily influenced by this issue.
I want to wrap up.
- I added the
OnReactive
widget, which implicitly detects its listeners.OnReactive( (){ //Widget to rebuild }, sideEffects: SideEffects( initState: (){ // Side effect to call when the widget is first inserted into the widget tree }, dispose: (){ // Side effect to call when the widget is removed from the widget tree }, onSetState: (snapState){ // Side effect to call when is notified to rebuild //if the OnReactive listens to many states, the exposed snapState is that of the state that emits the notification }, ) shouldRebuild: (oldSnap, newSnap){ // return bool to whether rebuild the widget or not. //if the OnReactive listens to many states, the exposed snapState is that of the state that emits the notification }, //Debug prints an informative message when the widget is rebuilding with the name of the state that has emitted the notification. debugPrintWhenRebuild: 'custom name', //Debug prints an informative message when a state is added to the list of subscriptions. debugPrintWhenObserverAdd: 'custom name, );
- I also added OnBuilder widget with its named constructor variants OnBuilder.data; OnBuilder.all and OnBuilder.orElse;
OnBuilder( listenTo: myState, //called whenever myState emits a notification builder: () => Text('${counter.state}'), sideEffect: SideEffect( initState: () => print('initState'), onSetState: On(() => print('onSetState')), onAfterBuild: On(() => print('onAfterBuild')), dispose: () => print('dispose'), ), shouldRebuild: (oldSnap, newSnap) { return true; }, debugPrintWhenRebuild: 'myState', ), //Rebuild only when the myState emits notification with isData equals to true OnBuilder.data( listenTo: myState, builder: (data) => Text('$data'), ), //Handle all possible state status OnBuilder.all( listenTo: myState, onIdle: () => Text('onIdle'), onWaiting: () => Text('onWaiting'), onError: (err, errorRefresh) => Text('onError'), onData: () => Text('{myState.state}'), ), //Handle all possible state status with orElse fallback for the undefined status. OnBuilder.orElse( listenTo: myState, onWaiting: () => Text('onWaiting'), orElse: () => Text('{myState.state}'), ),
For each
OnBuilder
widget flavor there is method like equivalent://Widget-like OnBuilder( listenTo: myState, builder: () => Text('${myState.state}'), ), //Method-like myState.rebuild( () => Text('{myState.state}'), ), // //Widget-like OnBuilder.data( listenTo: myState, builder: (data) => Text('$data')), ), //Method-like myState.rebuild.onData( (data) => Text(data), ), //Widget-like OnBuilder.all( listenTo: myState, onIdle: () => Text('onIdle'), onWaiting: () => Text('onWaiting'), onError: (err, errorRefresh) => Text('onError'), onData: (data) => Text('{myState.state}'), ) //Method-like myState.rebuild.onAll( onIdle: () => Text('onIdle'), onWaiting: () => Text('onWaiting'), onError: (err, errorRefresh) => Text('onError'), onData: (data) => Text('{myState.state}'), ), // //Widget-like OnBuilder.orElse( listenTo: myState, onWaiting: () => Text('onWaiting'), or: (data) => Text('{myState.state}'), ), //Method-like myState.rebuild.onOrElse( onWaiting: () => Text('onWaiting'), orElse: (data) => Text('{myState.state}'), ), //Widget-like OnBuilder.orElse( listenTo: [myState1 , myState2], onWaiting: () => Text('onWaiting'), orElse: (data) => Text('$data'), ), //Method-like [myState1 , myState2].rebuild.onOrElse( onWaiting: () => Text('onWaiting'), orElse: (data) => Text('$data'), ),
I would be very grateful if you could help me improve the readme and wiki docs.
Yes, that's my pleasure, and it's also a good idea to add some commented example code into the source code, so the developer can get hints on vscode.
@samuelstroschein It'd be great if we can do it together, your document structure looks well-organized. I believe this change can grow up the community, it's impressive. 💯
@GIfatahTH Any schedule to push this update?
I would be very grateful if you could help me improve the readme and wiki docs.
@samuelstroschein It'd be great if we can do it together, your document structure looks well-organized. I believe this change can grow up the community, it's impressive. 💯
Sad news, as you might know, I recently graduated. Besides my studies, I was running my IoT company which used Flutter for the app, and States Rebuilder as the state management solution. Over the past weeks, I had to decide if I either go all-in on the company or do a master's, explicitly excluding the option to do both as that is "Nichts halbes und nichts ganzes". Ultimately, I decided to do a master's. I can run a company later, a master's I can only start now (studying in the EU is free, some countries even pay students to study). The latter means I folding the business and seize the development of the flutter app. Seizing the development of the flutter app also means that, for now, I am not using states rebuilder any longer and will not invest time in this OSS project.
@GIfatahTH Thanks for your in-depth answers on issues I opened and overall for creating and maintaining this great package! @amoslai5128 Thank you for participating in our discussions!
@samuelstroschein Thank you for your contribution. Hope to hear from you sooner.
Another day, another suggestion.
introduction
Ever since V4 of states rebuilder the
On(() => child)
widget is an an alternative to the previousfooState.rebuilder(() => child)
syntax. Although the On widget (syntax) seems more appropriate in a widget tree because of the syntax highlighting, it has one major downside why I sometimes don't use it.the "problem"
The
On
widget moves rebuilding logic at the end of the widget, opposite to the previous function syntax. That becomes problematic the longer a widget tree becomes:example 1: relatively small widget tree
example 2: bigger widget tree.
```Dart // keep in mind that this example is still simple because the on widget is the most outer widget and it's // relatively easy to see that the user state is listened to. in production code, that is most likely no the case // and the listenTo function is nested deeply in a widget tree. On( () { bool isLoading = user.isWaiting; return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children:[
Center(
child: SizedBox(
child: isLoading
? CircularProgressIndicator()
: Text(
'Sign In',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 30,
),
),
height: 40.0,
),
),
SizedBox(height: 32),
//NOTE2: IF can log with apple
if (canSignInWithApple.state) ...[
ElevatedButton(
child: Text('Sign in With Apple Account'),
onPressed: isLoading
? null
: () => user.auth.signIn(
(_) => UserParam(signIn: SignIn.withApple),
),
),
SizedBox(height: 8),
],
ElevatedButton(
child: Text('Sign in With Google Account'),
onPressed: isLoading
? null
: () => user.auth.signIn(
(_) => UserParam(signIn: SignIn.withGoogle),
),
),
SizedBox(height: 8),
ElevatedButton(
child: Text('Sign in With Email and password'),
onPressed: isLoading
? null
: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
//Display form screen
return SignInRegisterFormPage();
},
),
);
},
),
SizedBox(height: 8),
ElevatedButton(
child: Text('Sign in anonymously'),
onPressed: isLoading
? null
: () => user.auth.signIn(
(_) => UserParam(signIn: SignIn.anonymously),
),
),
SizedBox(height: 8),
],
);
},
).listenTo(user),
// immediately obvious what triggers rebuilds. the password state
user.rebuilder(
() {
bool isLoading = user.isWaiting;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: SizedBox(
child: isLoading
? CircularProgressIndicator()
: Text(
'Sign In',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 30,
),
),
height: 40.0,
),
),
SizedBox(height: 32),
//NOTE2: IF can log with apple
if (canSignInWithApple.state) ...[
ElevatedButton(
child: Text('Sign in With Apple Account'),
onPressed: isLoading
? null
: () => user.auth.signIn(
(_) => UserParam(signIn: SignIn.withApple),
),
),
SizedBox(height: 8),
],
ElevatedButton(
child: Text('Sign in With Google Account'),
onPressed: isLoading
? null
: () => user.auth.signIn(
(_) => UserParam(signIn: SignIn.withGoogle),
),
),
SizedBox(height: 8),
ElevatedButton(
child: Text('Sign in With Email and password'),
onPressed: isLoading
? null
: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
//Display form screen
return SignInRegisterFormPage();
},
),
);
},
),
SizedBox(height: 8),
ElevatedButton(
child: Text('Sign in anonymously'),
onPressed: isLoading
? null
: () => user.auth.signIn(
(_) => UserParam(signIn: SignIn.anonymously),
),
),
SizedBox(height: 8),
],
);
},
)
```
suggestion
listenTo
should be implemented in theOn
widget itself as first positional argument. The widget that is rebuilt should probably be given to thechild
parameter to further align with Flutter widget tree syntax.The syntax would also improve features like
On.future
:Although On.Future would then probably not work with listenTo (but why should it anyways? One could use the regular On with an InjectFuture state?).
What do other States Rebuilder users/you think about the suggestion?