How to wait multiple bloc events in same bloc - flutter

I have one bloc with multiple events. Here I load categories and locations and wait using BlocListener. But my condition for show circular progress indicator work incorrectly and after load categories and locations also shows. How I can use bloc correctly in this case?
Code
apiDataBloc.add(LoadCategoriesEvent());
apiDataBloc.add(LoadLocationsEvent());
------------------------
return BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, state) {
if (state is CategoriesLoaded) {
categories = state.categories;
print("Categories loaded");
print(categories.length);
}
},
child: BlocListener<ApiDataBloc, ApiDataState>(
listener: (context, s) {
if (s is LocationsLoaded) {
locations = s.locations;
print("Locations loaded");
print(locations.length);
}
},
child: locations != null &&
categories != null &&
categories.length > 0 &&
locations.length > 0
? Container(child: Center(child: Text('Categories and locations loaded!')))
: Container(child: Center(child: CircularProgressIndicator())),
),
);
I tried also like this but doesn't work.
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is LocationsLoaded) {
print("Locations loaded");
locations = state.locations;
print(locations.length);
return BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, s) {
if (s is CategoriesLoaded) {
print("Categories loaded");
categories = s.categories;
print(categories.length);
return Container(
child: Center(
child: Text('Categories and locations loaded!')));
}
return Container(
child: Center(child: CircularProgressIndicator()));
},
);
}
return Container(child: Center(child: CircularProgressIndicator()));
},
),
);

You should create one state DataLoaded with 2 fields categories and locations
Something like that:
class DataLoaded extends ApiDataState {
const DataLoaded(
this.categories,
this.locations,
);
final List<Type> categories;
final List<Type> locations;
#override
String toString() => 'DataLoaded';
}
Then you need to fetch data from API in the ApiDataBloc class:
class ApiDataBloc extends Bloc<YourEventType, ApiDataState> {
ApiDataBloc() : super(YourInitialState());
#override
Stream<ApiDataState> mapEventToState(YourEventType event) async* {
if (event is YourFetchApiEvent) {
yield YourLoadingState();
final categories = await _fetchCategories();
final locations = await _fetchLocations();
yield DataLoaded(categories,locations);
}
}
}
and the final step is BlocBuilder in your widget:
return BlocProvider<ApiDataBloc>(
create: (context) => apiDataBloc,
child: BlocBuilder<ApiDataBloc, ApiDataState>(
builder: (context, state) {
if (state is YouLoadingState) {
return Center(child: CircularProgressIndicator());
}
if (state is DataLoaded) {
print(state.locations);
print(state.categories);
return Center(
child: Text('Categories and locations loaded!'),
);
}
},
),
);

I would place the logic into the bloc. If I understand correctly, you get an event triggered as soon as the data is loaded. Then you could create 2 variables in the bloc bool categoriesLoaded, locationsLoaded which you set true upon the event. In mapEventToState you could forward from each of those event mappers to a common event mapper that checks if both variables are true and sends the proper state then. An inProgress state could display which of the data streams has already been loaded.

