I'm trying to understand the best practice for controlling a StatefulWidget's state outside of that Widgets State.
I have the following interface defined.
abstract class StartupView {
Stream<String> get onAppSelected;
set showActivity(bool activity);
set message(String message);
}
I would like to create a StatefulWidget StartupPage
that implements this interface. I expect the Widget to do the following:
When a button is pressed it would send an event over the onAppSelected stream. A controller would listen to this even and perform some action ( db call, service request etc ).
The controller can call
showActivity
orset message
to have the view show progress with a message.
Because a Stateful Widget does not expose it's State as a property, I don't know the best approach for accessing and modifying the State's attributes.
The way I would expect to use this would be something like:
Widget createStartupPage() {
var page = new StartupPage();
page.onAppSelected.listen((app) {
page.showActivity = true;
//Do some work
page.showActivity = false;
});
}
I've thought about instantiating the Widget by passing in the state I want it to return in createState()
but that feels wrong.
Some background on why we have this approach: We currently have a Dart web application. For view-controller separation, testability and forward thinking towards Flutter we decided that we would create an interface for every view in our application. This would allow a WebComponent or a Flutter Widget to implement this interface and leave all of the controller logic the same.
Have you considered lifting the state to the parent widget? It is a common, though less ideal than Redux, way to manage state in React as far as I know, and this repository shows how to apply the concept to a Flutter app.
You can expose the state's widget with a static method, a few of the flutter examples do it this way and I've started using it as well:
You can then access the state by calling
StartupPage.of(context).doSomething();
.The caveat here is that you need to have a BuildContext with that page somewhere in its tree.
There are multiple ways to interact with other stateful widgets.
1. ancestorStateOfType
The first and most straightforward is through
context.ancestorStateOfType
method.Usually wrapped in a static method of the
Stateful
subclass like this :This is how
Navigator
works for example.Pro:
Con:
State
properties or manually callsetState
State
subclassDon't use this method when you want to access a variable. As your widget may not reload when that variable change.
2. Listenable, Stream and/or InheritedWidget
Sometimes instead of a method, you may want to access some properties. The thing is, you most likely want your widgets to update whenever that value changes over time.
In this situation, dart offer
Stream
andSink
. And flutter adds on the top of itInheritedWidget
andListenable
such asValueNotifier
. They all do relatively the same thing: subscribing to a value change event when coupled with aStreamBuilder
/context.inheritFromWidgetOfExactType
/AnimatedBuilder
.This is the go-to solution when you want your
State
to expose some properties. I won't cover all the possibilities but here's a small example usingInheritedWidget
:First, we have an
InheritedWidget
that expose acount
:Then we have our
State
that instantiate thisInheritedWidget
Finally, we have our
CountBody
that fetch this exposedcount
Pros:
ancestorStateOfType
await for
orasync*
)Cons:
3. Notifications
Instead of directly calling methods on
State
, you can send aNotification
from your widget. And makeState
subscribe to these notifications.An example of
Notification
would be :To dispatch the notification simply call
dispatch(context)
on your notification instance and it will bubble up.Any given widget can listen to notifications dispatched by their children using
NotificationListener<T>
:An example would be
Scrollable
, which can dispatchScrollNotification
including start/end/overscroll. Then used byScrollbar
to know scroll information without having access toScrollController
Pros:
State
. It'sState
that subscribes to events triggered by its childrenState
propertiesCons: