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();
}
);
Related
I am currently displaying my icons like this:
Widget _buildPopupDialog(BuildContext context) {
List<IconData> _iconsTable = [
Icons.feedback,
Icons.eco,
Icons.support,
Icons.call,
Icons.nature_people,
Icons.directions_bike,
];
return new AlertDialog(
content: SingleChildScrollView(
child: new Container(
child: GridView.count(
children: new List.generate(6, (int index) {
return new Positioned(
child: new DailyButton(iconData: _iconsTable[index]),
);
}),
),
),
),
However, I am wanting to get the icon data from cloud firestore. I am very new to using both flutter and firebase so I am very unsure how I would be able to do this. So far, I have tried this but iconData: Icons.iconsData obviously doesnt work:
class MyApp3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage3(),
);
}
}
class MyHomePage3 extends StatefulWidget {
#override
_MyHomePageState3 createState() {
return _MyHomePageState3();
}
}
class _MyHomePageState3 extends State<MyHomePage3> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('icons').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.docs);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record3 = Record3.fromSnapshot(data);
var _iconsData = record3.name;
return Padding(
key: ValueKey(record3.name),
child: Container(
child: Card(
child: new MoodButton(
onTap: () => print("Mood"),
iconData: Icons.iconsData,
),
// trailing: Text(record3.votes.toString()),
// onTap: () => record3.reference.update({'votes': record3.votes+1})
),
),
);
}
}
class Record3 {
final String name;
final int votes;
final DocumentReference reference;
Record3.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
assert(map['votes'] != null),
name = map['name'],
votes = map['votes'];
Record3.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data(), reference: snapshot.reference);
#override
String toString() => "Record<$name:$votes>";
}
Any help would be greatly appreciated!
If anyone is interested, I was able to figure it out:
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record3 = Record3.fromSnapshot(data);
int iconCode = record3.votes;
return Padding(
key: ValueKey(record3.name),
child: Container(
child: new Container(
child: new ListView(
scrollDirection: Axis.horizontal,
children: new List.generate(1, (int index) {
return new Positioned(
child: new MoodButton(
onTap: () => print("Mood"),
iconData: (IconData(iconCode, fontFamily: 'MaterialIcons')),
),
);
})),
),
),
);
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.
flutter , I Want Change Qty List From StreamController ?
I want action ontap
IconButton Change data
Text(poduct[index].qty.toString()),
from StreamController
I don't want to use setState(() {});
import 'package:flutter/material.dart';
import 'dart:async';
void main() {
runApp(new MaterialApp(title: "Simple Material App", home: new MyHome()));
}
class MyHome extends StatefulWidget {
#override
MyHomeState createState() => new MyHomeState();
}
class Product {
String productName;
int qty;
Product({this.productName, this.qty});
}
class MyHomeState extends State<MyHome> {
List<Product> poduct = [Product(productName: "Nike",qty: 20),Product(productName: "Vans",qty: 30),];
var listPoduct = StreamController<List<Product>>();
#override
void initState() {
listPoduct.sink.add(poduct);
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("test stream"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: StreamBuilder(
stream: listPoduct.stream,
builder: (context, snapshot) {
return ListView.builder(
itemCount: poduct.length,
padding: EdgeInsets.all(10),
itemBuilder: (BuildContext context, int index){
return Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(poduct[index].productName,style: TextStyle(fontSize: 24.0),),
new IconButton(icon: new Icon(Icons.remove), onPressed: (){
// How to Add ? listPoduct.sink ?
}),
Text(poduct[index].qty.toString()), /// <<< I Want Change Qty List Form StreamController
new IconButton(icon: new Icon(Icons.add), onPressed: (){
// How to Add ? listPoduct.sink ?
}),
Divider(),
],
),
);
},
);
}
),
));
}
}
I want action ontap
IconButton Change data
Text(poduct[index].qty.toString()),
from StreamController
I don't want to use setState(() {});
void main() {
runApp(new MaterialApp(title: "Simple Material App", home: new MyHome()));
}
class MyHome extends StatefulWidget {
#override
MyHomeState createState() => new MyHomeState();
}
class Product {
String productName;
int qty;
Product({this.productName, this.qty});
}
class MyHomeState extends State<MyHome> {
List<Product> poduct = [ // <<<<<<<< TYPO HERE
Product(productName: "Nike",qty: 20),
Product(productName: "Vans",qty: 30)];
var listPoduct = StreamController<List<Product>>();
#override
void initState() {
listPoduct.sink.add(poduct);
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("test stream"),
),
body: Container(
padding: EdgeInsets.all(8.0),
child: StreamBuilder(
stream: listPoduct.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length, // <<<<<<<< . note that listbuilder relies on snapshot not on your poduct property
padding: EdgeInsets.all(10),
itemBuilder: (BuildContext context, int index){
return Padding(
padding: const EdgeInsets.only(top: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(poduct[index].productName,style: TextStyle(fontSize: 24.0),), // <<<<<<<< you can also use here the snapshot.data
new IconButton(icon: new Icon(Icons.remove), onPressed: () {
_update(index, -1);
}),
Text(poduct[index].qty.toString()), // <<<<<<<< you can also use here the snapshot.data
new IconButton(icon: new Icon(Icons.add), onPressed: (){
_update(index, 1);
}),
Divider(),
],
),
);
},
);
} else {
return Container()
}
}
),
));
}
_update(int index, int difference) {
for (int i = 0; i < poduct.length; i++ ) {
if (i == index) {
poduct[i] =
Product(productName: poduct[i].productName,
qty: poduct[i].qty + difference);
}
}
listPoduct.add(poduct);
}
}
some helpful links:
StreamBuilder-class
Example
So let's say I'm fetching different List from Different Url's
so The Future function will look like this
Future<List<City>> getCityDetails(Region selectedRegion) async {}
Future<List<Region>> getregionDetails() async {}
thus the Widget Builder will look like this
FutureBuilder<List<Region>>(
future: getregionDetails(),
builder: (context, snapshot) {}
is there a way or work around in this one? I'm trying to find an answer how to implement this one or is there another way?
i think you are looking for something like this
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget createRegionsListView(BuildContext context, AsyncSnapshot snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: values.length,
itemBuilder: (BuildContext context, int index) {
return values.isNotEmpty
? Column(
children: <Widget>[
ListTile(
title: Text(values[index].region),
),
Divider(
height: 2.0,
),
],
)
: CircularProgressIndicator();
},
);
}
Widget createCountriesListView(BuildContext context, AsyncSnapshot snapshot) {
var values = snapshot.data;
return ListView.builder(
itemCount: values == null ? 0 : values.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () {
setState(() {
selectedCountry = values[index].code;
});
print(values[index].code);
print(selectedCountry);
},
child: Column(
children: <Widget>[
new ListTile(
title: Text(values[index].name),
selected: values[index].code == selectedCountry,
),
Divider(
height: 2.0,
),
],
),
);
},
);
}
final String API_KEY = "03f6c3123ea549f334f2f67c61980983";
Future<List<Country>> getCountries() async {
final response = await http
.get('http://battuta.medunes.net/api/country/all/?key=$API_KEY');
if (response.statusCode == 200) {
var parsedCountryList = json.decode(response.body);
List<Country> countries = List<Country>();
parsedCountryList.forEach((country) {
countries.add(Country.fromJSON(country));
});
return countries;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load ');
}
}
Future<List<Region>> getRegions(String sl) async {
final response = await http
.get('http://battuta.medunes.net/api/region/$sl/all/?key=$API_KEY');
if (response.statusCode == 200) {
var parsedCountryList = json.decode(response.body);
List<Region> regions = List<Region>();
parsedCountryList.forEach((region) {
regions.add(Region.fromJSON(region));
});
return regions;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load ');
}
}
String selectedCountry = "AF";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Row(children: [
Expanded(
child: FutureBuilder(
future: getCountries(),
initialData: [],
builder: (context, snapshot) {
return createCountriesListView(context, snapshot);
}),
),
Expanded(
child: FutureBuilder(
future: getRegions(selectedCountry),
initialData: [],
builder: (context, snapshot) {
return createRegionsListView(context, snapshot);
}),
),
]),
);
}
}
class Country {
String name;
String code;
Country({this.name, this.code});
factory Country.fromJSON(Map<String, dynamic> json) {
return Country(
name: json['name'],
code: json['code'],
);
}
}
class Region {
String country;
String region;
Region({this.country, this.region});
factory Region.fromJSON(Map<String, dynamic> json) {
return Region(
region: json["region"],
country: json["country"],
);
}
}
I get the list of files from the user's folder. The names of the files I transfer to the ListView.builder. It's work, but I think, this is bad architecture.
A method _getFilesFromDir() call with a high frequency.
How to make the correct list generation, so as not to update the interface without changing the file list?
class CharacteristList extends StatefulWidget {
#override
_CharacteristListState createState() => new _CharacteristListState();
}
class _CharacteristListState extends State<CharacteristList> {
List<String> filesList = new List<String>();
List<String> filesL = new List<String>();
#override
void initState() {
super.initState();
filesList = [];
}
Future<List<String>> _getFilesFromDir() async{
filesL = await FilesInDirectory().getFilesFromDir();
setState(() {
filesList = filesL;
});
return filesList;
}
_getFilesCount(){
_getFilesFromDir();
int count = filesList.length;
return count;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Список документов'),
),
body: new Column(
children: <Widget>[
new Expanded(
child: new ListView.builder(
//TODO не успевает сформировать список файлов
itemCount: _getFilesCount(),
itemBuilder: (context, index){
return new CharacteristListItem(filesList[index]);
},
),
),
],
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context)
=> new StartScreen()),
);},
child: new Icon(Icons.add),
),
);
}
}
// add dependancy in pubspec.yaml
path_provider:
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart';
//Declare Globaly
String directory;
List file = new List();
#override
void initState() {
// TODO: implement initState
super.initState();
_listofFiles();
}
// Make New Function
void _listofFiles() async {
directory = (await getApplicationDocumentsDirectory()).path;
setState(() {
file = io.Directory("$directory/resume/").listSync(); //use your folder name insted of resume.
});
}
// Build Part
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'List of Files',
home: Scaffold(
appBar: AppBar(
title: Text("Get List of Files with whole Path"),
),
body: Container(
child: Column(
children: <Widget>[
// your Content if there
Expanded(
child: ListView.builder(
itemCount: file.length,
itemBuilder: (BuildContext context, int index) {
return Text(file[index].toString());
}),
)
],
),
),
),
);
}
Don't call _getFilesCount() in build(). build() can be called very frequently. Call it in initState() and store the result instead of re-reading over and over again.
I changed the architecture of the class - I used FutureBuilder.
class _CharacteristListState extends State<CharacteristList> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text('Список документов'),
),
body: new Center(
child: new Column(
children: <Widget>[
new FutureBuilder(
future: _inFutureList(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return new Text('Data is loading...');
}
else{
return customBuild(context, snapshot);
}
}
)
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
Navigator.push(context,
new MaterialPageRoute(builder: (context)
=> new StartScreen()),
);},
child: new Icon(Icons.add),
),
);
}
Widget customBuild(BuildContext context, AsyncSnapshot snapshot){
List<String> values = snapshot.data;
return new Container(
child: new Expanded(
child: new ListView.builder(
itemCount: values.length,
itemBuilder: (context, index){
return new CharacteristListItem(values[index]);
},
),
)
);
}
Future<List<String>>_inFutureList() async{
var filesList = new List<String>();
filesList = await FilesInDirectory().getFilesFromDir();
await new Future.delayed(new Duration(milliseconds: 500));
return filesList;
}
}
// add dependancy in pubspec.yaml
path_provider:
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart';
//Declare Globaly
String directory;
List file = new List();
#override
void initState() {
// TODO: implement initState
super.initState();
_listofFiles();
}
// Make New Function
void _listofFiles() async {
directory = "/storage/emulated/0/Android/data/"; //Give your folder path
setState(() {
file = io.Directory("$directory/resume/").listSync(); //use your folder name insted of resume.
});
}
// Build Part
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'List of Files',
home: Scaffold(
appBar: AppBar(
title: Text("Get List of Files with whole Path"),
),
body: Container(
child: Column(
children: <Widget>[
// your Content if there
Expanded(
child: ListView.builder(
itemCount: file.length,
itemBuilder: (BuildContext context, int index) {
return Text(file[index].toString());
}),
)
],
),
),
),
);
}