problem getting data from api as Future inside build() method in flutter - android

My problem is with Futures, because they should be obtained before build() method executed, as the documentation states:
The future must be obtained earlier, because if the future is created
at the same time as the FutureBuilder, then every time the
FutureBuilder's parent is rebuilt, the asynchronous task will be
restarted.
I know that Futures should be called in initstate() function before the build method executed, but my case is different.
I want to get data from api as a Future, but the request I am sending to the api needs some parameters that user should select inside the screen's build() method.
And I don't know what the parameter of the request will be until user selects in build() method, and I have to call the api in the build() method and use FutureBuilder there, but that makes FutureBuilder to get constantly called, and I don't want that.
basically, I don't want to call FutureBuilder indefinetely, and I can't put my Future inside initState() because the Future needs some parameters that user later selects when the screen is shown inside build() method.
inside the build method:
FutureBuilder<List<LatLng>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return PolylineLayer(
polylines: [
Polyline(
points: snapshot.data!,
strokeWidth: 4,
color: Colors.purple),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
} else {
return Container();
}
},
future: Provider.of<NavigationProvider>(context)
.getNavigationPoints(pointToGoTo!),
),
now if you look at the code, at the final lines, I am sending the parameter pointToGoTo to the function which calls the backend.
simply, I want to get rid of calling api and getting data back as a Future inside build method, I want to do it in initState or somewhere else that prevents the build methods calling backend indefinitely.
is there any way to fix this problem?
Thanks in advance.

Firstly, create future state variable and a nullable params and use it with conditional if while using FutureBuilder.
I will recommend checking Fixing a common FutureBuilder and StreamBuilder problem
Now you can follow this example. It is missing progressBar on API recall, StreamBuilder might be better option in cases like this.
class Foo extends StatefulWidget {
const Foo({super.key});
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int? params;
Future<int> fetch(int? data) async {
await Future.delayed(Duration(seconds: 1));
return (params ?? 0) * 2;
}
late Future<int> future = fetch(params);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
DropdownButton<int?>(
value: params,
items: List.generate(
12,
(index) => DropdownMenuItem(
value: index,
child: Text("$index"),
)).toList(),
onChanged: (value) {
future =
fetch(params); // this will only call api with update data
setState(() {
params = value;
});
},
),
if (params != null)
FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) return Text("${snapshot.data}");
return CircularProgressIndicator();
},
)
],
),
);
}
}

