How to display data of firestore DocumentReference in flutter - flutter

I got two collection in my firestore database and It's structure like,
Collection1 - movies
-movieTitle : String
-movieYear : String
-movieDirector : DocumentReference
.
.
etc
Collection2 - directors
-dirName: String
-dirImage: String
.
.
etc
I want to display movieTitle and dirName in a ListTile.
Here how I have tried to do so,
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _TestPageState();
}
}
class _TestPageState extends State<TestPage> {
DocumentSnapshot document;
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('movies').snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> movieSnapshot) {
if (!movieSnapshot.hasData) return const Text('Loading...');
final int messageCount = movieSnapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
document = movieSnapshot.data.documents[index];
return ListTile(
title: Text(document['movieTitle'] ?? 'title not retrieved'),
subtitle: Text(getValue(document["movieDirector"]) ??
'director not retrieved'),
);
},
);
},
),
);
}
String getValue(DocumentReference documentReference) {
String val;
documentReference.get().then((onData) {
val = onData.data["directorName"];
print(val);
});
return val;
}
}
Finally I couldn't be able to get the value on screen. What should I change in my implementation?

You need to learn about Futures and async/await so you can comfortably write such code.
The problem here is that getValue has to return immediately, but the directorName that it asks arrives sometime in the future. Therefore you simply can't get it right now.
Giving a FutureBuilder to the subtitle: is one of the options you can pursue.
Also you should consider caching the stream (and the future, if you implement it as per my suggestion above) so that you do not make unwanted requests. Right here I try to explain it in a presentation of mine: https://youtu.be/0gBsHLgCY6M?list=PL0bBHyAilexzBdvHookPcPZNMceciAaZf&t=1900

Related

Is there a way of showing information from a firebase array as string in flutter?

Basically, I have a set of tags done as an array in firebase and want to show them as string in flutter. Is this possible? I'm completely lost here.
I've gotten this far: but I'm not sure I understand what I'm doing here and it doesn't seem to work
class Tags {
List<dynamic>? selectedItems;
Tags fromMap(Map<String, dynamic> map) {
selectedItems =
(map[selectedItems] as List).map((item) => item as String).toList();
return this;
}
}
class TagsList extends StatelessWidget {
const TagsList({super.key});
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: Center(child: Text('${Tags}')),
);
});
}
}
I hope that I understood your question right , You want to render the items that you got from firebase in your screen? if yes then here is a code snippet .
void getDataBaseCarouselData() async {
final data = await _firestore.collection("Carousels").get();
carouselItems = [];
for (var item in data.docs) {
carouselItems.add(CarouselItem(
title: item.data()["title"],
description: item.data()["description"],
imageUrl: item.data()["imageUrl"],
id: item.id));
}
notifyListeners();
}
.get() return a Map that you can use to get data from Objects using the tags name ["field name in firebase"] and then you can use the List of object to render them into your screen .
If I didn't answer it please provide more information so I can get it clear . Thank you

UI is not updated after replacing an item in list when using notifyListeners()

I'm using the Provider package for state management in a Flutter app and I have a list model extending ChangeNotifier.
In the list model there is a method to replace a certain element in the list like this:
class MyListModel extends ChangeNotifier {
List<MyListItem> _myList = [];
void replace(Data data) {
int index = _findById(data.id);
if(index == -1) {
return;
}
_myList[index] = MyListItem(data);
log("After replace: " + _myList.toString());
notifyListeners();
}
void add(MyListItem myItem) {
_myList.add(myItem);
notifyListeners();
}
void remove(MyListItem myItem) {
_myList.remove(myItem);
notifyListeners();
}
}
This is the lis and the list item class where the provider is consumed:
class _MyListView extends StatelessWidget {
final Data _data;
const _SelectUpcomingMealList(this.upcomingMeal);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, index) {
return MyListItem(_data);
}
);
}
}
class MyListItem extends StatelessWidget {
final Data _data;
MyListItem(this._data);
#override
Widget build(BuildContext context) {
return Consumer<MyListModel>(
builder: (context, myListModel, children) => ListTile(
title: Text(_data.name),
subtitle: Text(_data.description),
trailing: const Icon(Icons.add),
onTap: () => replaceMyItem(myListModel, context),
)
);
}
void replaceMyItem(MyListModel myListModel, BuildContext context) {
myListModel.replace(_data);
Navigator.pop(context);
}
}
For some reason the UI is not updating and the replaced item is not displayed, the old item is visible. The logging shows that the list is properly updated (the index also properly calculated), the replaced element is there, but the UI does not update.
The add() and remove() methods work, in these cases the UI properly reflects the change.
Is there something I'm missing in case of an item being replaced?

