In summary I want the "context.read().add(EnabledEvent())" to work from the controller itself or some other alternative I can use to trigger an event from the controller and update the state:
class SplashCtrl extends GetxController {
#override
void onReady() {
User user = loadUser();
if (user.isExampleEnabled) {
context.read<ExampleBloc>().add(EnabledEvent());
}
}
Another thing I tried was this:
ExampleBloc testBloc = ExampleBloc();
testBloc.add(TestEvent());
in the controller, and it does seem to trigger the event, but the UI doesn't update with the state change. I seem to really need the Context for that to change. Any ideas?
MORE DETAILS AND CONTEXT:
I want to trigger bloc events not just in the page itself, but in the page's controller!
So I have pages in my flutter app with each view having a controller set up like this:
class SplashPage extends GetView<SplashCtrl> {
#override
Widget build(BuildContext context) {
Get.lazyPut(() => SplashCtrl());
return Scaffold(...);
}
}
SplashCtrl is the controller for this page. This splash view page is one of the first pages that loads it runs functions when it's added and ready (basically instantly), such as checking if the user is logged in and loading their data to the app, like this:
class SplashCtrl extends GetxController {
#override
void onReady() {
// run stuff here
}
I have been able to get away with creating lambda functions in the pages and triggering events based on what they do and toggle like this:
IconSwitchedButtonUi(
value: state.isExampleOn,
icon: Images.toggleIcon,
title: "Is Example enabled?",
onChanged: (value) {
if (value) {
context.read<ExampleBloc>().add(EnabledEvent());
} else {
context.read<ExampleBloc>().add(DisabledEvent());
}
},
),
but now I need a bloc event to trigger and change the state a bit if a user has a certain value for one of their fields. How do I do that? How do I trigger an event from within the controller? The "context.read" doesn't work because the controller doesn't have context, or does it?
ExampleBloc testBloc = ExampleBloc(); testBloc.add(TestEvent());
This does not work because you were declaring totally new instance of bloc that wasnt connected by any BlocProvider to your widget tree, hence there is no way you could trigger this bloc events that would emit state your BlocBuilder could react to. Since there is no bloc provided to widget tree read() wont find this bloc.
In order for this work you need to:
Pass BuildContext that have some Bloc or Cubit provided by BlocProvider
void foo(BuildContext context) {
User user = loadUser();
if (user.isExampleEnabled) {
context.read<ExampleBloc>().add(EnabledEvent());
}
}
..or listen to bloc changes directly inside controller
class SplashCtrl extends GetxController {
SplashCtrl(MyBloc bloc) {
bloc.stream.listen((event) {
// React to bloc changes here
if (event is MyBlocEvent) {
// Do something
}
}
}
}
Remember that even in this approach bloc instance given to SplashCrtl constructor must be the same that is provided by BlocProvider to a widget tree in order for this to work.
I was able to resolve this by adding a late BuildContext to my controller:
class SplashCtrl extends GetxController {
late BuildContext context;
#override
void onReady() {
// run stuff here
}
After that, in the page itself, I passed the context like this:
class SplashPage extends GetView<SplashCtrl> {
#override
Widget build(BuildContext context) {
controller.context = context;
Get.lazyPut(() => SplashCtrl());
return Scaffold(...);
}
}
Then I was able to use the context within the controller itself!!
if (value) {
context.read<ExampleBloc>().add(EnabledEvent());
} else {
context.read<ExampleBloc>().add(DisabledEvent());
}
},
Related
I want to add a Listener when the widget/state is visible and remove myself from the Listener when I leave the route.
But if the user clicks on the back button and returns to the current widget/state, I want to do the same thing again.
Currently initState, didChangeDependencies, didUpdateWidget and build are NOT called when the user clicks back from the next page, therefore I cannot detect when the user is returning and the widget was loaded from cache.
After much poking around the API, I've discovered that ModalRoute and RouteObserver is what I want.
https://api.flutter.dev/flutter/widgets/ModalRoute-class.html
https://api.flutter.dev/flutter/widgets/RouteObserver-class.html
If I just want to check if the current route is active I can call isCurrent on ModalRoute.of(context):
void onNetworkData(String data) {
if (ModalRoute.of(context).isCurrent) {
setState(() => list = data);
}
}
If I want to listen to route load/unload, I just create it and serve it up the hood with Provider like this:
class HomePage extends StatelessWidget {
final spy = RouteObserver<ModalRoute<void>>();
build(BuildContext context) {
return Provider<RouteObserver<ModalRoute<void>>>.value(
value: spy,
child: MaterialApp(
navigatorObservers: [spy],
),
);
}
}
Then somewhere in another widget:
class _AboutPageState extends State<AboutPage> with RouteAware {
RouteObserver<ModalRoute<void>>? spy;
void didChangeDependencies() {
super.didChangeDependencies();
spy = context.read<RouteObserver<ModalRoute<void>>>();
spy.subscribe(this, ModelRoute.of(context)!);
}
void dispose() {
spy.unsubscribe(this);
super.dispose();
}
void didPush() => attachListeners();
void didPopNext() => attachListeners();
void didPop() => removeListeners();
void didPushNext() => removeListeners();
attachListeners() {
}
removeListeners() {
}
}
Maybe I am misunderstanding something here, but I have followed the docs on https://pub.dev/packages/get to set up a controller for each of my pages.
e.g standard page stuff:
Pages:
PageIntro -> PageLogin -> PageHome etc.
Controllers:
- IntroController
- LoginController
- HomeController
I've defined each of my pages the same way, e.g.:
class PageIntro extends StatelessWidget {
#override
Widget build(BuildContext context) {
final IntroController c = Get.put(IntroController(context));
return Scaffold(
body: _buildContent(), // you get the idea.
);
}
}
I was expecting that when navigating away from PageIntro to PageLogin, that the IntroController would be deleted, but it's not.
I know I can manually delete controllers with:
Get.delete<IntroController>();
But I shouldn't have to.
Unless I'm missing something/doing something really stupid, this seems more hassle, more messy that just having PageIntro extend StatefulWidget, then doing whatever I need to do in the dispose override method as usual.
Please can someone more knowledgable on this inform me?
Thanks.
In your case, binding is the way to go.
On the other hand, controllers are removed when the route is removed from the stack. In your case, you maybe pushing to the next screen rather than replacing it.
I think it will help you to avoid this error. You should call in inside of build method.
#override
Widget build(BuildContext context) {
var controller = ControllerStateDispatcher<RecentScreenController>(
RecentScreenController(),true) // if you want to save state, false if you want reset state)
.get<RecentScreenController>();
}
abstract class Cleaner {
void clear();
}
abstract class GetControllerProvider<T extends GetxController> {
R get<R extends GetxController>();
}
class ControllerStateDispatcher<T extends GetxController> extends Cleaner
implements GetControllerProvider {
bool shouldSaveState;
T controller;
ControllerStateDispatcher(this.controller, [this.shouldSaveState = false]);
#override
void clear() {
Get.delete<T>();
}
#override
DispatcherController get<DispatcherController extends GetxController>() {
if (!shouldSaveState) clear();
controller = Get.put(controller);
return controller as DispatcherController;
}
}
I am new to Flutter and I have this simple use case: in my Cloud Firestore DB I have a list of JSON representing events. I want to show them through my Flutter app in a ListView.
My requirements is that the ListView doesn't refresh in real-time but only when a pull-on refresh (implemented using RefreshIndicator) is done by the user or when the app resumes from background
I tried to implement this in 2 ways (I am using provider package for state management):
Using StreamProvider to create a stream of records from the DB. This continuosly updates the list view (basically the widget changes while the user is looking at it and I don't want this)
Using a ChangeNotifierProvider that refers to a EventManager class which holds a List<Event>. This class has a pull method which updates its internal state. I call this method when the user does the pull-on refresh (in the onRefresh callback of RefreshIndicator).
Option 2 seems to work well however I do not know how to implement the refresh when the app resumes from background. As I said I am using provider (and therefore StatelessWidget) and apparently there is no way to bind to these events when using StatelessWidgets
Do you have any suggestions and best practices for this use case?
You need to access Flutters lifecycle methods and fire a callback when the app resumes.
You can add a stateful widget with WidgetsBindingObserver and put that somewhere in the scope of your Provider, but as a parent of whatever widget you use to display the info.
Or you can make your PullToRefresh widget stateful and do the same thing.
class LifeCycleWidget extends StatefulWidget {
#override
_LifeCycleWidgetState createState() => _LifeCycleWidgetState();
}
class _LifeCycleWidgetState extends State<LifeCycleWidget>
with WidgetsBindingObserver {
AppLifecycleState _appLifecycleState;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
refreshOnResume();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_appLifecycleState = state;
});
refreshOnResume();
}
void refreshOnResume() {
if (_appLifecycleState == AppLifecycleState.resumed) {
print('resumed');
// your refresh method here
}
}
#override
Widget build(BuildContext context) {
return HomePage();
}
}
Add the following to your main method if it's not there already.
WidgetsFlutterBinding.ensureInitialized();
Another way to do it without adding a stateful widget would be with GetX. You can still keep all your Provider stuff but only use the SuperController which provides lifecycle methods. This I can't test because I don't have your Provider code but you can probably get away with creating the class below and initializing the controller somewhere within the scope of the relevant Provider widget with
Get.put(LifeCycleController());
Then call the function in the onResumed override and you can use Get.context if you need context.
class LifeCycleController extends SuperController {
#override
void onDetached() {
debugPrint('on detached');
}
#override
void onInactive() {
debugPrint('on inactive');
}
#override
void onPaused() {
debugPrint('on pause');
}
#override
void onResumed() {
// your refresh function here. Access context with Get.context
debugPrint('on resume');
}
}
Is there any callbacks available in flutter for every time the page is visible on screen? in ios there are some delegate methods like viewWillAppear, viewDidAppear, viewDidload.
I would like to call a API call whenever the particular page is on-screen.
Note: I am not asking the app states like foreground, backround, pause, resume.
Thank You!
Specifically to your question:
Use initState but note that you cannot use async call in initState because it calls before initializing the widget as the name means. If you want to do something after UI is created didChangeDependencies is great. But never use build() without using FutureBuilder or StreamBuilder
Simple example to demostrate:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home: ExampleScreen()));
}
class ExampleScreen extends StatefulWidget {
ExampleScreen({Key key}) : super(key: key);
#override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
List data = [];
bool isLoading = true;
void fetchData() async {
final res = await http.get("https://jsonplaceholder.typicode.com/users");
data = json.decode(res.body);
setState(() => isLoading = false);
}
// this method invokes only when new route push to navigator
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: Text(data?.toString() ?? ""),
),
);
}
}
Some lifecycle method of StatefulWidget's State class:
initState():
Describes the part of the user interface represented by this widget.
The framework calls this method in a number of different situations:
After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
After calling deactivate and then reinserting the State object into the tree at another location.
The framework replaces the subtree below this widget with the widget
returned by this method, either by updating the existing subtree or by
removing the subtree and inflating a new subtree, depending on whether
the widget returned by this method can update the root of the existing
subtree, as determined by calling Widget.canUpdate.
Read more
didChangeDependencies():
Called when a dependency of this State object changes.
For example, if the previous call to build referenced an
InheritedWidget that later changed, the framework would call this
method to notify this object about the change.
This method is also called immediately after initState. It is safe to
call BuildContext.dependOnInheritedWidgetOfExactType from this method.
Read more
build() (Stateless Widget)
Describes the part of the user interface represented by this widget.
The framework calls this method when this widget is inserted into the
tree in a given BuildContext and when the dependencies of this widget
change (e.g., an InheritedWidget referenced by this widget changes).
Read more
didUpdateWidget(Widget oldWidget):
Called whenever the widget configuration changes.
If the parent widget rebuilds and request that this location in the
tree update to display a new widget with the same runtimeType and
Widget.key, the framework will update the widget property of this
State object to refer to the new widget and then call this method with
the previous widget as an argument.
Read more
Some widgets are stateless and some are stateful. If it's a stateless widget, then only values can change but UI changes won't render.
Same way for the stateful widget, it will change for both as value as well as UI.
Now, will look into methods.
initState(): This is the first method called when the widget is created but after constructor call.
#override
void initState() {
// TODO: implement initState
super.initState();
}
didChangeDependecies() - Called when a dependency of this State object changes.Gets called immediately after initState method.
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
didUpdateWidget() - It gets called whenever widget configurations gets changed. Framework always calls build after didUpdateWidget
#override
void didUpdateWidget (
covariant Scaffold oldWidget
)
setState() - Whenever internal state of State object wants to change, need to call it inside setState method.
setState(() {});
dispose() - Called when this object is removed from the tree permanently.
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
You don't need StatefulWidget for calling the api everytime the screen is shown.
In the following example code, press the floating action button to navigate to api calling screen, go back using back arrow, press the floating action button again to navigate to api page.
Everytime you visit this page api will be called automatically.
import 'dart:async';
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
),
);
}
}
class ApiCaller extends StatelessWidget {
static int counter = 0;
Future<String> apiCallLogic() async {
print("Api Called ${++counter} time(s)");
await Future.delayed(Duration(seconds: 2));
return Future.value("Hello World");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Api Call Count: $counter'),
),
body: FutureBuilder(
future: apiCallLogic(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snapshot.hasData)
return Text('${snapshot.data}');
else
return const Text('Some error happened');
},
),
);
}
}
This is the simple code with zero boiler-plate.
The simplest way is to use need_resume
1.Add this to your package's pubspec.yaml file:
dependencies:
need_resume: ^1.0.4
2.create your state class for the stateful widget using type ResumableState instead of State
class HomeScreen extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends ResumableState<HomeScreen> {
#override
void onReady() {
// Implement your code inside here
print('HomeScreen is ready!');
}
#override
void onResume() {
// Implement your code inside here
print('HomeScreen is resumed!');
}
#override
void onPause() {
// Implement your code inside here
print('HomeScreen is paused!');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Go to Another Screen'),
onPressed: () {
print("hi");
},
),
),
);
}
}
If you want to make an API call, then you must be (or really should be) using a StatefulWidget.
Walk through it, let's say your stateful widget receives some id that it needs to make an API call.
Every time your widget receives a new id (including the first time) then you need to make a new API call with that id.
So use didUpdateWidget to check to see if the id changed and, if it did (like it does when the widget appears because the old id will be null) then make a new API call (set the appropriate loading and error states, too!)
class MyWidget extends StatefulWidget {
Suggestions({Key key, this.someId}) : super(key: key);
String someId
#override
State<StatefulWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
dynamic data;
Error err;
bool loading;
#override
Widget build(BuildContext context) {
if(loading) return Loader();
if(err) return SomeErrorMessage(err);
return SomeOtherStateLessWidget(data);
}
#override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// id changed in the widget, I need to make a new API call
if(oldWidget.id != widget.id) update();
}
update() async {
// set loading and reset error
setState(() => {
loading = true,
err = null
});
try {
// make the call
someData = await apiCall(widget.id);
// set the state
setState(() => data = someData)
} catch(e) {
// oops an error happened
setState(() => err = e)
}
// now we're not loading anymore
setState(() => loading = false);
}
}
I'm brand new to Flutter (literally, just started playing with it this weekend), but it essentially duplicates React paradigms, if that helps you at all.
Personal preference, I vastly prefer this method rather than use FutureBuilder (right now, like I said, I'm brand new). The logic is just easier to reason about (for me).
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.