I know what you meant.
Example Case:
#some_bloc.dart (not in event or state file)
on<someEventNo1>((......) =>
emit(LoadingState());
emit(EmitResultAPI());
on<someEventNo2>((......) =>
emit(LoadingState());
emit(someState());
#main.dart
someMethod() {
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
BlocProvider.of<SomeBloc>(context).add(someEventNo2());
}
If you do your code like that, bloc builder will not catch state change when someEventNo1 emits EmitResultAPI, because you are sending 2 consecutive BlocProvider.of<>().
Solution:
BlocProvider.of<SomeBloc>(context).add(someEventNo1());
Future.delayed(Duration(miliseconds: 100)).then((valueFuture) => BlocProvider.of<SomeBloc>(context).add(someEventNo2()));

Related

How to access a variable present in state in cubit in flutter?

I am using cubit for state management in my app. I have a variable in the state called prices which I want to access:
Future<void> fetchMonthlyTotals(String userId) async {
//var userId = await getUserId();
var prices =
await myDB().getPrices(userId);
print(prices .toString());
// ignore: unnecessary_null_comparison
if (prices != null && prices .isNotEmpty) {
emit(MonthlyTotalsState(monthlyTotals: prices ));
} else {
debugPrint("prices is empty");
}
}
This is the class where I want to access prices variable:
void getExpenses() async {
var prices = //get price from cubit
print(prices);
}
Both the codes are in different classes and are present in the same package
How do I access the price variable?
Kindly comment if more information is needed.
You can use like this aswell
BlocBuilder<Cubit, CubitState>(
buildWhen: (previous, current) => current is MonthlyTotalsState
builder: (context, state) {
if(state is MonthlyTotalsState){
return Center(
child: Text('Monthly TotalPrices:${state.monthlyTotals}'),
);
}
return const SizedBox();
},
),
If you need to use this value inside the UI you should use a BlocBuilder.
It will update your UI whenever the state changes.
If you however want to do something like showing a dialog or navigating in response to a state change you should use a BlocListener. If you want to do a combination of both of the mentioned use-cases you can use a BlocConsumer
BlocBuilder<ReportinCubit, MonthlyTotalsState>(
builder: (_, monthlyTotalState) {
return Center(
child: Text('Monthly Total Prices: ${monthlyTotalState.prices}'),
);
},
),
If you need to use this value inside the UI you should use a CubitBuilder.
It will update your UI whenever the state changes.
CubitBuilder<ReportinCubit, MonthlyTotalsState>(
builder: (_, monthlyTotalState) {
return Center(
child: Text('Monthly Total Prices: ${monthlyTotalState.prices}'),
);
},
),

Futurebuilder is not updating data from firestore

So i am having issue with futurebuilder i want my app to update when a bool is set true but it wasn't working at all so i added a line to to see if the value of bool is changing or not and released it's not changing.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:databse_web_test/database_services/getsocials.dart';
import 'package:flutter/material.dart';
import 'database_services/request.dart';
class RequestWidget extends StatefulWidget {
RequestWidget({Key? key}) : super(key: key);
#override
State<RequestWidget> createState() => _RequestWidgetState();
}
class _RequestWidgetState extends State<RequestWidget> {
String Doc = "EobkN9fONF4IxmpErB1n";
CollectionReference request = FirebaseFirestore.instance
.collection('socails')
.doc("daaJgE8Pz5UQIlNh47UsmwWcqNi1")
.collection("requests");
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: request.doc("EobkN9fONF4IxmpErB1n").get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
}
if (snapshot.hasData && !snapshot.data!.exists) {
return const Text("Document does not exist");
}
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data =
snapshot.data!.data() as Map<String, dynamic>;
bool isRequested = data["isRequested"];
bool isApproved = data["isApproved"];
if (data["isRequested"] == true && data['isApproved'] == true) {
return GetSocialData();
}
// if (data['isApproved'] == false && data['isRequested'] == true) {
// return Column(
// children: [
// data['isApproved'] == false
// ? const CircularProgressIndicator()
// : GetSocialData()
// ],
// );
// }
if (data['isApproved'] == false && data["isRequested"] == false) {
return Center(
child: ElevatedButton(
onPressed: () {
SendRequest().updateUserData(
isApproved: false, isRequested: true);
setState(() {});
},
child: const Text("data send")));
} else {
return Column(children: [
CircularProgressIndicator(),
Text(snapshot.data!.data().toString())
]);
}
} else {
return const Text("Loading database");
}
});
// if (isRequested == true && isApproved == false) {
// return Center(
// child: ElevatedButton(
// onPressed: () {
// SendRequest()
// .updateUserData(isApproved: false, isRequested: true);
// },
// child: const Text("data send")));
// } else {
// return GetSocialData();
// }
}
}
i really don't know whats wrong since im new to flutter i dont know that much. if i were to use text widget to know if the value is changing i get to know that value isn't changing. this web app is connect to another android app and value of that bool is gonna be updated by that app
A flutter builder it is a one time read, because, if you want to use a realtime read, use a streambuilder, check that in documentation : Flutter Cloud Firestore.
FutureBuilder is used for one time response, like taking an image from Camera, getting data once from native platform (like fetching device battery), getting file reference, making an http request etc.
On the other hand, StreamBuilder is used for fetching some data more than once, like listening for location update, playing a music, stopwatch, etc.
In your case you should use StreamBuilder

2 Futures one Stream Flutter

