fifth.dart
import 'package:flutter/material.dart';
import 'package:emas_app/Dependant.dart' as Dep;
import 'package:emas_app/crm_dependent_list_model.dart';
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:emas_app/crm_dep_entitlement_model.dart';
final String url = "http://crm.emastpa.com.my/MemberInfo.json";
//Future to get list of dependent names
Future<List<DependantModel>> fetchUserInfo() async{
http.Response response = await http.get(url);
var responsejson = json.decode(response.body);
return(responsejson[0]['Dependents'] as List)
.map((user) => DependantModel.fromJson(user))
.toList();
}
class Fifth extends StatefulWidget {
#override
_FifthState createState() => _FifthState();
}
class _FifthState extends State<Fifth> {
static Future<List<DependantModel>> depState;
#override
void initState() {
depState = fetchUserInfo();
super.initState();
}
#override
Widget build(BuildContext context) {
//ListView.builder inside FutureBuilder
var futureBuilder = new FutureBuilder<List<DependantModel>>(
future: depState,
builder: (context, snapshot){
switch(snapshot.connectionState){
case ConnectionState.none:
case ConnectionState.waiting:
return new Center(
child: new CircularProgressIndicator(),
);
default:
if(snapshot.hasError){
return new Text(snapshot.error);
}else{
List<DependantModel> user = snapshot.data;
return new ListView.builder(
itemCount: user.length,
itemBuilder: (context, index){
return new Column(
children: <Widget>[
new ListTile(
title: new Text(user[index].name,
style: TextStyle(fontSize: 20.0)),
subtitle: new Text(user[index].relationship,
style: TextStyle(fontSize: 15.0)),
trailing: new MaterialButton(color: Colors.greenAccent,
textColor: Colors.white,
child: new Text("More"),
onPressed: (){
Navigator.push(context,
new MaterialPageRoute(builder: (context) => Dep.Dependents(name: user[index].name)));
}
),
)
],
);
});
}
}
});
return new Scaffold(
body: futureBuilder,
);
}
}
I've just had the following error occurring in my flutter project.
I/flutter (12737): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (12737): The following assertion was thrown building FutureBuilder>(dirty, state:
I/flutter (12737): _FutureBuilderState>#b09b6):
I/flutter (12737): type 'NoSuchMethodError' is not a subtype of type 'String'
I/flutter (12737): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter (12737): more information in this error message to help you determine and fix the underlying cause.
I/flutter (12737): In either case, please report this assertion by filing a bug on GitHub:
what is dirty state?
As the error suggests, NoSuchMethodError, you have assigned the results of Future to a variable but the FutureBuilder expects future asynchronous computation to which this builder is currently connected and depState is not a method. You can directly set future: fetchUserInfo in FutureBuilder
Try this,
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder<List<DependantModel>>(
future: fetchUserInfo, //your Future method
builder: (context, snapshot){
//Your code goes here
}
});
return new Scaffold(
body: futureBuilder,
);
}
Related
I am just trying out flutter and I cannot seem to get components to render conditionally based on a BehaviourStream in my Bloc.
I wish to initially show the "_buildPage()" widget (which is an auth form), then while _isLoading is true but (_loginSucceded is false) I wish to show the spinner. Lastly, when _loginSucceded is true and _isLoading is false, I wish to redirect the user.
Actual behaviour is once form is submitted loader shows as expected. Once the response is successfully received from the server however the auth for is rendered once again.
I think my logic is fine but it seems that when I set the values of the stream in the Bloc constructor something else is causing the app to rerender which results in null values in the stream.
Is there a way to ensure a stream always has base values after the constructor has run upon initialisation?
Or is there a better way to manage this scenario? I have only just started looking at Flutter from a JS background so I may well be missing something.
Bloc code:
import 'dart:async';
import 'dart:convert';
import 'package:rxdart/rxdart.dart';
import 'package:http/http.dart' as http;
import './auth_validator.dart';
class AuthBloc with AuthValidator {
final _email = BehaviorSubject<String>();
final _password = BehaviorSubject<String>();
final _isLoading = BehaviorSubject<bool>();
final _loginSucceded = BehaviorSubject<bool>();
AuthBloc() {
_isLoading.sink.add(false);
_loginSucceded.sink.add(false);
}
// stream getters
Stream<String> get email => _email.stream.transform(validateEmail);
Stream<String> get password => _password.stream.transform(validatePassword);
Stream<bool> get isLoading => _isLoading.stream;
Stream<bool> get loginSuccess => _loginSucceded.stream;
Stream<bool> get submitValid =>
Observable.combineLatest2(email, password, (e, p) => true);
// add data to sink onChange
Function(String) get emailChanged => _email.sink.add;
Function(String) get passwordChanged => _password.sink.add;
void submitForm() async {
try {
final Map user = {'email': _email.value, 'password': _password.value};
final jsonUser = json.encode(user);
_isLoading.sink.add(true);
// submit to server
final http.Response response = await http.post(
'http://192.168.1.213:5000/api/users/signin',
body: jsonUser,
headers: {'Content-Type': 'application/json'},
);
final Map<String, dynamic> decodedRes = await json.decode(response.body);
_isLoading.sink.add(false);
_loginSucceded.sink.add(true);
void dispose() {
_email.close();
_password.close();
_isLoading.close();
_loginSucceded.close();
}
} catch (e) {
print('error: $e');
_isLoading.sink.add(false);
}
}
}
Widget code:
import 'package:flutter/material.dart';
import '../blocs/auth_bloc.dart';
class LoginPage extends StatelessWidget {
final authBloc = AuthBloc();
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: authBloc.loginSuccess,
builder: (context, snapshot1) {
return StreamBuilder(
stream: authBloc.isLoading,
builder: (context, snapshot2) {
print('loginSuccess? ${snapshot1.data} isLoading? ${snapshot2.data}');
return Scaffold(
body: !snapshot1.data && snapshot2.data
? _circularSpinner()
: snapshot1.data && snapshot2.data
? Navigator.pushReplacementNamed(context, '/dashboard')
: _buildPage());
},
);
},
);
}
Widget _buildPage() {
return Container(
margin: EdgeInsets.all(20.0),
child: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_emailField(authBloc),
_padding(),
_passwordField(authBloc),
_padding(),
_submitButton(authBloc)
],
),
),
),
);
}
Widget _circularSpinner() {
return Center(
child: CircularProgressIndicator(),
);
}
Widget _emailField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.email,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.emailChanged,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: 'you#example.com',
labelText: 'Email Address',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _passwordField(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.password,
builder: (BuildContext context, snapshot) {
return TextField(
onChanged: authBloc.passwordChanged,
obscureText: true,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
hintText: '8 characters or more with at least 1 number',
labelText: 'Password',
errorText: snapshot.error,
border: OutlineInputBorder(),
),
);
},
);
}
Widget _padding() {
return Padding(
padding: EdgeInsets.only(top: 20.0),
);
}
Widget _submitButton(AuthBloc authBloc) {
return StreamBuilder(
stream: authBloc.submitValid,
builder: (context, snapshot) {
return RaisedButton(
child: Text('Login'),
color: Colors.blue,
onPressed: snapshot.hasError ? null : authBloc.submitForm,
);
});
}
}
main.dart
import 'package:flutter/material.dart';
import './app.dart';
import 'package:flutter/material.dart';
import './pages/auth.dart';
import './pages/dashboard.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (BuildContext context) => LoginPage(),
'/dashboard': (BuildContext context) => DashBoardPage(),
},
);
}
}
Log
Restarted application in 1,462ms.
I/flutter ( 4998): loginSuccess? false isLoading? false
I/flutter ( 4998): loginSuccess? null isLoading? null
I/flutter ( 4998): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4998): The following assertion was thrown building StreamBuilder<bool>(dirty, state:
I/flutter ( 4998): _StreamBuilderBaseState<bool, AsyncSnapshot<bool>>#34870):
I/flutter ( 4998): Failed assertion: boolean expression must not be null
I/flutter ( 4998):
I/flutter ( 4998): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 4998): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 4998): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 4998): https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 4998):
I/flutter ( 4998): When the exception was thrown, this was the stack:
I/flutter ( 4998): #0 LoginPage.build.<anonymous closure>.<anonymous closure>
as user #user10539074 mentioned, you need to set initial data for the first run of the streambuilder - ie. in your code - if authBloc.isLoading is bool, than change:
StreamBuilder(
stream: authBloc.isLoading,
builder: (context, snapshot2) {
to:
StreamBuilder(
stream: authBloc.isLoading,
initialData: true,
builder: (context, snapshot2) {
I am trying to fetch documents from firestore with the following code:
Future getCategories() async {
var firestore = Firestore.instance;
QuerySnapshot qn = await firestore.collection("categories").getDocuments();
return qn.documents;
}
#override
Widget build(BuildContext context) {
return Container(
child:FutureBuilder(
future:getCategories(),
builder:(context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child:Text("Loading...")
);
}
else
{
return GridView.builder(
itemCount: snapshot.data.length,
gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 6.0, mainAxisSpacing: 6.0, crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return SingleCategory(
category_name: snapshot.data[index].data["title"],
category_picture: snapshot.data[index].data["picture"],
);
}
);
}
}
)
);
When I run the code, I get the following error:
I/flutter ( 7555): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY
╞═══════════════════════════════════════════════════════════ I/flutter
( 7555): The following NoSuchMethodError was thrown building
FutureBuilder(dirty, state: I/flutter ( 7555):
_FutureBuilderState#c3e7b): I/flutter ( 7555): The getter 'length' was called on null. I/flutter ( 7555): Receiver: null
I/flutter ( 7555): Tried calling: length I/flutter ( 7555): I/flutter
( 7555): When the exception was thrown, this was the stack: I/flutter
( 7555): #0 Object.noSuchMethod
(dart:core/runtime/libobject_patch.dart:50:5)
Can anyone help me please.
Try this :
future: getData(),
builder: (context, AsyncSnapshot<List<User>> snapshot)
This is the complete solution for api integration using http
Please add the http dependency:
http: ^0.13.3
import the http package:
import 'package:http/http.dart' as http;
Add Internet permission in AndroidManifest.xml.
Here is the complete code of Fetch data from Api using http.
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Food recipe',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.white,
textTheme: TextTheme(
bodyText2: TextStyle(color: Colors.white),
),
),
home: DataFromApi(),
);
}
}
class DataFromApi extends StatefulWidget {
#override
_DataFromApiState createState() => _DataFromApiState();
}
class _DataFromApiState extends State<DataFromApi> {
Future<List<Data>> getData() async {
var response =
await http.get(Uri.https('jsonplaceholder.typicode.com', 'users'));
var jsonData = jsonDecode(response.body);
List<Data> dataList = [];
for (var u in jsonData) {
Data data = Data(u["name"], u["phone"], u["email"]);
dataList.add(data);
}
print(dataList.length);
return dataList;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Data Fetch"),
),
body: Container(
child: Card(
child: FutureBuilder<List<Data>>(
future: getData(),
builder: (context, snapshot) {
if (snapshot.data == null) {
return Container(
child: Text("Loading"),
);
}else{
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, i) {
return ListTile(
title: Column(
children: [
Text(snapshot.data![i].name),
Text(snapshot.data![i].phone),
Text(snapshot.data![i].email),
],
),
);
});
}
},
),
),
));
}
}
class Data {
final String name, phone, email;
Data(this.name, this.phone, this.email);
}
As we found out in the comments, you are using an auth rule, which denies access for all requests:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
I think you wanted to write something like this (read only mode):
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
allow write: if false;
}
}
}
Try this rule
You should add initialData attribute in FutureBuilder widget and set it to [] empty list. For example:
FutureBuilder( // only add initialData: []
initilData: [], // this is vital to get rid of null length error
future:getCategories(),
builder:(context, snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child:Text("Loading...")
);
} // and same code goes below
So, our main aim to prevent null length error, by adding initialData property to empty array can solve our problem.
Here is the official definition of this property---
The data that will be used to create the snapshots provided until a
non-null future has completed.
also we can add some loading widget to not showing empty screen to the user.
We can add a condition "when the snapshot has data which is equal to empty array (or data.length == 0) if it is then show loading widget else show list".
I had the same problem try to use this.
builder:(ctx, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot)
full StreamBuilder code with itemCount of listView if you need the length of documents
StreamBuilder(
stream: FirebaseFirestore.instance.collection('categories').snapshots(),
builder:
(ctx, AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (ctx, index) => Text('sample'));
});
You can use dynamic
child: FutureBuilder<dynamic>(
future: getApiData(),
...
...
Its works for me.
Simplest way is to use the hasData parameter of snapshot.
if (snapshot.hasData) { return GridViewBuilder(...);}
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.
I'm working on a Camera app. I'm using the following Camera plugin - https://github.com/flutter/plugins/tree/master/packages/camera
Here is my working code -
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
List<CameraDescription> cameras;
Future<Null> main() async {
cameras = await availableCameras();
runApp(new MaterialApp(
home: new CameraApp(),
));
}
class CameraApp extends StatefulWidget {
#override
_CameraAppState createState() => new _CameraAppState();
}
class _CameraAppState extends State<CameraApp> {
CameraController controller;
#override
void initState() {
super.initState();
controller = new CameraController(cameras[0], ResolutionPreset.medium);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// camera widget
Widget cameraView = new Container(
child: new Row(children: [
new Expanded(
child: new Column(
children: <Widget>[
new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller)
)
]
),
)
])
);
return new Scaffold(
body: new Stack(
children: <Widget>[
(!controller.value.initialized) ? new Container() : cameraView,
// ---On top of Camera view add one mroe widget---
],
),
);
}
}
When I'm building the app I'm getting following errors...
I/flutter ( 2097): The following NoSuchMethodError was thrown building CameraApp(dirty, state: _CameraAppState#a0666):
I/flutter ( 2097): The getter 'height' was called on null.
I/flutter ( 2097): Receiver: null
I/flutter ( 2097): Tried calling: height
Even though you have the ternary operator inside the body of the Stack, you are creating the Widget cameraView regardless of whether it is going to be used - so it is being created whether controller.value.initialized is true or false. Adjust the code so that the CameraPreview tree is only built if it is needed, i.e. if initialized is true. For example:
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
children: <Widget>[
(!controller.value.initialized) ? new Container() : buildCameraView(),
// ---On top of Camera view add one mroe widget---
],
),
);
}
Widget buildCameraView() {
return new Container(
child: new Row(
children: [
new Expanded(
child: new Column(
children: <Widget>[
new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller),
),
],
),
),
],
),
);
}
As you suggest in your comment, you can move the ternary operator lower in your build tree, too, and replace just the AspectRatio with an empty Container.
if even after using this check (!controller.value.initialized) ? new Container() : cameraView,still you are getting error that "getter 'height' was called on null",
and the error message is poping on your app only for fraction of second, then it means you are initializing your camera controller in didChangeDependencies()...if yes then use this technique.
bool cameraInitialized = false;
#override
void didChangeDependencies() {
if (cameraInitialized == false) {
final ScreenArguments arguments =
ModalRoute.of(context).settings.arguments;
int cameraIndex = Provider.of<XYZ>(context)
.XX
.firstWhere((element) => element.id == arguments.XX`enter code here`Id)
.cameraIndex;
controller = new CameraController(
widget.cameras[cameraIndex], ResolutionPreset.medium);
controller.initialize().then((value) {
if (!mounted) {
return;
}
setState(() {});
});
setState(() {
cameraInitialized = true;
});
}
super.didChangeDependencies();
}
I have the following screen:
import 'package:flutter/material.dart';
import '../models/patient.dart';
import '../components/patient_card.dart';
import '../services.dart';
class Home extends StatefulWidget {
var patients = <Patient>[];
#override
_HomeState createState() => new _HomeState();
}
class _HomeState extends State<Home> {
#override
initState() {
super.initState();
Services.fetchPatients().then((p) => setState(() => widget.patients = p));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Home'),
),
body: new Container(
child: new ListView(
children: widget.patients.map(
(patient) => new PatientCard(patient),
).toList()
)
)
);
}
}
As you can see I do the endpoint call when I overwrite initState() in _HomeState. But it only runs once initially when the app starts. I can't just type r in my terminal and let the app hot reload and call the endpoint again.. I have to use Shift + r to do a full restart first.
So the question is, am I calling the web service in the recommended spot? And if it not... where does it go? Also, shouldn't ListView have a function / property that gets called on "pull to refresh" or something?
As mentioned by #aziza you can use a Stream Builder or if you want to call a function every time widget gets built then you should call it in build function itself. Like in your case.
#override
Widget build(BuildContext context) {
Services.fetchPatients().then((p) => setState(() => widget.patients = p));
return new Scaffold(
appBar: new AppBar(
title: new Text('Home'),
),
body: new Container(
child: new ListView(
children: widget.patients.map(
(patient) => new PatientCard(patient),
).toList()
)
)
);
}
If you want to add pull-to-refresh functionality then wrap your widget in refresh indicator widget. Add your call in onRefresh property.
return new RefreshIndicator(child: //Your Widget Tree,
onRefresh: handleRefresh);
Note that this widget only works with vertical scroll view.
Hope it helps.
Have a look on StreamBuilder. This widget will allow you to deal with async data that are frequently updated and will update the UI accordingly by listening onValue at the end of your stream.
Flutter have FutureBuilder class, you can also create your widget as shown below
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: Services.fetchPatients().then((p) => setState(() => widget.patients = p)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return new Container(
child: new ListView(
children: snapshot.data.map(
(patient) => new PatientCard(patient),
).toList()
)
);
}
} else {
return new Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(16.0),
child: new CircularProgressIndicator());
}
});
return new Container(child: futureBuilder);
}
Example project : Flutter - Using the future builder with list view.