Using a FutureBuilder in a Flutter stateful widget with RefreshIndicator

I have a Flutter widget which gets data from a server and renders a List. After getting the data, I parse the data and convert it to an internal object in my application, so the function is something like this:
Future<List<Data>> getData(Thing thing) async {
var response = await http.get(Uri.parse(MY_URL));
// do some processing
return data;
}
After that, I've defined a stateful widget which calls this function and takes the future to render a List.
class DataList extends StatefulWidget {
const DataList({Key key}) : super(key: key);
#override
_DataListState createState() => _DataListState();
}
class _DataListState extends State<DataList> {
Widget createListView(BuildContext context, AsyncSnapshot snapshot) {
List<Data> values = snapshot.data;
if (values.isEmpty) {
return NoResultsWidget('No results.');
}
return ListView.builder(
itemCount: values.length,
itemBuilder: (BuildContext context, int index) {
return values[index];
},
);
}
#override
Widget build(BuildContext context) {
var data = getSomething().then((thing) => getData(thing));
return FutureBuilder(
future: data,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return CustomErrorWidget('Error');
case ConnectionState.waiting:
return LoadingWidget();
default:
if (snapshot.hasError) {
return CustomErrorWidget('Error.');
} else {
return createListView(context, snapshot);
}
}
},
);
}
}
Now, the code works just fine in this manner. But, when I try to move my data to be a class variable (of type Future<List>) that I update through the initState method, the variable just never updates. Example code below:
class _DataListState extends State<DataList> {
Future<List<Data>> data;
....
#override
void initState() {
super.initState();
updateData();
}
void updateData() {
data = getSomething().then((thing) => getData(thing));
}
....
}
I want to add a refresh indicator to update the data on refresh, and to do that I need to make my data a class variable to update it on refresh, but I can't seem to figure out how to make my data part of the state of the stateful widget and have it work. any help or guides to a github code example would be appreciated.
You need to wrap the assignment of the data variable in setState so that Flutter knows the variable changed and rebuilds your widget.
For example:
void updateData() {
setState(() {
data = getSomething().then((thing) => getData(thing));
});
}

Widgets with future builder not removing widget after provider was updated with async

