Futurebuilder is not updating data from firestore - flutter

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

Related

Flutter Firestore boolean value check

Well, I want to check if the profile is complete after creating the account so I added a bool to the firestore. When the user fills in all the data and clicks "complete" at the end, then bool "complete" will be true and I did it, but now I want to check before the user starts filling in the data if bool is true or false. If this is true, the user will be redirected to the dashboard, if it is false, he will have to complete all the data after logging in. User login details are stored in firebase and the rest of the information is stored in firestore.
If any more information is needed, I will try to specify it
I would like to check if the value is true or false before redirecting to "CreateProfile1 ();", if it's possible
class MainPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: ((context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasError) {
return Center(child: Text('Something went wrong!'));
} else if (snapshot.hasData) {
return CreateProfile1();
} else {
return AuthPage();
}
}),
));
}
}
I was trying to save bool value into variable, but i've got this error
external static Never _throw(Object error, StackTrace stackTrace);
Here is this var, final actually
final complete = FirebaseFirestore.instance
.collection('usersdData')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
if ((value.data() as dynamic)['complete'] == true) {
return true;
} else {
return false;
}
});

How to prevent making more URL request after an image is already downloaded via Internet?

var alreadyDdl = false;
getLogoUrl(context) async {
if(!alreadyDdl) {
final db = Localstore.instance;
final data = db.collection('inputs').doc("1").get();
var database = (await data)["content"].toString();
var form = new DGForm("project/getwebsitelogo", {"database": database});
var ret = await form.urlGET(context);
ResponseObject responseObject =
ResponseObject.fromJson(json.decode(ret.body));
print("hola");
var hola = (responseObject.datas[0][0].toString());
bandeauDuClient = hola;
print(hola);
return hola;
}
}
getLogoUrl(context).then((val) {
setState(() =>
logoUrl = val
);
alreadyDdl = true;
});
Will never display me the server downloaded image in the widget build
(logoUrl != null) ? Image.network(logoUrl): Image.asset('assets/none.png')
And so, when I removed all alreadyDdl variables from my code, It will make an http request every 15 miliseconds. I want to stop the http request once the image is really downloaded...
You can use a future builder to create widgets based on the latest snapshot of interaction with a Future. You can use it in combination with cached_network_image package as suggested above.
Here's a sample code that demonstrates so:
import "package:flutter/material.dart";
//Your other imports...
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyApp();
}
}
class _MyApp extends State<MyApp> {
var db;
#override
initState() {
db = Localstore.instance;
}
getLogoUrl(context) async {
final data = db.collection('inputs').doc("1").get();
var database = (await data)["content"].toString();
var form = new DGForm("project/getwebsitelogo", {"database": database});
var ret = await form.urlGET(context);
ResponseObject responseObject =
ResponseObject.fromJson(json.decode(ret.body));
print("hola");
var hola = (responseObject.datas[0][0].toString());
bandeauDuClient = hola;
print(hola);
return hola;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: getLogoUrl(context),
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// if we got our data
if (snapshot.hasData) {
return CachedNetworkImage(
imageUrl: snapshot.data,
progressIndicatorBuilder: (context, url, downloadProgress) =>
CircularProgressIndicator(value: downloadProgress.progress),
errorWidget: (context, url, error) => Icon(Icons.error),
);
} else {
// If we probably got an error check snapshot.hasError
return Center(
child: Text(
'${snapshot.error} occurred',
style: TextStyle(fontSize: 18),
),
);
}
} else {
return const CircularProgressIndicator();
}
},
),
);
}
}
Note: Never make networking calls in build method because build method is usually called 60 times per second to render. Make network calls in initState or in widgets like FutureBuilder which handle these things for you.

Realtime Update of UI with StreamBuilder and bool -ERROR Expected a value of type 'Map<dynamic, dynamic>', but got one of type '_JsonDocumentSnapshot'

In the title I explained what I want to do. I have a bool value named
'turnInvitingPlayer' stored somewhere in a document field in Firestore. The location of the document I know exactly from the instance Variables of GameTable.
This is what i tried:
class GameTable extends StatefulWidget {
GameTable({Key? key,
required this.player,
required this.invitationID,
required this.invitationIdPlayerInvited,
required this.invitationIdPlayerInviting})
: super(key: key);
final Player? player;
final String invitationIdPlayerInvited;
final String invitationIdPlayerInviting;
/// the invitation ID is the doc name of the gambling Table
final String invitationID;
#override
State<GameTable> createState() => _GameTableState();
}
class _GameTableState extends State<GameTable> {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('GameTables')
.doc(widget.invitationID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var dataGameTable = snapshot.data! as Map;
var turnInvitingPlayer =
dataGameTable['turnInvitingPlayer'] as bool;
if (turnInvitingPlayer == true) {
return Container(color: Colors.blue);
} else {
return Container(color: Colors.red);
}
} else if (!snapshot.hasData) {
return Container(
child: Text('There is no data'),
);
}
return CircularProgressIndicator();
});
}
}
I am getting the following error when I run the App
Expected a value of type 'Map<dynamic, dynamic>', but got one of type '_JsonDocumentSnapshot'
Can somebody show me a way how I can simple access the bool value of the stream and use it in if Clauses?
Thank's to everybody who will help.
Modify your stream builder as follows:
return StreamBuilder<Map<dynamic,dynamic>>(
stream: FirebaseFirestore.instance
.collection('GameTables')
.doc(widget.invitationID)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var dataGameTable = snapshot.data! as Map;
var turnInvitingPlayer =
dataGameTable['turnInvitingPlayer'] as bool;
if (turnInvitingPlayer == true) {
return Container(color: Colors.blue);
} else {
return Container(color: Colors.red);
}
} else if (!snapshot.hasData) {
return Container(
child: Text('There is no data'),
);
}
return CircularProgressIndicator();
});
I found the solution i have done following changes:
var dataGameTable = snapshot.data!; // remove as Map
var turnInvitingPlayer = dataGameTable['turnInvitingPlayer'] as bool; // remove this line
Now I have access to the boolean value with simple dataGameTable['turnInvitingPlayer'].