class Testing extends StatefulWidget {
const Testing({super.key});
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
bool isFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<SomethingProvider>(
builder: (context, prov, child) {
if (!isFetched) {
prov.getData("a", "b");
Future.delayed(const Duration(milliseconds: 200), () {
isFetched = true;
});
}
if (prov.newData.isNotEmpty) {
return Column(
// make widget tree from here
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
class SomethingProvider extends ChangeNotifier {
List newData = [];
Future getData(param1, param2) async {
newData = ["testingdata"];
}
}

Related

Why is setState function in flutter not working?

I am working with APIs and using a delete method to delete an item with a unique id from a list. The delete method is working but I need to reload the page everytime I want to see the results. I tried to add a setState() function inside a button and call the delete method from there but it is not working. I am not getting any errors however.
Delete method:
Future <void> deleteData(todo) async {
var urlToUpdate = Uri.parse('https://todoapp-api.apps.k8s.gu.se/todos/${todo.id}?key=${testKey}');
try {
await http.delete(urlToUpdate, headers: {"Content-Type": "application/json"}, body: jsonEncode({
"id": todo.id,
"title": todo.title,
"done": todo.done
}));
} catch (err) {
print(err);
}
}
setState method:
child: IconButton(
onPressed: () {
setState(() {
var deleteTodo = TodoItem(id: id, title: '', done: false);
deleteData(deleteTodo);
});
},
I can't provide the whole code because it is too large but the delete method comes right after :
class _TodoListState extends State {
and before initState and Widget build.
My TodoItemsList works like this:
Future fetchPosts() async {
try {
await getKey();
final response = await HTTP.get(Uri.parse('${url}${todos}${testKey}'));
final jsonData = jsonDecode(response.body);
setState(() {
TodoItemsList = jsonData;
});
print(jsonData);
} catch (err) {
print('Error');
}
}
This empty list is just above the Widget build
List TodoItemsList = [];
This widget is inside by body property:
Widget getBody() {
return ListView.builder(
itemCount: TodoItemsList.length,
itemBuilder: (context, index) {
return getCard(TodoItemsList[index]);
});
}
you should wait until the deleteData finished.
After that, remove the local TodoItem from the list by yourself.
child: IconButton(
onPressed: () async {
var deleteTodo = TodoItem(id: id, title: '', done: false);
await deleteData(deleteTodo);
setState(() {
=> remove TodoItem from the local list =<
});
}
Because network request usually takes times. You should display something like CircularProgressIndicator when deleteData is running. But that's another story.
setState method is used to reflect any change of data over some widget, if you need to remove a element from a list need has that element linked to a widget
example:
If you has
listOfMovie = ['Avatar, Avengers', 'Dune', 'Hulk'];
ListView.builder(
itemCount: listOfMovie.length,
itemBuilder: (_, index) => Text(listOfMovie[index],
));
then
child: IconButton(
onPressed: () {
setState(() {
listOfMovie = ['Avatar, Avengers'];
});
},
If you notice listOfMovie is linked to ListView widget

The getter 'docs' was called on null as QuerySnapshot is null even after it's initialised in initState()

Here's my code which results in the error:
It's also worth noting that print('mysnapshot.docs.length') prints the length fine indicating that mysnapshot is not null
after initState. However, in the widget builder block, mysnapshot's value is null for reasons I can't figure out.
I am new to Flutter and I'd appreciate a dumbed-down answer. This is also my first question on Stackoverflow. Thanks in advance.
class _PlayQuizState extends State<PlayQuiz> {
DatabaseService serv = new DatabaseService();
QuerySnapshot mysnapshot;
#override
void initState() {
serv.getQuestionData(widget.quizID).then((value){mysnapshot=value; print(mysnapshot.docs.length);});
print("${widget.quizID}");
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Row(
children: [
SizedBox(width: MediaQuery.of(context).size.width/5.8,),
appbar(context),
],
),backgroundColor: Colors.transparent,elevation: 0.0,iconTheme: IconThemeData(color: Colors.black54),),body:
Text(mysnapshot.docs.length.toString()), // Mysnapshot is null, can't figure out why
],),),);
}
}
InitState and build method are fired up very close to each other, so it's normal that a new variable declared in initState won't appear in your build method. What you can do instead is making use of FutureBuilder widget where it's future argument will be the exact same declaration as the one inside initState;
FutureBuilder<QuerySnapshot>(
future: serv.getQuestionData(widget.quizID),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
return Text(snapshot.data);
},
),

flutter app, list retrived from firestore duplicate it self

I have a function that is supposed to fetch me a list of Restaurants objects from firestore based on location.
the function does its job perfectly when i first run the app but after using the app from another device and updating resturants data in firestore documents, i somehow get duplicates of the restaurants list items.
here is the code for the function that fetch the the restaurants objects list:
Future<void> fetchRestaurantsList() async {
try {
Position position = await Geolocator().getCurrentPosition(
desiredAccuracy:
Platform.isIOS ? LocationAccuracy.lowest : LocationAccuracy.high);
final dbRestaurant = firestore
.collection('testing')
.document('users')
.collection('restaurant');
geo.collection(collectionRef: dbRestaurant)
.within(
center: GeoFirePoint(
position.latitude,
position.longitude
),
radius: 45.0,
field: 'resturantLocation')
.listen((event) {
restaurantList.clear();
await event.forEach((element){
final distance = Distance.getDistanceFromLatLonInKm( // calculating distance for each restaurant
position.latitude,
position.longitude,
element.data['location']['geopoint'].latitude,
element.data['location']['geopoint'].longitude)
restaurantList.add(Restaurant(
id: element.documentID,
logo: element.data['logo'],
name: element.data['name'],
distance: distance ,
));
notifyListeners();
});
});
} catch (e) {
print(e.toString());
}
} finally {
notifyListeners();
}
}
and this is the page that contains the list: (its under a parent widget which contains other tabs)
class RestruntsListTab extends StatefulWidget {
final MainModel model;
RestruntsListTab({#required this.model});
#override
State<StatefulWidget> createState() {
return _RestruntsListTabState();
}
}
class _RestruntsListTabState extends State<RestruntsListTab>
#override
void initState() {
widget.model.fetchRestaurantsList();
widget.model.checkLocationService().then((isActive) {
if (isActive) {
} else {
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
language.enableLcation,
style: TextStyle(
fontFamily: 'eff', fontSize: 18, fontWeight: FontWeight.bold),
),
backgroundColor: Colors.grey,
));
}
});
super.initState();
}
#override
Widget build(BuildContext context) {
return ScopedModelDescendant<MainModel>(
builder: (context, child, model) {
return ListView.builder(
itemCount:model.restaurantList.length,
itemBuilder: (context,index) {
return Row(
children: <Widget>[
Text(model.restaurantList[index].name),
Text(model.restaurantList[index].distance),
],
)
}
);
})
}
}
this is a simplified code for demonstration but the actual code is pretty similar.
if you have encountered similar issues kindly share your experience.
thank you all.
check that fetchRestaurantsList() method is not called on widget build
or it is in StreamBuilder method...it's because .listen((event) { this method it is like a stream so you have to use flag like bool variable to run the code inside it
if(mybool==false){// the other code goes.... setStste({mybool=true;})}
in this way it only excute the code once
There might be something wrong with the code, but I don't see it. What you can try doing is wrapping the content of forEach with
if(restaurantList.where((item) => item.id == element.documentID).isEmpty){
}
That should filter out duplicates.

One-time Read Firebase Cloud (Dart/Flutter)

I need one-time read Data from Firebase Cloud, thats why I use FutureBuilder in my project (dart/flutter). But when the application is started it reads without stopping (as stream). What should I do to fix this?
class Hello extends StatefulWidget {
#override
_HelloState createState() => _HelloState();
}
class _HelloState extends State<Hello> {
Future getPosts() async{
QuerySnapshot qn = await FirebaseFirestore.instance.collection("111").get();
return qn.docs;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: new Text('Hello'),
),
body: FutureBuilder(
future: getPosts(),
builder: (context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}
else{
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index){
return Text(snapshot.data[index].data()["subject"]);
},
);
}
},
),
);
}
}
From the FutureBuilder doc :
The future must have been obtained earlier, e.g. during
State.initState, State.didUpdateConfig, or
State.didChangeDependencies. It must not be created during the
State.build or StatelessWidget.build method call when constructing the
FutureBuilder. If the future is created at the same time as the
FutureBuilder, then every time the FutureBuilder's parent is rebuilt,
the asynchronous task will be restarted.
Example :
Future<QuerySnapshot> future;
#override
void initState() {
super.initState();
future = Firestore.instance.collection("111").getDocuments();
}
// future : future

