I was wondering what would be the best way to access/pass variables between widgets. For example,
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
int number = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Jungle Book',
home: new Scaffold(
appBar: new AppBar(title: new Text('Jungle Book')),
drawer: new AppMenu(),
body: new MainPageManager(),
floatingActionButton: new FloatingActionButton(onPressed: null,child: new Icon(Icons.shuffle),),
),
);
}
}
class MainPageManager extends StatefulWidget {
#override
_MainPageManagerState createState() => _MainPageManagerState();
}
class _MainPageManagerState extends State<MainPageManager> {
List<String> keys = ["flamingo"]; // more to add later
final _random = new Random();
#override
Widget build(BuildContext context) {
return Center(
child: new Image.asset('images/'+keys[_random.nextInt(keys.length)]+'.jpg',
fit: BoxFit.fill,)
);
}
}
If I need to pass a value during the floatingActionButton.OnPressed to the MainPageManager, what would be best way to do it.
What am attempting to do is, when the FloatingActionButton is pressed, i need to update the MainPageManager with a new image.
I read somewhere that using Global keys is not a good idea. What could be alternative ?
PS: am more of a beginner with flutter.
Flutter is not opinionated about how you approach state management.
Since MainPageManager is a direct child of MyApp you could turn MyApp into a stateful widget and MainPageManager into a stateless widget instead, as you are not using the setState method at all in your MainPageManager. Then, in your onPressed callback you'd use the setState method to set the state of your MyApp component, which will cause flutter to rebuild MyApp and all child widgets (so MainPageManager will be rebuild as well).
The last thing to do would be to add a property to your now stateless MainPageManager, that you'd pass down to that component.
Example:
class MainPageManager extends StatelessWidget {
final String value;
MainPageManager({this.value});
#override
Widget build(BuildContext context) {
return Container(
child: Text("$value"),
);
}
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String _valueBinding;
#override
void initState() {
_valueBinding = "Test";
super.initState();
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
MainPageManager(
value: _valueBinding,
),
MaterialButton(onPressed: () {
setState(() {
_valueBinding = "This will be shown in MainPageManager";
});
}, child: Text("Update Value"),)
],
);
}
}
For more complex use-cases:
Flutter comes with a state-management solution called InheritedWidget. However, the API is not that convenient and the flutter team actually promotes using a package called Provider instead, which builds on InheritedWidget.
If your app grows really big I actually recommend using the BloC pattern instead.
Related
Minimal reproducible code:
class MainScreen extends StatefulWidget {
#override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () => setState(() {})),
body: FooPage(key: ValueKey(0)),
);
}
}
class FooPage extends StatelessWidget {
FooPage({super.key});
final int _number = math.Random().nextInt(100);
#override
Widget build(BuildContext context) {
print('number = $_number');
return Container();
}
}
Each time you click the FAB, the number prints a new value. But as I'm already passing same key to the FooPage, why is it the MainScreen creates a new FooPage instance from scratch instead of using the existing one (with key 0)?
NOTE:
Please don't post answers to use const FooPage() after declaring a const constructor in the FooPage or assign FooPage to a final instance field in the MainScreen etc.
Adding a key to a StatelessWidget doesn't make it stateful (remebering the random int).
I.e. the framework will still dispose and rebuild it as a new instance when deemed necessary.
It's clear that in StatefulWidget if state changes then const Text('...') will not be rebuild.
class _SomeWidgetState extends State<SomeWidget> {
#override
Widget build(BuildContext context) {
return const Text('Some Static Text'); // doesn't rebuild
}
}
But, my question is: Is there any benefits of using const in StatelessWidget?
class ListItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const Text('Some Static Text') // Is `const` useful here?
}
}
Yes, it is useful.
Const constructors inside build is useful for all kinds of widgets, including StatelessWidget and other less common ones like InheritedWidget or RenderObjectWidget.
Bear in mind that all widgets may take parameters, so you may have:
class MyStateless extends StatelessWidget {
const MyStateless({Key key, this.count}): super(key: key);
final int count;
#override
Widget build(BuildContext context) {
return Row(
children: [
Text('$count'),
const Text('static text'),
],
);
}
}
In that situation, your stateless widget may rebuild with a different parameter, like going from:
MyStateless(count: 0);
to:
MyStateless(count: 42);
In that situation, using const Text('static text') will not cause this text to rebuild.
I have a Flutter where I display a list of elements in a Column, where the each item in the list is a custom widget. When I update the list, my UI doesn't refresh.
Working sample:
class Test extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestState();
}
}
class TestState extends State<Test> {
List<String> list = ["one", "two"];
final refreshKey = new GlobalKey<RefreshIndicatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(40),
child: Row(
children: <Widget>[
Container(
child: FlatButton(
child: Text("Update"),
onPressed: () {
print("Updating list");
setState(() {
list = ["three", "four"];
});
},
)
),
Column(
children: list.map((s) => ItemView(s)).toList(),
)
],
),
)
);
}
}
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState(s);
}
class ItemViewState extends State<ItemView> {
String s;
ItemViewState(this.s);
#override
Widget build(BuildContext context) {
return Text(s);
}
}
When I press the "Update" button, my list is updated but the UI is not. I believe this has something to do with using a custom widget (which is also stateful) because when I replace ItemView(s) with the similar Text(s), the UI updates.
I understand that Flutter keeps a track of my stateful widgets and what data is being used, but I'm clearly missing something.
How do I get the UI to update and still use my custom widget?
You should never pass parameters to your State.
Instead, use the widget property.
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState();
}
class ItemViewState extends State<ItemView> {
#override
Widget build(BuildContext context) {
return Text(widget.s);
}
}
I'm wondering what the recommended way of passing data to a stateful widget, while creating it, is.
The two styles I've seen are:
class ServerInfo extends StatefulWidget {
Server _server;
ServerInfo(Server server) {
this._server = server;
}
#override
State<StatefulWidget> createState() => new _ServerInfoState(_server);
}
class _ServerInfoState extends State<ServerInfo> {
Server _server;
_ServerInfoState(Server server) {
this._server = server;
}
}
This method keeps a value both in ServerInfo and _ServerInfoState, which seems a bit wasteful.
The other method is to use widget._server:
class ServerInfo extends StatefulWidget {
Server _server;
ServerInfo(Server server) {
this._server = server;
}
#override
State<StatefulWidget> createState() => new _ServerInfoState();
}
class _ServerInfoState extends State<ServerInfo> {
#override
Widget build(BuildContext context) {
widget._server = "10"; // Do something we the server value
return null;
}
}
This seems a bit backwards as the state is no longer stored in _ServerInfoSate but instead in the widget.
Is there a best practice for this?
Don't pass parameters to State using it's constructor.
You should only access the parameters using this.widget.myField.
Not only editing the constructor requires a lot of manual work ; it doesn't bring anything. There's no reason to duplicate all the fields of Widget.
EDIT :
Here's an example:
class ServerIpText extends StatefulWidget {
final String serverIP;
const ServerIpText ({ Key? key, this.serverIP }): super(key: key);
#override
_ServerIpTextState createState() => _ServerIpTextState();
}
class _ServerIpTextState extends State<ServerIpText> {
#override
Widget build(BuildContext context) {
return Text(widget.serverIP);
}
}
class AnotherClass extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: ServerIpText(serverIP: "127.0.0.1")
);
}
}
Best way is don't pass parameters to State class using it's constructor. You can easily access in State class using widget.myField.
For Example
class UserData extends StatefulWidget {
final String clientName;
final int clientID;
const UserData(this.clientName,this.clientID);
#override
UserDataState createState() => UserDataState();
}
class UserDataState extends State<UserData> {
#override
Widget build(BuildContext context) {
// Here you direct access using widget
return Text(widget.clientName);
}
}
Pass your data when you Navigate screen :
Navigator.of(context).push(MaterialPageRoute(builder: (context) => UserData("WonderClientName",132)));
Another answer, building on #RĂ©miRousselet's anwser and for #user6638204's question, if you want to pass initial values and still be able to update them in the state later:
class MyStateful extends StatefulWidget {
final String foo;
const MyStateful({Key key, this.foo}): super(key: key);
#override
_MyStatefulState createState() => _MyStatefulState(foo: this.foo);
}
class _MyStatefulState extends State<MyStateful> {
String foo;
_MyStatefulState({this.foo});
#override
Widget build(BuildContext context) {
return Text(foo);
}
}
For passing initial values (without passing anything to the constructor)
class MyStateful extends StatefulWidget {
final String foo;
const MyStateful({Key key, this.foo}): super(key: key);
#override
_MyStatefulState createState() => _MyStatefulState();
}
class _MyStatefulState extends State<MyStateful> {
#override
void initState(){
super.initState();
// you can use this.widget.foo here
}
#override
Widget build(BuildContext context) {
return Text(foo);
}
}
Flutter's stateful widgets API is kinda awkward: storing data in Widget in order to access it in build() method which resides in State object đŸ¤¦ If you don't want to use some of bigger state management options (Provider, BLoC), use flutter_hooks (https://pub.dev/packages/flutter_hooks) - it is a nicer and cleaner substitute for SatefullWidgets:
class Counter extends HookWidget {
final int _initialCount;
Counter(this._initialCount = 0);
#override
Widget build(BuildContext context) {
final counter = useState(_initialCount);
return GestureDetector(
// automatically triggers a rebuild of Counter widget
onTap: () => counter.value++,
child: Text(counter.value.toString()),
);
}
}
#RĂ©mi Rousselet, #Sanjayrajsinh, #Daksh Shah is also better. but I am also defined this is in from starting point.that which parameter is which value
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
String name = "Flutter Demo";
String description = "This is Demo Application";
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainActivity(
appName: name,
appDescription: description,
),
);
}
}
class MainActivity extends StatefulWidget {
MainActivity({Key key, this.appName, this.appDescription}) : super(key: key);
var appName;
var appDescription;
#override
_MainActivityState createState() => _MainActivityState();
}
class _MainActivityState extends State<MainActivity> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.appName),
),
body: Scaffold(
body: Center(
child: Text(widget.appDescription),
),
),
);
}
}
The best practice is to define the stateful widget class as immutable which means defining all dependencies (arrival parameter) as final parameters. and getting access to them by widget.<fieldName> in the state class. In case you want to change their values like reassigning you should define the same typed properties in your state class and re-assign them in the initState function. it is highly recommended not to define any not-final property in your stateful widget class and make it a mutable class. something like this pattern:
class SomePage extends StatefulWidget{
final String? value;
SomePage({this.value});
#override
State<SomePage> createState() => _SomePageState();
}
class _SomePageState extends State<SomePage> {
String? _value;
#override
void initState(){
super.initState();
setState(() {
_value = widget.value;
});
}
#override
Widget build(BuildContext context) {
return Text(_value);
}
}
To pass data to stateful widget, first of all, create two pages. Now from the first page open the second page and pass the data.
class PageTwo extends StatefulWidget {
final String title;
final String name;
PageTwo ({ this.title, this.name });
#override
PageTwoState createState() => PageTwoState();
}
class PageTwoStateState extends State<PageTwo> {
#override
Widget build(BuildContext context) {
return Text(
widget.title,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w700),
),
}
}
class PageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialButton(
text: "Open PageTwo",
onPressed: () {
var destination = ServicePage(
title: '<Page Title>',
provider: '<Page Name>',
);
Navigator.push(context,
MaterialPageRoute(builder: (context) => destination));
},);
}
}
I'm wondering what the recommended way of passing data to a stateful widget, while creating it, is.
The two styles I've seen are:
class ServerInfo extends StatefulWidget {
Server _server;
ServerInfo(Server server) {
this._server = server;
}
#override
State<StatefulWidget> createState() => new _ServerInfoState(_server);
}
class _ServerInfoState extends State<ServerInfo> {
Server _server;
_ServerInfoState(Server server) {
this._server = server;
}
}
This method keeps a value both in ServerInfo and _ServerInfoState, which seems a bit wasteful.
The other method is to use widget._server:
class ServerInfo extends StatefulWidget {
Server _server;
ServerInfo(Server server) {
this._server = server;
}
#override
State<StatefulWidget> createState() => new _ServerInfoState();
}
class _ServerInfoState extends State<ServerInfo> {
#override
Widget build(BuildContext context) {
widget._server = "10"; // Do something we the server value
return null;
}
}
This seems a bit backwards as the state is no longer stored in _ServerInfoSate but instead in the widget.
Is there a best practice for this?
Don't pass parameters to State using it's constructor.
You should only access the parameters using this.widget.myField.
Not only editing the constructor requires a lot of manual work ; it doesn't bring anything. There's no reason to duplicate all the fields of Widget.
EDIT :
Here's an example:
class ServerIpText extends StatefulWidget {
final String serverIP;
const ServerIpText ({ Key? key, this.serverIP }): super(key: key);
#override
_ServerIpTextState createState() => _ServerIpTextState();
}
class _ServerIpTextState extends State<ServerIpText> {
#override
Widget build(BuildContext context) {
return Text(widget.serverIP);
}
}
class AnotherClass extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: ServerIpText(serverIP: "127.0.0.1")
);
}
}
Best way is don't pass parameters to State class using it's constructor. You can easily access in State class using widget.myField.
For Example
class UserData extends StatefulWidget {
final String clientName;
final int clientID;
const UserData(this.clientName,this.clientID);
#override
UserDataState createState() => UserDataState();
}
class UserDataState extends State<UserData> {
#override
Widget build(BuildContext context) {
// Here you direct access using widget
return Text(widget.clientName);
}
}
Pass your data when you Navigate screen :
Navigator.of(context).push(MaterialPageRoute(builder: (context) => UserData("WonderClientName",132)));
Another answer, building on #RĂ©miRousselet's anwser and for #user6638204's question, if you want to pass initial values and still be able to update them in the state later:
class MyStateful extends StatefulWidget {
final String foo;
const MyStateful({Key key, this.foo}): super(key: key);
#override
_MyStatefulState createState() => _MyStatefulState(foo: this.foo);
}
class _MyStatefulState extends State<MyStateful> {
String foo;
_MyStatefulState({this.foo});
#override
Widget build(BuildContext context) {
return Text(foo);
}
}
For passing initial values (without passing anything to the constructor)
class MyStateful extends StatefulWidget {
final String foo;
const MyStateful({Key key, this.foo}): super(key: key);
#override
_MyStatefulState createState() => _MyStatefulState();
}
class _MyStatefulState extends State<MyStateful> {
#override
void initState(){
super.initState();
// you can use this.widget.foo here
}
#override
Widget build(BuildContext context) {
return Text(foo);
}
}
Flutter's stateful widgets API is kinda awkward: storing data in Widget in order to access it in build() method which resides in State object đŸ¤¦ If you don't want to use some of bigger state management options (Provider, BLoC), use flutter_hooks (https://pub.dev/packages/flutter_hooks) - it is a nicer and cleaner substitute for SatefullWidgets:
class Counter extends HookWidget {
final int _initialCount;
Counter(this._initialCount = 0);
#override
Widget build(BuildContext context) {
final counter = useState(_initialCount);
return GestureDetector(
// automatically triggers a rebuild of Counter widget
onTap: () => counter.value++,
child: Text(counter.value.toString()),
);
}
}
#RĂ©mi Rousselet, #Sanjayrajsinh, #Daksh Shah is also better. but I am also defined this is in from starting point.that which parameter is which value
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
String name = "Flutter Demo";
String description = "This is Demo Application";
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MainActivity(
appName: name,
appDescription: description,
),
);
}
}
class MainActivity extends StatefulWidget {
MainActivity({Key key, this.appName, this.appDescription}) : super(key: key);
var appName;
var appDescription;
#override
_MainActivityState createState() => _MainActivityState();
}
class _MainActivityState extends State<MainActivity> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.appName),
),
body: Scaffold(
body: Center(
child: Text(widget.appDescription),
),
),
);
}
}
The best practice is to define the stateful widget class as immutable which means defining all dependencies (arrival parameter) as final parameters. and getting access to them by widget.<fieldName> in the state class. In case you want to change their values like reassigning you should define the same typed properties in your state class and re-assign them in the initState function. it is highly recommended not to define any not-final property in your stateful widget class and make it a mutable class. something like this pattern:
class SomePage extends StatefulWidget{
final String? value;
SomePage({this.value});
#override
State<SomePage> createState() => _SomePageState();
}
class _SomePageState extends State<SomePage> {
String? _value;
#override
void initState(){
super.initState();
setState(() {
_value = widget.value;
});
}
#override
Widget build(BuildContext context) {
return Text(_value);
}
}
To pass data to stateful widget, first of all, create two pages. Now from the first page open the second page and pass the data.
class PageTwo extends StatefulWidget {
final String title;
final String name;
PageTwo ({ this.title, this.name });
#override
PageTwoState createState() => PageTwoState();
}
class PageTwoStateState extends State<PageTwo> {
#override
Widget build(BuildContext context) {
return Text(
widget.title,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w700),
),
}
}
class PageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialButton(
text: "Open PageTwo",
onPressed: () {
var destination = ServicePage(
title: '<Page Title>',
provider: '<Page Name>',
);
Navigator.push(context,
MaterialPageRoute(builder: (context) => destination));
},);
}
}