Why we should use Equatable in Flutter Bloc? - flutter

I understand that Equatable helps to compare two instances of object without doing it manually.
But where exactly I can use it in Flutter Bloc?
Here is the example of usage Equatable:
Where it could be useful?
abstract class TodosState extends Equatable {
const TodosState();
#override
List<Object> get props => [];
}
class TodosLoadInProgress extends TodosState {}
class TodosLoadSuccess extends TodosState {
final List<Todo> todos;
const TodosLoadSuccess([this.todos = const []]);
#override
List<Object> get props => [todos];
#override
String toString() => 'TodosLoadSuccess { todos: $todos }';
}
class TodosLoadFailure extends TodosState {}

Object and data comparison is always hard to do when it comes to stream as we need to decide state updation based on it.
we required Equatable as it overrides == and hashCode internally, which saves a lot of boilerplate code. In Bloc, we have to extend Equatable to States and Events classes to use this functionality.
abstract class TodosState extends Equatable {}
So, that means TodosState will not make duplicate calls and will not going to rebuild the widget if the same state occurs.
Let's see props usage in Equatable and what makes it special
Define State without props:
class TodosLoadSuccess extends TodosState {}
Define State with props:
props declared when we want State to be compared against the values which declared inside props List
class TodosLoadSuccess extends TodosState {
final String name;
final List<Todo> todos;
const TodosLoadSuccess([this.name, this.todos = const []]);
#override
List<Object> get props => [name, todos];
}
If we remove the name from the list and keep a list like [this.todos], then State will only consider the todos field, avoiding the name field. That is why we used props for handling State changes.
Bloc Stream Usage:
As we extending State with Equatable that makes a comparison of old state data with new state data. For example, let's look at the below example here TodosState will build a widget only once, which will avoid the second call as it is duplicated.
#override
Stream<TodosState> mapEventToState(MyEvent event) async* {
final List<Todo> todos = [Todo(), Todo()];
yield TodosLoadSuccess(todos);
yield TodosLoadSuccess(todos); // This will be avoided
}
Detail Blog: https://medium.com/flutterworld/flutter-equatable-its-use-inside-bloc-7d14f3b5479b

I think it is useful for comparing what state is in BlocBuilder.
Below code is a good example of using Equatable.
if(state is [Some State])
#override
Widget build(BuildContext context) {
return BlocBuilder<SongsSearchBloc, SongsSearchState>
bloc: BlocProvider.of(context),
builder: (BuildContext context, SongsSearchState state) {
if (state is SearchStateLoading) {
return CircularProgressIndicator();
}
if (state is SearchStateError) {
return Text(state.error);
}
if (state is SearchStateSuccess) {
return state.songs.isEmpty
? Text(S.EMPTY_LIST.tr())
: Expanded(
child: _SongsSearchResults(
songsList: state.songs,
),
);
} else {
return Text(S.ENTER_SONG_TITLE.tr());
}
},
);
}

Related

How Do I Properly Use Equatable To Recognise Deeply Nested Data Using Flutter_bloc 8.0.0+

