I understand that StatefulWidget subclasses are intended to be immutable, and that we tend to store state in a State subclass. (Not talking advanced state management techniques here.)
What then, are the perils of storing our model/state in the StatefulWidget class instead of the State class? Since the State instance has a widget property referring to the StatefulWidget, why not store the state there?
For example:
class Foo extends StatefulWidget {
int count = 0; // state
State createState() => FooState();
}
class FooState extends State<Foo> {
Widget build(BuildContext context) {
return Column(children:[
Text(widget.count.toString()),
FloatingActionButton(
onPressed: doIt
)
]);
}
void doIt() {
setState( () {
widget.count += 1;
});
}
}
Would those concerns still apply when state is an object, passed down to the State instance?
For example:
class Foo extends StatefulWidget {
final counter = Counter(0); // state
State createState() => FooState(counter);
}
class FooState extends State<Foo> {
final counter;
FooState(this.counter);
Widget build(BuildContext context) {
return Column(children:[
Text(counter.stringValue()),
FloatingActionButton(
onPressed: doIt
)
]);
}
void doIt() {
setState( () {
counter.increment();
});
}
}
Immutability is used for performance reasons. If the widget needs to change, create a new instance set up accordingly. It's quicker to check if two instances are identical than if their state is the same.
Related
am following this Bloc's official example and I couldn't find a way how to access the state without that if statement.
Let's have the example below, I would like to display a specific text based on the initial value of showText, the only possible solution to access the state is via:
if(statement is ExampleInitial) {state.showText? return Text("yes") : return Text("no")}
But am finding this solution hard to implement when you have more values with initial values. Or am I doing this wrong?
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
////////// event
part of 'example_bloc.dart';
abstract class ExampleEvent extends Equatable {
const ExampleEvent();
}
class ExampleStarted extends ExampleEvent {
#override
List<Object> get props => [];
}
////////// state
part of 'example_bloc.dart';
abstract class ExampleState extends Equatable {
const ExampleState();
}
////////// state
class ExampleInitial extends ExampleState {
final bool showText = false;
const ExampleInitial();
#override
List<Object> get props => [showText];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(
builder: (context, state) {
return state.showText ? Text("yes") :Text("no"); // can't access to state.showText
});
}
}
You can declare a variable inside Bloc Class which will be global and need to be set inside the 'bloc.dart' file like in the case of Provider Package. This variable does not need state to be checked before accessing it in UI. You can access this value from the Navigation tree using context.
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
bool showText = false;
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider.of<ExampleBloc>(context).showText
? const Text('Yes')
: const Text('No');
}
}
There is another way in which you declare abstract State Class to always have the boolean value. So, whatever new class extends those State will have inherited boolean value from parent class. This concept is called inheritance in OOP.
////////// state
abstract class ExampleState extends Equatable {
const ExampleState();
final bool showText = false;
}
////////// state
class ExampleInitial extends ExampleState {
const ExampleInitial();
// You can also set ExampleInitial to accept showText and send it to its
// parent class using 'super' method in constructor,
// if parent class has constructor with 'showText' as boolean
#override
List<Object> get props => [];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(builder: (context, state) {
return state.showText ? const Text("yes") : const Text("no");
});
}
}
A pragmatic usecase for different State Classes having different state variables is as follows:
Let's account for three states while fetching data from api
-if(state is DataLoadingState),
// there is no need for state
-if(state is DataLoadedState)
// state need to have a variable named weatherData containing temperatures, cities and so on.
-if(state is ErrorWhileLoadingState)
// state needs to have a reason for the error. For example: errorMsg: 'Internal Server Error'
So, you need to check the state before accessing its values.
I am unable to access a public static boolean from a different class, eg. I have a boolean isFull in my StudyjiosListviewScreen class as shown:
class StudyjiosListviewScreen extends StatefulWidget {
#override
_StudyjiosListviewScreenState createState() => _StudyjiosListviewScreenState();
}
class _StudyjiosListviewScreenState extends State<StudyjiosListviewScreen> {
static bool isFull = false;
...
I want to use this boolean isFull in another class JoinStudyjio.
I created an instance of the StudyjiosListviewScreen class in the JoinStudyjio class like this:
StudyjiosListviewScreen listviewScreen = StudyjiosListviewScreen();
But when I try to use the boolean isFull like this:
if (listviewScreen.isFull) {
...
I get an error. I have already imported the file for the StudyjiosListviewScreen class inside the file for the JoinStudyjio class.
This is because StudyjiosListviewScreen and _StudyjiosListviewScreenState are 2 different classes.
The static variable isFull which you are trying to access is of the later one and you are trying to access it by creating an instance of the first one. If it had been a static variable of the class StudyjiosListviewScreen, you could have accessed it without even creating an instance of that class like this StudyjiosListviewScreen.isFull
If I understood your issue correctly, and following the suggestion I made in my comment, here is a code example of sharing a variable and a method to change it's value, down to two classes from a parent class:
class VariableSharing62951032 extends StatefulWidget {
#override
_VariableSharing62951032State createState() => _VariableSharing62951032State();
}
class _VariableSharing62951032State extends State<VariableSharing62951032> {
bool isFull = false;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ClassA62951032(isFull: isFull, swapIsFull: swapIsFull,),
ClassB62951032(isFull: isFull, swapIsFull: swapIsFull,),
],
);
}
void swapIsFull(){
setState(() {
isFull = !isFull;
});
}
}
class ClassA62951032 extends StatefulWidget {
final bool isFull;
final Function swapIsFull;
ClassA62951032({
this.isFull,
this.swapIsFull
});
#override
_ClassA62951032State createState() => _ClassA62951032State();
}
class _ClassA62951032State extends State<ClassA62951032> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Class A'),
Text(widget.isFull.toString()),
RaisedButton(
child: Text('Swap isFull'),
onPressed: () => widget.swapIsFull(),
),
],
);
}
}
class ClassB62951032 extends StatefulWidget {
final bool isFull;
final Function swapIsFull;
ClassB62951032({
this.isFull,
this.swapIsFull
});
#override
_ClassB62951032State createState() => _ClassB62951032State();
}
class _ClassB62951032State extends State<ClassB62951032> {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('Class B'),
Text(widget.isFull.toString()),
RaisedButton(
child: Text('Swap isFull'),
onPressed: () => widget.swapIsFull(),
),
],
);
}
}
Sharing variables and methods between classes it's a huge deal in Flutter.
First of all, you are passing it in the wrong way. That variable is saved in your state widget, which is defined as private.
So, or you define it as public and than you pass a key associated with your state, or you change complitelly approach. I don't like passing keys and it is not good for production, so I will give you a better example using providers:
add provider library to your pubspec.yaml:
provider: ^4.3.1 // Or latest version
Create a class where you can save that value:
class valuesHelper {
//In this class we are storing global, dynamic values
bool _isSeen;
valuesHelper() {
this._isSeen = false;
}
void setValue(bool value) {
this._isSeen = value;
}
bool getValue(){
return this._isSeen;
}
}
Now wrap your main with the provider and pass the valuesHelper();
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return Provider(
create: (_) => valuesHelper(),
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
Now call the Provider.of(context) wherever you want.
//Somwhere in your code when you have access to context:
ValueHelper helper = Provider.of<valueHelper>(context);
helper.setValue(true);
//Somwhereelse in your code when you have access to context:
ValueHelper helper = Provider.of<valueHelper>(context);
bool theValueIWant = helper.getValue();
If you have asynchronous stuff and huge state managment Blocs are even better and fancier, but for this kind of things Providers are more than enough.
I have a Flutter App with Provider as a State Manager. The ChangeNotifierProvier is at the very top of my app (ie. above MaterialApp widget).
I have a ChangeNotifier class as follows:
class AmountManager extends ChangeNotifier {
String amount;
void changeAmount(String newAmount) {
amount = newAmount;
notifyListeners();
}
}
Then I have another class with a TextField :
class MyTextField extends StatelessWidget {
#override
Widget build(BuildContext context) {
return TextField(
//some decorations here
onChanged(value) {
Provider.of<AmountManager>(context).changeAmount(value);
},
);
}
}
And in another class under the Main App, I call the amount variable:
class MyText extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text(
Provider.of<AmountManager>(context).amount,
);
}
}
The problem is the Provider.of(...) method cannot be called. I watched some tutorials and couldn't find out the reason behind it. If I use static text instead of the AmountManager object, it works. The program only uses the initial value of amount in MyText class.
What do you think I'm wrong with?
Thank you in advance,
You need listen: false when you use Provider.of() in onChanged.
onChanged: (value) {
Provider.of<AmountManager>(context, listen: false).changeAmount(value);
},
See this for details.
I'm new to Flutter. I have made a stateful widget that has multiple options for states, and I have no clue how to switch between them, or if it's even possible. Basically I have:
class SWidget extends StatefulWidget {
State<StatefulWidget> createState(){
return _State1();
}
}
class _State1 extends State<SWidget> {
...
}
class _State2 extends State<SWidget> {
...
}
I want SWidget to switch from _State1 to _State2
Sorry if I don't understand your question but state is often tied to UI. If you need new state then chances are you're wanting to display something different. In that case, you'd want to distinguish the two states (and their UI components) by placing them in their own stateful widgets. You can then switch between the two widgets in a stateful or stateless widget:
class SWidget extends StatelessWidget {
SWidget(this.condition);
final bool condition;
#override
Widget build(BuildContext context) {
return condition ? Foo() : Bar();
}
}
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
}
class _FooState extends State<Foo> {
var _foo = 'foo';
#override
Widget build(BuildContext context) {
return Text(_foo);
}
}
class Bar extends StatefulWidget {
#override
_BarState createState() => _BarState();
}
class _BarState extends State<Bar> {
var _bar = 'bar';
#override
Widget build(BuildContext context) {
return Text(_bar);
}
}
I don't think I've ever seen anyone actually switch the state object out from underneath a widget. You can though easily change a widget's state by calling setState and toggling between values that way too:
class SWidget extends StatefulWidget {
#override
_SWidgetState createState() => _SWidgetState();
}
class _SWidgetState extends State<SWidget> {
var _value = true;
#override
Widget build(BuildContext context) {
return Switch(
value: _value,
onChanged: (value) => setState(() => _value = value),
);
}
}
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).