Related
Im new to flutter and dart so i have created simple counter flutter app , explained in article.
But when stream does not update widget when subject adds values.
can someone help me to find the issue.
my main widget class
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CounterBloc _counterBloc = new CounterBloc(initialCount: 0);
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Counter(),
floatingActionButton: FloatingActionButton(
onPressed: _counterBloc.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
and Counter widget
class Counter extends StatefulWidget {
#override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
CounterBloc _counterBloc = new CounterBloc(initialCount: 1);
#override
void dispose() {
_counterBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _counterBloc.counterObservable,
builder: (context, snapshot) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${snapshot.hasData}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
);
}
}
and here is my bloc class
class CounterBloc {
int initialCount =
1; //if the data is not passed by paramether it initializes with 0
BehaviorSubject<int> _subjectCounter;
CounterBloc({this.initialCount}) {
_subjectCounter = new BehaviorSubject<int>.seeded(
this.initialCount); //initializes the subject with element already
}
Observable<int> get counterObservable => _subjectCounter.stream;
void increment() {
initialCount++;
print(initialCount);
_subjectCounter.add(initialCount);
}
void decrement() {
initialCount--;
_subjectCounter.add(initialCount);
}
void dispose() {
_subjectCounter.close();
}
}
Can some one help me to find the issue.
Thanks.
You are using two separate instances of CounterBloc in MyHomePage and Counter classes. A simple solution would be to pass the CounterBloc of MyHomePage to Counter:
MyHomePage
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
MyHomePageState
class _MyHomePageState extends State<MyHomePage> {
CounterBloc _counterBloc = CounterBloc(initialCount: 0);
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Counter(
bloc: _counterBloc,
),
floatingActionButton: FloatingActionButton(
onPressed: _counterBloc.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Counter
class Counter extends StatefulWidget {
Counter({Key key, this.bloc}) : super(key: key);
final CounterBloc bloc;
#override
_CounterState createState() => _CounterState();
}
CounterState
class _CounterState extends State<Counter> {
CounterBloc _counterBloc;
#override
void dispose() {
_counterBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
CounterBloc _counterBloc = widget.bloc;
return StreamBuilder(
stream: _counterBloc.counterObservable,
builder: (context, snapshot) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
);
}
}
But in the long run, rather than passing your Bloc explicitly as a parameter, you should use a BlocProvider, which will implicitly assigns the instance of the parent classes' Bloc to your children.
I'm wondering how I can pass a string from a class into its state class.
I tried passing a string into a Text widget using widget.foodName but it returns me the error "Evaluation of this constant expression throws an exception".
I guess since the constructor is declaring that string it returns me that issue.
Any ideas how to solve this?
import 'package:flutter/material.dart';
class FoodCard extends StatefulWidget {
#override
FoodCard(this.chosenIcon, this.foodName, this.expireDate);
int chosenIcon;
String foodName;
String expireDate;
_FoodCardState createState() => _FoodCardState();
}
class _FoodCardState extends State<FoodCard> {
#override
Widget build(BuildContext context) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
leading: Icon(Icons.apps),
title: Text(widget.foodName), // <<<--------------
subtitle: Text("xyz"),
)
],
),
);
}
}
Change your FoodCard class into the following structure, and you'll be able to call foodName property without any problems. These changes are made following the Stateful widget class documentation:
class FoodCard extends StatefulWidget {
FoodCard({Key key, this.chosenIcon, this.foodName, this.expireDate}): super(key: key);
final int chosenIcon;
final String foodName;
final String expireDate;
#override
_FoodCardState createState() => _FoodCardState();
}
And you should also remove const which is added before ListTile widget:
class _FoodCardState extends State<FoodCard> {
#override
Widget build(BuildContext context) {
return Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.apps),
title: Text(widget.foodName), // <<<--------------
subtitle: Text("xyz"),
)
],
),
);
}
}
I have created an Homepage and from that user can sign in for the app and in the next screen user can see their profile info(Only profile name) and under that their is signOut button. User can signOut from the app using signOut button.But it's not working for me.
I want to call signOut method from main.dart by pressing signOut button in details.dart(both the classes are in different file)
But when i press signOut Button in details.dart nothing happens!
And code is given below:
main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'details.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.blue,
),
debugShowCheckedModeBanner: false,
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
static bool _LoginButton = true;
void signOut(){
googleSignIn.signOut();
setState((){
_LoginButton = true;
});
print(_LoginButton);
print("User Signed Out");
}
Future<FirebaseUser> _signIn() async{
if(_LoginButton==true){
setState((){
_LoginButton=false;
});
GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount.authentication;
FirebaseUser firebaseUser = await firebaseAuth.signInWithGoogle(idToken: googleSignInAuthentication.idToken, accessToken: googleSignInAuthentication.accessToken);
print("Username is "+firebaseUser.displayName);
setState((){
_LoginButton = true;
});
Navigator.push(context, MaterialPageRoute(builder: (context) => details(firebaseUser.displayName,signOut)));
return firebaseUser;
}
}
bool _LoginButtonBool(){
return _LoginButton;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Google auth with firebase"),),
body: Center(
child: _LoginButtonBool()?Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
MaterialButton(onPressed: _LoginButtonBool() ? () => _signIn().then((FirebaseUser firebaseuser ) =>print(firebaseuser)).catchError((e) => print(e)): null,
child: Text("Login"),color: Colors.orange,),
],
),
):CircularProgressIndicator(backgroundColor: Colors.greenAccent.withOpacity(0.01),),
),
);
}
}
details.dart
import 'package:flutter/material.dart';
import 'package:flutter_auth/main.dart';
class details extends StatelessWidget {
String name;
final Function callback;
details(this.name,this.callback);
#override
Widget build(BuildContext context) {
return Scaffold(
body:Center(child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(name),
MaterialButton(onPressed: () => callback,
child: Text("Log out"),color: Colors.orange),
],
),),
);
}
}
It is simple let me explain with an example
class Animals
{
var animalList = ['dog','cat','cow'];
// function for printing the list of animals
void animalListPrinter(){
for(var animal in animalList){
print(animal);
}
}
}
Calling the above function to another class
class ShowingAnimalList extends StatelessWidget {
final Animals ani= new Animals();
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap:()=> ani.animalListPrinter(),
);
}
}
You can call any Widget with this from the parent class
You must be careful with what you are trying to do because you might be accessing a page/widget that is not mounted. Imagine you do a pushReplacement(new MaterialPageroute(...)). The previous page is no longer available in the tree so you can't access it nor any of its methods.
Unless you have a clear parent child relationship in your tree, you should abstract away your logic to external or business logic classes. Thus you are sure that you are calling active instances of your classes.
Here is an example of what you could use passing around the Business object. It would be even better if you use other patterns like BLOC, ScopedModel, Streams, etc. But for the sake of simplicity I think this should be enough.
import "package:flutter/material.dart";
void main() {
runApp(MyApp(new Logic()));
}
class Logic {
void doSomething() {
print("doing something");
}
}
class MyApp extends StatelessWidget {
final Logic logic;
MyApp(this.logic);
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomePage(widget.logic),
);
}
}
class HomePage extends StatelessWidget {
final Logic logic;
HomePage(this.logic);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: () { Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => AnotherPage(logic),
))},
child: Text("Go to AnotherPage"),
),
),
);
}
}
class AnotherPage extends StatelessWidget {
final Logic logic;
AnotherPage(this.logic);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: logic.doSomething,
child: Text("Press me"),
),
),
);
}
}
If you still want to call a function in the other Page and you are sure the page is mounted (you have done a push instead of a pushReplacement) you could do the following. (handle with care)
class HomePage extends StatelessWidget {
HomePage();
void onCalledFromOutside() {
print("Call from outside");
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: () { Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => AnotherPage(onCalledFromOutside),
))},
child: Text("Go to AnotherPage"),
),
),
);
}
}
class AnotherPage extends StatelessWidget {
final Function callback
AnotherPage(this.callback);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FlatButton(
onPressed: callback,
child: Text("Press me"),
),
),
);
}
}
We can access it easily just like below.
className().MethodName(),
you can create another logout() function and give context of home to push back to sign in screen/home screen , works for me as :
logout() async {
await googleSignIn.signOut();
Navigator.push(context, MaterialPageRoute(builder: (context) => Home()));
}
Import HomePage class in DetailsPage and make a new instance out of it, then call the method you want if it's a public one.
We can take help instance Make instance given below
var objectName = new ClassName(<constructor_arguments>)
Note: We can use an empty constructor like this example.
class Student{
void female(){
print('This is female method');
}
void male(){
print('This is malemethod'); }
}
step1: var _instance1 = new Student(); here empty constructor it dos't matter.
step2: _instance1.male(); Call method _instance1 what we want.
A global key given to a StatefulWidget can give us access to its methods from anywhere.
GlobalKey example
WidgetA below has a method login().
To access login() from other widgets, instantiate WidgetA with a GlobalKey.
Then use that key to access WidgetA state to call login().
The structure below is:
ExamplePage
WidgetA
LoginDialog
LoginDialog will call WidgetA.login() using the global key.
login() will update the AppBar in WidgetA with the user name.
WidgetA
Here is the StatefulWidget WidgetA:
class WidgetA extends StatefulWidget {
const WidgetA({Key? key}) : super(key: key);
#override
State<WidgetA> createState() => WidgetAState();
static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}
class WidgetAState extends State<WidgetA> {
String loginStatus = 'Signed Out';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$loginStatus'),
),
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () =>
showDialog(context: context,
builder: (context) => LoginDialog()),
),
),
);
}
void login(String msg) {
setState(() {
loginStatus = msg;
});
}
}
Notes
static method createKey() is a convenience method to create the type of global key we need, a GlobalKey with a Type of WidgetAState
when we instantiate WidgetA, we'll need to give it a key of type GlobalKey<WidgetAState>
GlobalKey<WidgetAState>
Here's the global key we'll create of type WidgetAState, using the convenience static method createKey() we added to WidgetA class.
final widgetA = WidgetA.createKey();
To make accessing WidgetAState cleaner, we can create an optional extension class on GlobalKey<WidgetAState> types that gives us direct access to WidgetAState:
extension WidgetAKeyExt on GlobalKey<WidgetAState> {
void login(String user) => currentState?.login(user);
}
This allows us to make this call:
widgetA.login()
instead of this:
widgetA.currentState?.login()
LoginDialog
This login dialog will access WidgetA's login() method using the global key.
class LoginDialog extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Login'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text('Login'),
onPressed: () => widgetA.login('Billy'), // WidgetA method call
),
ElevatedButton(
child: Text('Logout'),
onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
),
],
),
);
}
}
Entire Code Sample
Here's the entire code sample in a single block to copy/paste. Also includes another page WidgetB which also accesses WidgetA state through the global key.
When a login is perfomed in the LoginDialog, the app bar in WidgetA will reflect the loginStatus state. That state is also accessible in WidgetB using the global key.
import 'package:flutter/material.dart';
/// Share access to a widget's methods & state by using a GlobalKey.
/// Create a GlobalKey<T> using that widget's state class as T.
/// For example below we use GlobalKey<WidgetAState>.
///
/// Give that key to WidgetA's constructor / instantiation.
///
/// Then use that key from anywhere to call methods on WidgetAState.
/// Create the key for [WidgetAState] globally so it can be accessed across your app.
/// i.e. do this outside of a Widget, such as in your main.dart or in a state
/// management solution that you access globally, etc.
///
/// For readability/ease we've created a static method [createKey] on [WidgetA] to
/// instantiate the global key we need.
///
/// This could also just be `final widgetA = GlobalKey<WidgetAState>();
final widgetA = WidgetA.createKey(); // see static method inside WidgetA
/// Just an empty page/route to hold [WidgetA] instantiation with global key.
class ExampleGlobalKeyPage extends StatelessWidget {
const ExampleGlobalKeyPage();
#override
Widget build(BuildContext context) {
return WidgetA(key: widgetA);
}
}
/// [WidgetA] takes a key argument. We'll give it the global key we created so
/// we can access its state object from anywhere.
class WidgetA extends StatefulWidget {
const WidgetA({Key? key}) : super(key: key);
#override
State<WidgetA> createState() => WidgetAState();
/// Convenience static method to create the [WidgetAState] key to provide to [WidgetA].
/// By being specific with the [Key] class as "GlobalKey<WidgetAState>" instead
/// of just "Key", we can use create & use an extension class [WidgetAKeyExt]
/// to make accessing the widget state cleaner & easier. See that extension
/// class below.
static GlobalKey<WidgetAState> createKey() => GlobalKey<WidgetAState>();
}
/// This is [WidgetA]'s state object. By default it's private, but we'll
/// make it public by removing '_' from the name, so [_WidgetAState]
/// becomes [WidgetAState].
///
/// Its [login] method will be available anywhere using the global key we gave
/// [WidgetA] constructor.
///
class WidgetAState extends State<WidgetA> {
String loginStatus = 'Signed Out'; // this state can be changed with the GlobalKey
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$loginStatus'),
actions: [
IconButton(icon: Icon(Icons.arrow_circle_right_rounded), onPressed: gotoB,)
],
),
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () => showDialog(context: context,
builder: (context) => LoginDialog()),
),
),
);
}
/// This method can be called from [LoginDialog] widget using the GlobalKey
void login(String msg) {
setState(() {
loginStatus = msg;
});
}
void gotoB() => Navigator.push(context, MaterialPageRoute(builder: (c) => WidgetB()));
}
/// This extension class is not needed, but makes access to the [login] method
/// a little cleaner.
///
/// This extension method on GlobalKey<WidgetAState> performs the null check
/// for [currentState] existence before calling [login],
/// wherever/whenever we use [widgetA] key.
///
/// Also it uses "currentState" for us.
/// So our call `widgetA.currentState?.login()` is now `widgetA.login()`.
///
extension WidgetAKeyExt on GlobalKey<WidgetAState> {
void login(String user) => currentState?.login(user);
String get loginStatus => currentState?.loginStatus ?? 'Signed Out';
}
/// This Dialog is a separate route & widget. It makes calls to [WidgetA] methods
/// using the global key [widgetA].
class LoginDialog extends StatelessWidget {
/// For visibility you may want to pass the global key as a constructor arg
/// rather than using it directly. If so, you'd do something like:
//final GlobalKey<WidgetAState> widgetA;
//const LoginDialog({super.key, required this.widgetA});
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Login'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
child: Text('Login'),
onPressed: () => widgetA.login('Billy'), // WidgetA method call
),
ElevatedButton(
child: Text('Logout'),
onPressed: () => widgetA.login('Signed Out'), // WidgetA method call
),
],
),
);
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widgetA.loginStatus}'),
),
body: Center(
child: Text('Some other page accessing WidgetA'),
),
);
}
}
For example, I have two StatefulWidget to monitor the same callback method. How should I do this? In case I have more than three StatefulWidget to monitor its events?
class WidgetOne extends StatefulWidget {
#override
_WidgetOneState createState() => new _WidgetOneState();
}
class _WidgetOneState extends State<WidgetOne> {
// this is the callback, the widget two want listen the callback too
bool _onNotification(ScrollNotification notification){
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new NotificationListener(child: new ListView(shrinkWrap: true,),
onNotification: _onNotification),
new WidgetTwo()
],
);
}
}
class WidgetTwo extends StatefulWidget {
#override
_WidgetTwoState createState() => new _WidgetTwoState();
}
class _WidgetTwoState extends State<WidgetTwo> {
// in this,How Can I get the callback in WidgetOne?
#override
Widget build(BuildContext context) {
return new Container();
}
}
You can't and shouldn't. Widgets should never depends on the architecture of other widgets.
You have two possibilities :
Merge WidgetTwo and WidgetOne. As separating them doesn't makes sense (at least with what you provided).
Modify WidgetTwo to take a child. And add that ListView as child of WidgetTwo. So that it can wrap the list into it's own NotificationListener.
Your solution could be possible by using setState() and pass your state function in constructor of WidgetTwo. I did an example below, main idea of this example is I have MyHomePage as my main Widget and MyFloatButton (which I want to customise as another StatefulWidget), so when pressing the FAB i need to call increment counter function in MyHomePage. Lets take a look below how I do that.
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;
//Consider this function as your's _onNotification and important to note I am using setState() within :)
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
widget.title,
style: TextStyle(color: Colors.white),
),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button $_counter times:',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
],
),
),
floatingActionButton: new MyFloatButton(_incrementCounter),//here I am calling MyFloatButton Constructor passing _incrementCounter as a function
);
}
}
class MyFloatButton extends StatefulWidget {
final Function onPressedFunction;
// Here I am receiving the function in constructor as params
MyFloatButton(this.onPressedFunction);
#override
_MyFloatButtonState createState() => new _MyFloatButtonState();
}
class _MyFloatButtonState extends State<MyFloatButton> {
#override
Widget build(BuildContext context) {
return new Container(
padding: EdgeInsets.all(5.0),
decoration: new BoxDecoration(color: Colors.orangeAccent, borderRadius: new BorderRadius.circular(50.0)),
child: new IconButton(
icon: new Icon(Icons.add),
color: Colors.white,
onPressed: widget.onPressedFunction,// here i set the onPressed property with widget.onPressedFunction. Remember that you should use "widget." in order to access onPressedFunction here!
),
);
}
}
Now Consider MyHomePage as your WidgetOne, MyFloatButton as your WidgetTwo and _incrementCounter function as your _onNotification. Hope you will achieve what you want :)
(I did example generically so anyone can understand according to scenario they are facing)
You can use the built-in widget property for stateful widgets.
https://api.flutter.dev/flutter/widgets/State/widget.html
So in WidgetOne it will be
new WidgetTwo(callback: callback)
in WidgetTwo
class WidgetTwo extends StatefulWidget {
final Function callback;
WidgetTwo({this.callback});
#override
_WidgetTwoState createState() => new _WidgetTwoState();
}
and in _WidgetTwoState you can access it as
widget.callback
Is there a way to force Flutter to redraw all widgets (e.g. after locale change)?
Your Widget should have a setState() method, everytime this method is called, the widget is redrawn.
Documentation : Widget setState()
Old question, but here is the solution:
In your build method, call the rebuildAllChildren function and pass it the context:
#override
Widget build(BuildContext context) {
rebuildAllChildren(context);
return ...
}
void rebuildAllChildren(BuildContext context) {
void rebuild(Element el) {
el.markNeedsBuild();
el.visitChildren(rebuild);
}
(context as Element).visitChildren(rebuild);
}
This will visit all children and mark them as needing to rebuild.
If you put this code in the topmost widget in your widgets tree, it will rebuild everything.
Also note you must order that specific widget to rebuild. Also you could have some boolean so that the rebuild of that widget only rebuilds all of its children when you really need it (it's an expensive operation, of course).
IMPORTANT: This is a hack, and you should only do this if you know what you are doing, and have strong reason to do so. One example where this is necessary is in my internationalization package: i18_extension. As Collin Jackson explained in his answer, you are really not supposed to do this in general.
This type of use case, where you have data that children can read but you don't want to explicitly pass the data to the constructor arguments of all your children, usually calls for an InheritedWidget. Flutter will automatically track which widgets depend on the data and rebuild the parts of your tree that have changed. There is a LocaleQuery widget that is designed to handle locale changes, and you can see how it's used in the Stocks example app.
Briefly, here's what Stocks is doing:
Put a callback on root widget (in this case, StocksApp) for handling locale changes. This callback does some work and then returns a customized instance of LocaleQueryData
Register this callback as the onLocaleChanged argument to the MaterialApp constructor
Child widgets that need locale information use LocaleQuery.of(context).
When the locale changes, Flutter only redraws widgets that have dependencies on the locale data.
If you want to track something other than locale changes, you can make your own class that extends InheritedWidget, and include it in the hierarchy near the root of your app. Its parent should be a StatefulWidget with key set to a GlobalKey that accessible to the children. The State of the StatefulWidget should own the data you want to distribute and expose methods for changing it that call setState. If child widgets want change the State's data, they can use the global key to get a pointer to the State (key.currentState) and call methods on it. If they want to read the data, they can call the static of(context) method of your subclass of InheritedWidget and that will tell Flutter that these widgets need to rebuilt whenever your State calls setState.
Refreshing the whole widget tree might be expensive and when you do it in front of the users eyes that wouldn't seem sweet.
so for this purpose flutter has ValueListenableBuilder<T> class. It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.
you can see the documents here ValueListenableBuilder flutter docs
or just the sample code below:
return Scaffold(
appBar: AppBar(
title: Text(widget.title)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
ValueListenableBuilder(
builder: (BuildContext context, int value, Widget child) {
// This builder will only get called when the _counter
// is updated.
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text('$value'),
child,
],
);
},
valueListenable: _counter,
// The child parameter is most helpful if the child is
// expensive to build and does not depend on the value from
// the notifier.
child: goodJob,
)
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.plus_one),
onPressed: () => _counter.value += 1,
),
);
And also never forget the power of setState(() {});
I explain how to create a custom 'AppBuilder' widget in this post.
https://hillelcoren.com/2018/08/15/flutter-how-to-rebuild-the-entire-app-to-change-the-theme-or-locale/
You can use the widget by wrapping your MaterialApp with it, for example:
Widget build(BuildContext context) {
return AppBuilder(builder: (context) {
return MaterialApp(
...
);
});
}
You can tell the app to rebuild using:
AppBuilder.of(context).rebuild();
Simply Use:
Navigator.popAndPushNamed(context,'/screenname');
Whenever you need to refresh :)
What might work for your use case is using the Navigator to reload the page. I do this when switching between "real" and "demo" mode in my app. Here's an example :
Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext context){
return new SplashPage();
}
)
);
You can replace "new SplashPage()" in the above code with whatever main widget (or screen) you would like to reload. This code can be called from anywhere you have access to a BuildContext (which is most places in the UI).
Just use a Key on one of your high-level widgets, everything below this will lose state:
Key _refreshKey = UniqueKey();
void _handleLocalChanged() => setState((){
_refreshKey = UniqueKey()
});
Widget build(BuildContext context){
return MaterialApp(
key: _refreshKey ,
...
)
}
You could also use a value key like:
return MaterialApp(
key: ValueKey(locale.name)
...
);
Why not just have Flutter.redrawAllWidgetsBecauseISaidSo();? –
TimSim
There kinda is:
Change to key to redraw statefull child widgets.
Jelena Lecic explained it good enough for me on medium.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
var _forceRedraw; // generate the key from this
void _incrementCounter() {
setState(() {
_counter++;
_forceRedraw = Object();
});
}
#override
void initState() {
_forceRedraw = Object();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
MyStatefullTextWidget(
key: ValueKey(_forceRedraw),
counter: _counter,
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class MyStatefullTextWidget extends StatefulWidget {
final int counter;
const MyStatefullTextWidget({
required this.counter,
Key? key,
}) : super(key: key);
#override
_MyStatefullTextWidgetState createState() => _MyStatefullTextWidgetState();
}
class _MyStatefullTextWidgetState extends State<MyStatefullTextWidget> {
#override
Widget build(BuildContext context) {
return Text(
'You have pushed the button this many times:${widget.counter}',
);
}
}
Simply Use:
Navigator.popAndPushNamed(context,'/xxx');
I my case it was enough to reconstruct the item.
Changed:
return child;
}).toList(),
To:
return SetupItemTypeButton(
type: child.type,
icon: child.icon,
active: _selected[i] == true,
onTap: ...,
);
}).toList(),
class SetupItemTypeButton extends StatelessWidget {
final dynamic type;
final String icon;
estureTapCallback onTap;
SetupItemTypeButton({Key? key, required this.type, required this.icon, required this.onTap}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container();
}
}
class SetupItemsGroup extends StatefulWidget {
final List<SetupItemTypeButton> children;
final Function(int index)? onSelect;
SetupItemsGroup({required this.children, this.onSelect});
#override
State<SetupItemsGroup> createState() => _SetupItemsGroupState();
}
class _SetupItemsGroupState extends State<SetupItemsGroup> {
final Map<int, bool> _selected = {};
#override
Widget build(BuildContext context) {
int index = 0;
return Container(
child: GridView.count(
children: widget.children.map((child) {
return SetupItemTypeButton(
type: child.type,
icon: child.icon,
active: _selected[i] == true,
onTap: () {
if (widget.onSelect != null) {
int i = index++;
child.active = _selected[i] == true;
setState(() {
_selected[i] = _selected[i] != true;
child.onTap();
widget.onSelect!(i);
});
}
},
);
}).toList(),
),
);
}
}