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

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();

Related

Flutter - How to call functions (and variables) between stateful widgets?

I have this function in a widget (homescreen):
void toggleRecording() async {
// HERE IS THE CONFUSION I GUESS
_isRecording = !_isRecording;
recorder = SoundStream(isRecording: _isRecording);
//recorder.toggleRecording(_isRecording);
setState(() {
_isRecording = recorder.isRecording;
});
if (_isRecording) {
startTimer();
_stopwatch.start();
} else {
stopTimer();
_stopwatch.stop();
}
}
It needs to call (trigger) another function in my recorder class:
void toggleRecording() async {
widget.isRecording ////// currently being passed as an argument from homescreen
? {_recorder.stop, await save(_micChunks, 44100)}
: _recorder.start;
}
Also, the boolean variable _isRecording is present in both the classes. How do I sync the state?
In your situation passing reference of function through widgets will work. However best practice of this will be using provider package.
Managing functions from provider will allow you to control functions from all pages.
If you change a variable you can call notifylistener() function inside your provider function. So that you can change state of widget.
I will try to explain it in a glance however this is an important subject of flutter.
Here is my folder structure
At provider folder we define our provider classes.
Firstly i define class which extends changeNotifier class. This is what make this class provider.
Side note: notifyListener() function here calls setState of every widget if you use any variables inside that class and this is what you are searching for.
Then i import it into my main.dart file or whatever file you want. Only condition is being above the widget that you will use provider at.
At last you can use your function at everywhere if you import provider package and define your provider like i did in this code.
At last here is the visualized stucture of provider package.
I wish i explained it well. There is more about it on youtube.
Pass the function to other widget
using Function keyword
Say ContentWidget is your child and ParentWidget is parent
class ParentWidget extends StatefulWidget {
//Do Something
void onSomeFunction()
{
ContentWidget(onTimerUpdate:onTimerClosed)
}
void onTimerClosed()
{
//Perform Operation on Timer Change
}
}
class ContentWidget extends StatefulWidget {
final Function onTimerUpdate;
ContentWidget({
Key key,
#required this.onTimerUpdate,
}) : super(key: key);
void onAnyActionFromChild()
{
widget.onTimerUpdate() //Note () can have any param or can be null
}

How to clear data from Streams of blocpattern?

My this function isn't clearing the cart.
clearCart(){
_listController.close();
}
Am I supposed to call some other property or implement some other approach in clear cart function?
Here is my CartListBloc code:
class CartListBloc extends BlocBase {
CartListBloc();
var _listController = BehaviorSubject<List<FoodItem>>.seeded([]);
//provider class
CartProvider provider = CartProvider();
//output
Stream<List<FoodItem>> get listStream => _listController.stream;
//input
Sink<List<FoodItem>> get listSink => _listController.sink;
addToList(FoodItem foodItem) {
listSink.add(provider.addToList(foodItem));
}
removeFromList(FoodItem foodItem) {
listSink.add(provider.removeFromList(foodItem));
}
clearCart(){
// What should I put here to clear the bloc of Streams from cart
}
//dispose will be called automatically by closing its streams
#override
void dispose() {
_listController.close();
super.dispose();
}
}
The Stream class has a drain method, which removes all data from a Stream. However, you seem to be trying to clear a BehaviorSubject so you can't use drain (It doesn't actually clear the subject). Instead, you should probably simply add an empty List or null (in which case you need to deal with this null in your UI) to _listController, which will give you a new event with no items.
Edit:
Example:
_listController.add([]); //Now your listeners will receive
//new event with empty list of items

Where and when should I store values in BLoC itself or its state

Context
I come from Redux and I am learning the BLoC pattern for global state management. I am having troubles defining where should I store values as properties inside the BLoC class, and when I should store values inside States.
Use case
I have a home page where I show some Ads, depending on its category (category should never be null). I implemented an AdsBloc like this:
class AdsBloc extends Bloc<AdsEvent, AdsState> {
final AdsRepository repository;
AdsBloc({#required this.repository})
: super(AdsInitial(category: Category.DOGS));
#override
Stream<AdsState> mapEventToState(
AdsEvent event,
) async* {
// my code
}
}
And these are my AdsState:
abstract class AdsState {
final Category category;
AdsState({this.category});
}
class AdsInitial extends AdsState {
AdsInitial({category}) : super(category: category);
}
class AdsSuccess extends AdsState {
final PaginatedAds paginatedAds;
AdsSuccess({this.paginatedAds, category}) : super(category: category);
}
class AdsFailure extends AdsState {
AdsFailure({category}) : super(category: category);
}
Problem
Because of the way I implemented the pattern, I need to pass the category every time I change the state.
Solution?
So I was thinking if I can consider the category property to be of AdsBloc and remove it from state, this way I can get a better control of this property.
Implement "category" as a parameter for the event that is triggering ad loading process. This way, for example, you will tell BLoC to "LoadAds" with "category: Category.DOGS".
class LoadAds extends AdsEvent {
final Category category;
LoadAds({
#required this.category,
});
#override
List<Object> get props => [category];
}
This way you will be able to use single bloc instance to load different ad types when needed. Load DOGS first, 2 minutes later load CATS instead of dogs.
If you do not need this ability - then defining category inside bloc itself is perfectly fine.

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.

Flutter listen Bloc state from Other Bloc

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));