I have been learning flutter for 2-3 months now and I feel I have a reached a fundamental roadblock with understanding state management. This post will be long unfortunately so please bare with me and I hope I put the right detail.
Problem Definition
I have a list of widgets in a shopping cart,im at the point where I click minus and it only has 1 left the widget must be removed.No matter what I try I cant get that widget to be removed. If I click back button and go back into cart the Item will not appear anymore.
I have considered other methods, like disposing the widget(that didn't seem to work) and I was busy implementing Visibility Show/hide widgets in Flutter programmatically
but that doesn't feel like the right way.If my understanding of providers,changeNotifiers,async and future builders,is correct the below method should work and I think its fundamental to my flutter journey to understand why it doesn't work.
Overview:The idea was to use the minus button on CartItemWidget to call a method that updates Json stored on the local device, then repopulate the List cartProdList in ProductProvider which calls
notifyListeners() and then should propagate everywhere the provider is used. Now I have used this pattern successfully 5 times now, the only different this time is it will be removing a widget which I haven't done before. But this should work dynamically if the future is based of the same provider right ?
function call order
CartItemWidget.onPressed:()
calls >>>
ProductProvider.cartMinusOne(String id)
calls >>>
ProductProvider.Future<List<Product>> cartProducts()
well here goes the code.I also wouldn't mind comments on things I could be doing better in all areas.
CartWidget
class CartWidget extends StatefulWidget {
#override
_CartWidgetState createState() => _CartWidgetState();
}
class _CartWidgetState extends State<CartWidget> {
var providerOfProd;
ProductProvider cartProdProvider = new ProductProvider();
#override
void initState() {
_productsList = new ProductsList();
super.initState();
providerOfProd = Provider.of<ProductProvider>(context, listen: false).cartProducts();
}
#override
Widget build(BuildContext context) {
........
Column(children: <Widget>[
FutureBuilder(
future: providerOfProd,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Container(
width: 0,
height: 0,
);
case ConnectionState.done:
return ListView.separated(
..............
},
itemBuilder: (context, index) {
return CartItemWidget(
product: cartProdProvider.cartProdList.elementAt(index),
heroTag: 'cart',
quantity: cartProdProvider.cartProdList.elementAt(index).cartqty,
key: UniqueKey(),
);
},
);
.........
CartItemWidget
class CartItemWidget extends StatefulWidget {
CartItemWidget({Key key, this.product, this.heroTag, this.quantity = 1}) : super(key: key);
// ProductProvider cartProd = new ProductProvider();
String heroTag;
Product product;
int quantity;
#override
_CartItemWidgetState createState() => _CartItemWidgetState();
}
class _CartItemWidgetState extends State<CartItemWidget> {
#override
Widget build(BuildContext context) {
return Consumer<ProductProvider>(
builder: (context, productProv, _) => InkWell(
child: Container(
.............
child: Row(
children: <Widget>[
.............
IconButton(
onPressed: () {
setState(() {
productProv.cartMinusOne(widget.product.id);
widget.quantity = this.decrementQuantity(widget.quantity);
});
}
.............
ProductProvider
class ProductProvider with ChangeNotifier {
ProductProvider() {
cartProducts();
}
List<Product> cartProdList;
cartMinusOne(String id) async {
//Code to minus item,then return as a string to save as local jason
var test = jsonEncode(cartList);
saveLocalJson(test, 'cart.json');
cartProducts();
notifyListeners();
}
Future<List<Product>> cartProducts() async {
String jsonString = await JsonProvider().getProductJson();
String cartString = await getCartJson();
var filterProdList = (json.decode(jsonString) as List).map((i) => Product.fromJson(i)).toList();
//code to get match cart list to product list
cartProdList = filterProdList.where((element) => element.cartqty > 0).toList();
notifyListeners();
return cartProdList;
}
........................

Listen to changes to a Firebase collection and all documents within

I'm building an app that includes discussion threads. Each Thread has a subcollection of Replies, and each Reply has a map of Reactions like this. So each Thread's subcollection would look like this if JSONified:
{
'replyDocument1': {
'authorName': 'Steve Dave',
'text': 'this is a reply',
'reactions': {'👍': ['userid1', 'userid2']}
},
...
}
I'm trying to use a Stream/Sink set up to listen to changes in the Replies subcollection. ie: Rebuild the Replies ListView anytime someone adds a Reaction to any Reply in the subcollection.
This is what I've got so far. The models and RepliesApi class don't do anything exciting so I've left them out.
class RepliesBloc {
final String threadId;
RepliesApi _api;
final _controller = StreamController<List<Reply>>.broadcast();
Stream<List<Reply>> get stream =>
_controller.stream.asBroadcastStream();
StreamSink<List<Reply>> get sink => _controller.sink;
RepliesBloc(this.threadId) {
this._api = RepliesApi(this.threadId);
this
._api
.collectionReference
.getDocuments()
.asStream()
.listen(_repliesUpdated);
this._api.collectionReference.snapshots().listen(_repliesUpdated);
}
void _repliesUpdated(QuerySnapshot querySnapshot) {
List<Reply> replies = _api.fromQuerySnapshot(querySnapshot);
this.sink.add(replies);
}
void dispose() {
_controller.close();
}
}
class ThreadReplyList extends StatefulWidget {
ThreadReplyList(this.threadId, {Key key}) : super(key: key);
final String threadId;
#override
_ThreadReplyListState createState() => _ThreadReplyListState();
}
class _ThreadReplyListState extends State<ThreadReplyList> {
RepliesBloc _bloc;
#override
void initState() {
super.initState();
this._bloc = RepliesBloc(this.widget.threadId);
}
#override
Widget build(BuildContext context) {
dev.log('starting ThreadReplyList.build', name: "ThreadReplyList.build");
return StreamBuilder(
stream: this._bloc.stream,
builder: (context, snapshot) {
return ListView.separated(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ThreadReply(snapshot.data[index]);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox.shrink();
},
);
});
}
}
This loads the initial data fine and if I change the collection, eg: add or remove a Reply, the ListView rebuilds. However if I modify a Reply, eg: update the reactions map on a single Reply, nothing happens.
I've seen solutions which add listeners to each document in a QuerySnapshot but it's unclear to me how to implement that here.