I simply cannot make heads or tails out of this. So, this is the main widget:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Anliegen Foo',
home: Home(),
);
}
}
while the Home widget is supposed to be a stateful one and defined thus:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _currentIndex = 42;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Foo - $_currentIndex"),
),
body: Text('bar baz'),
);
}
}
Similarly to how the official docs recommend you do this.
You would expect the AppBar title to be "Foo - 42". But it is "Foo - 0", i.e. while the variable is found it is found to be null (and thus seemingly automatically interpreted as 0).
The debugger says that the variable exists and is set to 42, both in the context of the class and the build function.
This also goes for anything else I try to declare - Lists, strings, whatever, all not initialized but at the same time initialized.
But when I then do set the variable inside the build method like this:
#override
Widget build(BuildContext context) {
_currentIndex = 42;
[...]
now it begins to work.
So, where am I going wrong here?
Yes, thank you guys, a mere rebuild/hot restart was indeed the answer. I was thrown by the fact that the debugger shows the variable just fine with the proper value while the Widget does rely on the old class definition from before the hot reload.
tl;dr: Restart or hot restart.
Related
I am trying to understand why the Flutter / Dart HotReload does not work if I reference the appBar: widget as opposed to defining the code inline. Why?
Here is the sample code:
class AppBarWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: appBar,
);
}
}
AppBar appBar = AppBar(
title: new Text("App Title"),
);
Notice appBar: appBar is a reference to the AppBar(...) widget definition.
In this case, if I change the title: property text, it will not hot reload even though both IDE (VSCode or AStudio) says it reloads. I need to rebuild to make it work.
But if I move the AppBar(...) widget definition into appBar: AppBar(...) and change the title, it Hot Reloads.
This happens because Flutter's hot-reload is "stateful". It preserves the state of all variables – including the variable you defined.
As such, while you can change the sources of a variable, it will keep its previous state.
In any cases, you shouldn't extract widgets as global variables – but instead, extract them as StatelessWidget and use const constructor.
This has the same performances benefits while being compatible with hot-reload.
class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
const MyAppBar({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return AppBar(
title: const Text('App title'),
);
}
#override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
I have an asset file that need to be processed before it can be used. This asset file will be heavily edited and I would like to not to have to restart the application each time I make an edit.
I'm aware of the existence of the reassemble method on the State class. However, this requires having a dummy widget that overrides this method and putting it inside the app somewhere to get notified about hot reload.
class WdHotReloadNotifier extends StatefulWidget
{
final Function callback;
WdHotReloadNotifier(this.callback);
#override
State<StatefulWidget> createState() => WdHotReloadNotifierState(this.callback);
}
class WdHotReloadNotifierState extends State<WdHotReloadNotifier>
{
Function callback;
WdHotReloadNotifierState(this.callback);
#override
void reassemble()
{
super.reassemble();
callback();
}
#override
Widget build(BuildContext context) {
return Container();
}
}
Then I can use it like this:
WdHotReloadNotifier((){print("HOT REALOADED 1");}),
WdHotReloadNotifier((){print("HOT REALOADED 2");}),
However, adding these to a single page means that it will work as long as the page is in the stack. And adding them to multiple pages means the hooks will execute more than once.
Is there a way in flutter to get notified globally about a hot reload?
Overriding the reassemble method on a State subclass is what you want.
But you can position the widget to a different location to change the behavior.
Consider the following widget which calls a callback on hot-reload and does nothing else:
class ReassembleListener extends StatefulWidget {
const ReassembleListener({Key key, this.onReassemble, this.child})
: super(key: key);
final VoidCallback onReassemble;
final Widget child;
#override
_ReassembleListenerState createState() => _ReassembleListenerState();
}
class _ReassembleListenerState extends State<ReassembleListener> {
#override
void reassemble() {
super.reassemble();
if (widget.onReassemble != null) {
widget.onReassemble();
}
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
You're free to insert that widget wherever you like.
Be it on a single page:
MaterialApp(
home: ReassembleListener(onReassemble: () => print("Foo"), child: Home()),
)
Or globally by wrapping the whole application:
ReassembleListener(
onReassemble: () => print('foo'),
child: MaterialApp(
home: Home(),
),
)
If I were using a StatefulWidget, then I would be listening to a Stream for example inside the initState method. Where would I do the equivalent in a StatelessWidget (like to use Bloc with streams for state management)? I could do it in the build method but since these are repetitively I wondered if there is a more efficient way than checking for existent listeners like below. I know that this is a redundant and useless example but it's just to show the problem.
import "package:rxdart/rxdart.dart";
import 'package:flutter/material.dart';
final counter = BehaviorSubject<int>();
final notifier = ValueNotifier<int>(0);
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
if (!counter.hasListener)
counter.listen((value) => notifier.value += value);
return MaterialApp(
home: Scaffold(
body: Center(
child:FlatButton(
onPressed: () => counter.add(1),
child: ValueListenableBuilder(
valueListenable: notifier,
builder: (context, value, child) => Text(
value.toString()
),
),
)
),
)
);
}
}
There is no clean way to have a StatelessWidget listen to a Listenable/Stream.
You will always need a StatefulWidget.
On the other hand, you can use composition to write that StatefulWidget just once, and be done with it.
Common examples for that pattern are widgets such as ValueListenableBuilder, StreamBuilder, or AnimatedBuilder. But it is possible to do the same thing, for listening too.
You'd use it this way:
class Foo extends StatelessWidget {
Foo({Key key, this.counter}): super(key: key);
final ValueListenable<int> counter;
#override
Widget build(BuildContext context) {
return ValueListenableListener(
valueListenable: counter,
onChange: (value) {
// TODO: do something
},
child: Something(),
);
}
}
Where ValueListenableListener is implemented this way:
class ValueListenableListener<T> extends StatefulWidget {
const ValueListenableListener(
{Key key, this.valueListenable, this.onChange, this.child})
: super(key: key);
final ValueListenable<T> valueListenable;
final ValueChanged<T> onChange;
final Widget child;
#override
_ValueListenableListenerState createState() =>
_ValueListenableListenerState();
}
class _ValueListenableListenerState extends State<ValueListenableListener> {
#override
void initState() {
super.initState();
widget.valueListenable?.addListener(_listener);
_listener();
}
#override
void didUpdateWidget(ValueListenableListener oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.valueListenable != widget.valueListenable) {
oldWidget.valueListenable?.removeListener(_listener);
widget.valueListenable?.addListener(_listener);
_listener();
}
}
#override
void dispose() {
widget.valueListenable?.removeListener(_listener);
super.dispose();
}
void _listener() {
widget.onChange?.call(widget.valueListenable.value);
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
You shouldn't. Not handling variables that might have their values modified is the very purpose of a Stateless widget:
A stateless widget never changes.
UPDATE:
I think this is a problem of understanding Flutter's state management concepts. This new recommended way by the Flutter team should clear some confusions.
You could do something like this:
class ExampleWidget extends StatelessWidget {
bool _initialized = false;
#override
Widget build(BuildContext context) {
if (!_initialized) {
_initialized = true;
// Add listeners here only once
}
return Container();
}
}
But you shouldn't! In fact, your IDE will give you a warning, because this is not the way to go with Stateless widget as it is marked as #immutable. If you need to use lifecycle methods (like initState()) you should make it a Stateful widget. There's no big deal.
This is achievable with flutter_bloc package. The code to be run in initstate can be added inside BlocListener on whatever state you want.
BlocProvider(
create: (BuildContext context) =>
CategoryBlock()..add(LoadCategories()),
child: BlocListener<CategoryBlock, CategoryStates>(
listener: (context, state) {
//Example to add a listener for listview
if (state is LoadCategoriesSuccess) {
itemPositionsListener.itemPositions.addListener(() {
print(itemPositionsListener.itemPositions.value);
});
}
}
You could have your streams being instantiated in a StatefulWidget and then passed down to your StatelessWidgets as an option, so the parent widget would only have a role of controlling the lifecycle of the stream while the child would be using the stream to update the view.
Regarding the earlier answer:
There's no problem in using StreamBuilders inside your StatelessWidgets since the StreamBuilder itself is a a Widget that extends from StatefulWidget and will take care of it's own state and dispose correctly on its own.
The Flutter documentation for InheritedWidget says
Base class for widgets that efficiently propagate information down the tree.
To obtain the nearest instance of a particular type of inherited widget from > a build context, use BuildContext.inheritFromWidgetOfExactType.
Inherited widgets, when referenced in this way, will cause the consumer
to rebuild when the inherited widget itself changes state.
Given that widgets in Flutter are immutable, and in the example code..
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
#required this.color,
#required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
#override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
the color property is final so cannot be reassigned. Assuming this widget is right at the top of the tree, as in most examples, when will this ever be useful. For the widget to be replaced, a new instance will have to be created.
Presumably where this is done, a new instance of whatever is passed as child will be created too, causing that child's descendants to also rebuild, creating new instances of its childresn etc..
Ending up with the whole tree rebuilt anyway. So the selective updating applied by using inheritFromWidgetOfExactType is pointless, when the data of an instance of InheritedWidget will never change for that instance?
Edit:
This is the simplest example of what I don't understand that I can put together.
In this example, the only way to "change" the InheritedWidget/FrogColor which is near the root of the application is to have its parent (MyApp) rebuild. This causes it to rebuild its children and create a new instance of FrogColor and which gets passed a new child instance. I don't see any other way that the InheritedWidget/FrogColor
would change its state as in the documentation
... will cause the consumer to rebuild when the inherited widget itself changes state.
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(MyApp());
}
class FrogColor extends InheritedWidget {
const FrogColor({
Key key,
#required this.color,
#required Widget child,
}) : assert(color != null),
assert(child != null),
super(key: key, child: child);
final Color color;
static FrogColor of(BuildContext context) {
return context.inheritFromWidgetOfExactType(FrogColor);
}
#override
bool updateShouldNotify(FrogColor old) => color != old.color;
}
class MyApp extends StatefulWidget {
// This widget is the root of your application.
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp>
{
#override
Widget build(BuildContext context) {
var random = Random(DateTime.now().millisecondsSinceEpoch);
return FrogColor(
color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
child:MaterialApp(
title: 'Flutter Demo',
home: Column (
children: <Widget>[
WidgetA(),
Widget1(),
FlatButton(
child:Text("set state",style:TextStyle(color:Colors.white)),
onPressed:() => this.setState((){})
)
]
)
)
);
}
}
class WidgetA extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return WidgetB();
}
}
class WidgetB extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
}
}
class Widget1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Widget2();
}
}
class Widget2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Ran Build ${this.runtimeType.toString()}");
return Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
}
}
Further, the output of this is
I/flutter (24881): Ran Build WidgetA
I/flutter (24881): Ran Build WidgetB
I/flutter (24881): Ran Build Widget1
I/flutter (24881): Ran Build Widget2
So all the child widgets are always rebuilt. Making the registration done in inheritFromWidgetOfExactType pointless also.
Edit2:
In response to #RémiRousselet answer in the comments, modifying the above example, something like
class MyAppState extends State<MyApp>
{
Widget child;
MyAppState()
{
child = MaterialApp(
title: 'Flutter Demo',
home: Column (
children: <Widget>[
WidgetA(),
Widget1(),
FlatButton(
child:Text("set state",style:TextStyle(color:Colors.white)),
onPressed:() => this.setState((){})
)
]
)
);
}
#override
Widget build(BuildContext context) {
var random = Random(DateTime.now().millisecondsSinceEpoch);
return FrogColor(
color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
child: child
);
}
}
works by storing the tree that shouldn't be modified outside of the build function so that the same child tree is passed to the InhertedWidget on each rebuild. This does work only causing the rebuild of the widgets that have registered with inheritFromWidgetOfExactType to get rebuilt, but not the others.
Although #RémiRousselet says it is incorrect to store the subtree as part of the state, I do not believe there is any reason that this is not ok, and infact they do this in some google tutorial videos. Here She has a subtree created and held as part of the state. In her case 2 StatelessColorfulTile() widgets.
Presumably where this is done, a new instance of whatever is passed as a child will be created too, causing that child's descendants to also rebuild, creating new instances of its children etc..
Ending up with the whole tree rebuilt anyway.
That's where your confusion comes from
A widget rebuilding doesn't force its descendants to rebuild.
When a parent rebuild, the framework internally check if newChild == oldChild, in which case the child is not rebuilt.
As such, if the instance of a widget didn't change, or if it overrides
operator== then it is possible for a widget to not rebuild when its parent is updated.
This is also one of the reasons why AnimatedBuilder offer a child property:
AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Container(child: child,);
},
child: Text('Hello world'),
);
This ensures that when for the whole duration of the animation, child is preserved and therefore not rebuilt. Leading to a much more optimized UI.
Here's my code:
import 'package:flutter/material.dart';
void main() {
runApp(new MyStatefulApp(key: App.appStateKey));
}
/// Part [A]. No difference when appStateKey is defined as variable.
class App {
static final GlobalKey<MyAppState> appStateKey = new GlobalKey<MyAppState>();
}
/// Part [B]
class MyStatefulApp extends StatefulWidget {
MyStatefulApp({Key key}) :super(key: key);
#override
MyAppState createState() => new MyAppState();
}
class MyAppState extends State<MyStatefulApp> {
int _counter = 0;
add() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "App",
theme: new ThemeData(
primarySwatch: _counter % 2 == 0 ? Colors.blue : Colors.red,
),
home: new MyHomePage(),
);
}
}
/// Part [C]
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Main"), ),
body: new FlutterLogo(),
floatingActionButton: new FloatingActionButton(
onPressed: () {
App.appStateKey.currentState.add(); // (X)
},
tooltip: "Trigger color change",
child: new Icon(Icons.add),
),
);
}
}
In the code above, when the FAB is clicked, MaterialApp should rebuild, and the primary color will switch between blue and red.
In fact, the code worked, until I attempted to split the portions of the code to different files. App.appStateKey.currentState on line (X) will be become null when:
Part A (The App class, or the variable) is moved to another file;
Part C (MyHomePage and _MyHomePageState) is moved to another file;
Part A and C are moved to another file
So it looks like the GlobalKey.currentState only work when everything involving this GlobalKey is in the same file.
The doc only states that currentState will be null when (1) there is no widget in the tree that matches this global key, (2) that widget is not a StatefulWidget, or the associated State object is not a subtype of T. It doesn't state that everything has to be in the same file.
Breaking classes into files may not be "the Dart way", but I assume it should work anyhow (they're all public). So this puzzles me, and I suspect if I have stumbled upon certain Flutter feature that I am not aware of. Thanks.
That is due to how dart import works.
In dart, there is two way to import sources :
import './relative/path.dart'
import 'myApp/absolute/path.dart'
The thing is, they are not compatible with each others. Both these imports will have a different runtimeType.
But how is that a problem ? I never used relative import
That's a problem, because in some situations you implicitly use "relative imports" : When using a class A defined in foo.dart inside foo.dart.
So, how do I solve the problem ?
There are multiple solutions :
Have everything related to your class App should be inside the same file. (That's the recommended thing in dart)
Extract App into it's own file. And import it everywhere using absolute imports.
Don't use GlobalKey to begin with. As your use case is definitely in the scope of InheritedWidget.