Flutter listen Bloc state from Other Bloc - flutter

Hello I'm trying to listen state of bloc form other bloc.
I'm using this package https://pub.dev/packages/bloc
From my UserBloc I want listen AuthBloc and when It has the state AuthenticationAuthenticated the UserBloc should fire an event.
final UserRepository userRepository;
final authBloc;
StreamSubscription authSub;
UserBloc({ #required this.userRepository, #required this.authBloc}) {
authSub = authBloc.listen((stateAuth) {
//here is my problem because stateAuth, even is AuthenticationAuthenticated it return always false.
if (stateAuth is AuthenticationAuthenticated) {
this.add(GetUser()) ;
}
});
}
#override
Future<void> close() async {
authSub?.cancel();
super.close();
}
For now I have this problem:
When in debug I'm trying to print stateAuth it return:
stateAuth = {AuthenticationAuthenticated} AuthenticationAuthenticated
props = {_ImmutableList} size = 0
But stateAuth is AuthenticationAuthenticated return always false.
Is there any way for listen blocState From Other Bloc class?

Actually in one of the examples of the bloc library they listen to a Bloc (TodosBloc) from another Bloc (FilteredTodosBloc).
class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
final TodosBloc todosBloc;
StreamSubscription todosSubscription;
FilteredTodosBloc({#required this.todosBloc}) {
todosSubscription = todosBloc.listen((state) {
if (state is TodosLoadSuccess) {
add(TodosUpdated((todosBloc.state as TodosLoadSuccess).todos));
}
});
}
...
You can check this example's explanation here.

To answer Sampir's question, yes, you're right, but sometimes you may want to do it in another way.
A bloc is something that manages an event for someone else. If you are working with ui events, your bloc manages them for your ui, but if you are working also with other kind of events (i.e. position events, or other streams events) you can have a bloc that manages your ui events and antoher bloc that manages the other kind of events (i.e. a bluetooth connection). So the first bloc must listen to the second one (i.e. because is waiting for establishing bluetooth connection). Think about an app that uses a lot of sensors, each one with its stream of data, and you'll have a chain of blocs that have to cooperate.
You can do it with multi-provider and multi-listener but your chain could be very long and writing your listener cases can be hard, or you may want to hide it from your ui, or you want to reuse it in another part of your app, so you may want to build your chain inside your blocs.
You can add a listener to a bloc almost everywhere. Using StreamSubscription, you can add a listener to every kind of streams, even the one in another bloc. The bloc must have a method to expose his stream, so you can listen to him.
Some code (I use flutter_bloc - flutter_bloc has multi-providers, but it's just for example):
class BlocA extends Bloc<EventA, StateA> {
final BlocB blocB;
StreamSubscription subscription;
BlocA({this.blocB}) {
if (blocB == null) return;
subscription = blocB.listen((stateB) {
//here logic based on different children of StateB
});
}
//...
}
class BlocB extends Bloc<EventB, StateB> {
//here BlocB logic and activities
}

You might not want your bloc to depend on another bloc with direct bloc-to-bloc dependency, instead, you might want to connect your bloc through the presentation layer.
You can use a BlocListener to listen to one bloc and add an event to another bloc whenever the first bloc changes.
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<WeatherCubit, WeatherState>(
listener: (context, state) {
// When the first bloc's state changes, this will be called.
//
// Now we can add an event to the second bloc without it having
// to know about the first bloc.
BlocProvider.of<SecondBloc>(context).add(SecondBlocEvent());
},
child: TextButton(
child: const Text('Hello'),
onPressed: () {
BlocProvider.of<FirstBloc>(context).add(FirstBlocEvent());
},
),
);
}
}
The code above prevents SecondBloc from needing to know about FirstBloc, encouraging loose-coupling.
Check the official documentation for more info.

A recent update in the bloc source code requires a small change to the solution.
You now have to listen to a bloc/cubit's stream attribute, please see below example.
class FilteredTodosBloc extends Bloc<FilteredTodosEvent, FilteredTodosState> {
final TodosBloc todosBloc;
StreamSubscription todosSubscription;
FilteredTodosBloc({#required this.todosBloc}) {
todosSubscription = todosBloc.stream.listen((state) {
// ^^^^^
if (state is TodosLoadSuccess) {
add(TodosUpdated((todosBloc.state as TodosLoadSuccess).todos));
}
});
}
...

For people who don't want to disturb the bloc,
we can use this library Event Bus . It is based on stream. we can fire and listen events from anywhere between the screens.
// declare this globally
EventBus eventBus = EventBus();
// create event
class UserLoggedInEvent {
User user;
UserLoggedInEvent(this.user);
}
// listen event
eventBus.on<UserLoggedInEvent>().listen((event) {
// All events are of type UserLoggedInEvent (or subtypes of it).
print(event.user);
});
// fire event
User myUser = User('Mickey');
eventBus.fire(UserLoggedInEvent(myUser));

Related

Flutter clean architecture with Bloc, RxDart and StreamBuilder, Stateless vs Stateful and dispose

I'm trying to implement a clean architecture with no dependency of the framework in the business' logic layers.
The following example is a Screen with only a Text. I make an API Rest call in the repository and add the response to a BehaviorSubject that is listened through a StreamBuilder that will update the Text. Since is an StatefulWidget I'm using the dispose method to close the BehaviorSubject's StreamController.
The example is simplified, no error/loading state handling, no dependency injection, base classes, dispose interfaces etc.
class Bloc {
final UserReposiotry _userReposiotry;
final BehaviorSubject<int> _activeUsersCount = BehaviorSubject.seeded(0);
Bloc(this._userReposiotry) {
_getActiveUsersCount();
}
void _getActiveUsersCount() async {
final response = await _userReposiotry.getActiveUsersCount();
_activeUsersCount.add(response.data);
}
ValueStream<int> get activeUsersCount => _activeUsersCount.stream;
void dispose() async {
await _activeUsersCount.drain(0);
_activeUsersCount.close();
}
}
class StatefulScreen extends StatefulWidget {
final Bloc bloc;
const StatefulScreen({Key? key, required this.bloc}) : super(key: key);
#override
State<StatefulScreen> createState() => _StatefulScreenState();
}
class _StatefulScreenState extends State<StatefulScreen> {
#override
Widget build(BuildContext context) {
final stream = widget.bloc.activeUsersCount;
return StreamBuilder<int>(
stream: stream,
initialData: stream.value,
builder: (context, snapshot) {
return Text(snapshot.data.toString());
}
);
}
#override
void dispose() {
widget.bloc.dispose();
super.dispose();
}
}
I have the following doubts regarding this approach.
StreamBuilder cancels the stream subscription automatically, but it doesn't close the StreamController. I know that you should close it if you are reading a file, but in this case, if I don't manually close it, once the StatefulScreen is no longer in the navigation stack, could it be destroyed, or it would be a memory leak?
I've seen a lot of people using StatelessWidget instead of StatefulWidget using Stream and StreamBuilder approach, if it is really needed to close the BehaviorSubject it is a problem since we don't have the dispose method, I found about the WillPopScope but it won't fire in all navigation cases and also and more important would it be more performant an approach like WillPopScope, or having an StatefulWidget wrapper (BlocProvider) inside an StatelessWidget just to do the dispose, than using an StatefulWidget directly, and if so could you point to an example of that implementation?
I'm currently choosing StatefulWidget for widgets that have animations o controllers (map, text input, pageview...) or streams that I need to close, the rest StatelessWidget, is this correct or am I missing something?
About the drain method, I'm using it because I've encountered an error navigating back while an API rest call was on progress, I found a member of the RxDart team saying it isn't really necessary to call drain so I'm confused about this too..., the error:
You cannot close the subject while items are being added from addStream
Thanks for your time.

How to listen to multiple stream subscriptions in a bloc?

I'm trying to create a bloc that depends on two other blocs. For example, I have Bloc C which depends on Bloc A and Bloc B. I'm trying to do something like the following using flutter_bloc in order to achieve it :
class BlocC
extends Bloc< BlocCEvent, BlocCState> {
final BlocA blocA;
final BlocC blocB;
StreamSubscription blocASubscription;
StreamSubscription blocBSubscription;
BlocC({
#required this.blocA,
#required this.blocB,
}) : super((blocA.state is blocALoaded &&
blocB.state is blocBLoaded)
? BlocCLoaded(
blocA: (blocA.state as blocALoaded).arrayFromBlocA,
blocB:
(blocB.state as blocBLoaded).arrayFromBlocB,
)
: BlocCLoading()) {
blocASubscription = blocA.stream.listen((state) {
if (state is blocALoaded) {
add(BlocAUpdated((blocA.state as blocALoaded).arrayFromBlocA));
}
});
blocBSubscription = blocB.stream.listen((state) {
if (state is BlocBLoaded) {
add(BlocBUpdated((blocB.state as BlocBLoaded).arrayFromBlocB));
}
});
}
...
#override
Future<void> close() {
blocASubscription.cancel();
BlocBSubscription.cancel();
return super.close();
}
}
The problem is that I'm getting the following error: Bad state: Stream has already been listened to. I found information about that error in the next post.
I understand the error is happening because a stream can only listen to one bloc at a time, and not to multiple ones. In my case, the stream is already listening to blocA when I try to listen to blocB. However, I'm not sure how to fix this problem.
I will really appreciate any help on this.
You have to merge the two streams into one and act based on the event type:
import 'package:async/async.dart' show StreamGroup;
//...
final blocAStream = blocA.stream;
final blocBStream = blocB.stream;
var blocAandBStreams = StreamGroup.merge([blocAStream, blocBStream]);
blocAandBStream.listen((event){
if(event is BlocAState){
if (event is blocALoaded) { //<-- for readability
add(BlocAUpdated((blocA.state as blocALoaded).arrayFromBlocA));
}
}else if(event is BlocBState){
if (event is BlocBLoaded) {//<-- for readability
add(BlocBUpdated((blocB.state as BlocBLoaded).arrayFromBlocB));
}
}
})
I've implemented event bus pattern to communicate between blocs. The advantage of this approach is that your block instances are not coupled to each other so you don't need to
instantiate or inject their instances Manage Global Events by bloc

flutter: inter-bloc communication, passing data events between different blocs

I haven't found much about inter-bloc communication, so I came up with an own, simple solution that might be helpful to others.
My problem was: for one screen I use 2 blocs for different information clusters, one of them also re-used on another screen. While passing data is well documented, I had issues with figuring out how to pass events or trigger states to/of the other bloc.
There are probably much better solutions, but for other flutter or bloc beginners like me it might be helpful. It is fairly simple and the logic is easy to follow.
If you inject Bloc A as dependency to Bloc B (looked simple to me and I do not need further Blocs), I can get/set values in Bloc A from Bloc B (not vice versa). If I want to get data back to Bloc A, or if I just want the Bloc A build to reload, I can trigger events in the BlocBuilder of B to pass the information.
// ========= BLOC FILE ===========
class BlocA extends BlocAEvent, BlocAState> {
int myAVar = 1;
}
class BlocB extends BlocBEvent, BlocBState> {
BlocB({#required this.blocA}) : super(BInitial());
final BlockA blockA;
// passing data back and forth is straight forward
final myBVar = blockA.myAVar + 1;
blockA.myAVar = myBVar;
#override
Stream<BState> mapEventToState(BEvent event) async* {
if (event is BInitRequested) {
// trigger state change of Bloc B and request also reload of Bloc A with passed argument
yield LgSubjectShowSingle(blocAReloadTrigger: true);
}
}
}
// ========= UI FILE ===========
class MyPage extends StatelessWidget {
MyPage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
// inject dependency of page on both Blocs: A & B
return MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) =>
BlocA().add(BlocAInit()),
),
BlocProvider<BlocB>(
create: (BuildContext context) =>
BlocB(BlocA: BlocProvider.of<BlocA>(
context),).add(BInitRequested()),
),
],
child: BlocBuilder<BlocB, BState>(
builder: (context, state) {
if (state is BShowData) {
// If a reload of Bloc A is requested (we are building for Bloc B, here) this will trigger an event for state change of Bloc A
if (state.triggerStmntReload) {
BlocProvider.of<BlocA>(context).add(AReloadRequested());
};
return Text("abc");
}
}
)
);
}
}

