Here's my database (screenshot)
So I can display all books to the user in a pretty standard way using listview:
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('books').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading...');
default:
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document['text']),
subtitle: // I want to display the author's name
);
}).toList(),
);
}
},
);
But the problem is, document['author'] is a DocumentReference, which I can get by DocumentSnapshot ds = await documentSnapshot['author'].get(). But the problem is, this is a future which I cannot use inside the ListView. What is the best way to go about this? Use a Futurebuilder for each document snapshot? I also have multiple document references for each book in my actual project
You might want to use a FutureBuilder instead of a StreamBuilder. According to this answer it also removes some builder plate code. Let me know how this unfolds. You can always return a ListView.builder if your snapshot does not contain any errors. That's my approach.
Related
I am trying to make an API call using FutureBuilder in flutter but it seems like the request is not sent because I do not see the response printing. here is my future builder:
FutureBuilder(
future: authBloc.login(user, pass),
builder: (context, AsyncSnapshot snapshotItem) {
Map<String, dynamic> data = snapshotItem.data[0];
print(data['response']);
if (data.containsKey('id')) {
saveId(data['id']);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) {
return MainPage();
}));
}
if (data.containsKey("response")) {
if (data['response'] == false) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('An Error Has Occurred'),
content: Text(
'Please Make Sure That You Are Entering Valid UserName And Password'),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () =>
Navigator.of(context).pop(),
)
],
);
});
}
}
},
);
the authBloc.login(user, pass), part is login function in another file that makes the API call and I thought it is not necessary to include that file here.
Although I can't see the rest of your code, I think what you are trying to achieve should be done with an method and not a widget. When the user presses submit, call a function that basically contains what you wrote on that builder.
Calling navigator from within the builder is a bad idea. Any builder is expected to be called multiple times and in your case will lead to unexpected behaviour, that might be what you are seeing
Here is what I am trying to do
child: FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: <Widget>[
Card(
child: Text('I just want to loop over this card :)'),
),
],
)
);
}
),
I always find people looping over listView.builder. Anyone can help, please. Thank you.
I think you are making a bit of confusion about the use of these widgets.
Indeed both ListView and Column can be used to display a list of widgets, BUT, ListView.builder(...) provides a way to reuse the widgets thus is more memory efficient when you have to create a large number of widgets.
For example, when you want to display a list of electronics for an e-commerce app. For each electronic item you have a photo, title & price, in this case you would want to use a ListView.builder, because the list can be huge and you don't want to run out of memory.
Now on the other hand, the Column should be used when you have a small number of widgets that should be displayed in a list-like way (or one beneath the other).
For your case, if you want to transform the list of objects that you have, into a list of cards you can do something like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: res.data.map((item) {
return Card(child: Text(item.name));
}).toList());
);
}
),
I've assumed that res.data is a list of elements and each element has a property called name. Also in the return Card(...) line you can do extra processing of the item if you need to do so.
Hope that this can help you :).
If you have to do more processing
You can extract the processing in a method or a chain of methods something like this:
List<Widget>prepareCardWidgets(List<SomeObject> theObjects){
//here you can do any processing you need as long as you return a list of ```Widget```.
List<Widget> widgets = [];
theObjects.forEach((item) {
widgets.add(Card(child: Text(item.name),));
});
return widgets;
}
Then you can use it like this:
FutureBuilder(
future: ProductRepo().getMyProduct(),
builder: (BuildContext context, AsyncSnapshot res){
if(res.data==null){
return Container(
child: Text('this is nice'),
);
}
return Container(
child: Column(
children: prepareCardWidgets(res.data)
);
}
),
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 have a code, that uses dismissible in the listview (showes items from database). After dismissing an item it is supposed to show snackbar but it is not showing it and it seems that the dismissible is still part of the tree. Can you help me with that?
return ListView.builder(
itemCount: count,
itemBuilder: (BuildContext context, int position) {
final ThemeData theme = Theme.of(context);
return Dismissible(
key: Key(this.objs[position].id.toString()),
onDismissed: (direction) {
setState(() async {
int result = await helper.delete(this.objs[position].id);
});
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text(this.objs[position].title + "dismissed")));
},
background: Container(
color: Colors.red,
child: const ListTile(
leading: Icon(Icons.delete, color: Colors.white, size: 36.0)
)
),
child: ListTile(
leading: CircleAvatar(
backgroundColor: getColor(this.objs[position].priority),
child: Text(this.objs[position].id.toString()),
),
title: Text(obj[position].title),
subtitle: Text(objs[position].date),
onTap: () {
debugPrint("Tapped on " + objs[position].id.toString());
navigateToDetail(this.objs[position]);
},
),
);
},
);
this is called inside a Scaffold. And objs is a list that contains all my objects from the database.
Here is my delete code that is called inside onDismissed:
Future<int> delete(int id) async {
Database db = await this.db;
var result = await db.rawDelete("DELETE FROM $tblT WHERE $colId=$id");
return result;
}
I've noticed if I delete one item, and immediately try to create another one (I have an option to insert to DB):
It sometimes throws the error: A dismissed Dismissible widget is still part of the tree
Update:
Moved the delete part, before setState and I am getting the error: A dismissed Dismissible widget is still part of the tree every time I swipe to dismiss
You could try the following code for the onDismissed: property.
The problem is the future inside the onDismissed function. We need to reorder the async and await keywords.
Anyway, take care with the timings when removing successive items.
onDismissed: (direction) async {
String title = this.obj[position].title;
await helper.delete(this.obj[position].id);
setState(() {});
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("$title dismissed")));
},
It also moves the async out of the setState() and stores the title to be used later by the SnackBar.
Inside content in SnackBar you can try :
Text(this.obj[position].title.toString() + "dismissed")
I'm currently developing a reader and using PageView to slide the page of images. How do I make the next page preload so that the user can slide to next page without waiting for the page to load? I don't want to download all the pages first because it will load the server and freezes my app. I just want to download just next one or two pages when the user browsing on current page.
Here is the excerpt of my code.
PageController _controller;
ZoomableImage nextPage;
Widget _loadImage(int index) {
ImageProvider image = new CachedNetworkImageProvider("https://example.com/${bookId}/${index+1}.jpg}");
ZoomableImage zoomed = new ZoomableImage(
image,
placeholder: new Center(
child: CupertinoActivityIndicator(),
),
);
return zoomed;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: PageView.builder(
physics: new AlwaysScrollableScrollPhysics(),
controller: _controller,
itemCount: book.numPages,
itemBuilder: (BuildContext context, int index) {
return index == 0 || index == 1 ? _loadImage(index) : nextPage;
},
onPageChanged: (int index) {
nextPage = _loadImage(index+1);
},
),
),
);
}
Thank you!
Simple! Just set allowImplicitScrolling: true, // in PageView.builder
I ended up using FutureBuilder and CachedNetworkImageProvider from the package cached_network_image to prefetch all the images. Here is my solution:
PageController _controller;
ZoomableImage currPage, nextPage;
Future<List<CachedNetworkImageProvider>> _loadAllImages(Book book) async {
List<CachedNetworkImageProvider> cachedImages = [];
for(int i=0;i<book.numPages;i++) {
var configuration = createLocalImageConfiguration(context);
cachedImages.add(new CachedNetworkImageProvider("https://example.com/${bookId}/${index+1}.jpg}")..resolve(configuration));
}
return cachedImages;
}
FutureBuilder<List<CachedNetworkImageProvider>> _futurePages(Book book) {
return new FutureBuilder(
future: _loadAllImages(book),
builder: (BuildContext context, AsyncSnapshot snapshot){
if(snapshot.hasData) {
return new Container(
child: PageView.builder(
physics: new AlwaysScrollableScrollPhysics(),
controller: _controller,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
ImageProvider image = snapshot.data[index];
return new ZoomableImage(
image,
placeholder: new Center(
child: CupertinoActivityIndicator(),
),
);
},
onPageChanged: (int index) {},
),
);
} else if(!snapshot.hasData) return new Center(child: CupertinoActivityIndicator());
},
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: _futurePages(widget.book),
);
}
As people mentioned before the cached_network_image library is a solution, but not perfect for my situation. There are a full page PageView(fit width and height) in my project, when I try previous code my PageView will show a blank page first, then show the image.
I start read PageView source code, finally I find a way to fit my personal requirement. The basic idea is change PageView source code's cacheExtent
This is description about how cacheExtent works:
The viewport has an area before and after the visible area to cache items that are about to become visible when the user scrolls.
Items that fall in this cache area are laid out even though they are not (yet) visible on screen. The cacheExtent describes how many pixels the cache area extends before the leading edge and after the trailing edge of the viewport.
Change flutter's source code directly is a bad idea so I create a new PrelodPageView widget and use it at specific place when I need preload function.
Edit:
I add one more parameter preloadPagesCount for preload multiple pages automatically.
https://pub.dartlang.org/packages/preload_page_view