This is a simplified version of the scenario:
class ParentWdiegt extends StatelessWidget{
//
//
floatinActionButton: FloatingActionButtonWidget(onPressed:()=>CustomWidgetState.someMethod(someValue))
//
//somewhere in the ParentWidget tree
child: CustomWidget() //is stateful
}
CustomWidgetState
class CustomWidgetState extends State<CustomWidget>{
//trigger this function when FAB is pressed in parent widget
someMethod(SomeValue) {//}
}
Is there any way that I can expose someMethod in the state object to be triggered when FAB is pressed without using InheritedWidget?
While GlobalKey allows for an easy access to any widget's state ; avoid it.
Widgets should not interact with other widgets directly. This is one of the core principle of Flutter.
Flutter uses reactive programming instead. Where widgets communicate with each others by submitting events. Not by directly editing the desired widget.
The obvious benefit is that widgets stays independant. And potentially dozens of widgets can communicate with each others using the same principle.
I already made an example here on how to make two different widgets share a common editable value.
If you want to call methods instead, this uses the same principle : A Listenable or Stream shared between widgets. But without using AnimatedWidget or StreamBuilder for the listening.
Instead we'll do the listening manually (which requires slighly more boilerplate) to trigger a custom function.
Here's an example using Stream.
import 'dart:async';
import 'package:flutter/material.dart';
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
final changeNotifier = new StreamController.broadcast();
#override
void dispose() {
changeNotifier.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new AnotherWidget(
shouldTriggerChange: changeNotifier.stream,
),
new RaisedButton(
child: new Text("data"),
onPressed: () => changeNotifier.sink.add(null),
)
],
);
}
}
class AnotherWidget extends StatefulWidget {
final Stream shouldTriggerChange;
AnotherWidget({#required this.shouldTriggerChange});
#override
_AnotherWidgetState createState() => _AnotherWidgetState();
}
class _AnotherWidgetState extends State<AnotherWidget> {
StreamSubscription streamSubscription;
#override
initState() {
super.initState();
streamSubscription = widget.shouldTriggerChange.listen((_) => someMethod());
}
#override
didUpdateWidget(AnotherWidget old) {
super.didUpdateWidget(old);
// in case the stream instance changed, subscribe to the new one
if (widget.shouldTriggerChange != old.shouldTriggerChange) {
streamSubscription.cancel();
streamSubscription = widget.shouldTriggerChange.listen((_) => someMethod());
}
}
#override
dispose() {
super.dispose();
streamSubscription.cancel();
}
void someMethod() {
print('Hello World');
}
#override
Widget build(BuildContext context) {
return Container();
}
}
In this example, someMethod of AnotherWidget will be called whenever a click on the RaisedButton instantiated by _ParentWidgetState is performed.
You can use GlobalKey for that:
// some global place
final customWidgetKey = new GlobalKey<CustomWidgetState>();
...
// import the file with "customWidgetKey"
new CustomWidget(key: customWidetKey, ...)
...
// import the file with "customWidgetKey"
floatinActionButton: FloatingActionButtonWidget(
onPressed: ()=>customWidgetKey.currentState.someMethod(someValue))
Related
I have the following variables
String _warningMessage;
bool _warningVisibility;
Which I want to update via a Class which implements an interface
class _UserSignupInterface extends _SignupSelectUsernamePageState
implements UserSignupInterface {
#override
void onSuccess() {
_hideWarning();
_navigateToUserPage();
}
#override
void onError(String message) {
_isSignupClickable = true;
if(message != null) {
_displayWarning(message);
}
}
}
with the _displayWarning code (which is inside the _SignupSelectUsernamePageState)
void _displayWarning(String message) {
if (message != null) {
setState(() {
widget._warningMessage = message;
widget._warningVisibility = true;
});
}
}
However, whenever I call the _displayWarning(message) from outside the _SignupSelectUsernamePageState. I get an error saying
Unhandled Exception: setState() called in constructor
Is there a proper way of updating these variable states outside their class? Which in my case, I'm calling the _displayWarning(message) from another class that implements an interface
You have to decide whether this is a value that is changed internally within the widget, or if that's a value that changes externally to it.
If it's internal, the common thing is to place them in the State class with the _ on them, they could start with a value for instance set on initState and every time they change you call setState to indicate that.
However, if they change outside the widget, then you place them on the StatefulWidget class (as you seem to have done), you leave them without the _ as they are actually public and you even make them final and place them in the constructor to allow them to be set.
In this last case, if in the State class you must be aware of a change in the widget, you can implement didUpdateWidget, but that's not mandatory.
Of course you can mix both things, having a _warningMessage in the State, so you can update it with setState, but with an initial value defined in initState that comes from the widget.
Again, if the widget changes externally, you can again update the value of the _warningMessage with the new widgets value.
Something like that: (I didn't test this code)
class YourWidget extends StatefulWidget {
YourWidget({this.warningMessage});
final String warningMessage;
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = widget.warningMessage;
}
#override
didUpdateWidget(ReorderableListSimple oldWidget) {
super.didUpdateWidget(oldWidget);
_warningMessage = widget.warningMessage;
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
So in this example you can change the warningMessage externally, like in the parent Widget you are able to pass a different message. However, if you need, you can also set it internally using setState, as it's happening in the button's onPressed.
What you might check is wether you actually need that property exposed in the Widget, maybe you don't! Then, the example would look like that:
class YourWidget extends StatefulWidget {
#override
State<YourWidget> createState() => new _YourWidgetState();
}
class _YourWidgetState extends State<YourWidget> {
String _warningMessage;
#override
void initState() {
super.initState();
_warningMessage = "default message, no need for widget";
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text(_warningMessage),
RaisedButton(
child: Text("Change Message"),
onPressed: () {
setState(() {
_warningMessage = "new message from within the State class";
});
}
)
],
);
}
}
Just create a static value in the state of your widget class, then when you build the widget, set it's value to the widget. So whenever you want to call it to setState(), just call the static value.
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.
I've created my own simple bottom nav bar implementation in Flutter. When a tab is pressed, Flutter is currently re-creating the widget (initState() gets called every time) which is non-desirable.
I want the widgets to be persisted in memory so if they've already been created, they're simply popped straight in.
Main Widget
class _MainRootScreenState extends State<MainRootScreen> {
int _selectedIndex = 0;
List<Widget> _screens;
#override
void initState() {
// load pages
_screens = [
PageOne(),
PageTwo(),
PageThree()
];
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_selectedIndex],
bottomNavigationBar: _buildBottomTabBar(context)
);
}
}
so when _selectedIndex gets updated, the selected page is getting re-created.
I've tried using AutomaticKeepAliveClientMixin on the pages with no luck.
If you want that your widget/page should not rebuild when you click on tab button. You just need to follow this code
just add State<PageOne> with AutomaticKeepAliveClientMixin<PageOne> to your state class. after this you need to override a method called wantKeepAlive and make wantKeepAlive as true that's it.
By default wantKeepAlive is false because of it saves our memory .
PageOne
class PageOne extends StatefulWidget {
#override
_PageOneState createState() => _PageOneState();
}
class _PageOneState extends State<PageOne> with AutomaticKeepAliveClientMixin<PageOne> {
// Your code are here
#override
bool get wantKeepAlive => true;
}
Do the same from pageTwo and PageThree also that's it