How to clear data from Streams of blocpattern? - flutter

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

Related

How to handle stream list in flutter MVVM architecture?

I have a list of values coming from my server and I want to display each of these values in a checkbox. I want a stream for each checkbox but I do not know the number of values which will come from the api. It can be any number. This is my code for view model class:
class SettingsViewModel extends BaseViewModel
with SettingsViewModelInputs, ViewModelOutputs {
final StreamController _deliverySettingCheckboxValue =
StreamController<List<String>>.broadcast();
#override
void start() {
settings();
}
#override
void dispose() {
_deliverySettingCheckboxValue.dispose();
}
#override
List<Stream<void>> get outputCheckboxValue =>
_deliverySettingCheckboxValue.stream.map((checkBoxValue) => checkBoxValue);
}
abstract class SettingsViewModelInputs {
}
abstract class SettingsViewModelOutputs {
List<Stream<void>> get outputCheckboxValue;
}
In the settings() method I make my api call and get the number of checkbox I wish to have in my screen. How do I handle the list of streams in this scenario?

how can I get the other controller's variable inside one controller in flutter using getx

This is an issue related to the getx in flutter.
I have 2 controllers. ContractsController and NotificationController.
In ContractsController I have put the value into observer variable by calling the Api request.
What I want now is to get that variable's data in another controller - NotificationController.
How to get that value using getx functions?
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the api
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
The expiringContracts is updated successfully with data after the api request.
Now, I want to get that value in NotificationController
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
contractsController = Get.find<ContractsController>();
print(contractsController.expiringContracts); // This shows an empty list ?????
super.onInit();
}
}
Overview
A couple solutions come to mind:
pass the expiringContracts list as a constructor argument to NotificationsController if you only need this done once at instantiation, or
use a GetX worker to update NotificationsController every time expiringContracts is updated
The first solution isn't related to GetX, rather it's just async coordination between ContractsController and NotificationsController, so lets focus on the 2nd solution: GetX Workers.
Details
In NotificationsController, create a method that will receive expiringContracts.
Something like:
class NotificationsController extends GetxController {
void refreshContracts(List<ExpiringContract> contracts) {
// do something
}
}
Please note: none of this code is tested. I'm writing this purely in StackOverflow, so consider this pseudo-code.
In ContractsController we'll supply the above callback method as a constructor arg:
In ContractsController, something like:
class ContractsController {
final expiringContracts = <ExpiringContract>[].obs
final Function(List<ExpiringContract>) refreshContractsCallback;
ContractsController(this.refreshContractsCallback);
#override
void onInit() {
super.onInit();
refreshContracts(); // do your stuff after super.onInit
ever(expiringContracts, refreshContractsCallback);
// ↑ contracts → refreshContractsCallback(contracts)
// when expiringContracts updates, run callback with them
}
}
Here the GetX ever worker takes the observable as first argument, and a function as 2nd argument. That function must take an argument of type that matches the observed variable, i.e. List<ExpiringContract>, hence the Type of refreshContractsCallback was defined as Function(List<ExpiringContract>).
Now whenever the observable expiringContracts is updated in ContractsController, refreshContractsCallback(contracts) will be called, which supplies the list of expiring contracts to NotificationsController via refreshContracts.
Finally, when instantiating the two controllers inside the build() method of your route/page:
NotificationsController nx = Get.put(NotificationsController());
ContractsController cx = Get.put(ContractsController(nx.refreshContracts));
Timeline of Events
NotificationsController gets created as nx.
nx.onInit() runs, slow call of refreshContracts() starts
ContractsController gets created, with nx.refreshContracts callback
your page paints
nx has no contracts data at this point, so you'll prob. need a FutureBuilder or an Obx/ GetX + StatelessWidget that'll rebuild when data eventually arrives
when refreshContracts() finishes, ever worker runs, sending contracts to nx
nx.refreshContracts(contracts) is run, doing something with contracts
Notes
async/await was removed from nx.onInit
ever worker will run when refreshContract finishes
There were some powerful approaches in GetX. I solved this issue with Get.put and Get.find
Here is the code that I added.
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the API
// ******************************** //
Get.put(ContractsController()); // Added here
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
// ******************************** //
contractsController = Get.find<ContractsController>(); // Added here.
print(contractsController.expiringContracts); // This shows the updated value
super.onInit();
}
}
Finally, I have found that GetX is simple but powerful for state management in flutter.
Thanks.

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

Bloc: is it possible to yield 2 time the same state?

