im using post method to get api data/response.body in initState and i get all the response/data then i put it in my variables surveyData, but when i load the pages with widget looped by the response.body length it goes error and say response.body.length is null but when i save the text editor/ hot reload, all the data entered and it's not null anymore and the widget appear.
fyi: the http.post method is my own function not from import 'package:http/http.dart' as http; so don't mind it
Variables that contain the response
dynamic surveyData;
initState Code
#override
void initState() {
super.initState();
// GET PAGES
surveyPages = widget.surveyPages;
// GET FORM DETAIL
http.post(
'survey-pre-mitsu/form-detail',
body: {
"survey_form_header_id": 1,
},
).then(
(res) {
surveyData = res['data'];
},
);
}
Widget that looped by surveyData.length
for (var i = 0; i < surveyData.length; i++)
AdditionalForm(questionLabel: surveyData[i]['label'],
questionType: surveyData[i]['type'],),
This is what the error
And this is what it looks like when i do hot reload
First, I suggest you to use future builder to resolve this problem.
First, I suggest performing post(even though its your own code) as a asynchronous operation, hence not inside initState(). Preferably write the logic in a separate class/file with packages like a provider package. Try this first and then post the error after that.
You need to add Some delay , In this Delay you can show a loading icon . for delay you can use :
Future.delayed(const Duration(milliseconds: 500), () {
// Here you can write your code
setState(() {
// Here you can write your code for open new view
});
});
This will solve your problem
Related
Hi I am using this code here to retrieve data from Firestore. The data is in a Map and here is the picture of my Firestore.
And here is my code when I retrieve from Firestore
Future <void> getRoute() async{
Firestore.instance.collection('routes').snapshots().listen((RouteData) {
if(RouteData.documents.isNotEmpty){
for (int i = 0; i < RouteData.documents.length; i++){
_getPolyline(RouteData.documents[i].data, RouteData.documents[i].documentID);
}
}
});
setState(() {
});
}
void _getPolyline(info, infoID) async{
print(info);}
When Im printing the info it will display the data many times.
here is the result. As you can see it starts off in test then test1 then test again and repeated for some time. Is there something wrong with my code? Thank you in advance.
Calling setState will trigger a rebuild of your widget. What i think is happening is that when getRoute() u are also triggering your build function which will trigger getRoute() to run again.
solution:
make sure your function is not being triggered during a build. you can do this by triggering your function only once in initState
Either that or because u are using snapshots().listen. this will listen to any changes that happen on that collection.
solution:
use .get() instead of .listen()
To read a collection or document once, call the Query.get or DocumentReference.get methods. In the below example a FutureBuilder is used to help manage the state of the request:
https://firebase.flutter.dev/docs/firestore/usage/#one-time-read
I am implementing an TabBar but getting the error, as state above. I know the reason this is happening, but can't figure how to fix this.
I have an async function designed to pull data from Firebase which populates a list. The function is described below. The data pulled by this function is used to pass the length to the TabController.
Function to call data from Firebase:
Future functionName async {
await function actions...
List example = ['item1', 'item2', 'item3', 'item4'];
}
This function is used in a Future Builder, which returns a widget to display, as soon as the function execution is complete.
The future of the Future Builder is initialised in the initState() of the class. The init() state looks like:
#override
void initState() {
super.initState();
_future = functionName();
tabController = TabController(length: example.length, vsync: this, initialIndex: 0);
tabController.addListener(_setActiveTabIndex);
}
Now, I get the error, as stated above. Which is obvious, why!
As the function is an async function and is built in a Future Builder, initially the list 'example' is empty. And the TabController gets a length of 0. But as this list gets populated, the length increases, but the length of the TabController does not change.
I have tried using :
#override
void initState() {
super.initState();
_future = functionName();
setState(() {
tabController = TabController(length : example. length, vsync : this, initialIndex : 0)
}
)
But this doesn't work either.
It's annoying to know the issue, but not being able to fix it. Any help on this will be highly appreciated.
You already know the reason of the problem.
The hole idea it's kind of backwards.
You are trying to build the TabBar, before you know how many tabs you need.
You can
Execute your FutureBuilder and then build the TabBar with the data received.
Or you can
Get the data before you go to that screen, so have the data already.
I hope this put you on the right track
We have a Features class that we are trying to fill when a screen loads. Its an http call that returns the object. Im struggling with how to do this. All of our http calls are done on a button click:
here is the call
Future<Features> getFeatureStatus(String userID) async {
Features _features;
final response =
await http.post('http://url/api/Features',
headers: {"Content-Type": "application/json",
'Accept': 'application/json',},
body: json.encode({'userID' : userID }));
_features = Features.fromJson(json.decode(response.body));
return _features;
}
When i try to call it at the top of the class I get errors and cant get to the values.
class FlutterReduxApp extends StatelessWidget {
static final User user;
static final Features features = getFeatureStatus(user.userId);
The error I get is -- "A value of type 'Future' can't be assigned to a variable of type 'Features'.
Try changing the type of the variable, or casting the right-hand type to 'Features'.dart(invalid_assignment)"
Im sure im doing something incorrect here but I havent done a screen load call yet.
The getFeatureStatus function is returning a Future<Features> while you're trying to read it as type Features in the stateless widget.
There are different ways to read the value but since you have a button, you could convert the widget into a StatefulWidget then use the onPressed function to read the value and update the state afterwards such as.
onPressed: () async {
features = await getFeatureStatus(user.userId);
setState((){
// do something
});
}
In this case, the value features cannot be a static final so you'll have to change it to Features features.
Edit based on comment:
You could also do this inside an initState:
Features features;
#override
void initState () {
super.initState();
_asyncMethod();
}
_asyncMethod() async {
features = await getFeatureStatus(user.userId);
setState((){});
}
so in the widget build method you could do:
return (features == null)
? CircularProgressIndicator()
: MyWidget(...); // where features is used.
getFeatureStatus(user.userId).than((features)
{
// you will get the features object
//you can work on that object
}
);
calling the getFeaturesStatus method in the initState() when using the statefull.
First thing first, this line static final Features features = getFeatureStatus(user.userId); will not work as you are trying to assign a type Future to the type Features.
The solution for this is to await the future so that it resolves and returns a Feature data type that satisfies your variable named 'features'.
This goes as follows: static final Features features = await getFeatureStatus(user.userId); but this has to be in a separate function which is explicitly defined with the async parameter.
This solves the error in the respect of code that you have written, but as you stated that you want this to load after the screen loads (Or technically, when the main widget is "mounted").
The solution for this logical aspect can be the use of this.mounted.
All widgets have a bool this.mounted property. It turns true when the buildContext is assigned.
In short, suppose you want to run a function after any widget is mounted/loaded, you can test it via
if(this.mounted){
//Whatever you want to do when the widget has been mounted...
}
I have a function in my initState:
#override
void initState() {
_fetchListItems();
super.initState();
}
This function is very simple. It has two async await operations of sqflite, one of which waits for the other to complete in it:
_fetchListItems() async {
wait() async {
number = await db.getNumber(userId); }
await wait();
List rawFavouriteList = await db.getList(number);
setState((){
rawFavouriteList.forEach((item){
_favouriteList.add(Model.map(item));
}});
}
I have to wait for number to be fetched, only then I can fetch serialized List, which then I deserialize and populate then add to the List which is displayed in ListView.
The problem is, this is not working in initState. It's working fine when it's called through onPressed() of a button, but not in initState.
Points to Note:
1) There is no error being thrown
2) I have already tried more conservative alternatives by using await for even rawFavouriteListthrough a separate function like wait() before using setState() top populate the _favouriteList, even though it's working fine manually through buttons.
3) Everything works fine if I manually enter number value in the second database query and just remove the first query, i.e.
_fetchListItems() async {
List rawFavouriteList = await db.getList(42);
setState((){
rawFavouriteList.forEach((item){
_favouriteList.add(Model.map(item));
}});
}
I'm developing a Flutter app which has some tabs inside, each of them depend on the database that is loaded on the first run. State is stored in a ScopedModel.
On every tab I have this code:
#override
void initState() {
super.initState();
loadData();
}
void loadData() async {
await MyModel.of(context).scopedLoadData();
_onCall = MyModel.of(context).onCall;
setState(() {
});
}
And this is the code snippet that matters for the ScopedModel:
Future<Null> scopedLoadData() async {
if (_isLoading) return;
_isLoading = true;
(...)
_isLoading = false;
}
If the user waits on the first tab for a few seconds everything is fine, since Database is loaded. However, if the user switches tabs right after app launch, the method scopedLoadData is still being executed so that I get a runtime error ("Unhandled Exception: NoSuchMethodError: The method 'ancestorWidgetOfExactType' was called on null.").
This exception happens because the scopedLoadData has not yet been completed. So I'm looking for a way to wait for a method that is still being executed.
Thanks!
Not sure without seeing your build method but I would start your build method with a guard clause.
if (_oncall == null) return Container(); // or Text("loading") or similar
use should be using a FutureBuilder on each tab to make sure the data is loaded before you try to build the widget... more code would be helpful
I solved the exception by getting rid of every:
setState(() { });
and implementing ScopedModelDescendant on every relevant tab on top of using notifyListeners() at the end of the database loading method.
This pulls the responsibility from the tabs for updating views and gives it to the scopedLoadData().