I am trying to fetch some data from the internet. With the use of FutureBuilder, handling for various cases like offline, online,error is quite easy but I am using StreamBuilder and I am not able to understand how to handle offline case
Following is my code to use StreamBuilder which works but I have not handled offline data or error
return StreamBuilder(
builder: (context, AsyncSnapshot<SchoolListModel> snapshot) {
if (snapshot.hasError) {
return Expanded(
child: Center(
child: Text(SOMETHING_WENT_WRONG),
));
}
if (!snapshot.hasData) {
return Expanded(
child: Center(
child: CircularProgressIndicator(),
),
);
}
if (snapshot.data != null) {
if (snapshot.data.status == 1) {
return buildSchoolList(snapshot.data.schoolListData);
} else {
showMessageDialog(snapshot.data.msg.toString(), context);
}
}
},
stream: schoolListBloc.schoolList,
);
}
Now to handle the offline case I am doing the following two options which don't work in my case
Option one.
return StreamBuilder(
builder: (context, AsyncSnapshot<SchoolListModel> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text(SOMETHING_WENT_WRONG);
case ConnectionState.active:
case ConnectionState.waiting:
return Expanded(
child: Center(
child: CircularProgressIndicator(),
),
);
case ConnectionState.done:
if (snapshot.hasError) {
return errorData(snapshot);
} else {
if (snapshot.data.status == 1) {
return buildSchoolList(snapshot.data.schoolListData);
} else {
showMessageDialog(snapshot.data.msg.toString(), context);
}
}
}
},
stream: schoolListBloc.schoolList,
);
}
I keep seeing the CircularProgressIndicator and no error on the console.
I fail to understand why the above switch case works for FuturBuilder and not StreamBuilder.
Second Option.
Future<bool> checkInternetConnection() async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
return true;
}
} on SocketException catch (_) {
print('not connected');
return false;
}
return false;
}
return StreamBuilder(
builder: (context, AsyncSnapshot<SchoolListModel> snapshot) {
checkInternetConnection().then((isAvailable) {
if (isAvailable) {
if (!snapshot.hasData || snapshot.data == null) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.data != null) {
if (snapshot.data.status == 1) {
return buildSchoolList(snapshot.data.schoolListData);
} else {
showMessageDialog(snapshot.data.msg.toString(), context);
}
}
} else {
return Center(
child: Column(
children: <Widget>[
Text(CHECK_YOUR_INTERNET_CONNECTION),
RaisedButton(
onPressed: () {},
child: Text(TRY_AGAIN),
)
],
),
);
}
}); },
stream: schoolListBloc.schoolList,
);
}
Using this option throws the following error
the following assertion was thrown building StreamBuilder<SchoolListModel>(dirty, state:
I/flutter ( 5448): _StreamBuilderBaseState<SchoolListModel, AsyncSnapshot<SchoolListModel>>#dd970):
I/flutter ( 5448): A build function returned null.
I/flutter ( 5448): The offending widget is: StreamBuilder<SchoolListModel>
I/flutter ( 5448): Build functions must never return null. To return an empty space that causes the building widget to
I/flutter ( 5448): fill available room, return "new Container()". To return an empty space that takes as little room as..
What approach should I take with the following cases of offline, online and error data when using StreamBuilder
You can add Error to Stream and catch it in StreamBuilder like this:
_someStreamCtrl.addError(error); // Client is offline
And in StreamBuilder:
StreamBuilder<String>(
stream: someStream,
initialData: [],
builder: (BuildContext context,
AsyncSnapshot<String> snap) {
if (snap.hasError)
return ErrorWidget(); //Error
if (snap.hasData)
return // Desired widget
//if waiting
return CircularProgressIndicator();
);
},
);
Related
Im trying to get LayoutBuilder working with some items from my API.
with this code :
class _ContactsScreenState extends State<ContactsScreen> {
final _contacts = _dummyData();
final _selection = ValueNotifier<ResumenPublicaciones>(null);
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _dummyData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('loading...');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return buildLayoutBuilder();
}
},
);
return new Scaffold(
body: futureBuilder,
);
}
LayoutBuilder buildLayoutBuilder() {
return LayoutBuilder(builder: (context, dimens) {
if (dimens.maxWidth >= kTabletBreakpoint) {
const kListViewWidth = 300.0;
return Row(
children: <Widget>[
Container(
width: kListViewWidth,
child: buildListView((val) {
_selection.value = val;
}),
),
VerticalDivider(width: 0),
Expanded(
child: ValueListenableBuilder<ResumenPublicaciones>(
valueListenable: _selection,
builder: (context, contact, child) {
if (contact == null) {
return Scaffold(
appBar: AppBar(),
body: Center(child: Text('No Contact Selected')),
);
}
return ContactDetails(contact: contact);
},
))
],
);
}
return buildListView((val) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ContactDetails(contact: val),
),
);
});
});
}
Widget buildListView(ValueChanged<ResumenPublicaciones> onSelect) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text('Contacts'),
),
body: ListView.separated(
separatorBuilder: (context, index) => Divider(height: 0),
itemCount: _contacts.length,
itemBuilder: (context, index) {
final _contact = _contacts[index];
return ListTile(
leading: Icon(Icons.person),
title: Text(_contact.name),
subtitle: Text(_contact.count),
onTap: () => onSelect(_contact),
);
},
),
);
}
}
_dummyData() async {
var res = await fetchJobs(http.Client());
return res;
}
Im receiving this error : Class 'Future' has no instance getter 'length'.
Any Idea on how could I do this? Ias per what I read, I would need to do somethin like var _contact = Await dummyData(). But im not sure where should where.
You can't await in field initializers or in constructors. Your best choice is to keep the type of _contacts as Future<Object>, and then await it where you use the value:
itemCount: (await _contacts).length
That makes your build method asynchronous, which I believe is an issue for Flutter. You may need to use a FutureBuilder.
I am trying to fetch some data from the internet and show it int a list.
Following is my bloc code
class StudentsBloc {
final _repository = Repository();
final _students = BehaviorSubject<StudentModel>();
final BehaviorSubject<bool> _showProgress = BehaviorSubject<bool>();
final BehaviorSubject<bool> _showNoInternetViews = BehaviorSubject<bool>();
Observable<StudentModel> get students => _students.stream;
Observable<bool> get showProgress => _showProgress.stream;
Observable<bool> get showNoInternetViews => _showNoInternetViews.stream;
//FetchStudent from my Api
fetchStudents(String disciplineId, String schoolId, String year_id,
String lastIndex) async {
final student = await _repository.fetchStudents(
disciplineId, schoolId, year_id, lastIndex);
_students.sink.add(student);
}
//Check to see if user has internet or not
isNetworkAvailable(String disciplineId, String schoolId, String year_id,
String lastIndex) async {
checkInternetConnection().then((isAvailable) {
if (isAvailable) {
fetchStudents(disciplineId, schoolId, year_id, lastIndex);
} else {
_students.sink.addError(NO_NETWORK_AVAILABLE);
}
});
}
Function(bool) get changeVisibilityOfProgress => _showProgress.sink.add;
Function(bool) get changeVisibilityOfNoInternetViews =>
_showNoInternetViews.sink.add;
dispose() {
_students.close();
_showProgress.close();
_showNoInternetViews.close();
}
}
Following is my main code to hide unide Widgets
Widget buildList(StudentsBloc bloc) {
return StreamBuilder(
stream: bloc.students,
builder: (context, AsyncSnapshot<StudentModel> snapshot) {
if (snapshot.hasError) {
bloc.changeVisibilityOfProgress(false);
bloc.changeVisibilityOfNoInternetViews(true);
return StreamBuilder(
stream: bloc.showNoInternetViews,
builder: (context, snapshot) {
bool showNoInternetView = snapshot.hasData ?? false;
return Visibility(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("No Network Available"),
RaisedButton(
onPressed: () {
fetchStudents();
},
child: Text("Retry"),
)
],
),
),
visible: showNoInternetView ? true : false,
);
},
);
}
if (snapshot.hasData) {
bloc.changeVisibilityOfProgress(false);
bloc.changeVisibilityOfNoInternetViews(false);
return Refresh(
year_id: "2",
schoolId: "1",
lastIndex: "0",
disciplineId: "1",
child: ListView.builder(
itemBuilder: (context, int index) {
return buildTile(
snapshot.data.toBuilder().data.studentData[index]);
},
itemCount: snapshot.data.toBuilder().data.studentData.length,
),
);
}
if (!snapshot.hasData) {
return StreamBuilder(
builder: (context, snapshot) {
bool showProgressIndicator = snapshot.data ?? false;
return Visibility(
child: Center(
child: CircularProgressIndicator(),
),
visible: showProgressIndicator ? true : false,
);
},
stream: bloc.showProgress,
);
}
},
);
}
The buildList method is called in the body of Scaffold
void fetchStudents() {
bloc?.changeVisibilityOfNoInternetViews(false);
bloc?.changeVisibilityOfProgress(true);
bloc?.isNetworkAvailable("1", "1", "2", "0");
}
Suppose the user has internet when app is opened then i am able to see circularprogressindicator and then the list of data is visible
but suppose at the start when app is opened and the user does not have internet then i am showing the No Network Available Text and a button to retry,
now if the user has connected to the internet and then click on button to retry i am directly seeing the list of data after few seconds instead of the circularprogressindicator.I am not able to understand why the NoInternetviews are not hiding and progressindicator is showing when retry button is clicked before showing list of data.
My stream is not getting updated on retry button called. Are there any caveats for StreamBuilder within StreamBuilder?
I tried changing the StreamBuilder order as mentioned by #ivenxu in the answer but it still does not work.
Following are the links of attached code
https://drive.google.com/file/d/15Z8jXw1OpwTB1CxDS8sHz8jKyHhLwJp7/view?usp=sharing
https://drive.google.com/open?id=1gIXV20S1o5jYRnno_NADabuIj4w163fF
in view layer you can use Visibility() widget and pass visible parameter true or false when load data from Internet.
let's think about how to change the visible variable from bloc.
The parent of Visibility() widget the StreamBuilder() to stream on changes data.
for your case you need a PublishSubject inside your bloc to stream and add new data, and Observable to stream on this data on your widget.
let's show a snippet code to help you how you can implement it
The bloc contains PublishSubject and Observable to stream and add data
//this Subject allows sending data, error and done events to the listener
final PublishSubject<bool> _progressStateSubject = new PublishSubject();
//the listener are streaming on changes
Observable<bool> get progressStateStream => _progressStateSubject.stream;
//to change your progress state
void changeProgressState({bool state}) => _progressStateSubject.sink.add(state);
Here you can change your state of your progress
void callWebService() async {
//when you call your func to fetch your data change your state to true
changeProgressState(state: true);
.
.
.
if(response != null){
//when your call is done change the state to false
changeProgressState(state: false);
}
}
Your progress widget is
// Loading Widget
Widget _buildLoadingWidget(Bloc bloc) {
return StreamBuilder<bool>(
stream: bloc.progressStateStream,//streaming on changes
builder: (context, snapshot) {
return Visibility(
visible: snapshot.data ?? false,//calling when data changes
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...",
textDirection: TextDirection.ltr),
CircularProgressIndicator()
],
),
),
);
});
}
It seems the cause is that CircularProgressIndicator is inside the students stream's update cycle. And the student steam only get next snapshot when the data returned from internet call in the case of retry.
Have a try on changing the order of stream builders, try putting the student streambuilder inside of the progress stream builder.
Widget buildList(StudentsBloc bloc) {
return StreamBuilder(
stream: bloc.showProgress,
builder: (context, snapshot) {
bool showProgressIndicator = snapshot.data ?? false;
if (!showProgressIndicator) {
StreamBuilder(
stream: bloc.students,
builder: (context, AsyncSnapshot<StudentModel> snapshot) {
....
//your original code without progress StreamBuilder
}
}
return Visibility(
child: Center(
child: CircularProgressIndicator(),
),
visible: showProgressIndicator ? true : false,
);
},
);
}
The getIngredients() method below returns a list from firestore.
Future getIngredients() async {
return Firestore.instance
.collection('ingredients')
.where("name", isEqualTo: widget.dish_name.toString().toLowerCase()).getDocuments();
}
Now I want to display this list in an item builder below:
new ListView.builder(
itemExtent: 90,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return SingleIngredient(
ingredient_name: snapshot.data[index].ingredients,
);
});
I get the following error message:
_FutureBuilderState#7cbda): I/flutter (12164): Class 'QuerySnapshot' has no instance getter 'length'. I/flutter (12164):
Receiver: Instance of 'QuerySnapshot' I/flutter (12164): Tried
calling: length
Here's the scturcture of my firestore. I am fetching the ingredients list:
UPDATE
I have updated the code but I am only getting the first item from the list of ingredients (i.e, Onions). I want the itembuilder to build each item in the list because I am trying to display an image and the the ingredient list. That's what the SingleIngredient widget is doing. So, how can I loop through each list one by one?
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: getIngredients(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot user = snapshot.data.documents[index];
return SingleIngredient(
// Access the fields as defined in FireStore
ingredient_name: user.data['ingredients'][index].toString(),
);
},
);
} else if (snapshot.connectionState == ConnectionState.done &&
!snapshot.hasData) {
// Handle no data
return Center(
child: Text("No users found."),
);
} else {
// Still loading
return CircularProgressIndicator();
}
}),
);
}
}
Here's an example using StreamBuilder where i retrieve all documents from a collection and build a ListView to show them:
Widget buildUserList(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot user = snapshot.data.documents[index];
return ListTile(
// Access the fields as defined in FireStore
title: Text(user.data['firstName']),
subtitle: Text(user.data['lastName']),
);
},
);
} else if (snapshot.connectionState == ConnectionState.done && !snapshot.hasData {
// Handle no data
return Center(
child: Text("No users found."),
);
} else {
// Still loading
return CircularProgressIndicator();
}
}
Usage:
Scaffold(
body: StreamBuilder(
stream:
Firestore.instance.collection('users').snapshots(),
builder: buildUserList,
)
)
or
Scaffold(
body: FutureBuilder(
future:
Firestore.instance.collection('users').getDocuments(),
builder: buildUserList,
)
)
Dart does not know what type you are returning from the Future so it's interpreting it as a dynamic object, which has no length getter on it. Change your method
Future getIngredients() async {
return Firestore.instance
.collection('ingredients')
.where("name", isEqualTo: widget.dish_name.toString().toLowerCase()).getDocuments();
}
To
Future<List<YourType>> getIngredients() async {
return Firestore.instance
.collection('ingredients')
.where("name", isEqualTo: widget.dish_name.toString().toLowerCase()).getDocuments();
}
Where YourType is the type that's returned from the getDocuments() function. You might need to do a toList() on it as well.
use FirestoreListView from flutterfire_ui package
final usersQuery = Firestore.instance
.collection('ingredients')
.where("name", isEqualTo: widget.dish_name.toString().toLowerCase());
FirestoreListView<Map<String, dynamic>>(
query: usersQuery,
itemBuilder: (context, snapshot) {
Map<String, dynamic> recipes = snapshot.data();
return SingleIngredient(ingredient_name: recipes['ingredients'].first,);
// using 'first' because ingredients is an array
},
);
Find documentation here.
body: StreamBuilder(
stream: Firestore.instance
.collection('ups')
.where('pc',
isEqualTo: widget.post.data["pc"])
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: Text("Loading..."),
);
} else {
final ups = snapshot.data.documents;
List<String> stateWidget = [];
for (var message in ups) {
final stateText = message.data['state'];
// final stateCollection = Text('$stateText');
stateWidget.add(stateText);
}enter code here
return ListView(
children: stateWidget
.map((data) => ListTile(`enter code here`
title: Text(data),
onTap: () => {_selectedItem(data)}))
.toList(),`enter code here`
)
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.