According to Flutter's documentation here (https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple),
one way to control the state management is to use ChangeNotifierProvider(or InheritedWidget) when one of its descendants is a Consumer which is rebuilt when the underlying ChangeNotifier changes.
Flutter's team reiterates that approach on the official youtube Flutter channel.
However, that means that some business logic element (ChangeNotifier) is now part of the Widget tree (which otherwise is all about how the UI will look like). So I don't understand why those classes (ChangeNotifierProvider,InheritedWidget,Consumer) even exist.
In particular, why isn't the following approach superior:
-Business logic is a singleton and a ChangeNotifier.
-Instead of Provider(...), for every Widget depending on the business logic simply do this:
BusinessLogicSingleton.instance.addListener(() {
setState(() {
});
at initState(...).
What am I missing here?
You can indeed do that, but then you'd also have to remember to close the listener on dispose. Providers handle that automatically.
How do you plan to set state in stateless widgets? Also following your suggested method, you would be rebuilding the entire widget with setstate, vs building only a specific part even for complex widgets if you were to use consumers.
I think there are benefits to have the ChangeNotifier a part of the widget tree. You need to ask yourself: what are the features of the Widget Tree that can benefit ChangeNotifier? Or rather - by using Widget Tree, can I get everything I get from Singleton? And can I do some things Singleton does not provide?
First - if you put your ChangeNotifier high in the tree, and you create only one ChangeNotifier for your class (e.g. ChangeNotifier) - you practically have a singleton. With added benefit of a Widget Tree logic taking care of disposing your ChangeNotifier etc.
Second - you can have multiple ChangeNotifiers of the same class in different parts of the tree, practically scoping your ChangeNotifier any way you want. This is not something a Singleton approach can offer. I'm not sure this is a good example - but imagine your ShoppingCart demo: it has a single ShoppingCart ChangeNotifier provider at the top of the tree.
Now imagine one of the items you sell are customizable - like Pizza. And when user selects Pizza - you open additional screen where a user will chose toppings. Now, this additional screen could have a new instance of ShoppingCart - tracking the toppings you added, with all the features of the shopping cart - add, remove etc. This way your module (Pizza toppings selection) becomes self-contained - it does not expect a Singleton to be provided for it, but it will inject it's own ChangeNotifier into the tree.
With the Singleton approach you would need to come up with another way - extending your shopping cart for the sake of creating another Singleton. Not very practical.
Following up on the question, the following hyper-simple class saves me a lot of boilerplate code, and illogical code (forcing ChangeNotifier to be a part of the Widget tree via Provider etc)
import 'package:flutter/material.dart';
//A class which makes it easier to worker with change notifier without provider/consumer which looks wrong since changenotifier shouldn't be forced into widgets tree for no reason
//eg widgets should represent UI and not business logic
class ListenerWidget extends StatefulWidget {
const ListenerWidget({Key? key,required ChangeNotifier notifier,required Widget Function(BuildContext) builder,void Function()? action}) : notifier=notifier,builder=builder,action=action,super(key: key);
final Widget Function(BuildContext) builder;
final ChangeNotifier notifier;
final void Function()? action;
#override
_ListenerWidgetState createState() => _ListenerWidgetState();
}
class _ListenerWidgetState extends State<ListenerWidget> {
void emptyListener(){
setState(() {
});
}
#override
void initState() {
widget.notifier.addListener(widget.action??emptyListener);
super.initState();
}
#override
void dispose() {
widget.notifier.removeListener(widget.action??emptyListener);
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.builder(context);
}
}
Related
I'm trying to make a authentication screen with the BLoC pattern, but I have doubts about best practices even after reading the documentation of the flutter_bloc lib and reading multiple implementations.
The main problem I have is about state management. My authentication screen should handle the following states:
Password visible or not.
Show register or login screen.
TextEdit validation.
Loading (waiting for a Firebase response).
if authenticated, show home. Else show the authentication page
I really wanted to do all the logic in blocs to make a clean architecture.
Some people say they use a bloc per screen, sometime a bloc for the whole app, but flutter_bloc library says you should use a bloc for any widget complex enough.
My problem is, how can I deal with multiple states?
If I store variables in states classes extended from the mother AuthState class, I can't access it in mapEventToState
because the block receives the mother just the AuthState class.
I tried handling all states in the same bloc by passing the current state in a event (as I will show in the code below), but then I can't properly set the initial state.
What's the best practice solution?
Passing all state classes as variables in the mother AuthState class?
Then I could persist data using "state.passwordFieldState". But I
never saw something like that. I bet it's a wrong approach.
Create a model to store the state and manipulate the model when a change event enter the bloc?
Creating multiple blocs or cubits? One cubit for authentication, 1 cubit for password visibility, one bloc for authentication handling? My concern with that would be nesting bloc builders.
Or should I forget about blocs for simple things and use providers instead? I wouldn't like to do that because it can lead to spaghetti code.
Here's my code:
class AuthState extends Equatable{
#override
List<Object> get props => [];
}
class PasswordFieldState extends AuthState{
final bool isObscured;
PasswordFieldState({this.isObscured});
#override
List<Object> get props => [isObscured];
}
class AuthEvent extends Equatable{
#override
List<Object> get props => [];
}
class SetObscurePassword extends AuthEvent{
bool isObscured = false;
}
class passwordTextEditChanged extends AuthEvent{}
class emailTextEditChanged extends AuthEvent{}
class AuthBloc extends Bloc<AuthEvent,AuthState> {
AuthBloc(AuthState initialState) : super(initialState);
#override
Stream<AuthState> mapEventToState(AuthEvent event) async* {
if (event is SetObscurePassword) {
yield PasswordFieldState(isObscured: !event.isObscured);
} else if (event is passwordTextEditChanged) {
print("validation handling");
} else if (event is emailTextEditChanged) {
print("validation handling");
}
}
I think I found the answer.
It's not possible to make a bloc for the entire application using the flutter_bloc library. It's only possible to do such thing using stream controllers to create a bloc without the library.
But it's not a bad thing to create a bloc for each different task. It makes the code more testable and easier to understand.
In my Flutter app I'm using get_it to retrieve non-widget related dependencies (e.g. an http service).
Most of the Flutter Widget examples that I see don't save any variables in the widget class, but rather they retrieve them in the #build method. This makes sense for dependencies that rely on the context, but for other dependencies that don't rely on context is it ok to save them as instance variables when the constructor is called?
I'm not very familiar with the Widget lifecycle so I'm not sure if this could lead to memory leaks, etc if widget instances are discarded. I want to save them as variables because it more clearly documents the dependencies.
Fetching in #build
class MyWidget extends StatelessWidget {
#override
Widget build() {
var myService = GetIt.instance.get<MyService>();
}
}
Storing in instance variable
class MyWidget extends StatelessWidget {
final MyService myService;
MyWidget(): myService = GetIt.instance.get();
#override
Widget build() {
}
}
I see no reason why you couldn't store them as an instance variable.
You obviously cannot dispose of instance variables in a manual/clean way and must rely on the garbage collector to do what it should. That could be seen as a drawback. In your case I assume that isn't an issue since the service might live throughout the lifecycle of the app.
If you are very nervous about memory leaks, use a stateful widget and dispose the things needing disposal.
If you very clearly want to document the dependencies you could go further and require it as a parameter to the widget. It would also make the widget more easily testable.
I was learning flutter and came across this code:
class MyStatelessWidget extends StatelessWidget {
final String name;
MyStatelessWidget(this.name);
#override
Widget build(BuildContext context) {
return Text('Hello, $name!');
}
}
Sorry, I would like to ask some questions on the above code. Firstly, why need to use #override, that is,I know it is needed for method overriding but is it true that build method in StatelessWidget is defined like this build(){} therefore we need to override it and add some logic? Secondly, here Widget build Does Widget mean that build returns a Widget? Thirdly, why do we need to use BuildContext here build(BuildContext context)?
CONTEXT
From the docs, BuildContext is:
A handle to the location of a widget in the widget tree.
context is a BuildContext instance which gets passed to the builder of a widget in order to let it know where it is inside the Widget Tree of your app.
One of the common uses is passing it to the of method when using an Inherited Widget.
Calling Something.of(context), for example, returns the Something relative to the closest widget in the tree that can provide you that Something.
BUILD METHOD
Build method is required because it describes the part of the user interface represented by this widget.The framework calls this method in a number of different situations.
Read more about build method here Build Method
STATELESS WIDGET
A widget that does not require mutable state.
A stateless widget is a widget that describes part of the user interface by building a constellation of other widgets that describe the user interface more concretely.
Read more about stateless widget here Stateless widget
I hope this helps.
I use a lot StatelessWidgets when I have to create "templates" of widgets that are used multiple times inside my app because the docs say so:
Stateless widget are useful when the part of the user interface you
are describing does not depend on anything other than the
configuration information in the object itself and the BuildContext in
which the widget is inflated.
Here is an example:
class StepInputButton extends StatelessWidget {
final int pos;
final String value;
const StepInputButton({
this.pos,
this.value
});
#override
Widget build(BuildContext context) {
return Row(
// Text, Icon and a tiny button
);
}
}
The above is good because I can use const StepInputButton(val, "val"), in the code with CONST which improves performances.
PROBLEM
I am using the famous Provider widget to manage the state and the page of my apps usually look like this:
class SuccessPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var prov = Provider.of<Type>(context);
return Scaffold(...);
}
}
That's a page of my app with Scaffold that has a Drawer, a float action button and an appTitle.
Here I use a StatelessWidget because I do not use setState() since provider does all the work for me. But still in the official flutter doc they say:
For compositions that can change dynamically, e.g. due to having an
internal clock-driven state, or depending on some system state,
consider using StatefulWidget.
So do I have to change class SuccessPage extends StatelessWidget to class SuccessPage extends StatefulWidget? Do I get advantages?
Note: if you want to put the question in another way: should I use StatefulWidgets to create "app pages" whose state is going to change and StatelessWidgets for "reusable widgets" whose state doesn't change?
StatefulWidget is necessary for when the widget itself is maintaining its own state. In the example you gave, the Provider package is handling the state for you, assuming you're using the correct provider type higher up the widget tree (for example, ChangeNotifierProvider). There also doesn't seem to be anything in this code that would benefit from having access to the widget's lifecycle, so you wouldn't need access to methods like initState or dispose.
As such, there's nothing for the widget itself to manage, so converting your class to be stateful is unnecessary.
One thing I might suggest, though, is to use a Consumer instead of calling Provider.of directly. A Consumer handles the call for you and removes any ambiguity on whether your widget will get updated when the Provider detects a state change.
You use StatelessWidget for widgets that don't change their state, that will stay the same all the time. Example, appBar is stateless.. The build(...) function of the StatelessWidget is called only once and no amount of changes in any Variable(s), Value(s) or Event(s) can call it again.
Therefore, when you need to change state(ex value) then use StatefulWidgets, basically StatelessWidget is used for building UI widgets that are static
Keeping it simple:
If you have non-final global variables in your widget then you need a StatefulWidget
If all global variables are final then you should use StatelessWidget;
Reason:
If your global variable is non final that means it is allowed to change and if it's value is changed that means state of your object(Widget) is changed (basic oops concept I am talking about). In such case you would like to call build method of your widget so that your changes get applied on the UI (if it matters for your UI). We do it by calling setState(); and so we use StatefulWidget for such use-case.
If it is enough that once you initialize your global variable in constructor, you don't need to assign it any value in future then in such case use StatelessWidget.
I have tried to keep it very simple and not technical enough so, if you still have any doubts please comment on this answer.
I am using Provider. I have got two classes: class TenderApiData {} it's stand alone class (not widget). How I can write accesstoken to AppState?
class AppState extends ChangeNotifier // putted to ChangeNotifierProvider
{
String _accesstoken; // need to fill not from widget but from stand alone class
String _customer; // Fill from widget
List<String> _regions; // Fill from widget
List<String> _industry; // Fill from widget
...
}
I need way to read\write accesstoken from stand alone classes.
Or I have issue with architecture of my app?
Here is full source code.
You cannot and should not access providers outside of the widget tree.
Even if you could theoretically use globals/singletons or an alternative like get_it, don't do that.
You will instead want to use a widget to do the bridge between your provider, and your model.
This is usually achieved through the didChangeDependencies life-cycle, like so:
class MyState extends State<T> {
MyModel model = MyModel();
#override
void didChangeDependencies() {
super.didChangeDependencies();
model.valueThatComesFromAProvider = Provider.of<MyDependency>(context);
}
}
provider comes with a widget built-in widgets that help with common scenarios, that are:
ProxyProvider
ChangeNotifierProxyProvider
A typical example would be:
ChangeNotifierProxyProvider<TenderApiData, AppState>(
initialBuilder: () => AppState(),
builder: (_, tender, model) => model
..accessToken = tender.accessToken,
child: ...,
);
TL;DR
Swap provider for get_it. The later does DI globally without scoping it to a BuildContext. (It actually has its own optional scoping mechanism using string namedInstance's.)
The rest...
I ran into a similar problem and I believe it comes down to the fact that Provider enforces a certain type of (meta?) architecture, namely one where Widgets are at the top of what you might call the "agency pyramid".
In other words, in this style, widgets are knowledgable about Business Logic (hence the name BLoC architecture), they run the show, not unlike the ViewController paradigm popularised by iOS and also maybe MVVM setups.
In this architectural style, when a widget creates a child widget, it also creates the model for the widget. Here context could be important, for example, if you had multiple instances of the same child widget being displayed simultaneously, each would need its own instance of the underlying model. Within the widget or its descendents, your DI system would need the Context to select the proper one. See BuildContext::findAncestorWidgetOfExactType to get an idea why/how.
This architectural style is the one seemingly encouraged by plain vanilla Flutter, with its paradigms of app-as-a-widget ("turtles all the way down"), non-visual widgets, layout-as-widgets and InheritedWidget for DI (which provider uses I believe)
BUT
Modern app frameworks libs (e.g. redux, mobx) encourage the opposite kind of meta-architecture: widgets at the bottom of the pyramid.
Here widgets are "dumb", just UI signal generators and receivers. The business logic is encapsulated in a "Store" or via "Actions" which interact with a store. The widgets just react to the relevant fields on the store being updated and send Action signals when the user interacts with them.
Which should you use?
In my experience, at least on mobile where the screen realestate is less, scoping a model to a branch in the render tree is seldom required. If it suddenly becomes important then there are plenty of other ways to handle it (indexed array, id lookup map, namedInstances in get_it) than to require linking it to the semantics of UI rendering.
Currently, having spent too much time in iOS ViewControllers, I'm a fan of new systems which enforce better SoC. And personally find Flutter's everything-is-a-widget pardigm to appear a bit messy at times if left untended. But ultimately it's a personal preference.
you can use navigator key
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
and put this key in MaterialApp and wrap it with your provider (TenderApiData)
ChangeNotifierProvider<TenderApiData>(
create: (_) => TenderApiData(),
child: Consumer<TenderApiData>(builder: (context, tenderApiData , child) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'title',
home: SplashScreen());
}),
);
and listen to this provider from anywhere with this navigator key
navigatorKey.currentContext?.read<TenderApiData>();