My understanding of state management is that calling setState() alone opens up a variety of messy issues, code files become huge and difficult to debug, and it prevents holding a sensible structure to a project. In cases where the widget's appearance changes slightly, it makes little sense to have a complex architecture like BLoC or ScopedModel just to show/hide a widget (for example). However, the way I have understood it is that you can't mix setState() and an architecture together, otherwise what's the point of the architecture?
Let's use BLoC for this question (simply because I happen to be using it), specifically this package. Let's say I have this super simple example code:
class MyWidget extends StatefulWidget {
#override
void createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget>() {
bool _isShowing = false;
MyBloc bloc;
#override
void initState() {
super.init();
bloc = MyBloc();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: bloc,
builder: (context, state) {
return Column(
children: <Widget>[
Text(state.myText),
if (_isShowing)
Text("Button has been pressed!"),
RaisedButton(
child: Text("Show label"),
onTap: () => setState(() => _isShowing = true),
),
RaisedButton(
child: Text("Run event"),
onTap: () => bloc.add(NewEvent()),
),
],
);
},
);
}
}
In the crude example above, is it right/acceptable to mix the BLoC pattern with setState()? Why would I not use BLoC to handle showing the Text widget? Where do I draw the line? What are the pros/cons? Is there a performance difference?
Note: I'm not looking for "just merge the two Text widgets together" answers. I'm looking for purely architectural perspectives.
You can.
Architecture like scoped_model/bloc/etc aren't about removing calls to setState.
They are about separating concerns and simplifying the implementation
You can and should use setState when it makes sense to use it, such as with animations.
To begin with, even these architectures use setState. You just don't see it, but it's there
Related
Can someone give me idea how provider notify the state?
I don't want to use ChangeNotifierProvider, Can you give me a suggestion without library?
I just need better explanation with example.
How provider combine InheritedWidget.
What do you think about the following example (inspired by an answer here) with an AnimatedBuilder:
import 'package:flutter/material.dart';
class MyChangeNotifier extends ChangeNotifier {
int count = 0;
void addOne() {
count++;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
final MyChangeNotifier myChangeNotifier = MyChangeNotifier();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ExampleButton(myChangeNotifier),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: myChangeNotifier.addOne,
),
),
);
}
}
class ExampleButton extends StatelessWidget {
final MyChangeNotifier myChangeNotifier;
const ExampleButton(this.myChangeNotifier, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: myChangeNotifier,
builder: (context, child) {
return OutlinedButton(
onPressed: myChangeNotifier.addOne,
child: Text(
'Tap me - or the floating button\n\n${myChangeNotifier.count}',
textAlign: TextAlign.center,
));
});
}
}
void main() => runApp(MyApp());
The ChangeNotifier implements the Listenable class. You can see here how to listen to that Listenable, for example with an AnimatedBuilder (what my code does).
A ChangeNotifyProvider (I know, you don't want that) would also implement that for you and notify your widgets lower in the widget tree about changes.
Here is some idea for you :
widgets listen to changes and notify each other if there is a rebuild. As soon as the state changes, that particular widget rebuilds without affecting other widgets in the tree.
Three major components make all of this possible: the ChangeNotifier class in Flutter, the ChangeNotifierProvider (primarily used in our sample app), and the Consumer widgets.
Whatever change in the state observed from the ChangeNotifier class causes the listening widget to rebuild. The Provider package offers different types of providers – listed below are some of them:
The Provider class takes a value and exposes it, regardless of the value type
ListenableProvider is the specific provider used for listenable objects. It will listen, then ask widgets depending on it and affected by the state change to rebuild any time the listener is called
ChangeNotifierProvider is similar to ListenableProvider but for ChangeNotifier objects, and calls ChangeNotifier.dispose automatically when needed
ValueListenableProvider listens to a ValueListenable and exposes the value
StreamProvider listens to a stream, exposes the latest value emitted,
and asks widgets dependent on the stream to rebuild FutureProvider
takes a Future class and updates the widgets depending on it when the
future is completed
As a suggestion to learn provider from this article-
https://medium.com/flutter-community/making-sense-all-of-those-flutter-providers-e842e18f45dd
Is it true that Flutter encourages inline event handlers because it is the only way to obtain the BuildContext?
e.g.,
class X extends StatelessWidget {
handler() {
//cannot use this if I need the BuildContext
}
Widget build(BuildContext ctx) {
return Scaffold(
home: TextButton(
text: Text("Click me"),
onPressed: () { //must be inline, cannot reference "handler" because need "ctx"
Scaffold.of( ctx ).showSnackBar(/*...*/);
}
)
)
}
}
Most languages encourage simplifying the code by moving event handling code away from the UI but with Flutter, if BuildContext object is needed, there is no "pretty" way to do it except to put the handler inline.
Have I mistaken?
Most languages encourage simplifying the code by moving event handling code away from the UI
Actually, it seems like the way the industry is moving is towards this declarative "component" model; we have SwiftUI, React, Jetpack Compose etc.
Part of the attraction of declarative UI is the fact that the hierarchy of the code matches the hierarchy of the created widgets in the UI. The syntax of Dart makes this quite nice, for example you can rewrite your build method to (using Flutter 2.5.2):
class X extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(
body: TextButton(
child: Text("Click me"),
onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
content: Text("You clicked me!"),
)),
),
);
}
and the indentation provides a good visual representation of how the elements are nested in the final UI.
Now, you aren't wrong for being concerned about excessive in-lining, but the declarative way of dealing with this seems to be splitting a component up into sub-components. For example, with your X widget, the TextButton could be broken out into a specialised component:
class X extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(
body: SnackButton(),
);
}
class SnackButton extends StatelessWidget {
Widget build(BuildContext context) => TextButton(
child: Text("Click me"),
onPressed: () => Scaffold.of(context).showSnackBar(SnackBar(
content: Text("You clicked me!")
)),
);
}
This still keeps the onPressed handler near the item it is acting on, so readers of the code don't need to jump around looking for the definition of the handler.
But why did Flutter design it that way in the first place? Why didn't they make all event handlers (onPress, onTap) pass in the context by default?
I can see a through-line from the choice of declarative UI to expecting that everything that needs a BuildContext will be contained in-line in the build method, as this way it's all declared as it will be laid out, without having to look elsewhere. It is definitely a trade-off (as are all things), but I think if you are sensible about the implementation, and look for places to split off components or groups of components, this won't be as annoying as you are finding it now.
It should be something like that. Pass ctx to functions to have access.
class X extends StatelessWidget {
handler(BuildContext ctx) {
Scaffold.of( ctx ).showSnackBar(/*...*/);
}
Widget build(BuildContext ctx) {
return Scaffold(
home: TextButton(
text: Text("Click me"),
onPressed: () => handler(ctx)
)
)
}
}
Edit, just like #msbit explained
I am using Bloc for my Flutter project. I have created three blocs. These are AuthenticationBloc, FirebaseDatabaseBloc, and ChatMessagesBloc. When the user gets authenticated, AuthenticationBloc emits a state called authenticated with a user object.
I want to make this user object available inside FirebaseDatabaseBloc and ChatMessagesBloc. What is the clean way of doing this?
Well, This is year 2022 and a lot has changed. Bloc to Bloc to communication via the constructor is now considered a bad practice. Nobody said it won't work though but trust me, you'd end up tightly coupling your code.
Generally, sibling dependencies between two entities in the same architectural layer should be avoided at all costs, as it creates tight-coupling which is hard to maintain. Since blocs reside in the business logic architectural layer, no bloc should know about any other bloc.
documentation.
You should rather try this:
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<WeatherCubit, WeatherState>(
listener: (context, state) {
// When the first bloc's state changes, this will be called.
//
// Now we can add an event to the second bloc without it having
// to know about the first bloc.
BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());
},
child: TextButton(
child: const Text('Hello'),
onPressed: () {
BlocProvider.of<FirstBloc>(context).add(FirstBlocEvent());
},
),
);
}
}
I hope it helps!
This is achievable by BLoC-to-BLoC communication. The simplest way is to pass your BLoC reference by the other's constructor and subscribe to BLoC changes:
#override
Widget build(BuildContext context) {
final authenticationBloc = AuthenticationBloc();
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>.value(value: authenticationBloc),
BlocProvider<FirebaseDatabaseBloc>(
create: (_) => FirebaseDatabaseBloc(
authenticationBloc: authenticationBloc,
),
),
],
child: ...,
);
}
Then, inside the FirebaseDatabaseBloc you can subscribe to changes:
class FirebaseDatabaseBloc extends Bloc<FirebaseDatabaseEvent, FirebaseDatabaseBloc> {
final AuthenticationBloc authenticationBloc;
StreamSubscription<AuthenticationState> _authenticationStateStreamSubscription;
FirebaseDatabaseBloc({
#required this.authenticationBloc,
}) : super(...) {
_authenticationStateStreamSubscription = authenticationBloc.listen(_onAuthenticationBlocStateChange);
}
#override
Future<void> close() async {
_authenticationStateStreamSubscription.cancel();
return super.close();
}
void _onAuthenticationBlocStateChange(AuthenticationState authState) {
// Do whatever you want with the auth state
}
}
For more info, you can check this video: https://www.youtube.com/watch?v=ricBLKHeubM
My docs and Flutter videos, the explanation of the design of the StatefulWidget (+(Widget)State) is that it:
promotes a declarative design (good)
formalizes the process by which Flutter to efficiently decide which components need to be re-rendered (also good)
From the example:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {...}
}
However:
since we have to explicitly remember call setState in order to invalidate the state, is this really a declarative design?
Flutter doesn't automatically detect changes in the State object and decide to call build (although it could have), and so it doesn't really formalize/automate/make-safe the invalidation of view components. Since we have to explicitly call setState, what's the benefit of the Flutter's (Widget)State/StatefulWidget pattern over, let's say:
class MyHomePage extends StatefulWidget // Define dirty method
{
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
int _counter = 0;
_incrementCounter() {
_counter++;
this.dirty(); // Require the view to be rebuilt. Arranges generateView to be called.
}
#override
Widget generateView(BuildContext context) {return ... rendering description containing updated counter ... ;}
}
... which would place the same burden of marking the UI dirty on the programmer, is no less decalrative, and avoids additional abstraction that obfuscates the intention of the program.
What have I missed? What's the benefit of separating of StatefulWidget from (Widget)State in Flutter?
[Before people chime in with MVC comments, note that the Flutter model rather explicitly only manages only the widget's state and its tightly coupled to the UI's Widget through the build method - there is no separation of concern here and it doesn't have a lot to say about larger application state that's not attached to a view.]
[Also, moderators, these not the same questions: Why does Flutter State object require a Widget?, What is the relation between stateful and stateless widgets in Flutter?. My question is one about what's the benefit of the present design, not how this design works.]
Update: #Rémi Rousselet -- Here's a declarative example with only a new state class needing to be declared. With some work, you could even get rid of that (though it may not be better).
This way of declaring interaction with need didn't require (the user) declaring two new circularly type-referencing class, and the widget that is changing in response to state is decoupled from the state (its constructed a pure function of the state and does not need to allocate the state).
This way of doing things doesn't survive hot-reload. (sad face).
I suspect this is more of an issue with hot-reload, but if there's a way to make it work it would be great,
import 'dart:collection';
import 'package:flutter/material.dart';
////////////////////////////////
// Define some application state
class MyAppState with ChangeSubscribeable<MyAppState> {
/***
* TODO. Automate notifyListeners on setter.
* Binds changes to the widget
*/
int _counter;
get counter => _counter;
set counter(int c) {
_counter = c;
notifyListeners(); // <<<<<< ! Calls ... .setState to invalidate widget
}
increment() {
counter = _counter + 1;
}
MyAppState({int counter: 0}) {
_counter = counter;
}
}
void main() => runApp(MyApp5());
class MyApp5 extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Declare the mutable state.
// Note because the state is not coupled to any particular widget
// its possible to easily share the state between concerned.
// StateListeningWidgets register for, and are notified on changes to
// the state.
var state = new MyAppState(counter: 5);
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo'),
),
body: Center(
child: Column(
children: [
// When the button is click, increment the state
RaisedButton(
onPressed: () => {
state.increment(),
print("Clicked. New state: ${state.counter}")
},
child: Text('Click me'),
),
// Listens for changes in state.
StateListeningWidget(
state,
// Construct the actual widget based on the current state
// A pure function of the state.
// However, is seems closures are not hot-reload.
(context, s) => new Text("Counter4 : ${s.counter}"),
),
],
))),
);
}
}
// //////////////////////
// Implementation
// This one is the onChange callback should accept the state.
//typedef OnChangeFunc<ARG0> = void Function(ARG0);
typedef OnChangeFunc = void Function();
mixin ChangeSubscribeable<STATE> {
final _listener2Notifier =
new LinkedHashMap<Object, OnChangeFunc>(); // VoidFunc1<STATE>>();
List<OnChangeFunc> get _listeners => List.from(_listener2Notifier.values);
void onChange(listenerKey, OnChangeFunc onChange) {
// onChange(listenerKey, VoidFunc1<STATE> onChange) {
assert(!_listener2Notifier.containsKey(listenerKey));
_listener2Notifier[listenerKey] = onChange;
print("Num listeners: ${_listener2Notifier.length}");
}
void removeOnChange(listenerKey) {
if (_listener2Notifier.containsKey(listenerKey)) {
_listener2Notifier.remove(listenerKey);
}
}
void notifyListeners() {
// _listener2Notifier.forEach((key, value)=>value(state));
// Safer, in-case state-update triggers add/remove onChange:
// Call listener
_listeners.forEach((value) => value());
}
}
typedef StateToWidgetFunction<WIDGET extends Widget,
STATE extends ChangeSubscribeable>
= WIDGET Function(BuildContext, STATE);
void noOp() {}
class _WidgetFromStateImpl<WIDGET extends Widget,
STATE extends ChangeSubscribeable> extends State<StatefulWidget> {
STATE _state;
// TODO. Make Widget return type more specific.
StateToWidgetFunction<WIDGET, STATE> stateToWidgetFunc;
_WidgetFromStateImpl(this.stateToWidgetFunc, this._state) {
updateState(){setState(() {});}
this._state.onChange(this, updateState);
}
#override
Widget build(BuildContext context) => stateToWidgetFunc(context, this._state);
#override
dispose() {
_state.removeOnChange(this);
super.dispose();
}
}
class StateListeningWidget<WIDGET extends Widget,
STATE extends ChangeSubscribeable> extends StatefulWidget {
STATE _watched_state;
StateToWidgetFunction<WIDGET, STATE> stateToWidgetFunc;
StateListeningWidget(this._watched_state, this.stateToWidgetFunc) {}
#override
State<StatefulWidget> createState() {
return new _WidgetFromStateImpl<WIDGET, STATE>(
stateToWidgetFunc, _watched_state);
}
}
I've been directed at the ChangeProvider pattern: https://github.com/flutter/samples/blob/master/provider_counter/lib/main.dart
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Flutter Demo Home Page'),),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Consumer<Counter>( // <<< Pure. Hidden magic mutable parameter
builder: (context, counter, child) => Text(
'${counter.value}',
style: Theme.of(context).textTheme.display1,
),),],),),
floatingActionButton: FloatingActionButton(
onPressed: () =>
// <<< Also a hidden magic parameter
Provider.of<Counter>(context, listen: false).increment(),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
... but this also suffers problems:
its not clear to reader of what the state requirements are or how to provide them -- the interface (at least in this github example HomePage) example does not require Counter as a formal parameter. Here we have new HomePage() that has configuration that is not provided in its parameters - this type of access suffers similar problems to global variables.
access to state is by class type, not object reference - so its not clear (or at least straightforward) what to do if you want two objects of the same type (e.g. shippingAddress, billingAddress) that are peers in the model. To resolve this, the state model likely needs to be refactored.
I think I'm with user48956 on this. (Catchy name by the way).
Unfortunately, the Flutter authors seem to have suffixed their View class with the word 'State'. This has rather confused the whole Flutter state management discussions.
I think the purpose of the two classes is actually to make the painting more performant but it comes with a very heavy plumbing cost for us developers.
As to the naming convention:
The dirty flag approach allows the widget painter to optimise their painting without knowing about our state, thereby alleviation the need for two classes.
Also generateView() is kinda meaningful (unless of course, you start using these widgets to hold model-fragments (as per Package:provider).
After reading the docs here and the State lifecycle here, I am still not sure about how didChangeDependencies works.
As far as I understand it will be triggered after initState and after any change in an InheritedWidget, but what are these changes? I think it's important to understand what changes trigger didChangeDependencies, so we can understand when and how to use it properly.
When Flutter calls updateShouldNotify() and it returns true, then widgets that requested an inherited widget in build() previously are notified by didChangeDependencies being called.
updateShouldNotify should return true if its state changed since the last time it was called.
TLDR
As the creator of the Widget, you set it's dependencies (InheritedWidget) by using of or maybeOf, etc., so you should understand when they call updateShouldNotify (or they're dependencies call it). It's quite complex:
Usage
You usually don't have to override this method in State because your widget will rebuild when a dependency changes anyway. If you want to do expensive work, like making a network request, then you would avoid making that network request in a normal build, and put this expensive work in didChangeDependencies instead. This allows you to only make these expensive operations when a dependency changes. Tbh, I struggled to think of a situation where you'd want to make an expensive operation purely if an InheritedWidget changes if you're properly structuring your app's logic into services. So I looked at the Flutter framework internals...
Examples of didChangeDependencies overrides:
Flutter's Image widget (the comments are my commentary, and tbh load is probably not the right word I'm using, but resolve isn't much better IMHO)
#override
void didChangeDependencies() {
_updateInvertColors(); // Checks if the image should inverted or not.
_resolveImage(); // Reloads the image if necessary?
if (TickerMode.of(context)) // This method returns a bool: Whether tickers in the given subtree should be enabled or disabled.
_listenToStream(); // Keep loading the image
else
_stopListeningToStream(keepStreamAlive: true); // Be efficient and not read from the stream if not needed. (ticker is false)
super.didChangeDependencies();
}
And when is didChangeDependencies called? aka. what are Image's dependencies (which are all InheritedWidgets)? It uses both of and maybeOf to register the InheritedWidget dependencies, including MediaQuery, Directionality, DefaultAssetBundle, TickerMode, Localizations. So when these dependencies change/ get updated, Image's didChangeDependencies will be called.
What is a dependency?
You make a widget depend on a subtype of InheritedWidget with InheritedWidgetType.of(context), which internally calls context.dependOnInheritedWidgetOfExactType<InheritedWidgetType>();. So the widget has a dependency on that InheritedWidget. For example, Theme.of(BuildContext context) can be seen here.
From the docs inside the Flutter Framework's comments about dependOnInheritedWidgetOfExactType:
Obtains the nearest widget of the given type T, which must be the
type of a concrete [InheritedWidget] subclass, and registers this
build context with that widget such that when that widget changes (or
a new widget of that type is introduced, or the widget goes away),
this build context is rebuilt so that it can obtain new values from
that widget.
Once a widget registers a dependency on a particular type by calling
this method, it will be rebuilt, and [State.didChangeDependencies]
will be called, whenever changes occur relating to that widget until
the next time the widget or one of its ancestors is moved (for
example, because an ancestor is added or removed).
There's more docs, which are really interesting, have a read.
Jitesh's answer is currently just wrong. Dependencies are not Widget's state, they are only relevant for InheritedWidget.
didChangeDependencies() Called when a dependency of this [State] object changes.
So, exactly How it gets called? as by the above definition, it looks like it will be called after state changes but how we come to know the state is changed?
Example:
The below example uses the Provider state management mechanism to update the child widget from the parent widget. The Provider has an attribute called updateShouldNotify which decides whether to state is changed or not. If it's returning true then only didChangeDependencies get called in ChildWidget class.
updateShouldNotify is returning true by default internally, as it knows the state got changed.
Why do we need updateShouldNotify?
It’s needed because if someone wants to update the state on a specific condition. Eg: if UI required to show only even values then we can add a condition like
updateShouldNotify: (oldValue, newValue) => newValue % 2 == 0,
Code Snippet:
class ParentWidget extends StatefulWidget {
ParentWidget({Key key, this.title}) : super(key: key);
final String title;
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Life Cycle'),
),
body: Provider.value(
value: _counter,
updateShouldNotify: (oldValue, newValue) => true,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Press Fab button to increase counter:',
),
ChildWidget()
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class ChildWidget extends StatefulWidget {
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
int _counter = 0;
#override
void initState() {
print('initState(), counter = $_counter');
super.initState();
}
#override
void didChangeDependencies() {
_counter = Provider.of<int>(context);
print('didChangeDependencies(), counter = $_counter');
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
print('build(), counter = $_counter');
return Text(
'$_counter',
);
}
}
Output Logs:
I/flutter ( 3779): didChangeDependencies(), counter = 1
I/flutter ( 3779): build(), counter = 1
For more info:
https://medium.com/#jitsm555/differentiate-between-didchangedependencies-and-initstate-f98a8ae43164