How to reset to the initial state/value whenever I close my app using flutter_bloc

I am still a beginner when it comes to using flutter_bloc.
I have tried flutter_bloc and curious how to reset my bloc class to its initial value when I have closed the page.
my_bloc_class.dart
class sumtotal_detail_transactionsbloc extends Bloc<String, String>{
#override
String get initialState => '0';
#override
Stream<String> mapEventToState(String sumtotal_detail_transactions) async* {
yield sumtotal_detail_transactions.toString();
}
}
My widget with a BlocBuilder.
BlocBuilder<sumtotal_detail_transactionsbloc, String>(
builder: (context,sumtotal_detail_transactions) => Text(
sumtotal_detail_transactions,style: TextStyle(
fontSize: 12,
color: Colors.brown[300]
),
)
),
Whenever I close the page or navigate to the page, how can I always/automatically reset the sumtotal_detail_transactions back to its initial value?
It will break my app if the value is always kept/store as it is.
Hey 👋 I would recommend providing the bloc in the page so when the page is closed the bloc is disposed automatically by BlocProvider. No need to have a reset event, just make sure to scope blocs only to the part of the widget tree that needs it. Hope that helps!
As mentioned by the plugin author here,
I don't think it's a good idea to introduce a reset() because it directly goes against the bloc library paradigm: the only way to trigger a state change is by dispatching an event.
With that being said, you must add an event/state the will be used to trigger an initialisation event.
For example:
Add an initialisation event.
some_page_bloc_events.dart
class InitializePageEvent extends SomePageEvent {
// You could also pass on some values here, if they come from the UI/page of your app
#override
String toString() => 'InitializePageEvent';
}
Add an initialisation state.
some_page_bloc_states.dart
class InitializePageState extends SomePageState {
#override
String toString() => 'InitializePageState';
}
Next, utilise these inside your bloc class to filter incoming events and notify the UI with the appropriate states.
some_page_bloc.dart
#override SomePageState get initialState => InitializePageState();
#override
Stream<SomePageState> mapEventToState(SomePageEvent event) async* {
try {
if(event is InitializePageEvent) {
// Do whatever you like here
yield InitializePageState();
}
} catch (e) {
...
}
}
Finally, you can invoke the initialisation event wherever you deemed in necessary. In your case, it should be on the initState() method of your screen.
some_page.dart
#override
void initState() {
super.initState();
_someTransactionBloc.dispatch(InitializePageEvent());
}
Felix provided a well-written documentation for his plugin, I suggest that you go over the intro concepts how BLoC works. Please read it here.