I have a problem with Streams. I'm getting null form the stream. My objective is to combine 2 futures so I'm using first future when user has not searched any parameters and other one when user searched because I have 2 endpoints one for searched list and one that has no filters. I'm not really sure if that's standard industry practice for app dev. If its not please tell me so and how should I set up back end
Widget Body(BuildContext context, TabController taby) {
Future<List<littleOffers>> searchdata = null;
Stream<List<littleOffers>> _dataSwitch() async* {
if (searchdata == null)
yield* Stream.fromFuture(fetchOffers());
else
yield* Stream.fromFuture(searchdata);
}
void resetSearchData() {
searchdata = null;
}
void setSearchData(Future<List<littleOffers>> data) {
searchdata = data;
}
return Column(
children: <Widget>[
Search_Sort(context),
Expanded(
child: StreamBuilder(
stream: _dataSwitch(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.none || !snapshot.hasData) {
print('project snapshot data is: ${snapshot.data}');
print('project snapshot state is: ${snapshot.connectionState.toString()}');
return Container();
} else {
return TabBarView(controller: taby, children: [
getList(snapshot.data),
getList(snapshot.data),
getList(snapshot.data),
getList(snapshot.data),
]);
}
}),
),
],
);
}
That could work but I think you missed async and await on setSearchData
void setSearchData(Future<List<littleOffers>> newData) {
setState(()async{
searchdata = await newData;
});
}
and to remove data
void clearSearchData() {
setState((){
searchdata = null;
});
}
You can define only one List<littleOffers> (not future) ;
Widget Body(BuildContext context, TabController taby) {
List<littleOffers> searchdata = null;
Future<void> setSearchData(Future<List<littleOffers>> newData) async {
var _data = await newData;
setState((){
searchdata = _data;
});
}
return Column(
children: <Widget>[
Search_Sort(context),
Expanded(
child: searchData == null ? Center(child: CircularProcessIndicator()) : yourWidget;
),
],
);
}
Creating Stream from 2 futures so that one is alway actice and other one is on stand by when switch is needed.
Stream<List<littleOffers>> _dataOfferSwitch() async* {
if (searchOfferData == null)
yield* Stream.fromFuture(fetchOffers());
else
yield* Stream.fromFuture(searchOfferData);}

Infinite List with bloc (List length not changing in the presentation layer) in Flutter

