Flutter, render widget after async call
The best way to do this is to use a FutureBuilder.
From the FutureBuilder documentation:
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}');
}
},
)
The other thing is that you're building your widget outside of the State.build method and saving the widget itself, which is an anti-pattern. You should be actually building the widgets each time in the build method.
You could get this to work without FutureBuilder, but you should save the result of the http call (processed appropriately) and then use the data within your build function.
See this, but note that using a FutureBuilder is a better way to do this and I'm just providing this for you to learn.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'async demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List data;
@override
initState() {
super.initState();
new Future<String>.delayed(new Duration(seconds: 5), () => '["123", "456", "789"]').then((String value) {
setState(() {
data = json.decode(value);
});
});
}
@override
Widget build(BuildContext context) {
if (data == null) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Loading..."),
),
);
} else {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new ListView(
children: data
.map((data) => new ListTile(
title: new Text("one element"),
subtitle: new Text(data),
))
.toList(),
),
),
);
}
}
}
Full Example
Best way for rander widget after async call is using FutureBuilder()
class _DemoState extends State<Demo> {
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: downloadData(), // function where you call your api
builder: (BuildContext context, AsyncSnapshot<String> snapshot) { // AsyncSnapshot<Your object type>
if( snapshot.connectionState == ConnectionState.waiting){
return Center(child: Text('Please wait its loading...'));
}else{
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return Center(child: new Text('${snapshot.data}')); // snapshot.data :- get your object which is pass from your downloadData() function
}
},
);
}
Future<String> downloadData()async{
// var response = await http.get('https://getProjectList');
return Future.value("Data download successfully"); // return your response
}
}
In future builder, it calls the future function to wait for the result, and as soon as it produces the result it calls the builder function where we build the widget.
AsyncSnapshot has 3 state:
1. connectionState.none -- In this state future is null
2. connectionState.waiting -- [future] is not null, but has not yet completed
3. connectionState.done -- [future] is not null, and has completed. If the future completed successfully, the [AsyncSnapshot.data] will be set to the value to which the future completed. If it completed with an error, [AsyncSnapshot.hasError] will be true