Flutter: Convert firestore snapshot to list in streambuilder - list

I need to convert a snapshot from cloud firestore to a list, i know this is unnecessary to show the data but i need it to reorder the data based in other parameters, this is my code
Stream chatRooms;
List item = [];
Widget chatRoomsList() {
return StreamBuilder(
stream: chatRooms,
builder: (context, snapshot) {
if (snapshot.hasData &&
!snapshot.hasError) {
item = [];
item = snapshot.data;
return ListView.builder(
itemCount: item.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return ChatRoomsTile(
otherUserUid:item[index]['arrayUsers']
.replaceAll("[", "")
.replaceAll(widget.user.uid, "")
.replaceAll("]", "")
.replaceAll(",", "")
.replaceAll(" ", ""),
chatRoomId:
item[index]["chatRoomId"],
user: widget.user,
);
});
} else
return Container();
},
);
}
#override
void initState() {
getUserInfogetChats();
super.initState();
}
getUserInfogetChats() async {
DatabaseMethods().getUserChats(widget.user.uid).then((snapshots) {
setState(() {
chatRooms = snapshots;
});
});
}
and im getting this error
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following _TypeError was thrown building StreamBuilder<dynamic>(dirty, state: _StreamBuilderBaseState<dynamic, AsyncSnapshot<dynamic>>#48820):
type 'QuerySnapshot' is not a subtype of type 'List<dynamic>'

Change:
item = snapshot.data;
into this:
item = snapshot.data.documents;
documents should return a List<DocumentSnapshot>, so also change the type of item:
List<DocumentSnapshot> item = [];

Related

List is Empty (Flutter)

I am creating a List in Flutter and displaying it in a Column, When I Run it is just Empty and when I print the list it just prints an Array
I/flutter (24613): []
I am using this code to create the List:-
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
This is My User Class:-
class User {
final String id;
final String username;
final String email;
final String photoUrl;
User({
this.id,
this.username,
this.email,
this.photoUrl,
});
factory User.fromDocument(DocumentSnapshot doc) {
return User(
id: doc.data()['id'],
username: doc.data()['username'],
email: doc.data()['email'],
photoUrl: doc.data()['photoUrl'],
);
}
}
The Code Is Showing No Errors and the Column Is not Displaying, Also When I print The length of the List it Shows it is Zero:-
I/flutter (24613): 0
What Could be The problem ??
I guess we need to tweak some of your code little bit to make the logic working. :)
builder param should be specified with Type otherwise it will be of type dynamic. To be in safer side in this case it will be QuerySnapshot. So,
builder: (context, snapshot) in your code becomes
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot).
Next, there is no need of looping through foreach and instead you can try something like below.
snapshot.data.docs.map((document) { .... }
snapshot.data.documents in your code is not valid way of getting the Firestore Documents. Please refer official doc
And you need to return a widget from builder which you have done correctly. But, by mistake you are passing the List<UserList> to Column which will be expecting List<Widget>
return Column (children: usersList);
Here I can see you are passing usersList which is of type List<UserList>. So you can replace Column with ListView or similar kind of other widget since, Column doesn't support scroll.
So combining all these bits and pieces you will get the below snippet.
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.orderBy('timestamp', descending: true)
.limit(30)
.snapshots(), // Just for simplicity.
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
//When there is no data returned from the firebase.
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return ListView(
children: snapshot.data.docs.map((document) {
return Text("Title: " + document['username']);
}).toList(),
);
},
);
For simplicity, I have returned the Text widget. You can implement your own UI there.
NOTE : This is the basic working example and you need to fine tune accordingly like using model classes instead of directly accessing based on your requirements.
Your Code
myFunction() {
return StreamBuilder(
stream:
users.orderBy('timestamp', descending: true).limit(30).snapshots(),
builder: (context, snapshot) {
List<UserList> usersList = [];
snapshot.data.documents.forEach((doc) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
});
return Column (children: usersList);
}
),
}
It is because you have to await for the json to actually get parse to the dart model. Second thing is forEach method is synchronous it doesn't wait for the async operation to complete, this is the reason why your list is empty.
This SO question has lot of different ways to make a list work asynchronously in flutter.
Column shows data before fetching data, so it shows empty list. For this use setstate according to your state management type ("notifylisteners" in provider) after getting data, so by this the screen will be updated and column also shows the updated list.
I'm not very sure how you're handling the scope of the variable.
Here's my minimal reproducible code which can give you some idea on how to add the items to the list.
class MyPage extends StatefulWidget {
#override
_MyPageState createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
final List<Widget> _list = [FlutterLogo()];
#override
void initState() {
super.initState();
Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick >= 2) timer.cancel();
setState(() => _list.add(FlutterLogo()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: _list),
);
}
}
As 'Ashutosh patole' said, 'forEach' method does not wait iteration's complete.
I think that because of this reason, although you made a 'usersList',
there is no data when build widget in 'usersList'.
To fix this, you'd better change from 'forEach' to 'for'.
void main() async {
List<String> data = [ 'a', 'b', 'c'];
List<String> result = [];
data.forEach((data) async {
await Future.delayed(Duration(seconds: 1));
result.add(data);
});
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
print('-----------------');
result = [];
for (var item in data) {
await Future.delayed(Duration(seconds: 1));
result.add(item);
}
print(result);
await Future.delayed(Duration(seconds: 3));
print(result);
}
In your code, you can change like below.
List<UserList> usersList = [];
for (var doc in snapshot.data.documents) {
User user = User.fromDocument(doc);
UserList userList = UserList(user);
usersList.add(userList);
}
Before calling the data, check all fields:
Firestore Docs
Add a print() to see where the problem
FutureBuilder<DocumentSnapshot>(
future: users.doc(documentId).get(),
builder:
(BuildContext context, AsyncSnapshot<DocumentSnapshot> snapshot) {
//This
if (snapshot.hasError) {
return Text("Something went wrong");
}
print(snapshot.data);
//This
if (snapshot.hasData && !snapshot.data!.exists) {
return Text("Document does not exist");
}
print(snapshot.data);
//This
if (snapshot.connectionState == ConnectionState.done) {
Map<String, dynamic> data = snapshot.data!.data() as Map<String, dynamic>;
return Text("Full Name: ${data['full_name']} ${data['last_name']}");
}
return Text("loading");
},
);
This is what i typically use.Try out this! Please balance the brackets in the code
FutureBuilder(
future: users.orderBy('timestamp', descending: true).limit(30),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
List<User>ulist=snapshot.data;
return ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.only(top: 25,bottom: 35),
itemCount: evlist==null?0:evlist.length,
itemBuilder: (BuildContext context, int index) {
String evtime=evlist[index].fromdate.substring(11,16);
String ontime=evlist[index].fromdate.substring(0,16);
return Container(
decoration: BoxDecoration(
border: Border.all(width: 1.8,color: Colors.indigo[900]),
borderRadius: BorderRadius.circular(12.0),
color: Colors.grey[200]
),
margin:
const EdgeInsets.symmetric(horizontal: 18.0, vertical: 4.0),
child: ListTile(
leading: Icon(Icons.notifications),
title: Text(ulist[index].username.toString()),
subtitle:Text("next data"),
),
);
},
);