How to wait multiple bloc events in same bloc

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

Provider rebuilds the widget, but nothing shows up until a "Hot restart"

I am building a flutter app and I get some data from a future, I also got the same data with a changenotifier. Well the logic is that while some object doesn't have data because its waiting on the future then display a spinning circle. I have already done this in the app and I have a widget called Loading() when the object has not received data. The problem I have run into is that I get the data, but it doesn't display anything.
the data displays correctly until I perform a hot refresh of the app. a capital R instead of a lowercase r. The difference is that it starts the app and deletes all aggregated data.
when this happens it seems that the data fills the object but I hypothesize that it is becoming not null meaning [] which is empty but not null and is displaying the data "too quickly" this in turn displays nothing for this widget until I restart "r" which shows me the above screenshot.
here is the offending code.
import 'package:disc_t/Screens/LoggedIn/Classes/classTile.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpage.dart';
import 'package:disc_t/Screens/LoggedIn/Classes/classpageroute.dart';
import 'package:disc_t/Services/database.dart';
import 'package:disc_t/models/user.dart';
import 'package:disc_t/shared/loading.dart';
import 'package:flutter/material.dart';
import 'package:morpheus/page_routes/morpheus_page_route.dart';
import 'package:provider/provider.dart';
class ClassList extends StatefulWidget {
#override
_ClassListState createState() => _ClassListState();
}
class _ClassListState extends State<ClassList> {
#override
void initState() {
ClassDataNotifier classdatanotif =
Provider.of<ClassDataNotifier>(context, listen: false);
// final user = Provider.of<User>(context);
// getTheClasses(classdatanotif);
// List<ClassData> d = classes;
}
#override
Widget build(BuildContext context) {
ClassDataNotifier classdatanotif = Provider.of<ClassDataNotifier>(context);
List<ClassData> cData = Provider.of<List<ClassData>>(context);
bool rebd = false;
Widget checker(bool r) {
if (cData == null) {
return Loading();
} else {
if (rebd == false) {
setState(() {
rebd = true;
});
rebd = true;
return checker(rebd);
// return Text("Still Loading");
} else {
return PageView.builder(
scrollDirection: Axis.horizontal,
itemCount: cData.length,
// controller: PageController(viewportFraction: 0.8),
itemBuilder: (context, index) {
return Hero(
tag: cData[index],
child: GestureDetector(
onTap: () {
// Navigator.of(context).push(ClassPageRoute(cData[index]));
Navigator.push(
context,
MorpheusPageRoute(
builder: (context) =>
ClassPage(data: cData[index]),
transitionToChild: true));
},
child: ClassTile(
classname: cData[index].classname,
description: cData[index].classdescription,
classcode: cData[index].documentID,
),
),
);
});
}
}
}
return checker(rebd);
}
}
here is how the provider is implemented
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
// final DatabaseService ds = DatabaseService();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(
value: AuthService().user,
// child: MaterialApp(
// home: Wrapper(),
// ),
),
ChangeNotifierProvider<ClassDataNotifier>(
create: (context) => ClassDataNotifier(),
),
FutureProvider(
create: (context) => DatabaseService().fetchClassdata,
)
],
child: MaterialApp(home: Wrapper()),
);
}
}
and here is the function that is ran to get the data
Future<List<ClassData>> get fetchClassdata async {
QuerySnapshot snapshot = await classesCollection.getDocuments();
List<ClassData> _classList = List<ClassData>();
snapshot.documents.forEach((element) async {
QuerySnapshot pre = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Pre")
.getDocuments();
List<Preq> _preList = List<Preq>();
pre.documents.forEach((preClass) {
Preq preqData = Preq.fromMap(preClass.data);
if (preClass.data != null) {
_preList.add(preqData);
}
});
ClassData data =
ClassData.fromMap(element.data, element.documentID, _preList);
if (data != null) {
_classList.add(data);
}
});
return _classList;
}
I think the logic of your provider is fine, the problem lies in the line
snapshot.documents.forEach((element) async {
...
}
The forEach is not a Future (what is inside it's a future because the async, but the method itself not) so the code runs the first time, it reaches the forEach which does its own future on each value and propagate to the next line of code, the return, but the list is empty because the forEach isn't done yet.
There is a special Future.forEach for this case so you can wait for the value method before running the next line
Future<List<ClassData>> get fetchClassdata async {
QuerySnapshot snapshot = await classesCollection.getDocuments();
List<ClassData> _classList = List<ClassData>();
await Future.forEach(snapshot.documents, (element) async {
QuerySnapshot pre = await Firestore.instance
.collection("Classes")
.document(element.documentID)
.collection("Pre")
.getDocuments();
List<Preq> _preList = List<Preq>();
pre.documents.forEach((preClass) {
Preq preqData = Preq.fromMap(preClass.data);
if (preClass.data != null) {
_preList.add(preqData);
}
});
ClassData data =
ClassData.fromMap(element.data, element.documentID, _preList);
if (data != null) {
_classList.add(data);
}
});
return _classList;
}
Here is a similar problem with provider with a forEach. Maybe it can help you understand a bit better