Global key is not restoring state sometimes of child widget - flutter

Basically, in this example, I am using global key to restore value of by child,
This work,
But sometimes, it appears that the global key does not restore the value of the child items.
To make it clear, I have added a checkbox widget and a slider widget to the first and last pages of this code (green and red).
When I change the slider value on the first page and try to see it on the third page, it works fine.
Same thing if I change checkbox value (like true) on first page and then click on third page it shows mostly true, but sometimes does not.
I am unable to understand the reason behind this issue. Please refer to the following gif to better understand my question.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
final _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Global Key Example")),
body: PageView.builder(
itemCount: 3,
itemBuilder: (context, index) {
switch (index) {
case 0:
return Container(
color: Colors.green,
child: ActionPage(_key),
);
break;
case 1:
return Container(
color: Colors.blue,
child: Text("Blank Page"),
);
break;
case 2:
return Container(
color: Colors.red,
child: ActionPage(_key),
);
break;
default:
throw "Not found";
}
},
),
);
}
}
class ActionPage extends StatefulWidget {
#override
_ActionPageState createState() => _ActionPageState();
ActionPage(key) : super(key: key);
}
class _ActionPageState extends State<ActionPage> {
bool _switchValue = false;
double _sliderValue = 0.5;
#override
Widget build(BuildContext context) {
return Column(
children: [
Switch(
value: _switchValue,
onChanged: (v) {
setState(() => _switchValue = v);
},
),
Slider(
value: _sliderValue,
onChanged: (v) {
setState(() => _sliderValue = v);
},
)
],
);
}
}

I'd recommened you to not use a GlobalKey here. Make sure to pass the needed data not the widget sate. If you take a look at the documentation the following pitfalls are listed:
GlobalKeys should not be re-created on every build. They should
usually be long-lived objects owned by a State object, for example.
Creating a new GlobalKey on every build will throw away the state of
the subtree associated with the old key and create a new fresh subtree
for the new key. Besides harming performance, this can also cause
unexpected behavior in widgets in the subtree. For example, a
GestureDetector in the subtree will be unable to track ongoing
gestures since it will be recreated on each build.
Instead, a good practice is to let a State object own the GlobalKey,
and instantiate it outside the build method, such as in
State.initState.
So you have to make sure that two widgets with the same key are never rendered on the screen at the same time. With a PageView this obviously can happen since you can display (eventhough there might be a transition state) two of your widgets with the same key at the same time. This can mess things up. Also they changed quite a bit in the past implementation wise if I recall correctly.
You should either pass the value directly to your widget or use something like an InheritedWidget to be safe.
Generally speaking I'd always try to avoid using GlobalKeys because they are quite complex and can have side effects which might not be always fully comprehensible. Also there are good alternatives.

Related

Riverpod state update does not rebuild the widget

