How to force Flutter to rebuild / redraw all widgets?
DartFlutterDart Problem Overview
Is there a way to force Flutter to redraw all widgets (e.g. after locale change)?
Dart Solutions
Solution 1 - Dart
Your Widget
should have a setState()
method, everytime this method is called, the widget is redrawn.
Solution 2 - Dart
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 ofLocaleQueryData
- Register this callback as the
onLocaleChanged
argument to theMaterialApp
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
.
Solution 3 - Dart
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.
Solution 4 - Dart
I explain how to create a custom 'AppBuilder' widget in this post.
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();
Solution 5 - Dart
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(() {});
Solution 6 - Dart
Simply Use:
Navigator.popAndPushNamed(context,'/screenname');
Whenever you need to refresh :)
Solution 7 - Dart
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).
Solution 8 - Dart
> 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}',
);
}
}
Solution 9 - Dart
Use a AppBuilder widget to make the redraw widget
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
void main() {
final store =
Store<AppState>(reducer, initialState: AppState(enableDarkMode: false));
runApp(MyApp(store: store));
}
class MyApp extends StatefulWidget {
const MyApp({Key key, this.store}) : super(key: key);
final Store<AppState> store;
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final store = widget.store;
return StoreProvider<AppState>(
store: store,
child: AppBuilder(
builder: (BuildContext context) {
return MaterialApp(
theme: ThemeData(
brightness: store.state.enableDarkMode
? Brightness.dark
: Brightness.light),
home: Scaffold(
appBar: AppBar(
title: Text('Test Redux App'),
),
body: SettingsView(),
),
);
},
),
);
}
}
class SettingsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreBuilder(builder: (BuildContext context, Store<AppState> store) {
final state = store.state;
return SwitchListTile(
title: Text('Dark Mode'),
value: state.enableDarkMode,
onChanged: (value) {
store.dispatch(UpdateDarkMode(enable: !state.enableDarkMode));
AppBuilder.of(context).rebuild();
},
secondary: Icon(Icons.settings),
);
});
}
}
// Redux Claaes
class AppState {
AppState({this.enableDarkMode});
bool enableDarkMode;
}
class UpdateDarkMode {
UpdateDarkMode({this.enable});
final bool enable;
}
AppState reducer(AppState state, dynamic action) {
if (action is UpdateDarkMode) {
return AppState(
enableDarkMode: action.enable,
);
}
return state;
}
// App Builder
class AppBuilder extends StatefulWidget {
const AppBuilder({Key key, this.builder}) : super(key: key);
final Function(BuildContext) builder;
@override
AppBuilderState createState() => new AppBuilderState();
static AppBuilderState of(BuildContext context) {
return context.ancestorStateOfType(const TypeMatcher<AppBuilderState>());
}
}
class AppBuilderState extends State<AppBuilder> {
@override
Widget build(BuildContext context) {
return widget.builder(context);
}
void rebuild() {
setState(() {});
}
}
Solution 10 - Dart
Simply Use:
Navigator.popAndPushNamed(context,'/xxx');
Solution 11 - Dart
If you are creating a multi lang app I'd suggest you be using Localization Library to handle that. Even using conventions used by other applications.
flutter_i18n - It also has a method to regenerate over runtime - documented at the bottom
await FlutterI18n.refresh(buildContext, languageCode, {countryCode});
What it does is generating widgets with state that is managed by a combined singelton resource that handles both dynamic data and visibility.