create the list from the query collection of documents from firestore

I am successfully able to fetch the documents from the firestore collection in flutter application and create a list view builder.
Apart from this I want to create a list of particular field which is common to all documents in collection.
For example , every document has common field of username and after fetching all the documents from the firestore collection I want to create a list of usernames List usernames = []; in which I can add all the usernames from the collection.
Query query2 = FirebaseFirestore.instance
.collection('users');
StreamBuilder<QuerySnapshot>(
stream: query2.snapshots(),
builder: (BuildContext context,AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index){
return ListTile(title:Text(snapshot.data.docs[index]['username'] ?? "",
subtitle:Text(snapshot.data.docs[index]['email'] ?? ""
),);
});
}
)
The above code is working fine all I want to create a separate list to use for the Usernames List usernames = [];
How can I do it ? Please Advise
var usernames = snapshot.data.docs.map((e) => e['username']);
Try this :
Query query2 = FirebaseFirestore.instance
.collection('users').where("username",isNull: false );
StreamBuilder(
stream: query2.snapshots(),
builder: (BuildContext context,AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index){
return ListTile(title:Text(snapshot.data.docs[index]['username'] ?? "",
subtitle:Text(snapshot.data.docs[index]['email'] ?? ""
),);
});
}
)
What this basically does is check the whole collection and returns the data where username is not null.

Retrive data from Firebase Firestore to List<Object>

