i try to use FutureBuilder widget in flutter to get some data from network. for this i use below code :
Future<List<Product>> getWishList() async {
http.Response response = await http.post(
MY_URL,
headers: {
HttpHeaders.acceptHeader: 'application/json',
HttpHeaders.contentTypeHeader: 'application/json; charset=utf-8'
},
);
if (response.statusCode == 200) {
List<Product> ret = List();
Map<String, dynamic> result;
try {
result = json.decode(response.body);
for (var i = 0; i < result['data'].length; i++) {
ret.add(Product.fromJson(result['data'][i]));
}
return ret;
} catch (e) {
return throw Exception("Json parse error");
}
} else {
return throw Exception("network connection failed");
}
}
AND:
FutureBuilder(
future: getWishList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.connectionState == ConnectionState.done){
if(snapshot.hasError){
controller.forward(from: 0.0);
return Material(
child: Container(
margin: const EdgeInsets.only(top: 36.0),
width: double.infinity,
child: FadeTransition(
opacity: animation,
child: PButton(
onPressed: (){
setState(() {
getWishList();
});
},
child: Column(
children: <Widget>[
Icon(Icons.perm_scan_wifi,color: Colors.black,size: 76.0,),
SizedBox(height:24.0),
Text("Try again",style: TextStyle(fontSize: 16.0,color: const Color(0XFF222222)),),
],
),
),
),
),
);
}else{
return new ListView(
children: <Widget>[
GestureDetector(
onTap:(){
setState(() {
getWishList();
});
},
child: new Text("Every thing ok"))
]);
}
}else{
return Center(
child: Container(
margin: const EdgeInsets.only(top: 36.0),
child: CircularProgressIndicator()),
);
}
})
now if http response return error in first time every thing good but with click on Try again and if error again this message display on console:
[VERBOSE-2:shell.cc(184)] Dart Error: Unhandled exception: Exception:
network connection failed
_WishListState.getWishList (package:parchino/screen/screen_wish_list.dart:127:14)
_WishListState.build...
(package:parchino/screen/screen_wish_list.dart:65:33)
State.setState (package:flutter/src/widgets/framework.dart:1130:30)
_WishListState.build.. (package:parchino/screen/screen_wish_list.dart:64:31)
GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:102:24)
TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:242:9)
TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:175:7)
PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:315:9)
PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart<…>
When you call setState, you mark the widget as Dirty and basically tells the framework to rebuild it but only after getWishList has been called (the one inside setState). As it is an async method, it launches quickly and rebuild the widget.
By rebuilding the widget, you rebuild the FutureBuilder which tries to evaluate its future. As the future is a function, it calls it and makes a new call to getWishList.
That makes two calls to the same method and thus two calls to an http server very quickly. Those calls are probably in conflict, and throws an error.
You should not invoke a Future directly in the FutureBuilder but use a previously-obtained Future instead.
Future<List<Product>> myFuture;
#override
void initState() {
myFuture = getWishList();
super.initState();
}
Future<List<Product>> getWishList() async {
//Do your stuff
}
Then in your build, set myFuture as the future of the FutureBuilder and in your setState, set myFuture again:
FutureBuilder(
future: myFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) {
//...
setState(() {
myFuture = getWishList();
});
//...
}
);
That will make the setState set a new future in myFuture and ask the widget to rebuilt itself. As the FutureBuilder rebuild, it evaluates myFuture instead of calling http once again.
Related
There are two dropdown button with the list of countries and types of sport. If on them somsething is chosen it is need to show listTile with the leagues on it is chosen to the country and/or sport and if on them nothing is chosen - show all leagues.
But I get:
Dart Error: Unhandled exception:
setState () called after dispose (): _SportLigPageState # b5830 (lifecycle state: defunct, not mounted)
This is what happens if you see the widget tree (e.g.). This error can occur when a call is made. Dispose () callback. It is necessary to ensure that the object is still in the tree.
This can be a memory card if it’s not. To avoid memory leaks, consider dispose ().
Api with leagues: https://www.thesportsdb.com/api/v1/json/1/all_leagues.php:
class LigList extends StatefulWidget {
#override
_LigListState createState() => _LigListState();
}
class _LigListState extends State<LigList> {
String sport;
String country;
List data;
Future<String> getJsonData() async {
http.Response response;
if (sport != null) {
if (country != null) response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport&s=$country'), headers: {"Accept": "application/json"});
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport'), headers: {"Accept": "application/json"});}
else if (country == null){ response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php'), headers: {"Accept": "application/json"});}
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$country'), headers: {"Accept": "application/json"});
var convertDatatoJson = json.decode(response.body);
data = convertDatatoJson['leagues'];
return "Success";
}
static const menuItems = countriesList;
final List<DropdownMenuItem<String>> _dropDownItems = menuItems
.map((String CountruValue) =>
DropdownMenuItem<String>(
value: CountruValue,
child: Text(CountruValue),
),
).toList();
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(children: <Widget>[
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {});
},
);}),
SizedBox(width: 5),
FutureBuilder(
future: _getSports(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? DropdownButton(
value: sport,
hint: Text("Choose a sport league of which you want to find"),
items: snapshot.data,
onChanged: (value) {
sport = value;
print(sport);
setState(() {});
},
)
: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: CircularProgressIndicator());
}),
Flexible(
child:FutureBuilder(
future: getJsonData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return ListView.separated(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int i) {
return Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment
.stretch,
children: <Widget>[
ListTile(
title: Text(data[i]['strLeague']),
subtitle: Text(
data[i]['strSport']),
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (
BuildContext context) =>
new ComandListScreen()
// (data[i])
));
},
),
]
)
)
);
});
}))
]),
),
);
}
}
Any assistance is very much appreciated.
There's a lot of things wrong with your code. The first child in your code is wrapped in a FutureBuilder but you're not using any Future functionality.
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {}); // Remove this line
},
);}),
In addition to that you also are calling setState() randomly in your onChanged callback with nothing inside of it. I'd suggest you take that widget out of the FutureBuilder and just use the DropdownButton on it's own.
Then also in this line
itemCount: data == null ? 0 : data.length,
You're using data, which is set in the future that you call there. You might want to read up on how to properly use the FutureBuilder widget. Just return the data object from your _getJsonData() Future because it's always returning "Success" anyway. Return the list you want from the Future and then access it using snapshot.data
And lastly there's literally only one setState call in there so remove it and you'll be fine. My assumption is that there's some additional dispose you're calling or navigating away and the app crashes. Will need a lot more info to figure out, but you'll have to fix the way you use Futures and the Future builder so we can ensure it's not because of latent threads coming back and setting the state once you've left the view you were on.
I have a Flutter Text widget and its content is populated from an external REST call.I would like to refresh the widget content periodically every 5 mins by calling the REST endpoint.
So far I managed to call the endpoint every 5 mins but unable to update/refresh the widget content with new data from network.
class PatientCount {
int count;
double amount;
PatientCount({this.count, this.amount});
PatientCount.fromJson(Map<String, dynamic> map)
: count = map['count'],
amount = map['amount'];
}
Future<PatientCount> fetchPatientCount() async {
var url = "http://localhost:9092/hms/patients-count-on-day";
Map<String, String> requestHeaders = new Map<String, String>();
requestHeaders["Accept"] = "application/json";
requestHeaders["Content-type"] = "application/json";
String requestBody = '{"consultedOn":' + '16112018' + '}';
http.Response response =
await http.post(url, headers: requestHeaders, body: requestBody);
final statusCode = response.statusCode;
final Map responseBody = json.decode(response.body);
if (statusCode != 200 || responseBody == null) {
throw new FetchPatientCountException(
"Error occured : [Status Code : $statusCode]");
}
return PatientCount.fromJson(responseBody['responseData'].
['PatientCountDTO']);
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
#override
void initState() {
super.initState();
setState(() {
const oneSecond = const Duration(seconds: 25);
new Timer.periodic(oneSecond, (Timer t) => buildCountWidget());
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2.0,
backgroundColor: Colors.white,
title: Text('Dashboard'),
),
body: StaggeredGridView.count(
crossAxisCount: 2,
crossAxisSpacing: 12.0,
mainAxisSpacing: 12.0,
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
children: <Widget>[
_buildTile(
Padding(
padding: const EdgeInsets.all(24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Today\'s OPD',
style: TextStyle(
color: Colors.blueAccent, fontSize: 18.0),
),
buildCountWidget(),
],
),
Material(
color: Colors.blue,
borderRadius: BorderRadius.circular(24.0),
child: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Icon(Icons.timeline,
color: Colors.white, size: 30.0),
)))
]),
),
),
],
staggeredTiles: [StaggeredTile.extent(2, 110.0)],
));
}
Widget _buildTile(Widget child, {Function() onTap}) {
return Material(
elevation: 14.0,
borderRadius: BorderRadius.circular(12.0),
shadowColor: Color(0x802196F3),
child: InkWell(
// Do onTap() if it isn't null, otherwise do print()
onTap: onTap != null
? () => onTap()
: () {
print('Not set yet');
},
child: child));
}
Widget buildCountWidget() {
Widget vistitCount = new Center(
child: new FutureBuilder<PatientCount>(
future: fetchPatientCount(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
print(snapshot.data.count);
/* below text needs to be updated every 5 mins or so */
return new Text('#' + snapshot.data.count.toString(),
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w700,
fontSize: 34.0));
} else if (snapshot.hasError) {
return new Text("${snapshot.error}");
}
}
// By default, show a loading spinner
return new CircularProgressIndicator();
},
),
);
return vistitCount;
}
}
Inside the buildCountWidget method the Text widget needs to be refreshed with the latest data from the network.
I changed the implementation to use setState as below, still no luck
class _MainPageState extends State<MainPage> {
Future<PatientCount> _patientCount;
Timer timer;
#override
void initState() {
super.initState();
callApi();
timer = Timer.periodic(Duration(seconds: 15), (Timer t) => setState(() {}));
}
void callApi() {
setState(() {
_patientCount = fetchPatientCount();
});
}
..........................
Also changed the logic as below, with this I am able to call the REST endpoint but the widget data is not getting updated every 25 seconds.The widget is showing the old data .
class _MainPageState extends State<MainPage> {
Future<PatientCount> _patientCount;
Timer timer;
#override
void initState() {
super.initState();
//callApi();
timer = Timer.periodic(Duration(seconds: 15), (Timer t) => callApi());
}
void callApi() {
setState(() {
_patientCount = fetchPatientCount();
});
}
...........................
As per the code it is showing the same count , the count is not getting incremented after 25 seconds.However from the backend the Api is fired periodically and returning the data to UI, but the state to the widget is not changing.
Replace this :
new Timer.periodic(oneSecond, (Timer t) => buildCountWidget());
By this:
new Timer.periodic(oneSecond, (Timer t) => setState((){}));
And it should work, every time you call setState it'll refresh the widget and will call to the Future method again.
UPDATE
It's working fine, if you make these changes, you will notice how the data is refreshed (just for testing):
Future<String> fetchPatientCount() async {
print("fetchPatientCount");
return DateTime.now().toIso8601String();
}
...
new FutureBuilder<String>(
future: fetchPatientCount(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasData) {
/* below text needs to be updated every 5 mins or so */
return new Text('#' + snapshot.data.toString(),
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w700,
fontSize:7.0));
} else if (snapshot.hasError) {
return new Text("${snapshot.error}");
}
}
If the data changes every 25 seconds, it's working , you have to check your fetchPatientCount method. ( encode the data to json before send requestBody)
I'm trying to catch the error when my device has no internet connection. I've built out 2 future methods, 1 to import a json and 1 to look into the database. I have a future builder that's suppose to wait for both futures to finish before building out the grid view but it seems like the offlineFlashCardList is being prematurely called due to the connection error. Any idea how to make it wait for both futures to finish before the snapshot error gets called?
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:baby_sound/strings.dart';
import 'package:baby_sound/objects/flashCardList.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'dart:async' show Future;
import 'dart:convert';
import 'package:baby_sound/database/database.dart';
import 'package:baby_sound/objects/network.dart';
import 'package:http/http.dart' as http;
class GridViewWidget extends StatefulWidget{
#override
createState() => new GridViewState();
}
class GridViewState extends State<GridViewWidget>{
List<FlashCardList> flashCardList;
List<FlashCardList> offlineFlashCardList;
Future<List<FlashCardList>> fetchFlashCardList() async{
debugPrint("before response");
List<FlashCardList> tempFlashCardList;
final response = await http.get('some json url');
//checkConnection(url).then((response){
debugPrint ("after database load, response code: ${response.statusCode}");
if (response.statusCode == 200) {
var data = json.decode(response.body);
var flashCardListData = data["FlashCardList"] as List;
tempFlashCardList = flashCardListData.map<FlashCardList>((json) => FlashCardList.fromJson(json)).toList();
for (int i = 0; i < tempFlashCardList.length; i++){
debugPrint("DBProvider listID: ${await DBProvider.db.getFlashCardList(tempFlashCardList[i].flashCardListID)}, flashCardID: ${tempFlashCardList[i].flashCardListID}");
if (await DBProvider.db.getFlashCardList(tempFlashCardList[i].flashCardListID) == null){
DBProvider.db.newFlashCardList(tempFlashCardList[i]);
debugPrint("Adding ${tempFlashCardList[i].name}}}");
} else {
DBProvider.db.updateFlashCardList(tempFlashCardList[i]);
debugPrint("Updating ${tempFlashCardList[i].name}, getFlashCardList: ${DBProvider.db.getFlashCardList(tempFlashCardList[i].flashCardListID)}");
}
}
flashCardList = tempFlashCardList;
debugPrint("Standard flashCardList Size: ${flashCardList.length}");
}
debugPrint("flashCardList Size Before Return: ${flashCardList.length}");
return flashCardList;
}
Future<List<FlashCardList>> fetchFlashCardListFromDB() async{
offlineFlashCardList = await DBProvider.db.getAllFlashCardListFromDB();
debugPrint("fetchFromDB size: ${offlineFlashCardList.length}");
return offlineFlashCardList;
}
#override
void initState(){
debugPrint ('debug main.dart');
super.initState();
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
centerTitle: true,
title: new Text(Strings.pageTitle),
),
body: FutureBuilder<List<FlashCardList>>(
future: new Future(() async{
await fetchFlashCardList();
await fetchFlashCardListFromDB();
}),
builder: (BuildContext context, AsyncSnapshot<List<FlashCardList>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
debugPrint("Snapshot has error: ${snapshot.error}");
return new GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
childAspectRatio: 0.5),
itemCount: offlineFlashCardList.length,
itemBuilder: (BuildContext context, int index) {
return _getGridItemUI(context, offlineFlashCardList[index]);
});
// return new Center(child: new CircularProgressIndicator());
} else {
debugPrint("Grid ViewBuilder");
return new GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200.0,
childAspectRatio:0.5),
itemCount: flashCardList.length,
itemBuilder: (BuildContext context, int index) {
return _getGridItemUI(context, flashCardList[index]);
});
}
}else {
debugPrint("CircularProgress");
return new Center(child: new CircularProgressIndicator());
}
})
);
}
_getGridItemUI(BuildContext context, FlashCardList item){
return new InkWell(
onTap: () {
_showSnackBar(context, item);
},
child: new Card(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Image(image: new CachedNetworkImageProvider("https://babymozart.org/babymozartq92C9TLa9UMkulL2m81xHdn9u2R92e1e/image/" + item.image)),
/*new Expanded(
child:new Center(
child: new Column(
children: <Widget>[
new SizedBox(height: 8.0),
new Expanded(
child: AutoSizeText(
item.name, maxLines: 1,
)
)
],
),
)
)*/
],
),
elevation: 2.0,
margin: EdgeInsets.all(5.0),
)
);
}
_showSnackBar(BuildContext context, FlashCardList item){
}
}
You can use Future.wait to wait for several Future to be completed.
body: FutureBuilder<List<FlashCardList>>(
future: Future.wait([
fetchFlashCardList(),
fetchFlashCardListFromDB(),
]),
Here's an example based on Alexandre's answer (As I found myself looking for how to handle results):
FutureBuilder(
future: Future.wait([
firstFuture(), // Future<bool> firstFuture() async {...}
secondFuture(),// Future<bool> secondFuture() async {...}
//... More futures
]),
builder: (
context,
// List of booleans(results of all futures above)
AsyncSnapshot<List<bool>> snapshot,
){
// Check hasData once for all futures.
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
// Access first Future's data:
// snapshot.data[0]
// Access second Future's data:
// snapshot.data[1]
return Container();
}
);
I'm trying to change some variables in different methos in Flutter, but the value isn't changed.
An example is something like:
enum UserPlaceStatusType { NONE, GOING, THERE, OUT, CANCELLED }
class PlaceCardState extends State<PlaceCard> {
UserPlaceStatusType _isOtherPlaceActive = UserPlaceStatusType.NONE;
Widget build(BuildContext context) {
return Card(
child: Scaffold(
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this._getBody(),
),
bottomNavigationBar: this._getBottomNavigationBar()));
}
List<Widget> _getBody() {
return [
Expanded(child: Text('test'), flex: 3),
Expanded(child: Text('test'), flex: 6),
Expanded(child: this._getActionsMenu(), flex: 1)
];
}
Widget _getActionsMenu() {
return Container(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
color: Colors.grey[400],
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
leading: new Icon(Icons.train),
title: new Text(Utility.format(
Language.of(context).takePlace, [_place.title])),
onTap: () {
showUserStatusDialog<DialogActions>(
context: context,
//It opens a simple dialog
child: this._getCurrentUserPlaceStatus());
},
),
],
);
});
},
));
}
Widget _getCurrentUserPlaceStatus() {
return new GraphqlProvider(
client: new ValueNotifier(
Client(endPoint: 'GraphQLUrl', cache: new InMemoryCache()),
),
child: new Query(
'The GraphQL Query',
variables: {},
builder: ({
bool loading,
var data,
var error,
}) {
if (data != null && data['getCurrentUserPlaceStatus'] != null) {
this._isOtherPlaceActive = UserPlaceStatusType.THERE;
Navigator.pop(context, DialogActions.cancel);
return Container();
} else {
this._isOtherPlaceActive = UserPlaceStatusType.GOING;
Navigator.pop(context, DialogActions.cancel);
return Container();
}
},
));
}
void showUserStatusDialog<T>({BuildContext context, Widget child}) async {
//here there is a validation but the variable value is the initial one, I mean NONE
if (this._isOtherPlaceActive == UserPlaceStatusType.GOING) {
//Cod to do
return;
}
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
if (value != null) {
this._isOtherPlaceActive = UserPlaceStatusType.NONE;
Navigator.pop(context);
}
});
}
}
I changed the variable value through some methods, but when I need to apply the validation, that's the initial value, it isn't changed, and I could not apply SetState method cuz it breaks the modal and throws an exception.
I will appreciate any feedback.
The method setState() can't be called inside a widget directly. I'm curious with your use of GrapQLProvider since it returns an empty Container() widget just to check the status of the data.
While I'm unfamiliar with the use of GraphQL, if the client that you're using inherits either a Stream or Future, it can be used to listen when the query is done.
Here's some snippets as demo. Let _testFuture() as the sample for a Future callback.
Future _testFuture() async{
return null;
}
Future can be listened to inside a Widget. When the request finishes, we have the opportunity to call setState().
_testFuture().then((value) {
// Check for values here
setState(() {
// Update values
});
});
Or if the request is set in a Stream, it's also possible to listen for Stream changes inside a Widget.
_streamController.add(_testFuture());
_streamController.stream.listen((event) {
// Check for values here
setState(() {
// Update values
});
});
This may not be the exact answer that you're looking for, but I hope this can guide you for a solution to your approach. I also found a GraphQL sample that uses ObservableQuery as a Stream that you can try.
Your code is very complex and should be refactored. Please notice how dialogs must be called.
enum DialogResult {ok, cancel}
caller_widget.dart
FlatButton(
child: Text('Open dialog'),
onPressed: () async {
// Call dialog and wait for result (async call)
final dialogResult = await showDialog<DialogResult>(
context: context,
builder: (context) => DialogWidget(),
);
if (dialogResult == DialogResult.ok) {
// do something
}
},
),
dialog_widget.dart
...
FlatButton(
child: Text('Ok'),
onPressed: () => Navigator.pop(context, DialogResult.ok), // DialogResult.ok returns
),
FlatButton(
child: Text('Cancel'),
OnPressed: () => Navigator.pop(context, DialogResult.cancel), // DialogResult.cancel returns
),
So you can return required value from dialog and set it to required variable.
P.S. Try to avoid use of old fashion then process of futures and use async/await.
I am developing a flutter app,in which Build method calls fetchCitiesList method for list of cities,which sets cities variable to list of cities.And Build method use it for making a list of listtiles.
var cities;
void fetchCitiesList () async {
var url = 'https://gnsyms.000webhostapp.com/cities.php';
var json;
var httpClient = new HttpClient();
try {
var req = await httpClient.getUrl(Uri.parse(url));
var res = await req.close();
if (res.statusCode == HttpStatus.OK) {
json = await res.transform(UTF8.decoder).join();
cities = JSON.decode(json);
}
} catch (exception) {
print(exception);
}
}
#override
Widget build(BuildContext context) {
final double systemTopPadding = MediaQuery.of(context).padding.top;
fetchCitiesList();
return new Scaffold(
appBar: new AppBar(
title: new Center( child: new Text(widget.title,textScaleFactor: 1.3,), ),
),
body: new Center(
child: new Column(
children: <Widget>[
new Padding(padding: new EdgeInsets.fromLTRB(0.0, 228.0, 0.0, 15.0) , child: new Text('Select A City',style:_biggerFont)),
new DropdownButton(
items: new List.generate(cities.length, (int index){
return new DropdownMenuItem(child: new Container(
child: new Center(
child:new Text(cities[index],textScaleFactor: 1.4,))
,width: 250.0,),value: cities[index],);
}),
value: city,
onChanged: (arg){
setState((){
city = arg;
print(arg);
});
}),
new Padding(padding: new EdgeInsets.fromLTRB(0.0, 15.0, 15.0, 0.0),
child : new IconButton(icon: new Icon(Icons.arrow_forward,color: Colors.blue,size: 60.0,),
onPressed: _produceHospitalCards)),
],
),
)
);
}
But it causes following exception.So,how to stop execution until async function completes it's task.
The following NoSuchMethodError was thrown building MyHomePage(dirty, state:
I/flutter ( 7695): _MyHomePageState#ec54c):
I/flutter ( 7695): The getter 'length' was called on null.
I/flutter ( 7695): Receiver: null
I/flutter ( 7695): Tried calling: length
You should use the FutureBuilder widget.
Here is the example from the docs:
new FutureBuilder<String>(
future: _calculation, // a Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return new Text('Press button to start');
case ConnectionState.waiting: return new Text('Awaiting result...');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return new Text('Result: ${snapshot.data}');
}
},
)
Regarding your case: if you make fetchCitiesList() return the list of cities, then you can call fetchCitiesList() instead of _calculation from the example, and then get the data by doing snapshot.data.
Hope this helps.