I am trying to load data from the api and i am using a bloc pattern for state management but when i call
load more when scrolling the the presentation layer only change when the hasReachedMax parameter is true
if it was false the ui still with the loading indicator
here is the presentation layer
Flexible(
child: Container(
child: BlocConsumer<AllProjectsBloc, AllProjectsState>(
builder: (context, state) {
if (state is AllProjectsLoadingState) {
return Center(
child: CircularProgressIndicator(),
);
} **else if (state is AllProjectsLoadedState) {
return ListView.builder(
controller: _scrollController,
itemCount: state.hasReachedMax ? state.allProjectsData.length :
state.allProjectsData.length +1 ,
itemBuilder: (context, int i) {
return i >= 5 ? BottomLoader() : UnitCard(
price: state.allProjectsData[i].price,
date: state.allProjectsData[i].title.en,
image: state.allProjectsData[i].image,
bathroom: state.allProjectsData[i].bathroom,
bedroom: state.allProjectsData[i].bedroom,
area: state.allProjectsData[i].area,**
function:(){
Navigator.push(context, MaterialPageRoute(builder:
(context)=>DetailedProperty()));
});
},
);
} else if (state is FilteredProjectsLoadedState) {
return Expanded(
child: ListView.builder(
itemCount: state.filteredProjectsData.length,
itemBuilder: (context, i) {
return UnitCard(
price: 50,
date: '20/5/2020',
bedroom: 3,
bathroom: 2,
area: 120,
image:
'https://www.propertyturkey.com/uploads/realestate/larg/buyukcekmece_villa_1_8.jpg',
function:(){
Navigator.push(context, MaterialPageRoute(builder:
(context)=>DetailedProperty()));
} ,
);
},
));
} else if (state is AllProjectsError) {
return ErrorView(
errorMessage: state.error.errorMessage,
retryAction: () {
BlocProvider.of<AllProjectsBloc>(context)
.add(state.failedEvent);
});
}
return Container();
},
}
the bloc class
class AllProjectsBloc extends Bloc<AllProjectsEvents, AllProjectsState> {
List<Data> propertyList = List();
AllProjectsBloc() : super(AllProjectsInitialState());
bool _hasReachedMax(AllProjectsState state) =>
state is AllProjectsLoadedState && state.hasReachedMax;
#override
Stream<AllProjectsState> mapEventToState(AllProjectsEvents event) async* {
bool isUserConnected = await NetworkUtilities.isConnected();
if (isUserConnected == false) {
yield AllProjectsError(
failedEvent: event, error: Constants.CONNECTION_TIMEOUT);
return;
}
if (event is FetchAllProjectsData && !_hasReachedMax(state)) {
yield* _handleFetchingAllProject(event);
}
if (event is FetchFilteredProjectsData) {
yield* _handleFetchingFilteredProject(event);
return;
}
}
Stream<AllProjectsState> _handleFetchingAllProject(
FetchAllProjectsData event) async* {
if (state is AllProjectsInitialState) {
yield AllProjectsLoadingState();
ResponseViewModel<List<Data>> handleProjectsFetchingResponse =
await Repository.getAllPropertiesData(1);
propertyList = handleProjectsFetchingResponse.responseData;
print(propertyList.length);
if (handleProjectsFetchingResponse.isSuccess) {
yield AllProjectsLoadedState(
allProjectsData: propertyList,
hasReachedMax: false,
);
}
}
if (state is AllProjectsLoadedState) {
ResponseViewModel<List<Data>> handleProjectsFetchingMoreResponse =
await Repository.getAllPropertiesData(2);
List<Data> tempList = handleProjectsFetchingMoreResponse.responseData;
propertyList.addAll(tempList);
if (handleProjectsFetchingMoreResponse.isSuccess) {
yield AllProjectsLoadedState(
allProjectsData: propertyList, hasReachedMax: true);
print(propertyList.length);
}
}
return;
}
state class
class AllProjectsLoadedState extends AllProjectsState {
final List<Data> allProjectsData;
final bool hasReachedMax ;
AllProjectsLoadedState({this.allProjectsData , this.hasReachedMax}) :
super([allProjectsData , hasReachedMax]);
AllProjectsLoadedState copyWith ({List<Data> allProjectsData , bool hasReachedMax }){
return AllProjectsLoadedState(
allProjectsData: allProjectsData ?? this.allProjectsData ,
hasReachedMax:hasReachedMax ?? this.hasReachedMax
);
}
}
the problem i was yielding the same state and i am using equatable so the bloc consumer cannot recognize the changes in the state .. the solution is yield the loading state before yielding the LoadedState .

Flutter application freezes after notifyListeners

I'm trying to write a simple Flutter app using the Google Maps plugin. I need to use multiple BLoC/ChangeNotifier objects in order to manage the object shown on-screen.
The issue comes out when I call notifyListeners() on a ChangeNotifier. The method which calls notifyListeners() completes its execution, and then the app freezes completely (no widget update, unable to interact with existing widgets).
I've tried to understand where's the problem: the only thing I understood is that it works fine while CompaniesData (which is the ChangeNotifier that causes the problem) is empty.
class CompaniesData extends ChangeNotifier {
Map<MarkerId, Company> _companiesMap;
set companies(Set<Company> companies) {
_companiesMap = companies != null
? Map.fromIterable(
companies,
key: (company) => MarkerId(company.id.toString()),
value: (company) => company,
)
: null;
notifyListeners();
;
}
bool get available => _companiesMap != null;
Company companyWithId(MarkerId id) => available ? _companiesMap[id] : null;
Map<MarkerId, Company> get companiesIfAvailable =>
available ? _companiesMap : Map();
Iterable<Company> companiesFromIds(BuildContext context, Set<int> ids) {
Set<int> idsCopy = Set.from(ids);
return companiesIfAvailable.entries
.where((entry) => idsCopy.remove(entry.value.id))
.map<Company>((entry) => entry.value);
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Consumer<CompaniesData>(
builder: (context, data, child) {
return BlocBuilder(
bloc: BlocProvider.of<ShownCompaniesBloc>(context),
builder: (context, shownCompaniesState) {
return BlocBuilder(
bloc: BlocProvider.of<FavoriteCompaniesBloc>(context),
builder: (context, favoriteCompaniesState) {
return BlocBuilder(
bloc: BlocProvider.of<MapPropertiesBloc>(context),
builder: (context, mapPropertiesState) {
CompaniesData data =
Provider.of<CompaniesData>(context, listen: false);
// ...
As you can see, the build method contains multiple nested BLoC/Consumer objects.
#override
void initState() {
_fetchCompanies();
super.initState();
}
void _fetchCompanies() {
findUser().then((location) {
Set<Company> companies = Set.from([Company.fake()]);
// CompaniesData.companies is a setter, which calls
// notifyListeners
_companiesData.companies = companies;
});
}
I don't get error messages, exception, my app simply dies after the end of the execution of the callback given to findUser().then().
EDIT:
I changed the code a little bit, and I figured out that the problem isn't notifyListeners (or at least it isn't now).
final Completer<Map<MarkerId, Company>> _companiesData = Completer();
_AeroMainViewState() {
findUser()
.then(_fetchCompanies)
.then((companies) => _companiesData.complete(Map.fromIterable(
companies,
key: (company) => MarkerId(company.id.toString()),
value: (company) => company,
)));
}
Future<Set<Company>> _fetchCompanies(LatLng location) async =>
Set.from([Company.fake()]);
// ...
child: FutureBuilder<Map<MarkerId, Company>>(
future: _companiesData.future,
builder: (context, snapshot) {
// this builder function isn't called at all
// when the Completer _companiesData is completed
if (snapshot.connectionState == ConnectionState.done) {
return Provider<Map<MarkerId, Company>>.value(
value: snapshot.data,
child: // ...
} else {
return Center(child: CircularProgressIndicator());
}
}),
// ...
Removing the ChangeNotifier doesn't fix the issue.
I post my error for future reference. I was doing this in a class:
static Stream<Obj1> stream() async* {
while (true) {
yield Obj1();
}
}
_subscription = Obj1.stream().listen((event) {
// do something...
}
Since the Stream contains potentially an infinite number of objects, the subscription to that stream was blocking the main (and only) thread.