I have found a lot of solutions of how to retrive data from Firebase to Widget or with usage of async methods, although I am not able to use it for my case.
I want to use flutter_week_view from https://pub.dev/packages/flutter_week_view and in order to pass events from database I need to populate them to List<FlutterWeekViewEvent> events. I have tried the approach with StreamBuilder, but it creates a ListView widget and that's not what I intend to do:
StreamBuilder(
stream: FirebaseFirestore.instance.collection('Test').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading..');
return ListView.builder(
itemExtent: 80.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) => Text(snapshot
.data.documents[index]
.get('name')
.toString()));
})
I had also tried other approach:
Future<List<FlutterWeekViewEvent>> retriveRecords() async {
List<FlutterWeekViewEvent> events = [];
DateTime now = DateTime.now();
QuerySnapshot querySnapshot =
await FirebaseFirestore.instance.collection("Test").get();
for (int i = 0; i < querySnapshot.docs.length; i++) {
var a = querySnapshot.docs[i];
DateTime start = DateTime(now.year, now.month, now.day, now.hour - 7);
events.add(FlutterWeekViewEvent(
title: a.get('name'),
start: start,
end: start.add(const Duration(hours: 2)),
backgroundColor: Colors.red,
description: 'bla bla',
));
}
return events;
}
But in this case my problem is that I wanted to fill event list in build() method and it's not working, since retriveRecords is anync method.
Any help appreciated! I am begginer at Flutter.
You can call your async function retriveRecords() in FutureBuilder. It will wait for it to finish:
FutureBuilder<List<FlutterWeekViewEvent>>(
future: retriveRecords(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
List<FlutterWeekViewEvent> weekEvents = snapshot.data;
return DayView(
date: DateTime.now(),
events: weekEvents,
);
}
return const Text('Loading..');
},
)
Try replace
From : a.get(name);
To : a.data[name]
Let me know if it works...
StreamBuilder(
stream: FirebaseFirestore.instance.collection('Test').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Text("Loading");
} else if (snapshot.hasData && snapshot.connectionState == ConnectionState.active) {
List<QueryDocumentSnapshot> documentSnapshot = snapshot.data.documents;
List<FlutterWeekViewEvent> events = [];
for(QueryDocumentSnapshot doc in queryDocumentSnapshot) {
DateTime start = DateTime(now.year, now.month, now.day, now.hour - 7);
final flutterWeekViewEvent = FlutterWeekViewEvent(
title: doc.data()["name"],
start: start,
end: start.add(const Duration(hours: 2)),
backgroundColor: Colors.red,
description: "bla bla",
);
events.add(flutterWeekViewEvent);
}
}
return ListView.builder(
itemExtent: 80.0,
itemCount: event.length,
itemBuilder: (context, index) {
/// I got this part from the package
return DayView (
date: now,
event: events,
);
}
);
});

Flutter dynamically update date and time

I am new to flutter and this is my first app. I am trying to make a to do list app and want to display the time left for each task in the subtitle. I have a listview and in each element I want the have the subtitle display the minute, counting downwards towards 0. Can anyone help me with this ? Thanks!
Code : -
class toDoListState extends State<toDoList>
{
List<String> tasks = [];
List<String> completedTasks = [];
List<String> descriptions = [];
List<bool> importance = [];
List<String> time2completion = [];
List<DateTime> time = [];
Widget buildToDoList()
{
return new ListView.builder
(
itemBuilder: (context, index)
{
if(time2completion.length > 0 && index < time2completion.length && time2completion[index] != "none")
{
if(time2completion[index] == "30 minutes")
{
time[index] = DateTime.now().add(Duration(minutes: 30));
}
else if(time2completion[index] == "1 hour")
{
time[index] = DateTime.now().add(Duration(hours: 1));
}
else if(time2completion[index] == "12 hours")
{
time[index] = DateTime.now().add(Duration(hours: 12));
}
else if(time2completion[index] == "1 day")
{
time[index] = DateTime.now().add(Duration(days: 1));
}
}
if(index < tasks.length)
{
return row(tasks[index], descriptions[index], index);
}
},
);
}
Widget row(String task, String description, int index)
{
return Dismissible(
key: UniqueKey(),
background: Container(color: Colors.red, child: Align(alignment: Alignment.center, child: Text('DELETE', textAlign: TextAlign.center, style: TextStyle(color: Colors.white, fontSize: 18),))),
direction: DismissDirection.horizontal,
onDismissed: (direction) {
setState(() {
tasks.removeAt(index);
if(completedTasks.contains(task))
{
completedTasks.removeAt(index);
}
descriptions.removeAt(index);
importance.removeAt(index);
});
Scaffold.of(context).showSnackBar(SnackBar(content: Text(task+" dismissed")));
},
child: CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
title: Text(task, style: (completedTasks.contains(task)) ? TextStyle(decoration: TextDecoration.lineThrough) : TextStyle(),),
subtitle: Text((time[index].difference(DateTime.now()).toString())),
value: completedTasks.contains(task),
onChanged: (bool value) {
setState(() {
if(!completedTasks.contains(task))
{
completedTasks.add(task);
}
else
{
completedTasks.remove(task);
}
});
},
));
}
}
You can use a timer to calculate the time differences every minute.
Timer.periodic(
Duration(minutes: 1),
(Timer t) => setState(() {
// your calculation here
}),
);
The following will create a timer object in your stateful widget and dispose of it when you navigate away from the view:
Timer _timer;
#override
void initState() {
_timer = Timer.periodic(
Duration(minutes: 1),
(Timer t) => setState(() {
// your calculation here
}),
);
super.initState();
}
#override
void dispose() {
_timer.cancel();
super.dispose();
}
You can achieve this by following:
Suppose you already have saved date in your todo and have already a date available in
form of DateTime object here in my example I am assuming savedDateTime which can be achieved either by assigning it using .hour , .sec or parsing it from string.
Now what you do is to find what time is left that is differnce
//already assumed saved date as a DateTime object that is savedDateTime
// it contains our saved date of note
//now
final currentDateTime = DateTime.now();
final difference = currentDateTime.difference(savedDateTime);
difference in Seconds,Hours,Minutes,Days is given by
print(difference.inSeconds);
print(difference.inHours);
print(difference.inMinutes);
print(difference.inDays);