In the login view, if the user taps on the login button without having inserted his credentials, the LoginFailState is yield and the view reacts to it. If he taps again, this LoginFailstate is yield again, but the view doesn't react to it. So, is there a way to yield more times the same state?
There is some code to better explain my situation:
class LoginBloc extends Bloc<LoginEvent, LoginState> {
#override
LoginState get initialState => LoginUninitialized();
#override
Stream<LoginState> mapEventToState(LoginEvent event) {
if (event is loginButtonPressed) {
yield LoginFailState();
}
}
View:
#override
Widget build(BuildContext context) {
return BlocBuilder(
bloc: _loginBloc,
builder: (BuildContext context, LoginState state) {
if (state is LoginFail) {
print ('Login fail');
}
return Column(
...
)
You can receive an update for the "same" State if you don't extend Equitable, or implement your own '==' logic which makes the two LoginFailStates equal.
The solution is to yield a different State in between, like in the Bloc example.
yield LoginLoading();
It gets called on every login button tap. Felangel's LoginBloc example.
By default BLoC pattern will not emit state when the same state will be passed one after another. One way to do this is to pass your initial BLoC state after passing LoginFailState.
So after user clicks on the button with wrong credentials passed states will not be:
LoginFailState()
LoginFailState()
but
LoginFailState()
LoginEmptyState()
LoginFailState()
LoginEmptyState()
Which will make UI react to each of them.
But I think that the best and cleanest solution is to pass LoadingState from BLoC before passing LoginFailState().
You can follow the blog post that I have recently written regarding this topic.
Problem
When you try to emit a new state that compares equal to the current state of a Bloc, the new state won't be emitted.
This behavior is by design and is discussed here.
When I say "compares equal" I mean that the == operator for the two state objects returns true.
Solution
There are two proper approaches:
Your state class should NOT extend Equatable. Without Equatable, two objects of the same class with the same fields will NOT compare as equal, and this new state will always be emitted.
Sometimes you need your state class to extend Equatable. In this case, just add the result of the identityHashCode(this) function call to your props getter implementation:
class NeverEqualState extends Equatable {
#override
List<Object?> get props => [identityHashCode(this)];
}
Note that I use identityHashCode that works regardless the operator == is overloaded or not. In contrast, hashCode will not work here.
Warning:
Do not use random values in the getter implementation List<Object> get props => [Random().nextDouble()];. Random variables are random, meaning that with extremely low probability you still might get two equal values in a sequence that will break this workaround. This is extremely unlikely, so it's not possible to reproduce and debug this.
You can and should include fields in your get props implementation, but keep in mind that when all fields compare as equal the objects will also compare as equal.
Emitting some other state in-between two equal states works but it forces your BlocBuilder to rebuild part of UI and BlocListener to execute some logic. It's just inefficient.
Finally, why would you like to have a state class extend Equatable but still not compare equal? This might be needed when your state class is actually the root of a hierarchy, where some descendants need to implement the == operator properly, and some need to never compare equal. Here is the example:
class BaseMapState extends Equatable {
const BaseMapState();
#override
List<Object?> get props => [];
}
class MapState extends BaseMapState {
final Map<String, Report> reports;
final Report? selectedReport;
final LatLng? selectedPosition;
final bool isLoadingNewReports;
const MapState(
{this.reports = const {},
this.selectedReport,
this.selectedPosition,
this.isLoadingNewReports = false});
#override
List<Object?> get props => [
...reports.values,
selectedReport,
selectedPosition,
isLoadingNewReports
];
}
class ErrorMapState extends BaseMapState {
final String? error;
const ErrorMapState(this.error);
#override
List<Object?> get props => [identityHashCode(this), error];
}
class NeedsAuthMapState extends ErrorMapState {
const NeedsAuthMapState() : super('Authentication required');
}
class NoInternetMapState extends ErrorMapState {
const NoInternetMapState() : super("No Internet connection");
}
If you use Equitable and tries to emit two equal instances of the same State with different properties, make sure that you override props array. By overriding props array, Equitable will know how to compare state instances.
class TablesLoadedState extends Equatable {
final List<TableEntity> tablesList;
TablesLoadedState(this.tablesList);
#override
List<Object> get props => [tablesList];
}
So, when bloc emits two instances of the same state with different values, these state-instances will be passed to BlocListener and UI will be updated according to new data.
A late possible workaround would be adding a random double to the state get props, this way the state won't be equal and you can yield them one after the other if you want.
also, Random().nextDouble() complexity is O(1) so you don't need to worry about performance
class LoginFailState extends LoginState {
#override
List<Object> get props => [Random().nextDouble()];
}