How to use setState for just one widget - flutter

I have a Future which takes a value from my Cloud Firestore database and displays an icon in the body of my app. If the value in the database changes, the icon updates. I have been using setState to rebuild my widget tree and this works fine - the app reacts almost instantly to changes in my Cloud Firestore database. This is great! Code:
Future<void> takenSurvey2() async {
final sn = await Firestore.instance
.collection('Controller')
.document('Current Survey')
.get();
surveyName2 = sn['cs'];
final snapShot = await Firestore.instance
.collection('$surveyName2' + '_entrants')
.document(userid)
.get();
if (snapShot.exists) {
takenSurvey = true;
} else {
takenSurvey = false;
}
setState(() {});
}
and the place where the icon is shown is coded like this (within a stack):
Positioned(
right: 30,
top: 20,
child: FutureBuilder(
future: takenSurvey2(),
builder: (context, snapshot) {
if (takenSurvey == false) {
return Icon(
Foundation.burst_new,
size: 48,
color: Color(0xff303841),
);
} else if (takenSurvey == true) {
return Icon(
Foundation.check,
size: 48,
color: Color(0xff303841),
);
} else
return Container();
})),
However, I've added in my AdMob adverts using the 'admob_flutter' package, and because setState seems to be constantly running, the advert cannot load - I just see a flashing box. If I remove setState from the code above, the adverts load, but the icons in my app do not update when the value in Cloud Firestore changes.
How do I either exclude my admob_flutter widget from setState, or just use setState (or something else) to only update my FutureBuilder widget? I would like to do so in a way which limits the number of calls on Cloud Firestore, but this is not essential.
Thanks!

Related

How to populate DropdownSearch flutter with local database in flutter

I want to populate my DropdownSearch with fetching data from local database. Plese help me.
Future<dynamic> getList() async {
final db = await getDatabase();
final res = await db.rawQuery("SELECT * FROM tb_point_of_Sale");
List<dynamic> list =
res.isNotEmpty ? res.map((c) => PointOfSale.fromJson(c)).toList() : [];
}
body: Column(
children: [
DropdownSearch<String>(
mode: Mode.MENU,
items: PointOfSaleDao.db.getList(),
showSearchBox: true,
label: "Menu mode",
hint: "point of sale in menu mode",
onChanged: (value) {}
),
],
),
As getList returns a Future, use a FutureBuilder to draw the widget for each state the Future can be, whether it's still loading or finished fetching the data. The FutureBuilder will provide you with an AsyncSnapshot. After the Future has been resolved, you should be able to access the loaded items by calling .data on the AsyncSnapshot.
FutureBuilder(
future: getList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
snapshot.data // access the loaded list
} else if (snapshot.hasError) {
snapshot.error // access the error message
} else {
// the future is still resolving
}
}
)
In case you are using the dropdown search package, there seems to be a field for that named asyncItems on it you might want to look into.

Screen not updated after calling FutureBuilder again