Introduction: I have been working on this problem for a while. The problem involves updating the state of deeply nested data with the Bloc 8.0.0+ paradigm. I am using a class that holds deeply nested data in it (in this minimum viable code: a ‘Collection’ that contains a name and children are meant to be a Show Series, and the Series has children that are meant to be Seasons, and Seasons have children that are meant to be Episodes).
The nested structure is something like this:
List<CollectionState>
|-- List<CollectionState>
| |-- List<CollectionState>
| | |-- List<CollectionState>
An important functionality of the code is that a child is added to the children list of the correct parent so it will display in the correct order of the hierarchy of parents to their children in the ListView; i.e. Collection has one Series (8768), and that Series has two Seasons (1817 and 7623), and when pressing on a Season, an Episode is added to its correct parent Season instead of being added to the bottom of the ListView. In this case, pressing on Season 1817 four times adds Episodes 2175, 2773, 5420 and 8826 under itself instead of adding to Season 7623.
Problem: As I understand it, a good practice while working with BLoC 8.0.0+ would be extending the CollectionState class with Equatable. The following code I provide works; however, it does not use this best practice. I want it to do so, but I am having problems which I will explain shortly. I have commented in the code of collection_state.dart with:
// 1) where Equatable should be extended
I have located in the code where the issue occurs in collection_bloc.dart when the CollectionState class is extended with Equatable (please note that the problem happens only with changing the code by extending CollectionState class with Equatable, which the code does not do). I have commented in the code with this comment:
//TODO: Here is the problem. This code does not work properly when I extend the CollectionState class to Equatable.
Surprisingly, there is little information online that I could find about using deeply nested data with BLoC 8.0.0+.
I am new to BLoC 8.0.0+ and even newer to Equatable (I’ve always used Provider up to this point) and I don’t understand why my code is not updating correctly when extending Equatable. I guess I am having an immutability issue because the update to the class with the AddInfo bloc event is not considered different to Equatable. I am at a loss to understand how to change my code to use the best practices with deeply nested data with BLoC.
Question:
How do I change my code to extend the CollectionState class with Equatable and still have it update my UI correctly?
Bearing in mind that I have a cursory understanding of Equatable, I would like to know more about the underlying root of the problem. Is it the case that the bloc event method is not producing a class different enough to Equatable, so it is not updating or is something entirely different happening here?
Note: When I extend Equatable and add the props for name, children, showType; and click on the collection, it adds Series as normal. But, when I click on Series, the app does not update to show the addition of the Seasons and Episodes. However, when I press Hot Reload, the app is refreshed with all the correct items. I believe that it is not comparing the nested objects, and I don't know how to allow it to compare them with my code.
pub spec.yaml
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.1
equatable: ^2.0.5
main.dart
import 'package:deeply_nested_objects/bloc/add_to_collection_logic.dart';
import 'package:deeply_nested_objects/bloc/collection_bloc.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CollectionBloc(),
child: const MaterialApp(
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
#override
Widget build(BuildContext context) {
return BlocBuilder<CollectionBloc, CollectionState>(
builder: (context, state) {
return Scaffold(
appBar: AppBar(
title: const Text('Deeply nested data and Bloc 8.0.0+'),
),
body: ListView.builder(
itemCount: state.getAllNodes(state).length,
itemBuilder: (context, index) {
var nodes = state.getAllNodes(state)[index];
Color textColor = getColor(nodes);
double distance = getPaddingDistance(nodes);
return Padding(
padding: EdgeInsets.only(left: distance),
child: ListTile(
onTap: () => addToCollectionLogic(nodes.showType, index,
nodes.children.length + 1, context),
leading: Card(
child: Text(nodes.name, style: TextStyle(color: textColor)),
),
),
);
},
),
);
},
);
}
double getPaddingDistance(CollectionState nodes) {
switch (nodes.showType) {
case ShowType.collection:
return 0;
case ShowType.series:
return 20;
case ShowType.season:
return 40;
case ShowType.episode:
return 60;
}
}
Color getColor(CollectionState nodes) {
switch (nodes.showType) {
case ShowType.collection:
return Colors.black;
case ShowType.series:
return Colors.blue;
case ShowType.season:
return Colors.green;
case ShowType.episode:
return Colors.red;
}
}
}
add_collection_logic.dart
import 'package:deeply_nested_objects/bloc/collection_bloc.dart';
import 'package:deeply_nested_objects/bloc/collection_event.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void addToCollectionLogic(
ShowType showType, int index, int count, BuildContext context) {
void passToBloc(String name, ShowType showType) =>
BlocProvider.of<CollectionBloc>(context).add(
AddInfo(
index: index,
child: CollectionState(name: name, showType: showType, children: []),
),
);
switch (showType) {
case ShowType.collection:
passToBloc('Series $count', ShowType.series);
break;
case ShowType.series:
passToBloc('Season $count', ShowType.season);
break;
case ShowType.season:
passToBloc('Episode $count', ShowType.episode);
break;
case ShowType.episode:
break;
}
}
collection_event.dart
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:equatable/equatable.dart';
abstract class CollectionEvents extends Equatable {
#override
List<Object> get props => [];
}
class AddInfo extends CollectionEvents {
AddInfo({required this.index, required this.child});
final int index;
final CollectionState child;
}
collection_bloc.dart
import 'package:deeply_nested_objects/bloc/collection_event.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CollectionBloc extends Bloc<CollectionEvents, CollectionState> {
CollectionBloc() : super(CollectionState.initial()) {
on<AddInfo>((event, emit) {
if (event.child.showType == ShowType.series) {
emit(state.copyWith(children: [...state.children, event.child]));
}
if (event.child.showType == ShowType.season ||
event.child.showType == ShowType.episode) {
//TODO: Here is the problem. This code does not work properly when I extend the CollectionState class to Equatable.
// get the list of all nodes
List<CollectionState> list = state.getAllNodes(state);
// find the parent node while still in the list
CollectionState parent = list[event.index];
// add the child to the parent
parent.children.add(event.child);
// update the state
emit(state.copyWith(children: [...state.children]));
}
});
}
}
collection_state.dart
enum ShowType { collection, series, season, episode }
// 1) where should be Equatable
class CollectionState {
const CollectionState({
required this.name,
required this.children,
required this.showType,
});
final String name;
final List<CollectionState> children;
final ShowType showType;
factory CollectionState.initial() {
return const CollectionState(
name: "Collection",
showType: ShowType.collection,
children: [],
);
}
List<CollectionState> getAllNodes(CollectionState node) {
// empty list to store the result
List<CollectionState> result = [];
// add the current node
result.add(node);
// add the children too
for (CollectionState child in node.children) {
// composite design pattern seek and find
result.addAll(getAllNodes(child));
}
return result;
}
CollectionState copyWith({
String? name,
List<CollectionState>? children,
ShowType? showType,
}) {
return CollectionState(
name: name ?? this.name,
children: children ?? this.children,
showType: showType ?? this.showType,
);
}
}
You have to assign properties in equatable that you want to check equality on.
Example:
class SomeClass extends Equatable {
SomeClass({required this.index});
final int index;
#override
List<Object> get props => [];
}
If I would check some SomeClass(index: 10) == SomeClass(index: 9) it would be true because I didn't say equatable what properties it should look for on == operator
If I update my code to
class SomeClass extends Equatable {
SomeClass({required this.index});
final int index;
#override
List<Object> get props => [index];
}
Now same check would be false, cause it is looking on index property

