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);}
Related
Solved!
I am getting date from FireBase via a futurebuilder.
It returns a Row whose children is a list of a widget i created called SmallDogCard (since my app is about dogs).
On a screen i want users to be able to press on the SmallDogCard to select it and change the border color, howerver. This causes the futurebuilder to load the data again... How should i approach this?
My code:
Futurebuilder:
FutureBuilder(
future: DogOwnerModel().getUserData(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('error');
}
if (snapshot.connectionState ==
ConnectionState.done) {
List<SmallDogCard> dogCardList = [];
final userData =
snapshot.data as Map<String, dynamic>;
List<Map<String, dynamic>>? userDogs =
userData['user dog data'];
if (userDogs == null) {
return Text('no dogs yet');
} else {
for (Map<String, dynamic> dog
in userDogs) {
dogCardList.add(SmallDogCard(
dogName: dog['name'],
imageUrl: dog['profile image'],
isSelected: selectedDogs
.contains(dog['name'])
? true
: false,
selectCallback: selectDogCallback));
}
return Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: dogCardList);
}
}
return Text('Loading');
},
),
Onpress function callback passed to the SmallDogCard (which uses setstate and makes the futurbuilder get the data again).
selectDogCallback(String name, bool isSelected) {
if (isSelected == false) {
selectedDogs.add(name);
} else {
selectedDogs.remove(name);
}
setState(() {
selectedDogs;
});
}
How can i accomplish this without the futurebuilder being called again?
So i can setstate which changes the border color of the SmallDogCard which is returned from the FutureBuilder.
Thank you in advance!
Problem solved!
Initializing the future in initstate, so after initstate it wont be called again!
Like this:
#override
void initState() {
// TODO: implement initState
super.initState();
_future = setFuture();
}
setFuture() async {
return await DogOwnerModel().getUserData(email: email);
}
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
This is the future function I want to call:
Future<List<Marker>> getMarkers() async {
List<Marker> markerTemp = [];
List<String> friendsList = [];
QuerySnapshot snapshot = FireStoreUtils.getFriendsList(current.userID);
for (var doc in snapshot.docs) {
friendsList.add(doc.reference.id);
}
for (var friend in friendsList) {
DocumentSnapshot document = await locationRef.doc(friend).get();
MarkerTemp.add(Marker(...))
}
return markerTemp;
}
Now I want it to be called in FutureBuilder widget to save the results in a variable called markerList that is useful for my view. How can I do?
return FutureBuilder<List<Marker>>(
future: getMarkers(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
// async call has not finished
return const Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
// getMarkers() throws an exception
return Center(child: Text(snapshot.error.toString()));
}
if (!snapshot.hasData) {
// getMarkers() returns null
return const Center(child: Text("getMarkers() returns null!"));
}
markerList = snapshot.data as List<Marker>; // cast to List<Marker>
return SomeWidget(); // use markerList in this Widget
},
);
Your future builder, when the future finishes in your case, returns a list of markers. Now to use that list, you don't have to store it again, it's already returned and stored in your snapshot in your future builder. You can validate this by printing the length of it:
if(snapshot.hasData) print(snapshot.data.length.toString());
I have an asynchronous function that obtains information from my bd in firebase, when debugging this code fragment I can see that the data is obtained without any problem, this data will be used to display in widgets and I pass it through a future builder, the problem is that although when debugging I realize that the data are there, Future builder does not detect them and snapshot has null value, it is until after several iterations when snapshot finally has data and allows me to use them, I do not understand what is wrong in the construction of my Future Builder.
Here is the code of my function where I get the data and the construction of the Future Buider.
Function where data are obtained.
Future<List<Guide>> getGuidesList() async {
var guidesProvider = Provider.of<GuidesProvider>(context, listen: false);
Checkuser data = await ManagerDB().checkuser(auth.email);
List<Guide> aux = new List();
Guide guide;
List guides = await guidesProvider.setGuidesFromUser(data);
if (guides != null) {
for (var i = 0; i < guides.length; i++) {
await guides[i].get().then((DocumentSnapshot guides) {
guide = Guide.fromMap(guides.data(), guides.reference.path);
aux.add(guide);
});
}
if (this.mounted) {
setState(() {});
}
print('Guias cargadas correctamente');
return aux;
} else {
print('Lista vacia');
return aux;
}
}
Fragmento de Funcion donde creo mi FutureBuider.
return Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
appBar: appBar,
drawer: DrawerNavigationMenu(
getContext: widget.getcontext,
),
body: FutureBuilder<List<Guide>>(
future: getGuidesList(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return ListCourses(
getContext: widget.getcontext,
items: snapshot.data,
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
if (this.mounted) {
setState(() {});
}
Delete this part. You are unnecessarily rebuilding your scaffold and re-calling FutureBuilder. Let FutureBuilder take care of processing the future and rebuilding the scaffold for you.
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()));