I have a screen that loads shopping cart items in a ListView.builder:
Expanded(
child: RefreshIndicator(
onRefresh: refresh,
child: Container(
child: FutureBuilder(
future: loadData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic>? filteredList = snapshot.data as List;
...
The data are loaded using the function loadData()
Future<List<LineaCesta>> loadData() async {
await fetchLineasCesta(idCesta);
return fetchLineasCesta(idCesta);
}
Then, inside the item card, there are some buttons to add or remove product quantity.
Here you have the case for adding a new one:
onPressed: () async {
//añadir uno maas
final prefs = await SharedPreferences.getInstance();
idCesta = prefs.getString("cesta_id")!;
checkExistenciaCestaPoner( idCesta!,lineaCesta.producto,lineaCesta.precio,context);
print( "refrescando despues de añadir item");
});
});
}
Then there are called other functions that at the end are calling the function loadData() again.
The issue is that all made changes are not updated after calling loadData.
I need to leave the screen and load it again to get all data updated.
EDIT:
Future<List<LineaCesta>> fetchLineasCesta(String cesta) async {
String url = Constantes().URLProyecto+Constantes().APICarpeta+"get_lineas_cesta.php?cesta="+cesta;
final response = await http.get(Uri.parse(url));
return lineaCestaFromJson(response.body);
}
When you like to refresh the FutureBuilder, reassing the future variable. For this I will prefer creating a separate variable for FutureBuilder's future.
late Future<List<LineaCesta>> loadDateFuture = loadData();
And use
FutureBuilder<List<LineaCesta>>(
future: loadDateFuture,
Now to update, reassign the loadDateFuture
loadDateFuture = loadData(); // like this
You can check
Randal L. Schwartz's video

LateInitializationError: Field has not been initialized.' .then() command not running after running firebase query in FutureBuilder

I am trying to retrieve a Firestore Snapshot and my code doesn't seem to be working. I made sure fireUser.uid was working, and it printed the right ID but strangely my .then() code isn't running at all I put print('then') in it and isn't appearing on my console
this is where the error is occurring:
FutureBuilder(
future: Future.wait([
DatabaseService.getUserDataFromFirestore(FirebaseAuth.instance.currentUser!),
GeoService.getPosition(),
]),
builder: (context, snap) {
if (snap.connectionState == ConnectionState.done) {
return Frame();
}
else return Container(
color: Colors.black,
child: Center(
child: spinKit,
),
);
}
);
Future with error:
static Future<Userdata> getUserDataFromFirestore (User fireUser) async {
await usersRef.doc(fireUser.uid).get().then((val) {
print('then');
userdata = Userdata.fromDoc(val);
});
return userdata;
}
error message:
LateInitializationError: Field 'userdata' has not been initialized.
I had a different problem then I thought. Firestore must have been updated because the rules of my Firestore database kept me locked out so I updated the rules and now my code works fine. Thanks so much for the help

Getting a valid value from StreamBuilder<DocumentSnapshot> and a child StreamBuilder

I am trying to get a specific icon to appear based on whether the user has taken the survey or not.
At the moment, I am using StreamBuilder to listen for a given value in a document, which returns the survey name. I then want to use the survey name in the next StreamBuilder, which will look in a given collection (which is made up of the survey name followed by _entrants - so, for example, Survey_entrants) for a completed survey document, which will have the title of the user's unique id (named userid).
The problem I have now is that whilst surveyName does return the name of the survey put in Cloud Firestore, and updates it when I change the value (I can see this by the commented-out return new Text('$surveyName'); command).
However, it does not seem to be passing that value into the next StreamBuilder - regardless of what I put in as the survey name, I get the check icon showing, suggesting (snapshot1.hasData) - even when that document does not exist.
I know the surveyName variable is working, but if I do snapshot1.toString() I get the error Snapshot(ConnectionState.active, Instance of 'DocumentSnapshot', null). This must count has having data, hence showing the survey being taken. How do I correct this?
My code:
Positioned(
right: 30,
top: 20,
child: StreamBuilder<DocumentSnapshot>(
stream: Firestore.instance
.collection('Controller')
.document('Current Survey')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
var sn = snapshot.data;
surveyName = sn["cs"];
// return new Text('$surveyName');
return StreamBuilder(
stream: Firestore.instance
.collection('$surveyName' + '_entrants')
.document(userid)
.snapshots(),
builder: (BuildContext context, snapshot1) {
if (!snapshot1.hasData) {
return Icon(
Foundation.burst_new,
size: 48,
color: Color(0xff303841),
);
} else if (snapshot1.hasData) {
return Icon(
Foundation.check,
size: 48,
color: Color(0xff303841),
);
} else {
return Icon(
MaterialIcons.error_outline,
size: 48,
color: Color(0xff303841),
);
}
});
}
})),
The issue I had was that Cloud Firestore returns a DocumentSnapshot even when there is nothing in the database to return. So, I changed (snapshot1.hasData) to (snapshot1.data.exists) and this works as expected - the icon changes based on the response.

How to inform FutureBuilder that database was updated?

I have a group profile page, where a user can change the description of a group. He clicks on the description, gets on a new screen and saves it to Firestore. He then get's back via Navigator.pop(context) to the group profile page which lists all elements via FutureBuilder.
First, I had the database request for my FutureBuilder inside the main build method (directly inside future builder 'future: request') which was working but I learnt it is wrong. But now I have to wait for a rebuild to see changes. How do I tell FutureBuilder that there is a data update?
I am loading Firestore data as follows within the group profile page:
Future<DocumentSnapshot> _future;
#override
void initState() {
super.initState();
_getFiretoreData();
}
Future<void> _getFiretoreData() async{
setState(() {
this._future = Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();});
}
The FutureBuilder is inside the main build method and gets the 'already loaded' future like this:
FutureBuilder(future: _future, ...)
Now I would like to tell him: a change happened to _future, please rebuild ;-).
Ok, I managed it like this (which took me only a few lines of code). Leave the code as it is and get a true callback from the navigator to know that there was a change on the second page:
// check if second page callback is true
bool _changed = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProfileUpdate(userId: globals.userId.toString())),
);
// if it's true, reload future data
_changed ? _getFiretoreData() : Container();
On the second page give the save button a Navigator.pop(context, true).
i would advice you not to use future builder in this situation and use future.then() in an async function and after you get your data update the build without using future builder..!
Future getData() async {
//here you can call the function and handle the output(return value) as result
getFiretoreData().then((result) {
// print(result);
setState(() {
//handle your result here.
//update build here.
});
});
}
How about this?
#override
Widget build(BuildContext context) {
if (_future == null) {
// show loading indicator while waiting for data
return Center(child: CircularProgressIndicator());
} else {
return YourWidget();
}
}
You do not need to set any state. You just need to return your collection of users in your GetFirestoreData method.
Future<TypeYouReturning> _getFirestoreData() async{
return Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();
}
Inside your FutureBuilder widget you can set it up something like Theo recommended, I would do something like this
return FutureBuilder(
future: _getFirestoreData(),
builder: (context, AsyncSnapshot<TypeYouReturning> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.data.length == 0)
return Text("No available data just yet");
return Container();//This should be the desire widget you want the user to see
}
},
);
Why don't you use Stream builder instead of Future builder?
StreamBuilder(stream: _future, ...)
You can change the variable name to _stream for clarity.