Access state from ui without an if statement

am following this Bloc's official example and I couldn't find a way how to access the state without that if statement.
Let's have the example below, I would like to display a specific text based on the initial value of showText, the only possible solution to access the state is via:
if(statement is ExampleInitial) {state.showText? return Text("yes") : return Text("no")}
But am finding this solution hard to implement when you have more values with initial values. Or am I doing this wrong?
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
////////// event
part of 'example_bloc.dart';
abstract class ExampleEvent extends Equatable {
const ExampleEvent();
}
class ExampleStarted extends ExampleEvent {
#override
List<Object> get props => [];
}
////////// state
part of 'example_bloc.dart';
abstract class ExampleState extends Equatable {
const ExampleState();
}
////////// state
class ExampleInitial extends ExampleState {
final bool showText = false;
const ExampleInitial();
#override
List<Object> get props => [showText];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(
builder: (context, state) {
return state.showText ? Text("yes") :Text("no"); // can't access to state.showText
});
}
}
You can declare a variable inside Bloc Class which will be global and need to be set inside the 'bloc.dart' file like in the case of Provider Package. This variable does not need state to be checked before accessing it in UI. You can access this value from the Navigation tree using context.
////////// bloc
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
ExampleBloc() : super(const ExampleInitial()) {
on<ExampleStarted>(_onExampleStarted);
}
bool showText = false;
void _onExampleStarted(ExampleStarted event, Emitter<ExampleState> emit) {
emit(const ExampleInitial());
}
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider.of<ExampleBloc>(context).showText
? const Text('Yes')
: const Text('No');
}
}
There is another way in which you declare abstract State Class to always have the boolean value. So, whatever new class extends those State will have inherited boolean value from parent class. This concept is called inheritance in OOP.
////////// state
abstract class ExampleState extends Equatable {
const ExampleState();
final bool showText = false;
}
////////// state
class ExampleInitial extends ExampleState {
const ExampleInitial();
// You can also set ExampleInitial to accept showText and send it to its
// parent class using 'super' method in constructor,
// if parent class has constructor with 'showText' as boolean
#override
List<Object> get props => [];
}
// ui
class CreateExampleScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocBuilder<ExampleBloc, ExampleState>(builder: (context, state) {
return state.showText ? const Text("yes") : const Text("no");
});
}
}
A pragmatic usecase for different State Classes having different state variables is as follows:
Let's account for three states while fetching data from api
-if(state is DataLoadingState),
// there is no need for state
-if(state is DataLoadedState)
// state need to have a variable named weatherData containing temperatures, cities and so on.
-if(state is ErrorWhileLoadingState)
// state needs to have a reason for the error. For example: errorMsg: 'Internal Server Error'
So, you need to check the state before accessing its values.