Flutter Firebase - DropdownMenuItem: The method 'map' was called on null

In my application I would like to display vaccines according to the species of the animal (If is a dog or cat). I'm experiencing an error: The method 'map' was called on null. Tried calling: map DropdownMenuItem. Why is this happening? I already put async and await in the methods, I don't understand why it is still null. Bellow my code:
1) This is where I call my DropdownContent class in init to prepare my DropdownMenuItem in the row inside the widget
class _VaccineDetailFormState extends State<VaccineDetailForm> {
final DataRepository repository = DataRepository();
String selectedVaccine = "Select";
List<String> vaccinesBySpecie;
initState() {
DropdownContent.getVaccines(widget.selectedPetID).then((value) => vaccinesBySpecie = value);
}
#override
Widget build(BuildContext context) {
return Scaffold(
[...]
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new DropdownButton<String>(
value: widget.vaccine.name == null ? selectedVaccine: widget.vaccine.name,
underline: Container(
height: 2,
color: Colors.grey,
),
onChanged: (String newValue) {
setState(() {
selectedVaccine = newValue;
widget.vaccine.name = newValue;
});
},
items: vaccinesBySpecie.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
)
]),
[...]
2) Here is the DropdownContent class that, inside the getVaccines() method, searches the repository to find out the species of the current animal and then returns the appropriate vaccine list.
class Dropdown content
static Future<List<String>> getVaccines(String petId) async {
final DataRepository repository = DataRepository();
String currentSpecie = await repository.getSpecie(petId);
if (currentSpecie.contains('Dog')) {
return listOfVaccinesForDogs();
}
if (currentSpecie.contains('Cat')) {
return listOfVaccinesForCats();
}
}
3) Finally, the repository class that searches for the species of the animal
class Repository
Future<String> getSpecie(String petId) async {
DocumentReference documentReference = petCollection.document(petId);
await documentReference.get().then((snapshot) {
return snapshot.data['specie'].toString();
});
}
While your initState method may be asynchronous, your build method isn't. So at the time that the build method is called, your vaccinesBySpecie method is null.
The best way to fix this would be to initialize your List<String> vaccinesBySpecie like so List<String> vaccinesBySpecie = [];. This way it isn't null when the build method is called.
As a side note, I would suggest using a FutureBuilder or StreamBuilder if you can, that way you can handle when there isn't a value (i.e it is null) vs when there is a value(ie it is not null)
What Dean said illuminated my ideas. I managed to solve it by reaching the following answer:
1) DropdownMenuItem inside the widget
Container(
child: FutureBuilder <List<String>>(
future: DropdownContent.getVaccines(widget.selectedPetID),
builder: (context, AsyncSnapshot snapshot) {
if(snapshot.data == null) {
return CircularProgressIndicator();
}
else {
return DropdownButton<String>(
value: widget.vaccine.name == null? selectedVaccine: widget.vaccine.name,
underline: Container(
height: 2,
color: Colors.grey,
),
onChanged: (String newValue) {
setState(() {
selectedVaccine = newValue;
widget.vaccine.name = newValue;
});
},
items: snapshot.data.map<DropdownMenuItem<String>>((value) =>
new DropdownMenuItem<String>(
child: Text(value),
value: value,
))
.toList(),
);
}
})
)
2) DropdownContent class that searches the repository to find out the species of the current animal and then returns the appropriate vaccine list:
static Future<List<String>> getVaccines(String petId) async {
final DataRepository repository = DataRepository();
String currentSpecie;
await repository.getSpecie(petId).then((value) {
currentSpecie = value;
});
if (currentSpecie.contains('Dog')) {
return listOfVaccinesForDogs();
}
if (currentSpecie.contains('Cat')) {
return listOfVaccinesForCats();
}
}
3) The repository class that searches for the species of the animal
Future<String> getSpecie(String petId) async {
DocumentReference documentReference = petCollection.document(petId);
String specie;
await documentReference.get().then((snapshot) {
specie = snapshot.data['specie'].toString();
});
return specie;
}

Categories

Resources