I am trying to receive the return value of the future as a string. How do i go about it.
//Get a stock info
Future<String> getStock(int productID) async{
var dbClient = await db;
var result = await dbClient.rawQuery('SELECT * FROM $tableStock WHERE $columnProductID = $productID');
if(result.length == 0) return null;
return Stock.fromMap(result.first).currentStock;
}
Widget _buildProductInfo(Product data){
return Container(
child: ListView(
padding: EdgeInsets.all(8.0),
children: <Widget>[
_infoRow('Product ID', data.name),
_infoRow('Product Name', data.productID),
_infoRow('Cost Price', data.costPrice),
_infoRow('Selling Price', data.salePrice),
_infoRow('CategoryID', data.categoryID),
_infoRow('Currrent Stock', db.getStock(int.parse(data.productID)))
],
),
);
}
I expect this code to show a "value" rather it says "Instance of Future". But i can print the returned value when i try
final res = await db.getStock(int.parse(data.productID);
print(res);
You have to await for the future in order to unwrap the value. You can use a future builder to do this.
Instead of having this:
_infoRow('Currrent Stock', db.getStock(int.parse(data.productID))),
Have this:
FutureBuilder(
future: db.getStock(int.parse(data.productID),
builder: (context, snapshot) => _infoRow('Currrent Stock', snapshot.data),
),
Your complete code will look like this:
child: StreamBuilder<Product>(
initialData: barcode,
stream: bloc.scannedCode,
builder: (BuildContext context, AsyncSnapshot snapshot){
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Select lot');
case ConnectionState.waiting:
return _buildProductInfo(snapshot.data);
case ConnectionState.active:
case ConnectionState.done:
return _buildProductInfo(snapshot.data);
}
},
)
Widget _buildProductInfo(Product data){
return Container(
child: ListView(
padding: EdgeInsets.all(8.0),
children: <Widget>[
_infoRow('Product ID', data.name),
_infoRow('Product Name', data.productID),
_infoRow('Cost Price', data.costPrice),
_infoRow('Selling Price', data.salePrice),
_infoRow('CategoryID', data.categoryID),
FutureBuilder(
future: db.getStock(int.parse(data.productID),
builder: (context, snapshot) => _infoRow('Currrent Stock', snapshot.data),
)
],
),
);
}
You have to use async on your _buildProductInfo() method and use await before db.getStock(int.parse(data.productID)) . This way, the execution is suspended until the Future completes.
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 tried to get data from firebase but the function didn't work when i initialize it in initState.
and i tried to give it debugprint, it doesn't show in the terminal and there is no error or warning when i run the app.
void _getMarker() {
StreamBuilder(
stream: Firestore.instance
.collection('contentislamis')
.where('kategori', isEqualTo: 'Landmark')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.green)));
} else {
for (int i = 0; i < snapshot.data.documents.length; i++) {
_addMarkers(snapshot.data.documents[i]);
print("${snapshot.data.documents.length} markers added");
}
}
});
}
and this is the function to show the marker
void _addMarkers(DocumentSnapshot markData) {
final String markerIdVal = markData.documentID.toString();
final MarkerId markerId = MarkerId(markerIdVal);
double lat = double.tryParse(markData['posLat'].toString());
double long = double.tryParse(markData['posLong'].toString());
final Marker marker = Marker(
markerId: markerId,
position: LatLng(
lat,
long,
),
infoWindow: InfoWindow(
title: markData['judul'].toString(),
snippet: markData['posLat'] + markData['posLong']
),
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed)
);
setState(() {
markers[markerId] = marker;
});
}
You need to add the key of the data you want to get from firestore
for eg:
// title is the key in the firestore documents, you need to add your key here
addMarkers(snapshot.data.documents[i].data["title"])
Following code should help you, in this example i am getting the data from cloud firestore and displaying it in Listview :
Code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("posts").snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("Loading..."),
SizedBox(
height: 50.0,
),
CircularProgressIndicator()
],
),
);
} else {
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (_, index) {
return Card(
child: ListTile(
title: Text(
snapshot.data.documents[index].data["title"]), // getting the data from firestore
),
);
},
);
}
},
),
),
);
}
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'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 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.