I'm using StateProvider<List<String>> to keep track of user taps on the Tic Tac Toe board.
Actual board is a widget that extends ConsumerWidget and consists of tap-able GridView.
Within the onTap event of GridViews child - following is invoked to update the state:
ref.read(gameBoardStateProvider.notifier).state[index] = 'X';
For some reason this does not invoke widget rebuild event. Due to this I cannot see the 'X' in the GridView item which was tapped.
However, if I add additional "simple" StateProvider<int> and invoke it as well within the same onTap event then the widget gets rebuilt and I can see the 'X' in the GridView.
I am not even using or displaying this additional state provider but for some reason it invokes rebuild while my intended provided doesn't.
final gameBoardStateProvider = StateProvider<List<String>>((ref) => List.filled(9, '', growable: false));
final testStateProvider = StateProvider<int>((ref) => 0); //dummy state provider
class Board extends ConsumerWidget {
const Board({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final gameBoard = ref.watch(gameBoardStateProvider);
final testState = ref.watch(testStateProvider);
return Expanded(
child: Center(
child: GridView.builder(
itemCount: 9,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
shrinkWrap: true,
itemBuilder: ((BuildContext context, int index) {
return InkWell(
onTap: () {
//With this line only the widget does not get refreshed - and I do not see board refreshed with added 'X'
ref.read(gameBoardStateProvider.notifier).state[index] = 'X';
//??? If I add this line as well - for some reason the widget get refreshed - and I see board refreshed with added 'X'
ref.read(testStateProvider.notifier).state++;
},
child: Container(
decoration: BoxDecoration(border: Border.all(color: Colors.white)),
child: Center(
child: Text(gameBoard[index]),
),
),
);
}),
),
),
);
}
}
Once you do
ref.read(gameBoardStateProvider.notifier).state[index] = 'X'
Means changing a single variable. In order to update the UI, you need to provide a new list as State.
You can do it like
List<String> oldState = ref.read(gameBoardStateProvider);
oldState[index] = "X";
ref
.read(gameBoardStateProvider.notifier)
.update((state) => oldState.toList());
Or
List<String> oldState = ref.read(gameBoardStateProvider);
oldState[index] = "X";
ref.read(gameBoardStateProvider.notifier).state =
oldState.toList();
Why testStateProvider works:
Because it contains single int as State where gameBoardStateProvider contains List<String> as State.
Just updating single variable doesn't refresh ui on state_provider, You need to update the state to force a refresh
More about state_provider
You can also check change_notifier_provider
The reason the ""simple"" StateProvider triggers a rebuild and your actual doesn't is because you aren't reassigning its value.
StateProviders works like this:
StateProvider is just a StateNotifierProvider that uses a simple implementation of a StateNotifier that exposes its get state and set state methods (and an additional update method);
StateNotifier works like this: an actual state update consists of a reassignment. Whenever state gets reassigned it triggers its listeners (just like ChangeNotifier would do). See immutability patterns.
This means that, since you're exposing a List<String>, doing something like state[0] = "my new string will NOT trigger rebuilds. Only actions such as state = [... anotherList]; will do.
This is desirable, since StateNotifier pushes you to use immutable data, which is can be good pattern in the frontend world.
Instead, your int StateProvider will basically always trigger an update, since chances are that when you need to alter its state, you need to reassign its value.
For your use case you can do something like:
state[i] = 'x';
state = [...state];
This forces a re-assignment, and as such it will trigger the update.
Note: since you're implementing a tic-tac-toe, it is maybe desirable to handle mutable data for that grid. Try using a ChangeNotifier, implement some update logic (with notifiyListeners()) and exposing it with a ChangeNotifierProvider.
Riverpod actually advises against mutable patterns (i.e. against ChangeNotifiers), but, as there is no silver bullet in Computer Science, your use case might be different.
Big thanks to both Yeasin and venir for clarifying what it means to reassign the state and the difference between simple and complex types.
While keeping the initial StateProvider I've replaced
ref.read(gameBoardStateProvider.notifier).state[index] = 'X';
with
var dataList = ref.read(gameBoardStateProvider);
dataList[index] = 'X';
ref.read(gameBoardStateProvider.notifier).state = [...dataList];
And it works now - the state gets updated and the widget rebuilt.
I've also tried the StateNotifier approach and it also works - although it seems more convoluted to me :) I'm pasting the code bellow as it can maybe benefit someone else to see the example.
class GameBoardNotifier extends StateNotifier<List<String>> {
GameBoardNotifier() : super(List.filled(9, '', growable: false));
void updateBoardItem(String player, int index) {
state[index] = player;
state = [...state];
}
}
final gameBoardProvider = StateNotifierProvider<GameBoardNotifier, List<String>>((ref) => GameBoardNotifier());
class Board extends ConsumerWidget {
const Board({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final gameBoard = ref.watch(gameBoardProvider);
return Expanded(
child: Center(
child: GridView.builder(
itemCount: 9,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
shrinkWrap: true,
itemBuilder: ((BuildContext context, int index) {
return InkWell(
onTap: () {
ref.read(gameBoardProvider.notifier).updateBoardItem('X', index);
},
child: Container(
decoration: BoxDecoration(border: Border.all(color: Colors.white)),
child: Center(
child: Text(gameBoard[index]),
),
),
);
}),
),
),
);
}
}

Best way to pass widgets to child widget in flutter

I'm new to flutter but I have a widget that wraps a custom painter. I am trying to get it to work so I can supply a Widget to this child widget's constructor and then use that widget as the child of the custom painter.
For example:
class MyPainterWrapper extends StatefulWidget {
Widget _childWidget;
SceneRender([this._childWidget]);
#override
State<StatefulWidget> createState() {
return new MyPainterWrapperState(_childWidget);
}
}
class MyPainterWrapperState extends State<SceneRender> {
Widget _childWidget;
MyPainterWrapperState(this._childWidget);
#override
Widget build(BuildContext context) {
return Column(
children: [
CustomPaint(painter: MyPainter(), child: _childWidget)
],
);
}
}
And in another widget (called testWidget):
bool _answerCorrect = false;
bool _answerInputted = false;
var _msgController = TextEditingController();
FocusNode _answerFieldFocus = new FocusNode();
DictionaryEntry _currentEntry;
void _checkIfCorrect(String answerGiven) {
setState(() {
_answerCorrect = false;
if (_currentEntry.Full_Word == answerGiven)
_answerCorrect = true;
else if (_currentEntry.dictReadings.isNotEmpty) {
for (AlternateDictionaryEntryReading entryReading in _currentEntry
.dictReadings) {
if (entryReading.Alternate_Reading == answerGiven) {
_answerCorrect = true;
break;
}
}
}
_answerInputted = true;
_msgController.clear();
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('test'),
),
body: MyPainterWrapper(Center(Container(Column(children: <Widget>[
if (_answerCorrect && _answerInputted) Text('CORRECT!'),
if (!_answerCorrect && _answerInputted) Text('WRONG:'),
if (_answerInputted)
Text(_currentEntry.Full_Word),
if (_answerInputted)
for(AlternateDictionaryEntryReading reading in _currentEntry.dictReadings)
Text(reading.Alternate_Reading),
Container(
constraints: BoxConstraints.expand(
height: 100,
width: 1000
),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (DictionaryTranslation translation in _currentEntry.dictTranslations)
Text(translation.Translation),
],
)
),
),
Text('Enter Answer:',),
TextField(
controller: _msgController,
focusNode: _answerFieldFocus,
onSubmitted: (String value) {
_checkIfCorrect(value);
_answerFieldFocus.requestFocus();
},
)
This works to render the first time correctly, but any setState calls from checkIfCorrect from testWidget do not force the child widget to rebuild. I've tried testing it this way and it works, so that leads me to believe that I'm passing the widget incorrectly to have it redrawn via setState
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('test'),
),
body: CustomPaint(painter: TestPainter(), child: Center(
child: Container(...))
Your MyPainterWrapperState class reads like you are creating a new _childWidget property inside your state (which has a default value of null). You are then using it to initialize a new instance of MyPainterWrapperState, then throwing away the instance of MyPainterWrapper() that you just created.
You're not actually using the stateful part of your stateful widget at all; You're just calling a method that returns something once.
That said, your approach is basically right, but the implementation is off a little.
My advice:
First, you can use named properties to supply constructor arguments to your class. I've made that change in the code snippet shown below.
The State class of a stateful widget supplies a widget property that should be used to reference the properties of the widget that created it. The State widget should also have a solitary initializer that accepts no arguments.
Also good to know is that the State class provides an initState() method that you can override to initialize any class local variables that you declare. This should be used to give a value to your _childWidget property.
Finally, anything you expect to be rebuilt should be inside the MyPainterWrapperState() class. Your SceneRender() method doesn't appear in your code, but you might want to move it into MyPainterWrapperState() if you expect the scene to change based on the value of the child.
I suggest these changes.
Pass arguments to MyPainterWrapper via named arguments.
Remove the argument to MyPainterWrapperState() and reference the child through the widget property supplied to the state.
Initialize _childWidget by overriding initState()
If SceneRender() does anything that depends on the value of _childWidget, move it to the build() method of MyPainterWrapperState().
The Flutter docs are great, and the Flutter team has created a ton of helpful YouTube videos that explain in a couple of minutes examples of how to use dozens of them. For a better understanding of StatefulWidget, you can read about it here.
If you make these changes, your solution would look something like the code snippet below.
Presuming you make those changes, you would alter your call to MyPainterWrapper() to use named properties.
Change this line
body: MyPainterWrapper(Center(Container(Column(children: <Widget>[
To this
body: MyPainterWrapper(child: Center(Container(Column(children: <Widget>[
This won't get you to done, but it will get you closer. I also haven't run this through a compiler, so there are probably errors in the snippet, but this should serve to illustrate the approach.
class MyPainterWrapper extends StatefulWidget {
MyPainterWrapper(
{
#required child: this.childWidget,
}
);
final Widget childWidget;
// Not sure what this does, but I'm pretty sure that it doesn't
// provide anything into the widget tree.
// If it mutates its arguments, then you might still need it.
// SceneRender([this._childWidget]);
#override
State<StatefulWidget> createState() {
// Note no arguments.
return new MyPainterWrapperState();
}
}
class MyPainterWrapperState extends State<MyPainterWrapper> {
// This is an uninitialized variable inside this class.
Widget _childWidget;
// MyPainterWrapperState(this._childWidget);
// Initialize state variables here.
#override initState() {
// Assigns the widget class initializer to your state variable.
_childWidget = widget.childWidget;
}
#override
Widget build(BuildContext context) {
return Column(
children: [
CustomPaint(painter: MyPainter(), child: _childWidget)
],
);
}
}```

Flutter_bloc: Is there a cleaner way to access the state of a flutter Cubit than a redundant if/else statement?

I'm using flutter_bloc and have created a sessionState that gets triggered when a user has successfully authenticated and the state saves the user object (did).
I use a Cubit to change the state which then results in showing different screens in the ui. But whenever I want to access the did object of the Verified sessionState which holds the user information, I have to create an if else statement in every file to check if the sessionState is Verified and if that is true I can access the did object with state.did.
I would be interested to know if it's possible to provide this did object to all underlying widgets without passing it down manually every widget.
I would like to be able to just access the did object from the context so that I can access it everywhere below where it is provided.
My SessionState:
abstract class SessionState {}
class UnkownSessionState extends SessionState {}
class Unverified extends SessionState {}
class Verified extends SessionState {
Verified({required this.did});
final Did did;
}
The SessionCubit launches states that I use to define the global state of the app and show different screens as a result.
class SessionCubit extends Cubit<SessionState> {
SessionCubit(this.commonBackendRepo) : super(UnkownSessionState()) {
attemptGettingDid();
}
final CommonBackendRepo commonBackendRepo;
final SecureStorage secureStorage = SecureStorage();
Future<void> attemptGettingDid() async {
try {
//more logic
emit(Unverified());
} catch (e) {
emit(Unverified());
}
}
void showUnverified() => emit(Unverified());
void showSession(Did did) {
emit(Verified(did: did));
}
}
This is how I currently access the did object in every file:
BlocBuilder<SessionCubit, SessionState>(builder: (context, state) {
if (state is Verified) {
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
elevation: 0.0,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
title: Text(
state.did.username,
style: Theme.of(context).textTheme.headline5,
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: CredentialDetailsView(),
))
],
);
} else {
return const Text("Unverified");
}
});
Edit:
That's how I would imagine the optimal solution to my scenario:
Create if/else to check if state is verfied in one parent widget.
If state is verfied create a Provider as a child with the 'did' Object so that the children of this Provider don't have to access the SessionState but can just access the provided did object.
Response to Robert Sandberg's answer
I also have an AppNavigator that either start the authNavigator or sessionNavigator. So the sessionNavigator is the parent widget of all widgets that get shown only when the state is Verified. So I think this would be a great place to wrap the sessionNavigator with the Provider.value, as you explained it.
I wrapped my SessionNavigator with the Provider.value widget and provided the state object:
class AppNavigator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<SessionCubit, SessionState>(
builder: (context, state) {
return Navigator(
pages: [
if (state is UnkownSessionState)
MaterialPage(child: StartupScreen()),
//show auth flow
if (state is Unverified)
MaterialPage(
child: BlocProvider(
create: (context) => AuthCubit(context.read<SessionCubit()),
child: AuthNavigator(),
)),
//show session flow
if (state is Verified)
MaterialPage(
child: Provider.value(
// here I can access the state.did object
value: state,
child: SessionNavigator(),
))
],
onPopPage: (route, result) => route.didPop(result),
);
},
);
}
}
For testing I tried to watch the provided value inside of my Home screen which is a child of the SessionNavigator:
...
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
//
final sessionState = context.watch<SessionState>();
return CustomScrollView(
slivers: <Widget>[
SliverAppBar(
floating: true,
expandedHeight: 60.0,
backgroundColor: Colors.white,
flexibleSpace: FlexibleSpaceBar(
title: Text(
// I can't access state.did because value is of type SessionState
sessionState.did,
style: Theme.of(context).textTheme.headline6,
),
titlePadding:
const EdgeInsetsDirectional.only(start: 20, bottom: 16)),
)
],
);
}
}
But because the type of my value is SessionState I can't access the did object of the underlying Verified state. I though about providing the state.did object but I don't know how I would watch for that type.
I also got the error that my home screen couldn't find the Provider in the build context. Could that happen because my Home screen has a Navigator as parent?
This is a visualization of my app architecture:
I use this pattern sometimes, and it sounds like my solution very much fits your imagined optimal solution :)
In a parent:
BlocBuilder<MyBloc, MyBlocState>(
builder: (context, state) {
return state.when(
stateA: () => SomeWidgetA(),
stateB: () => SomeWidgetB(),
stateC: (myValue) {
return Provider.value(
value: myValue,
child: SomeWidgetC(),
);
}),
}
)
... and then in SomeWidgetC() and all it's children I do the following to get hold of myValue (where it is of type MyValueType)
final myValue = context.watch<MyValueType>();
If you want, you could also use regular InheritedWidget instead of using the Provider package but you get a lot of nifty features with Provider.
EDIT
Response to your further questions.
In your case, if you provide the entire state as you do in AppNavigator, then you should look for Verified state in SessionNavigator as such:
final sessionState = context.watch<Verified>();
Or, if you only need the Did object, then just provide that, so in AppNavigator:
if (state is Verified)
MaterialPage(
child: Provider.value(
value: state.did,
child: SessionNavigator(),
))
And then in SessionNavigator:
final did = context.watch<Did>();
Edit 3:
It might be that your problems arises with nested Navigators. Have a look at this SO answer for some guidance.
If you continue struggle, I suggest that you open new SO questions for the specific problems, as this is starting to get a bit wide :) I think that the original question is answered, even though you haven't reached a final solution.
You can use something like (state as Verified).did.username, but I would still recommend using the if/else pattern since you ensure that you are using the correct state.
In my personal projects, I use the Freezed package (https://pub.dev/packages/freezed) for such cases since there are some useful methods to cover this. For instance, by using freezed, your issue could be resolved as:
return state.maybeWhen(
verified: (did) => CustomScrollView(...),
orElse: () => const Text("Unverified"),
)

How to get StatefulWidget's state?

I am new to flutter and the way I get StatefulWidget's state is add a state property to widget, eg:
// ignore: must_be_immutable
class _CustomContainer extends StatefulWidget {
_CustomContainer({Key key}) : super(key: key);
#override
__CustomContainerState createState() {
state = __CustomContainerState();
return state;
}
__CustomContainerState state;
void changeColor() {
if (state == null) return;
// call state's function
this.state.changeColor();
}
}
class __CustomContainerState extends State<_CustomContainer> {
var bgColor = Colors.red;
#override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
color: bgColor,
);
}
void changeColor() {
setState(() {
bgColor = Colors.blue;
});
}
}
usage:
final redContainer = _CustomContainer();
final button = FlatButton(
onPressed: () {
// call widget function
redContainer.changeColor();
},
child: Text('change color'),
);
It works, but I wonder is there any hidden danger?
You'll notice it's very awkward to manipulate Flutter widgets in an imperative fashion, like in the question. This is because of the declarative approach Flutter has taken to building screens.
Declarative vs. Imperative
The approach / philosophy of Flutter UI is a declarative UI vs. an imperative UI.
The example in the question above leans toward an imperative approach.
create an object
object holds state (information)
object exposes method
use method to impose change on object → UI changes
A declarative approach:
there is state (information) above your object
your object is declared (created) from that state
if the state changes...
your object is recreated with the changed state
Below I've tried to convert the imperative approach above, into a declarative one.
CustomContainer is declared with a color; state known / kept outsideCustomContainer & used in its construction.
After construction, you cannot impose a color change on CustomContainer. In an imperative framework you would expose a method, changeColor(color) and call that method and the framework would do magic to show a new color.
In Flutter, to change color of CustomContainer, you declare CustomContainer with a new color.
import 'package:flutter/material.dart';
/// UI object holds no modifiable state.
/// It configures itself once based on a declared color.
/// If color needs to change, pass a new color for rebuild
class CustomContainer extends StatelessWidget {
final Color color;
CustomContainer(this.color);
#override
Widget build(BuildContext context) {
return Container(
color: color,
child: Text('this is a colored Container'),
);
}
}
/// A StatefulWidget / screen to hold state above your UI object
class DeclarativePage extends StatefulWidget {
#override
_DeclarativePageState createState() => _DeclarativePageState();
}
class _DeclarativePageState extends State<DeclarativePage> {
var blue = Colors.blueAccent.withOpacity(.3);
var red = Colors.redAccent.withOpacity(.3);
Color color;
// state (e.g. color) is held in a context above your UI object
#override
void initState() {
super.initState();
color = blue;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Declarative Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomContainer(color),
// color "state" ↑ is passed in to build/rebuild your UI object
RaisedButton(
child: Text('Swap Color'),
onPressed: () {
setState(() {
toggleColor();
});
}
)
],
),
),
);
}
void toggleColor() {
color = color == blue ? red : blue;
}
}
Read more on declarative vs imperative on Flutter.dev.
setState() Rebuilds & Performance
Performance-wise it seems wasteful to rebuild the entire screen when a single widget, way down in the widget tree needs rebuilding.
When possible (and sensible) it's better to wrap the particular elements that have state & need rebuilding in a StatefulWidget, rather than wrapping your entire page in a StatefulWidget and rebuilding everything. (Likely, this wouldn't even be a problem, I'll discuss further below.)
Below I've modified the above example, moving the StatefulWidget from being the entire DeclarativePage, to a ChangeWrapper widget.
ChangeWrapper will wrap the CustomContainer (which changes color).
DeclarativePage is now a StatelessWidget and won't be rebuilt when toggling color of CustomContainer.
import 'package:flutter/material.dart';
class ChangeWrapper extends StatefulWidget {
#override
_ChangeWrapperState createState() => _ChangeWrapperState();
}
class _ChangeWrapperState extends State<ChangeWrapper> {
final blue = Colors.blueAccent.withOpacity(.3);
final red = Colors.redAccent.withOpacity(.3);
Color _color; // this is state that changes
#override
void initState() {
super.initState();
_color = blue;
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomContainer(_color),
RaisedButton(
child: Text('Swap Color'),
onPressed: () {
setState(() {
toggleColor();
});
}
)
],
);
}
void toggleColor() {
_color = _color == blue ? red : blue;
}
}
/// UI object holds no state, it configures itself once based on input (color).
/// If color needs to change, pass a new color for rebuild
class CustomContainer extends StatelessWidget {
final Color color;
CustomContainer(this.color);
#override
Widget build(BuildContext context) {
return Container(
color: color,
child: Text('this is a colored Container'),
);
}
}
class DeclarativePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('Declarative Page re/built');
return Scaffold(
appBar: AppBar(
title: Text('Declarative Page'),
),
body: Center(
child: ChangeWrapper(),
),
);
}
}
When running this version of the code, only the ChangeWrapper widget is rebuilt when swapping colors via button press. You can watch the console output for "Declarative Page re/built" which is written to debug console only once upon first screen build/view.
If DeclarativePage was huge with hundreds of widgets, isolating widget rebuilds in the above manner could be significant or useful. On small screens like in the first example at top or even or average screens with a couple dozen widgets, the difference in savings are likely negligible.
Flutter was designed to operate at 60 frames per second. If your screen can build / rebuild all widgets within 16 milliseconds (1000 milliseconds / 60 frames = 16.67 ms per frame), the user will not see any jankiness.
When you use animations, those are designed to run at 60 frames (ticks) per second. i.e. the widgets in your animation will be rebuilt 60 times each second the animation runs.
This is normal Flutter operation for which it was designed & built. So when you're considering whether your widget architecture could be optimized it's useful to think about its context or how that group of widgets will be used. If the widget group isn't in an animation, built once per screen render or once per human button tap... optimization is likely not a big concern.
A large group of widgets within an animation... likely a good candidate to consider optimization & performance.
Btw, this video series is a good overview of the Flutter architecture. I'm guessing Flutter has a bunch of hidden optimizations such as Element re-use when a Widget hasn't materially/substantially changed, in order to save on CPU cycles instantiating, rendering & positioning Element objects.
add setState() method where you want to add state

Flutter: localization not working due to context being null. how to correctly pass it through stateless to stateful?

I have this stateless widget called myPage.dart. which contains a Stack of Texts and Stateful List View Builder.
here is the code (I commented out the 2nd group of Text and Stateful List View Builder for now:
Widget content(BuildContext context) =>
Container(
child: Stack(
children: <Widget>[
sameDayText(context),
SameDayWorkManagement(context),
// nextDayText(),
// nextDay.NextDayWorkManagement(),
],
),
);
The sameDayText is no problem. probably because the class for that is inside the myPage.dart but I can't seem to pass the context to sameDayWorkManagement.dart which is a stateful widget that contains a listview builder. keep in mind that everything worked in the past. its just that when I tried to add localization now, It seems that the context is null for some reason in the sameDayWorkManagement. Localization requires context. and I keep getting error on snippet of codes in the sameDayWorkManagement that localizes text. and again because of the context being null:
here is the sample code of the context being null in the sameDayWorkManagement.dart
Localization.of(widget.buildContext).getTranslatedValue('wakeup')
and here is the script for the sameDayWorkManagement.dart
class SameDayWorkManagement extends StatefulWidget {
BuildContext buildContext;
SameDayWorkManagement(buildContext);
#override
_SameDayWorkManagementState createState() => _SameDayWorkManagementState();
}
class _SameDayWorkManagementState extends State<SameDayWorkManagement>
with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return Container(
// backgroundColor: Color(app_background_color_blue),
child: LayoutBuilder(
builder: (context, constraints) => SafeArea(
child: Container(
child: new StoreConnector<AppState, MainPageViewModel>(
converter: (store) => MainPageViewModel.fromStore(store),
builder: ( _, viewModel) => content(viewModel, constraints),
),
),
),
),
);
}
#override
void initState () {
super.initState();
if(widget.buildContext != null) {
print("is true");
} else {
print("is not true");
}
}
In initState the result is is not true
to be more precise. here is the image of myPage that does not have Localization and instead uses static Japanese Text
The first dot and Japanese Text with a telephone icon in the right is the sameDayText widget. the card below it is the sameDayWorkManagement its a list view and its scrollable.
and then the rest bellow are those that I commented out ( for now) called next day
I created a really ugly work around, so I'm still hoping this would be fixed. my work around is I created a map of all the necessary translated text in myPage using the localization which again is working in there. and pass that map to the sameDayWorkManagement as a parameter. and use that map to populate my needed text. yes it is very ugly. but for now it is working.