I just got calling methods from a child class working with help from someone in this thread.
What I am trying to do now, and I am not sure if it is different, is call a method in one child from another child of the same parent.
So visually:
Parent class
- Method()
^
|
Child class
In the above, I can easily access the Parent class method from the child class using the callback function in the link provided above.
This does not appear to work in the below structure, and I can't figure this out from any of the threads I have read on calling methods from other classes:
Parent class
| |
Child class 1 Child class 2
- Method() <-- callback
Is the procedure for this structure handled differently? Is it possible or can you only ever callback to a parent method?
Although I think, in flutter, it would be better to use state changes to update/trigger calls on UI widgets, but for your speciffic case, the delegate pattern can work. Here's an example of I would do it.
abstract class TheTrigger { // you can use VoidCallback or whatever, this is just for the demo
void triggerMe();
}
class ChildOneWidget extends StatelessWidget with TheTrigger {
#override
Widget build(BuildContext context) {
return Container(); // add the content of the child one
}
#override
void triggerMe() {
// TODO: implement triggerMe
}
}
class ChildTwoWidget extends StatelessWidget {
final TheTrigger trigger;
const ChildTwoWidget({Key key, this.trigger}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
//something here that will trigger "the trigger"
child: RaisedButton(onPressed: () {
trigger?.triggerMe(); // you should use the "?" this will allow a bit more customisation on your widgets, you might want to use it without listener.
}),
);
}
}
class ParrentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
ChildOneWidget childOneWidget = ChildOneWidget();
ChildTwoWidget childTwoWidget = ChildTwoWidget(trigger: childOneWidget); // here you set the "delegate"
return ListView(
children: <Widget>[childOneWidget, childTwoWidget],
);
}
}
One more time, this is just a dumb example on how you can do it, but I would strongly recommend to use state to trigger changes on children, you'll have a more flexible widget tree.
The way flutter widgets work is that the children widgets in your case are in the parents widget tree. On the callback of Child class 2 you can use setState to rebuild your parent and thus rebuild any of its children, for example by changing the value of a parameter in Child class 1
Related
I have seen many times people calling widget. sth inside the code.
May I know what it is actually doing?
For example code below, (highlighted part is my confusion)
class _MyOwnClassState extends State<MyOwnClass> {
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: makeWidgetChildren(**widget.jsonObject)**),
),
);
}
}
In flutter's StatefulWidget, we have the following architecture.
You have a StatefulWidget like this,
class MyOwnClass extends StatefulWidget {
State createState () => _MyOwnClassState();
}
And you have a State class for your StatefulWidget like this,
class _MyOwnClassState extends State<MyOwnClass> {
}
Now, State class is meant to house variables that tend to change in order for your UI to be rebuilt.
So you can have variables in your State that you can update using setState.
But what if you had some data that doesn't change and you want to avoid putting them inside the State class.
That's where your StatefulWidget comes to play.
You can store variables in your MyOwnClass and the widget variable inside the State class gives you a way to access them.
For example,
class MyOwnClass extends StatefulWidget {
int numberThatDoesntChange = 1;
State createState () => _MyOwnClassState();
}
You can access them in your State class like this,
class _MyOwnClassState extends State<MyOwnClass> {
Widget build(BuildContext context) {
return Text('$widget.numberThatDoesntChange');
}
}
Apart from this, your StatefulWidget has many more internal instance members that you can access inside of your State class using the widget variable.
The widget refers to the actual view that renders on the screen. It extends the StatefulWidget class of the flutter framework and overrides the createState() method. The createState() method is used to create the instance of state class. We will look into createState().
The state class is used to maintain the state of the widget so that it can be rebuilt again. It extends the State class of the flutter framework and overrides the build method.
The framework calls build() method again and again whenever setState() method is called. The setState() method notifies the framework that the internal state of this object has changed and it should be rebuilt. Suppose we change the value of text in StatefulWidget then we need to call setState().
Edit As Nisanth pointed outh in his comment - I missed your question completely; please ignore the below....
Let me try my answer, I don't think others are getting your point.
In your exapmle, Column(children: x) expect a list of Widgets.
You have two options - either provide this list directly:
class _MyOwnClassState extends State<MyOwnClass> {
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: <Widget>[SomeWidget()]),
),
);
}
}
Or if you have more complex code that generates widget - based on input parameters, or you have the same widget generated multiple times and you want to avoid the code duplication - you would create the separate function to do the job.
Something like:
class _MyOwnClassState extends State<MyOwnClass> {
List<Widget> makeWidgetChildren(int param) {
/*
some very complex logic here
/*
if (param>3 && param<4) {
return List<Widget>.generate(4, (index)=>SomeWidget1(index));
} else {
return <Widget>[Center()];
}
}
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: makeWidgetChildren(**widget.jsonObject)**),
),
);
}
}
So basically, it is just to make the code nicer; and to avoid having code repeated over and over again in the build function.
I have a screen in my Flutter app that contains a pageview. In that pageview are four stateful widgets. I need to be able to access the members of the first three widgets so I can get the data from them, bring it into the main screen class, and send it to the fourth widget. Below is a model of what I'm trying to accomplish.
I'm thinking that I can do this with methods in each widget's state class but when I create a method there, I can't access it anywhere else even if it's public.
An example of one of the widgets:
import 'package:flutter/material.dart';
class WidgetSample extends StatefulWidget {
#override
_WidgetSampleState createState() => _WidgetSampleState();
}
class _WidgetSampleState extends State<WidgetSample> {
TextEditingController _sampleController = new TextEditingController();
//I want to access this method through an instance of the WidgetSample class
String getTextFromField(){
return _sampleController.text;
}
#override
Widget build(BuildContext context) {
return Container(
child: TextField(
controller: _sampleController,
),
);
}
}
I have tried creating a copy of the method in the widget sample class that calls the method in the state class but that hasn't worked.
Any help would be appreciated. Thanks in advance!
Edit: I have found an answer to my problem provided by BambinoUA on this post:
Controlling State from outside of a StatefulWidget
I'm learning flutter and trying to make a kind of MutableImage widget. The idea is to make a MutableImage StatefulWidget that would rebuild when a new image is provided. I try to avoid rebuilding the whole widget tree each time the image is changed because that seems overkill, and I plan to update the image several times per second. So I want to rebuild only that MutableImage widget.
So here is the code I have, with comments to explain where I'm stuck :
class MutableImage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MutableImageState();
}
void updateImage(List<int> bytes) {
// !!!!!! Would like to call this method here, but state is not available from Widget, which means I want to do something wrong, but not sure exactly how I should do it...
// this.state.updateImage(bytes);
}
}
class MutableImageState extends State<MutableImage> {
List<int> _bytes;
void updateImage(List<int> bytes) {
setState(() {
_bytes=bytes;
});
}
#override
Widget build(BuildContext context) {
if ((_bytes==null)||(_bytes.length==0)) {
return Center(child: CircularProgressIndicator());
}
return Image.memory(_bytes);
}
}
Then the idea was to use this widget like this for example in another stateful widget
MutableImage _mutableImage;
#override
Widget build(BuildContext context) {
if (_mutableImage == null) _mutableImage=MutableImage();
return : Row( //using Row as an example, the idea is that the mutable Image is deep into a tree of widgets, and I want to rebuild only _mutableImage when image changes, not all widgets.
children : <Widget>[
child0, child1, _mutableImage, child3, child4
]
);
}
void updateImage(List<int> bytes) {
_mutableImage?.updateImage(bytes);
}
So is there a good way to do this ? I'm quite confused, thx for any help/hint.
This is a place for an application of a GlobalKey. In the parent Widget of MutableImage make a global key and pass that to MutableImage. With that key you can access MutableImage state by using .currentState on the key and calling updateImage.
You'll have to add key as an argument of the MutableImage constructor and call super(key: key). updateImage should also be moved the the state of MutableImage.
Key:
final GlobalKey<MutableImageState> _imageKey = GlobalKey<MutableImageState>();
Pass the key:
MutableImage(key: _imageKey);
Access the state:
_imageKey.currentState.updateImage();
How do I update the state of a parent widget onTap of a child widget using provider
Currently what I am doing is pass a function from parent to child that calls the setState() function at the parent's end (Please refer the psuedocode below), but I'm looking for a more robust way of doing this.
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
_refresh() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return Container(
child: new ChildWidget(
notifyParent: _refresh(),
),
);
}
}
class ChildWidget extends StatelessWidget {
final Function notifyParent;
ChildWidget({#required this.notifyParent});
#override
Widget build(BuildContext context) {
return Container(
child: new FlatButton(
onPressed: notifyParent(), child: new Text("Update Parent")),
);
}
}
This is the simplest way I could represent the problem, the setState() in the parent (in the actual code) compels the parent widget to update its view.
In the actual code, I have the child widget handling the db queries, a click of the child widget changes the values in the db, and these values are to be updated by parent widget. Hence when I click the child widget, it updates the db then calls the notifyParent() which then leads to the parent querying the db again and updating the view.
Is there a better way of doing this without using the setState() by using the provider? How can I use say a ChangeNotifierProvider to notify of the db changes? Or is there any way I can notify the parent widget whenever something in the db is changed so that the parent can refresh itself?
Thank You.
I'm trying to understand the best practice for controlling a StatefulWidget's state outside of that Widgets State.
I have the following interface defined.
abstract class StartupView {
Stream<String> get onAppSelected;
set showActivity(bool activity);
set message(String message);
}
I would like to create a StatefulWidget StartupPage that implements this interface. I expect the Widget to do the following:
When a button is pressed it would send an event over the onAppSelected stream. A controller would listen to this event and perform some action ( DB call, service request, etc ).
The controller can call showActivity or set message to have the view show progress with a message.
Because a Stateful Widget does not expose its State as a property, I don't know the best approach for accessing and modifying the State's attributes.
The way I would expect to use this would be something like this:
Widget createStartupPage() {
var page = new StartupPage();
page.onAppSelected.listen((app) {
page.showActivity = true;
//Do some work
page.showActivity = false;
});
}
I've thought about instantiating the Widget by passing in the state I want it to return in createState() but that feels wrong.
Some background on why we have this approach: We currently have a Dart web application. For view-controller separation, testability, and forward-thinking towards Flutter, we decided that we would create an interface for every view in our application. This would allow a WebComponent or a Flutter Widget to implement this interface and leave all of the controller logic the same.
There are multiple ways to interact with other stateful widgets.
1. findAncestorStateOfType
The first and most straightforward is through context.findAncestorStateOfType method.
Usually wrapped in a static method of the Stateful subclass like this :
class MyState extends StatefulWidget {
static of(BuildContext context, {bool root = false}) => root
? context.findRootAncestorStateOfType<_MyStateState>()
: context.findAncestorStateOfType<_MyStateState>();
#override
_MyStateState createState() => _MyStateState();
}
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return Container();
}
}
This is how Navigator works for example.
Pro:
Easiest solution
Con:
Tempted to access State properties or manually call setState
Requires to expose State subclass
Don't use this method when you want to access a variable. As your widget may not reload when that variable change.
2. Listenable, Stream and/or InheritedWidget
Sometimes instead of a method, you may want to access some properties. The thing is, you most likely want your widgets to update whenever that value changes over time.
In this situation, dart offer Stream and Sink. And flutter adds on the top of it InheritedWidget and Listenable such as ValueNotifier. They all do relatively the same thing: subscribing to a value change event when coupled with a StreamBuilder/context.dependOnInheritedWidgetOfExactType/AnimatedBuilder.
This is the go-to solution when you want your State to expose some properties. I won't cover all the possibilities but here's a small example using InheritedWidget :
First, we have an InheritedWidget that expose a count :
class Count extends InheritedWidget {
static of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<Count>();
final int count;
Count({Key key, #required Widget child, #required this.count})
: assert(count != null),
super(key: key, child: child);
#override
bool updateShouldNotify(Count oldWidget) {
return this.count != oldWidget.count;
}
}
Then we have our State that instantiate this InheritedWidget
class _MyStateState extends State<MyState> {
int count = 0;
#override
Widget build(BuildContext context) {
return Count(
count: count,
child: Scaffold(
body: CountBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
count++;
});
},
),
),
);
}
}
Finally, we have our CountBody that fetch this exposed count
class CountBody extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Text(Count.of(context).count.toString()),
);
}
}
Pros:
More performant than findAncestorStateOfType
Stream alternative is dart only (works with web) and is strongly integrated in the language (keywords such as await for or async*)
Automic reload of the children when the value change
Cons:
More boilerplate
Stream can be complicated
3. Notifications
Instead of directly calling methods on State, you can send a Notification from your widget. And make State subscribe to these notifications.
An example of Notification would be :
class MyNotification extends Notification {
final String title;
const MyNotification({this.title});
}
To dispatch the notification simply call dispatch(context) on your notification instance and it will bubble up.
MyNotification(title: "Foo")..dispatch(context)
Note: you need put above line of code inside a class, otherwise no context, can NOT call notification.
Any given widget can listen to notifications dispatched by their children using NotificationListener<T> :
class _MyStateState extends State<MyState> {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: onTitlePush,
child: Container(),
);
}
bool onTitlePush(MyNotification notification) {
print("New item ${notification.title}");
// true meaning processed, no following notification bubbling.
return true;
}
}
An example would be Scrollable, which can dispatch ScrollNotification including start/end/overscroll. Then used by Scrollbar to know scroll information without having access to ScrollController
Pros:
Cool reactive API. We don't directly do stuff on State. It's State that subscribes to events triggered by its children
More than one widget can subscribe to that same notification
Prevents children from accessing unwanted State properties
Cons:
May not fit your use-case
Requires more boilerplate
You can expose the state's widget with a static method, a few of the flutter examples do it this way and I've started using it as well:
class StartupPage extends StatefulWidget {
static StartupPageState of(BuildContext context) => context.ancestorStateOfType(const TypeMatcher<StartupPageState>());
#override
StartupPageState createState() => new StartupPageState();
}
class StartupPageState extends State<StartupPage> {
...
}
You can then access the state by calling StartupPage.of(context).doSomething();.
The caveat here is that you need to have a BuildContext with that page somewhere in its tree.
There is another common used approach to have access to State's properties/methods:
class StartupPage extends StatefulWidget {
StartupPage({Key key}) : super(key: key);
#override
StartupPageState createState() => StartupPageState();
}
// Make class public!
class StartupPageState extends State<StartupPage> {
int someStateProperty;
void someStateMethod() {}
}
// Somewhere where inside class where `StartupPage` will be used
final startupPageKey = GlobalKey<StartupPageState>();
// Somewhere where the `StartupPage` will be opened
final startupPage = StartupPage(key: startupPageKey);
Navigator.push(context, MaterialPageRoute(builder: (_) => startupPage);
// Somewhere where you need have access to state
startupPageKey.currentState.someStateProperty = 1;
startupPageKey.currentState.someStateMethod();
I do:
class StartupPage extends StatefulWidget {
StartupPageState state;
#override
StartupPageState createState() {
this.state = new StartupPageState();
return this.state;
}
}
class DetectedAnimationState extends State<DetectedAnimation> {
And outside just startupPage.state
While trying to solve a similar problem, I discovered that ancestorStateOfType() and TypeMatcher have been deprecated. Instead, one has to use findAncestorStateOfType(). However as per the documentation, "calling this method is relatively expensive". The documentation for the findAncestorStateOfType() method can be found here.
In any case, to use findAncestorStateOfType(), the following can be implemented (this is a modification of the correct answer using the findAncestorStateOfType() method):
class StartupPage extends StatefulWidget {
static _StartupPageState of(BuildContext context) => context.findAncestorStateOfType<_StartupPageState>();
#override
_StartupPageState createState() => new _StartupPageState();
}
class _StartupPageState extends State<StartupPage> {
...
}
The state can be accessed in the same way as described in the correct answer (using StartupPage.of(context).yourFunction()). I wanted to update the post with the new method.
You can use eventify
This library provide mechanism to register for event notifications with emitter
or publisher and get notified in the event of an event.
You can do something like:
// Import the library
import 'package:eventify/eventify.dart';
final EventEmitter emitter = new EventEmitter();
var controlNumber = 50;
List<Widget> buttonsGenerator() {
final List<Widget> buttons = new List<Widget>();
for (var i = 0; i < controlNumber; i++) {
widgets.add(new MaterialButton(
// Generate 10 Buttons afterwards
onPressed: () {
controlNumber = 10;
emitter.emit("updateButtonsList", null, "");
},
);
}
}
class AState extends State<ofYourWidget> {
#override
Widget build(BuildContext context) {
List<Widget> buttons_list = buttonsGenerator();
emitter.on('updateButtonsList', null, (event, event_context) {
setState(() {
buttons_list = buttonsGenerator();
});
});
}
...
}
I can't think of anything which can't be achieved by event driven programming. You are limitless!
"Freedom cannot be bestowed — it must be achieved."
- Elbert Hubbard
Have you considered lifting the state to the parent widget? It is a common, though less ideal than Redux, way to manage state in React as far as I know, and this repository shows how to apply the concept to a Flutter app.