How to replace copyWith so as not to copy, but to create a new state?

I have a bloc which is responsible for switching indexes in the Navogation Bottom Bar.It is implemented in such a way that it copies the old state and changes it. I need to replace copyWith and make it not copy but create a new state. How can this be implemented and rewritten given bloc?
class BottomNavyBloc extends Bloc<BottomNavyEvent, BottomNavyState> {
BottomNavyBloc() : super(const BottomNavyState()) {
on<ChangePageEvent>(
(event, emit) => emit(
state.copyWith(index: event.index),
),
);
}
}
abstract class BottomNavyEvent extends Equatable {
const BottomNavyEvent();
#override
List<Object> get props => [];
}
class ChangePageEvent extends BottomNavyEvent {
final int index;
const ChangePageEvent({
required this.index,
});
#override
List<Object> get props => [index];
}
My state:
class BottomNavyState extends Equatable {
const BottomNavyState({
this.index = 0,
});
final int index;
#override
List<Object> get props => [index];
}
class ChangePageState extends BottomNavyState {
}
We use
emit(state.copyWith(index: event.index))
to say that we are copying all the elements from the previous state by changing index.
Your state BottomNavyState has only one variable as of now. So, the above copyWith acts similar to using emitting new state.
We should not try to change or override the method copyWith because it beats the method's actual purpose.
Instead, you could use
emit(BottomNavyState(index: event.index))
to use a new state constructor instead of copying from previous state.

Why when i use a class in dart with equatable and just a list as property the copyWith method return the same object, same hascode

Im using bloc and it was working as expected but today i notice a strage behaviour when i was sending the same state (RefreshState) using copyWith, the state wasnt trigger after second call. then i did a test creating two objects and compared them but the result was they are the same object, very odd.
So why is this happen?, this is my class:
class Model extends Equatable {
final List<Product> mostBuyProducts;
const Model({
this.mostBuyProducts,
});
Model copyWith({
List<Product> mostBuyProducts,
}) =>
Model(
mostBuyProducts: mostBuyProducts ?? this.mostBuyProducts,
);
#override
List<Object> get props => [
mostBuyProducts,
];
}
and then i use the CopyWith method like (inside the bloc):
Stream<State> _onDeleteProduct(OnDeleteProduct event) async* {
state.model.mostBuyProducts.removeWhere((p) => p.id == event.id);
var newMostBuyProducts = List<Product>.from(state.model.mostBuyProducts);
final model1 = state.model;
final model2 = state.model.copyWith(mostBuyProducts: newMostBuyProducts);
final isEqual = (model1 == model2);
yield RefreshState(
state.model.copyWith(mostBuyProducts: newMostBuyProducts));
}
isEqual return true :/
BTW this is my state class
#immutable
abstract class State extends Equatable {
final Model model;
State(this.model);
#override
List<Object> get props => [model];
}
Yes because lists are mutable. In order to detect a change in the list you need to make a deep copy of the list. Some methods to make a deep copy are available here : https://www.kindacode.com/article/how-to-clone-a-list-or-map-in-dart-and-flutter/
Using one such method in the solution below! Just change the copyWith method with the one below.
Model copyWith({
List<Product> mostBuyProducts,
}) =>
Model(
mostBuyProducts: mostBuyProducts ?? [...this.mostBuyProducts],
);

Controlling State from outside of a StatefulWidget

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.