How to clear data from a bloc when dispose the page?

I am using flutter_bloc on the second page of my flutter app, how can I clear bloc's data when I dispose of this page (like Navigate back to my first page)?
Bloc is using stream, unless you emit new event the state won't change. If you want "clear" the Bloc after navigating to different route, you can emit an event that yield an initialState of the Bloc in #override initS
If someone else is trying to clean the bloc data (for example, after a logout) the extensions could be a good approach. For example, if you have a bloc like the next one:
class TableUIBloc extends Bloc<TableUIEvent, TableUIState> {
TableUIBloc() : super(TableUIInitial()) {
on<TableUIEvent>((event, emit) {
switch (event.runtimeType) {
case UpdateShowedColumns:
// The columns the user is trying to show
final showedColumns = (event as UpdateShowedColumns).showedColumns;
// updating the showed columns
emit(
TableUIUpdated(
showedColumns: showedColumns
),
);
break;
}
});
}
}
#immutable
abstract class TableUIEvent {}
/// Updates the list of showed columns on the table
class UpdateShowedColumns extends TableUIEvent {
final List<TableColumn> showedColumns;
UpdateShowedColumns({
required this.showedColumns,
});
}
#immutable
abstract class TableUIState {
final List<TableColumn> showedColumns;
const TableUIState({
required this.showedColumns,
});
}
class TableUIInitial extends TableUIState {
TableUIInitial()
: super(
showedColumns: [
TableColumn.stone,
TableColumn.rating,
TableColumn.team,
]
);
}
You can create an extension an add the functionality of 'clean' the bloc data, emiting the initial state of the bloc:
/// An extension to reset a bloc to its default state.
extension BlocReset on Bloc {
void reset(dynamic initialState) {
// ignore: invalid_use_of_visible_for_testing_member
emit(initialState);
}
}
So when you want to clean up the state of a determined bloc you only have to import the extension and call the 'reset' method somewhere on your code, and provide the initial state datatype:
import 'package:your_project/path/to/extension.bloc.dart';
(context.read<TableUIBloc>()).reset<TableUIInitial>();
This is useful when you have a lot of blocs and you want to clean them all without having to create a new event to restore the state to the initial value for every single one. Here is an example of how I cleaned up the data of all the blocs on the system I'm developing.
// Cleaning all the blocs
(context.read<ClientBloc>()).reset(ClientInitial());
(context.read<TeamBloc>()).reset(TeamInitial());
(context.read<StoneBloc>()).reset(StoneInitial());
(context.read<AuthBloc>()).reset(AuthInitial());
(context.read<CalendarBloc>()).reset(const CalendarInitial());
(context.read<ColorBloc>()).reset(ColorInitial());
(context.read<OAuthBloc>()).reset(OAuthInitial());
(context.read<TableUIBloc>()).reset(TableUIInitial());
(context.read<OpportunityBloc>()).reset(OpportunityInitial());
(context.read<UserBloc>()).reset(UserInitial());
If you need to keep the emits and initialState implementations in the cubits, you can declare:
abstract class CubitWithClearState<State> extends Cubit<State> {
CubitWithClearState(super.initialState);
clearState();
}
And change all the app cubits to extend CubitWithClearState instead of the Cubit.
Example:
class SomeCubit extends CubitWithClearState<SomeState> {
SomeCubit() : super(SomeStateInitial())
#override
clearState() {
emit(SomeStateInitial());
}
}
And then:
context.read<SomeCubit>().clearState();