Infinite List in Flutter Application

I am migrating my application from android to flutter and till now I have used ListView in a flutter. my question is, is there any specialized technique to handle a large amount of data in the flutter? for reference, you can look at android RecyclerView. it handles in-memory views and recycles its runtime. so how to achieve functionality like RecyclerView in Flutter? or it's not necessary for the flutter?
The easiest way is to use a ListView.builder without specifying the itemCount parameter.
Here is the simplest example:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Infinite List"),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Text("$index");
},
),
);
}
}
Later, you can enhance this by fetching real data. You could show a 'CircularProgressIndicator' in the last item of the list while waiting for the new data.
body: ListView.builder(
itemBuilder: (context, index) {
if (index < data.length) {
// Show your info
return Text("$index");
} else {
getMoreData();
return Center(child: CircularProgressIndicator());
}
},
itemCount: data.length + 1,
),
You can see that we trick the list by adding an index, and calling for more data when displaying that final index.
getMoreData() would include a call to setState() to force a rebuild and to take into account the new data.
Below is a simple infinite list widget based on chemamolins's answer. It accepts an itemBuilder to build the current item and onRequest callback to request more data when the user scrolls to the bottom.
import 'package:flutter/material.dart';
typedef Future<List<T>> RequestFn<T>(int nextIndex);
typedef Widget ItemBuilder<T>(BuildContext context, T item, int index);
class InifiniteList<T> extends StatefulWidget {
final RequestFn<T> onRequest;
final ItemBuilder<T> itemBuilder;
const InifiniteList(
{Key? key, required this.onRequest, required this.itemBuilder})
: super(key: key);
#override
_InifiniteListState<T> createState() => _InifiniteListState<T>();
}
class _InifiniteListState<T> extends State<InifiniteList<T>> {
List<T> items = [];
bool end = false;
_getMoreItems() async {
final moreItems = await widget.onRequest(items.length);
if (!mounted) return;
if (moreItems.isEmpty) {
setState(() => end = true);
return;
}
setState(() => items = [...items, ...moreItems]);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
if (index < items.length) {
return widget.itemBuilder(context, items[index], index);
} else if (index == items.length && end) {
return const Center(child: Text('End of list'));
} else {
_getMoreItems();
return const SizedBox(
height: 80,
child: Center(child: CircularProgressIndicator()),
);
}
},
itemCount: items.length + 1,
);
}
}
Usage
child: InifiniteList<String>(
onRequest: requestItems,
itemBuilder: (context, item, index) => Container(
padding: const EdgeInsets.all(30),
color: index % 2 == 0 ? Colors.purple.shade100 : Colors.lime.shade100,
child: Text(item, style: Theme.of(context).textTheme.headline6),
),
),
// normally this is the place where you request the next batch of items
// on the network.
Future<List<String>> requestItems(int nextIndex) {
const pageSize = 15;
var result = List<String>.generate(pageSize, (i) => "Item: ${nextIndex + i + 1}");
return Future<List<String>>.delayed(
const Duration(milliseconds: 500),
() => result,
);
}
Live Demo
Displaying lists of data is a fundamental pattern for mobile apps. Flutter includes the ListView widget to make working with lists a breeze.
I have solved the issue by doing the following steps
Use the ListView Widget
There are four constructors of ListView Class
You have to use Builder Constructor (ListView.builder)
Builder Constructor is used when you have to make a list of elements on demand
It is appropriate for list views with a large (or infinite) number of children
HERE you can have Solution Video CLICK HERE