I am trying to fetch data from API as soon as the flutter app loads but I am unable to achieve so
class MarketBloc extends Bloc<MarketListEvent, MarketListState> {
MarketBloc() : super(MarketLoading()) {
on<MarketSelectEvent>((event, emit) async {
emit(MarketLoading());
final data = await ApiCall().getData(event.value!);
globalData = data;
emit(MarketDataFetched(marDat: globalData.data, dealType: event.value));
});
}
}
I have called MarketLoading state as the initial state and I want to call MarketSelectEvent just after that but in the current code, action is required to do so and i want to achieve it without any action.
You have 2 options:
add an event from the UI as soon you instantiate the MarketBloc
MarketBloc()..add(MarketSelectEvent())
add an event in the initialization code
MarketBloc() : super(MarketLoading()) {
add(MarketSelectEvent());
}
You could do this with in the initState of whatever the first page is that your app loads.
class TestPage extends StatefulWidget {
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
late MarketBloc marketBloc;
#override
void initState() {
super.initState();
marketBloc = BlocProvider.of<MarketBloc>(context);
marketBloc.add(MarketSelectEvent());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<MarketBloc, MarketListState>(
builder: (context, state) {
if (state is MarketLoading) {
return Text('loading...');
}
if (state is MarketDataFetched) {
return ...your UI that contains data from API call
}
},
),
),
);
}
}
Related
I am facing one issue in which when I am going from one page to another page using flitter BLoC, my first page rebuild before reaching to second. I am able to restrict the rebuild of the page using buildWhen in BlocBuilder, but the problem is when I come back to the first page again by back press then the page can not show the previous state widgets. I don't know how to manage navigation between pages without rebuild the page again, I am using flutter_bloc 6.1.1 below is my code.
FirstPage
class FirstPage extends StatefulWidget {
final MyData dataObj;
FirstPage({this.dataObj});
#override
_MyFirstPageState createState() => _MyFirstPageState();
}
class _MyFirstPageState extends State<FirstPage> {
FirstPageBloc _bloc = FirstPageBloc();
String _userAddress='';
#override
void initState() {
super.initState();
_bloc.add(UserInfoEvent(dataObj:widget.dataObj));
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text(StringConstants.APP_TITLE_HEADING),
),
body: BlocListener<FirstPageBloc, FirstPageState>(
cubit: _bloc,
listenWhen: (previousState, state) {
// return true/false to determine whether or not
// to call listener with state
return true;
},
listener: (context, state) async{
if (state is LoadingState) {
print('Loading ...');
}
if (state is DataInfoState) {
_userAddress=state.userAddress;
}
if(state is ConfirmationState){
Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage(dataObj: widget.dataObj)));
}
},
child: BlocBuilder<FirstPageBloc, FirstPageState>(
//bloc: _bloc,
cubit: _bloc,
buildWhen: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
if(state is ConfirmationState){
return false;
}
return true;
},
builder: (context, state) {
if (state is LoadingState) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(valueColor:
AlwaysStoppedAnimation<Color>(ColorConstants.Primary),),
Text(StringConstants.PLEASE_WAIT)
],),
);
}
return _mainWidget();
}),
),
);
}
}
BLoC
class FirstPageBloc extends Bloc<FirstPageEvent, FirstPageState>{
FirstPageBloc() : super(InitialState());
#override
Stream<FirstPageState> mapEventToState(FirstPageEvent event) async*{
// TODO: implement mapEventToState
if(event is DataInfoEvent){
yield* _getUserData(event.dataObj);
}
if(event is ConfirmationEvent){
yield* _confirmTaskData(event.dataObj);
}
}
Stream<DelConfirmState> _confirmTaskData(MyData dataObj) async* {
yield LoadingState();
//Performing some SQLite DB operations
yield ConfirmationState();
}
Stream<DelConfirmState> _getUserData(MyData dataObj) async* {
yield LoadingState();
String userAddress='ABDC001, PIN- 0091910, 5th Main USA';
//Fetching User data from SQLite database and passing to UI
yield DataInfoState(userAddress:userAddress);
}
}
State
abstract class FirstPageState extends Equatable {}
///This is our initial state
class InitialState extends FirstPageState {
#override
List<Object> get props => null;
}
//This state will call for loading the progress var
class LoadingState extends FirstPageState {
#override
List<Object> get props => [];
}
//This state will call for loading the progress var
class ErrorState extends FirstPageState {
final String errorMessage;
ErrorState({#required this.errorMessage});
#override
List<Object> get props => [];
}
//This state will retun the userdata
class DataInfoState extends FirstPageState {
final String userAddress;
DataInfoState({#required this.userAddress});
#override
// TODO: implement props
List<Object> get props => [];
}
class TaskConfirmationState extends FirstPageState {
ConfirmationState({});
#override
// TODO: implement props
List<Object> get props => [];
}
Event
abstract class FirstPageEvent extends Equatable {}
class GetUserInfoEvent extends FirstPageEvent {
final MyData dataObj;
GetUserInfoEvent({this.taskObj});
#override
List<Object> get props => [];
}
class ConfirmationEvent extends FirstPageEvent {
final MyData dataObj
ConfirmationEvent({this.dataObj});
#override
List<Object> get props => [];
}
Please advise
Thank You
You need to provide your bloc at a higher level widget, then you need to get it from the context. In this way the state will persist even through navigation.
You can do that wrapping your widget like this:
BlocProvider(
create: (context) => FirstPageBloc(),
child: FirstPage(),
)
and then inside of initState you can get it like this:
_bloc = BlocProvider.of<FirstPageBloc>(context);
I'm creating an app where you login and go to a page where you have a list of your restaurants, you have also a form where you can add a new restaurant.
This part works.
The problem is that when i click add the restaurant is added in firestore correctly, but the list doesn't refresh. I usually yield 2 states, a LoadingState and a LoadedRestaurantsListState, but with the last version of flutter_bloc this trick doesn't work, seems like just the last state yielded is received, but the previous was LoadedRestaurantsListState, so they are equals and the blocbuilder ignores the second one. So I've to use the equatable's props to distinguish the 2 states, but in the equatable documentation is written: "Note: Equatable is designed to only work with immutable objects so all member variables must be final".
So I've to make all the model's fields final, but if I do it how can i modify just one o two fields when I need it to?
What is the best practice?
If someone has examples, or videos, etc it would be very appreciated.
Thanks in advance
Without props
FirebaseBloc.dart
Stream<FirebaseState> mapEventToState(
FirebaseEvent event,
) async* {
print("event firebase ${event.runtimeType.toString()}");
if (event is CreateRestaurantFirebaseEvent) {
yield LoadingState();
await _databaseService.createRestaurant(event.restaurant, event.user);
List<Restaurant> restaurantsList = await _databaseService
.loadRestaurantsList(event.user.restaurantsIDsList);
yield LoadedRestaurantsListState(restaurantsList);
}
if (event is LoadRestaurantsListEvent) {
List<Restaurant> restaurantsList =
await _databaseService.loadRestaurantsList(event.restaurantsIDs);
yield LoadedRestaurantsListState(restaurantsList);
}
FirebaseState.dart
class LoadingState extends FirebaseState {
#override
List<Object> get props => [];
}
class LoadedRestaurantsListState extends FirebaseState {
List<Restaurant> restaurantsList;
LoadedRestaurantsListState(this.restaurantsList);
#override
List<Object> get props => [];
}
view.dart
class RestaurantSelectionScreen extends StatefulWidget {
final User user;
RestaurantSelectionScreen({
#required this.user,
});
#override
_RestaurantSelectionScreenState createState() =>
_RestaurantSelectionScreenState();
}
class _RestaurantSelectionScreenState extends State<RestaurantSelectionScreen> {
FirebaseBloc _firebaseBloc;
#override
void initState() {
super.initState();
_firebaseBloc = FirebaseBloc();
_firebaseBloc.add(LoadRestaurantsListEvent(widget.user.restaurantsIDsList));
}
#override
Widget build(BuildContext context) {
return BlocProvider<FirebaseBloc>(
create: (context) => _firebaseBloc,
child: Scaffold(
body: SingleChildScrollView(
child: Center(
child: BlocBuilder(
cubit: _firebaseBloc,
builder: (context, state) {
print("state ${state.runtimeType.toString()}");
if (state is InitialFirebaseState) {
return CircularProgressIndicator();
} else if (state is LoadedRestaurantsListState) {
return buildUI(state);
} else if (state is LoadingState) {
return CircularProgressIndicator();
} else {
return _CreateRestaurantFormWidget(widget.user);
}
},
),
),
),
),
);
}
example_states:
abstract class ExampleState extends Equatable {
const ExampleState();
}
class LoadingState extends ExampleState {
//
}
class LoadedState extends ExampleState {
//
}
class FailedState extends ExampleState {
//
}
example_events:
abstract class ExampleEvent extends Equatable {
//
}
class SubscribeEvent extends ExampleEvent {
//
}
class UnsubscribeEvent extends ExampleEvent {
//
}
class FetchEvent extends ExampleEvent {
//
}
example_bloc:
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
#override
ExampleState get initialState => LoadingState();
#override
Stream<ExampleState> mapEventToState(
ExampleEvent event,
) async* {
if (event is SubscribeEvent) {
//
} else if (event is UnsubscribeEvent) {
//
} else if (event is FetchEvent) {
yield LoadingState();
try {
// network calls
yield LoadedState();
} catch (_) {
yield FailedState();
}
}
}
}
example_screen:
class ExampleScreenState extends StatelessWidget {
// ignore: close_sinks
final blocA = ExampleBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<ExampleBloc, ExampleState>(
bloc: blocA,
// ignore: missing_return
builder: (BuildContext context, state) {
if (state is LoadingState) {
blocA.add(Fetch());
return CircularProgressBar();
}
if (state is LoadedState) {
//...
}
if (state is FailedState) {
//...
}
},
),
);
}
}
As you can see in example_bloc, initial state is LoadingState() and in build it shows circular progress bar. I use Fetch() event to trigger next states. But I don't feel comfortable using it there. What I want to do is:
When app starts, it should show LoadingState and start networking calls, then when it's all completed, it should show LoadedState with networking call results and FailedState if something goes wrong. I want to achieve these without doing
if (state is LoadingState) {
blocA.add(Fetch());
return CircularProgressBar();
}
Your discomfort really has reason - no event should be fired from build() method (build() could be fired as many times as Flutter framework needs)
Our case is to fire initial event on Bloc creation
Possibilities overview
case with inserting Bloc with BlocProvider - this is preferred way
create: callback is fired only once when BlocProvider is mounted & BlocProvider would close() bloc when BlocProvider is unmounted
class ExampleScreenState extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => ExampleBloc()..add(Fetch()), // <-- first event,
child: BlocBuilder<ExampleBloc, ExampleState>(
builder: (BuildContext context, state) {
...
},
),
),
);
}
}
case when you create Bloc in State of Statefull widget
class _ExampleScreenStateState extends State<ExampleScreenState> {
ExampleBloc _exampleBloc;
#override
void initState() {
super.initState();
_exampleBloc = ExampleBloc();
_exampleBloc.add(Fetch());
// or use cascade notation
// _exampleBloc = ExampleBloc()..add(Fetch());
}
#override
void dispose() {
super.dispose();
_exampleBloc.close(); // do not forget to close, prefer use BlocProvider - it would handle it for you
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<ExampleBloc, ExampleState>(
bloc: _exampleBloc,
builder: (BuildContext context, state) {
...
},
),
);
}
}
add first event on Bloc instance creation - this way has drawbacks when testing because first event is implicit
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
...
ExampleBloc() {
add(Fetch());
}
}
// insert it to widget tree with BlocProvider or create in State
BlocProvider( create: (_) => ExampleBloc(), ...
// or in State
class _ExampleScreenStateState extends State<ExampleScreenState> {
final _exampleBloc = ExampleBloc();
...
PS feel free to reach me in comments
Sergey Salnikov has a great answer. I think I can add another suggestion however.
In my main.dart file I am using a MultiBlocProvider to create all my blocs for use further down the tree. Like so
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: <BlocProvider<dynamic>>[
BlocProvider<OneBloc>(create: (_) => OneBloc()),
BlocProvider<TwoBloc>(create: (_) => TwoBloc()),
],
child: MaterialApp( // Rest of your app )
Then when I need to call an event when I load a page, in this case I wanted to fetch some data depending on a list tile selected, and I needed more options than FutureBuilder can provide me, I simple used initState(); and called the bloc provider and added an event.
class _ExampleScreenState extends State<ExampleScreen> {
#override
void initState() {
super.initState();
BlocProvider.of<OneBloc>(context)
.add(FetchData);
}
It works because the bloc has already been provided from the root widget.
In simple terms:
Using BlocProvider, call it during creation.
BlocProvider(create: (context) => ExampleBloc()..add(Fetch()))
Using BlocState, use it as
class _ExampleScreenStateState extends State<ExampleScreenState> {
ExampleBloc _exampleBloc;
#override
void initState() {
super.initState();
_exampleBloc = ExampleBloc()..add(Fetch());
}
#override
void dispose() {
super.dispose();
_exampleBloc.close();
}
Sorry if this is a novice question. I have the following repo file:
class TwitterRepo {
final TwitterRepoCallback _callback;
TwitterRepo(this._callback){
// do stuff
}
}
abstract class TwitterRepoCallback{
void onEvent();
}
In my UI file I have the following:
class TweetList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _TweetListState();
}
}
class _TweetListState extends State<TweetList> implements TwitterRepoCallback {
final TwitterRepo _twitterRepo = TwitterRepo(this);
// other stuff like initState, build and onEvent
}
There is an error on
final TwitterRepo _twitterRepo = TwitterRepo(this);
where I use "this", Invalid reference to 'this' expression.
I'm at a loss on how to pass in my callback to receive events.
Try this.
class ParentPageState extends State<ParentPage> implement Callback{
...
#override
void callback(){
...
}
#override
void callback1(String str){
....
}
#override
Widget build(BuildContext context){
return Scaffold(
body : Container(
child : ChildPage(callback : this.callback, callback1 : this.callback1)
)
);
}
}
And ChildPage
import .....
//Main Point
typedef Callback = void Function();
typedef Callback1 = void Function(String str);
class ChildPage extends StatelessWidget{
final Callback _callback;
final Callback1 _callback1;
ChildPage({Callback callback, Callback1 callback1}): _callback : callback, _callback1 : callback1;
.....
#override
Widget build(BuildContext context){
return Container(
child : InkWell(
onPressed : (){
this._callback();
this._callback1("test");
},
child : ....
)
);
}
This is may have issue. The main point is "typedef"
I probably wouldn't use callbacks for this type of need. Instead I'd use some kind of InheritedWidget like system to grab data and propagate it down the widget tree. I know you just started, but a great tool is the Provider package. To do what you're trying to do here it'd look something like this:
class TwitterRepo extends ChangeNotifier{
//construct Repo
TwitterRepo(){
_setupNetworkListener();
}
List<Data> data = [];
//set up the way to listen to and get data here then add it to your list,
//finally notify your listeners of the data changes
_setupNetworkListener()async{
var _data = await gettingInfo();
data.addAll(_data);
notifyListeners();
}
}
class TwitterRepoUI extends StatefulWidget {
#override
_TwitterRepoUIState createState() => _TwitterRepoUIState();
}
class _TwitterRepoUIState extends State<TwitterRepoUI> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<TwitterRepo>(
builder: (context)=> TwitterRepo(),
child: Consumer<TwitterRepo>(
builder: (context, model, child){
return ListView.builder(
itemCount: model.data.length,
itemBuilder: (context, index){
return Center(
child: Text(index.toString()),
);
});
},
),
);
}
}
If you want to use the callback to notify the UI to render some new data, you may want to use Future or Stream. Anyway, the question is how to implement a callback/listener so here I give you some examples:
You can't declare a variable using this, you could initialize the variable on the constructor
_TweetListState() {
_twitterRepo = TwitterRepo(this);
}
or inside initState()
#override
void initState() {
super.initState();
_twitterRepo = TwitterRepo(this);
}
A better way to do this would be:
final TwitterRepo _twitterRepo = TwitterRepo();
#override
void initState() {
super.initState();
_twitterRepo.listen(this);
}
And the listen method implemented on TwitterRepo would set the callback
You could also use VoidCallback instead of TwitterRepoCallback:
#override
void initState() {
super.initState();
_twitterRepo.listen(() {
// Do stuff
});
}
Or a callback function using Function(String a, int b) instead of TwitterRepoCallback
#override
void initState() {
super.initState();
_twitterRepo.listen((a, b) {
// Do stuff
});
}
I'm using an inherited Widget to access a Bloc with some long running task (e.g. search).
I want to trigger the search on page 1 and continue to the next page when this is finished. Therefore I'm listening on a stream and wait for the result to happen and then navigate to the result page.
Now, due to using an inherited widget to access the Bloc I can't access the bloc with context.inheritFromWidgetOfExactType() during initState() and the exception as I read it, recommends doing this in didChangeDependencies().
Doing so this results in some weird behavior as the more often I go back and forth, the more often the stream I access fires which would lead to the second page beeing pushed multiple times. And this increases with each back and forth interaction. I don't understand why the stream why this is happening. Any insights here are welcome. As a workaround I keep a local variable _onSecondPage holding the state to avoid pushing several times to the second Page.
I found now How to call a method from InheritedWidget only once? which helps in my case and I could access the inherited widget through context.ancestorInheritedElementForWidgetOfExactType() and just listen to the stream and navigate to the second page directly from initState().
Then the stream behaves as I would expect, but the question is, does this have any other side effects, so I should rather get it working through listening on the stream in didChangeDependencides() ?
Code examples
My FirstPage widget listening in the didChangeDependencies() on the stream. Working, but I think I miss something. The more often i navigate from first to 2nd page, the second page would be pushed multiple times on the navigation stack if not keeping a local _onSecondPage variable.
#override
void didChangeDependencies() {
super.didChangeDependencies();
debugPrint("counter: $_counter -Did change dependencies called");
// This works the first time, after that going back and forth to the second screen is opened several times
BlocProvider.of(context).bloc.finished.stream.listen((bool isFinished) {
_handleRouting(isFinished);
});
}
void _handleRouting(bool isFinished) async {
if (isFinished && !_onSecondPage) {
_onSecondPage = true;
debugPrint("counter: $_counter - finished: $isFinished : ${DateTime.now().toIso8601String()} => NAVIGATE TO OTHER PAGE");
await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
_onSecondPage = false;
} else {
debugPrint("counter: $_counter - finished: $isFinished : ${DateTime.now().toIso8601String()} => not finished, nothing to do now");
}
}
#override
void dispose() {
debugPrint("counter: $_counter - disposing my homepage State");
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder(
stream: BlocProvider.of(context).bloc.counter.stream,
initialData: 0,
builder: (context, snapshot) {
_counter = snapshot.data;
return Text(
"${snapshot.data}",
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
A simple Bloc faking some long running work
///Long Work Bloc
class LongWorkBloc {
final BehaviorSubject<bool> startLongWork = BehaviorSubject<bool>();
final BehaviorSubject<bool> finished = BehaviorSubject<bool>();
int _counter = 0;
final BehaviorSubject<int> counter = BehaviorSubject<int>();
LongWorkBloc() {
startLongWork.stream.listen((bool start) {
if (start) {
debugPrint("Start long running work");
Future.delayed(Duration(seconds: 1), () => {}).then((Map<dynamic, dynamic> reslut) {
_counter++;
counter.sink.add(_counter);
finished.sink.add(true);
finished.sink.add(false);
});
}
});
}
dispose() {
startLongWork?.close();
finished?.close();
counter?.close();
}
}
Better working code
If I however remove the code to access the inherited widget from didChangeDependencies() and listen to the stream in the initState() it seems to be working properly.
Here I get hold of the inherited widget holding the stream through context.ancestorInheritedElementForWidgetOfExactType()
Is this ok to do so? Or what would be a flutter best practice in this case?
#override
void initState() {
super.initState();
//this works, but I don't know if this is good practice or has any side effects?
BlocProvider p = context.ancestorInheritedElementForWidgetOfExactType(BlocProvider)?.widget;
if (p != null) {
p.bloc.finished.stream.listen((bool isFinished) {
_handleRouting(isFinished);
});
}
}
Personally, I have not found any reason not to listen to BLoC state streams in initState. As long as you remember to cancel your subscription on dispose
If your BlocProvider is making proper use of InheritedWidget you should not have a problem getting your value inside of initState.
like So
void initState() {
super.initState();
_counterBloc = BlocProvider.of(context);
_subscription = _counterBloc.stateStream.listen((state) {
if (state.total > 20) {
Navigator.push(context,
MaterialPageRoute(builder: (BuildContext context) {
return TestPush();
}));
}
});
}
Here is an example of a nice BlocProvider that should work in any case
import 'package:flutter/widgets.dart';
import 'bloc_base.dart';
class BlocProvider<T extends BlocBase> extends StatefulWidget {
final T bloc;
final Widget child;
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}) : super(key: key);
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context) {
final type = _typeOf<_BlocProviderInherited<T>>();
_BlocProviderInherited<T> provider =
context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
return provider?.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<BlocBase>> {
#override
Widget build(BuildContext context) {
return _BlocProviderInherited<T>(
bloc: widget.bloc,
child: widget.child,
);
}
#override
void dispose() {
widget.bloc?.dispose();
super.dispose();
}
}
class _BlocProviderInherited<T> extends InheritedWidget {
final T bloc;
_BlocProviderInherited({
Key key,
#required Widget child,
#required this.bloc,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
... and finally the BLoC
import 'dart:async';
import 'bloc_base.dart';
abstract class CounterEventBase {
final int amount;
CounterEventBase({this.amount = 1});
}
class CounterIncrementEvent extends CounterEventBase {
CounterIncrementEvent({amount = 1}) : super(amount: amount);
}
class CounterDecrementEvent extends CounterEventBase {
CounterDecrementEvent({amount = 1}) : super(amount: amount);
}
class CounterState {
final int total;
CounterState(this.total);
}
class CounterBloc extends BlocBase {
CounterState _state = CounterState(0);
// Input Streams/Sinks
final _eventInController = StreamController<CounterEventBase>();
Sink<CounterEventBase> get events => _eventInController;
Stream<CounterEventBase> get _eventStream => _eventInController.stream;
// Output Streams/Sinks
final _stateOutController = StreamController<CounterState>.broadcast();
Sink<CounterState> get _states => _stateOutController;
Stream<CounterState> get stateStream => _stateOutController.stream;
// Subscriptions
final List<StreamSubscription> _subscriptions = [];
CounterBloc() {
_subscriptions.add(_eventStream.listen(_handleEvent));
}
_handleEvent(CounterEventBase event) async {
if (event is CounterIncrementEvent) {
_state = (CounterState(_state.total + event.amount));
} else if (event is CounterDecrementEvent) {
_state = (CounterState(_state.total - event.amount));
}
_states.add(_state);
}
#override
void dispose() {
_eventInController.close();
_stateOutController.close();
_subscriptions.forEach((StreamSubscription sub) => sub.cancel());
}
}