I am newbie at Flutter, I want to fetch data once from Firebase but when I use foreach in setState, it gives infinite loop. I tried initState but I couldn't figure it out with Future.
Here is my code:
class _AnasayfaScreenState extends State<AnasayfaScreen> {
final databaseRef = FirebaseDatabase.instance.reference();
ActivityModel activity = ActivityModel();
List<Map<dynamic, dynamic>> lists = [];
}
Future getActivity() async {
return await databaseRef
.child('activities')
.limitToLast(3)
.once()
.then((DataSnapshot dataSnapshot) {
setState(() {
Map<dynamic, dynamic> values = dataSnapshot.value;
lists.clear();
values.forEach((key, values) {
lists.add(values);
});
});
});
}
#override
Widget build(BuildContext context) {
getActivity();
return Scaffold(
appBar: AppBar(
// some stuff
title: Text("Title: " +lists[index]["title"] +"\nDate: " + lists[index]["date"] +"\nN: Number of people"+lists[index]["maxPeople"] + "\nActivity Owner: " +lists[index]["ownername"]),
Any help would be awesome for me, thanks.
Here is a very basic example of what you can do:
class _AnasayfaScreenState extends State<AnasayfaScreen> {
final databaseRef = FirebaseDatabase.instance.reference();
ActivityModel activity = ActivityModel();
bool isLoading = true;
List<Map<dynamic, dynamic>> lists = [];
#override
void initState(){
super.initState();
getActivity();
}
Future getActivity() async {
await databaseRef
.child('activities')
.limitToLast(3)
.once()
.then((DataSnapshot dataSnapshot) {
Map<dynamic, dynamic> values = dataSnapshot.value;
lists.clear();
values.forEach((key, values) {
lists.add(values);
});
});
setState(() {
isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: isLoading ? Container() // Add your widget and work here instead of Container
: CircularProgressIndicator(),
);
}
Related
This question already has answers here:
What is a Future and how do I use it?
(6 answers)
Closed 10 months ago.
The idea
I want to display followers. the page take list of followers user id and then display their username.
Error
when I tried to I get an Error say type 'Future<dynamic>' is not a subtype of type 'Widget'
The issue in this line Text(user["username"]),
Code
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class Following extends StatefulWidget {
final following ;
const Following({Key? key, required this.following}) : super(key: key);
#override
_FollowingState createState() => _FollowingState();
}
class _FollowingState extends State<Following> {
/*attribute*/
var following =[];
bool islouded = false;
var usersData= [];
#override
void initState() {
super.initState();
setState(() {
following = widget.following;
});
getFollowing();
}
void getFollowing() {
for(var user in following){
setState(() {
print(user);
// print(getUser(user));
usersData.add( getUser(user));
});
}
setState(() {
islouded = true;
});
}
getUser(uid)async{
try {
if (uid != null) {
var userSnap = await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get();
var userData = userSnap.data()!;
// print(userSnap.data()!["username"].toString());
return userData;
}
}catch(e){
showSnackBar(context, e.toString());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !islouded?
const Center(
child: CircularProgressIndicator(),
):following.isNotEmpty?
Column(
children: [
for(var user in usersData)
Text(user["username"]),
],
):Text("No following yet!"),
);
}
}
Tried
I tried use FutureBuilder but I did not how to use it right because it return nothing. I believe I'm using it wrong.
the code as follow:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class Following extends StatefulWidget {
final following ;
const Following({Key? key, required this.following}) : super(key: key);
#override
_FollowingState createState() => _FollowingState();
}
class _FollowingState extends State<Following> {
/*attribute*/
var following =[];
bool islouded = false;
var usersData= [];
#override
void initState() {
super.initState();
setState(() {
following = widget.following;
});
getFollowing();
}
void getFollowing() {
for(var user in following){
setState(() {
print(user);
// print(getUser(user));
usersData.add( getUser(user));
});
}
setState(() {
islouded = true;
});
}
getUser(uid) async{
try {
if (uid != null) {
var userSnap = await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get();
return userSnap;
// print(userSnap.data()!["username"].toString());
// return userData;
}
}catch(e){
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !islouded?
const Center(
child: CircularProgressIndicator(),
):following.isNotEmpty?
Column(
children: [
for(var user in usersData)
FutureBuilder(
future: user,
builder: (context, snapshot){
switch(snapshot.connectionState){
case ConnectionState.none:
return Text("No following yet!");
case ConnectionState.active:
return Text("active");
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
print(user);//Instance of 'Future<dynamic>'
print(snapshot);//AsyncSnapshot<Object?>(ConnectionState.done, Instance of '_JsonDocumentSnapshot', null, null)
return Text("username");//i want to display username but getting different error
default:
return Text("No following yet");
}
}
)
// Text(user["username"]),
],
):Text("No following yet!"),
);
}}
Thank you for taking the time reading my question. I hope you have beautiful day like you <3
I feel this may be the culprit:
usersData.add( getUser(user));.
Try this instead: await usersData.add( getUser(user));.
As you call the async method getUser(user) async { ... } it returns a Future, and this Future gets added to the List not the user. This would explain the error complaining about an unexpected Future.
My problem is that when I receive information from Firestore, I see it in the console that it prints but my UI does not update. But until I press the icon that shows my screen again. The screen where my list of widgets is contained in a BottomNavigationBar.
What I hope will happen with the code is that when I select the tab that will contain my screen in the BottomNavigationBar, the list of Widgets appears with the names of the DocumentIDs. Well, currently I must select the tab again so that they appear.
I attach my code.
class PruebasVarias extends StatefulWidget {
#override
_PruebasVariasState createState() => _PruebasVariasState();
}
class _PruebasVariasState extends State<PruebasVarias> {
List<String> myData = [];
List<Widget> myListWidget = [];
#override
void initState() {
super.initState();
getallDocument();
}
Future getallDocument()async{
final QuerySnapshot result = await Firestore.instance
.collection("Users")
.getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++){
myListWidget.add(Text("${myData[i]}"));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Documents"),
actions: <Widget>[
],
),
body: Center(
child:
Column(children: myListWidget),
)
);
}
}
An easy fix : use then to call a callback function and inside callback function use setState to update the UI.
class PruebasVarias extends StatefulWidget {
#override
_PruebasVariasState createState() => _PruebasVariasState();
}
class _PruebasVariasState extends State<PruebasVarias> {
List<String> myData = [];
List<Widget> myListWidget = [];
#override
void initState() {
super.initState();
getallDocument().then(()=>updateUI()).catchError((error)=>print(error));
}
Future<void> getallDocument()async{
final QuerySnapshot result = await Firestore.instance
.collection("Users")
.getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++){
myListWidget.add(Text("${myData[i]}"));
}
}
void updateUI()
{
setState((){});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Documents"),
actions: <Widget>[
],
),
body: Center(
child:
Column(children: myListWidget.isEmpty?[Text("Waiting...")]:myListWidget),
)
);
}
}
You can fix this by calling setState(() {}) method; setState
Notify the framework that the internal state of this object has changed.
Future getallDocument() async {
final QuerySnapshot result =
await Firestore.instance.collection("Users").getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++) {
myListWidget.add(Text("${myData[i]}"));
}
setState(() {});
}
I have a flutter app here connected to firestore backend and I face a strange behaviour here.
When the app starts or I do hot restart the items fetched from the database are shown for one second and then disappear again and I have to apply RefreshIndicator and drag the screen down to refresh the products and let them appear again.
Here is my code to fetch items:
Future<void> fetchitems() async {
try {
final List<Product> loadedProducts = [];
final response = await Firestore
.instance
.collection("products")
.getDocuments();
response.documents.forEach((element) {
loadedProducts.add(Product(
id: element.documentID,
title: element.data['title'],
description: element.data['description'],
price: element.data['price'],
imageUrl: element.data['imageUrl']
));
});
_items = loadedProducts;
notifyListeners();
} catch (error) {
print(error);
}
}
Here is GridView and how it receives items:
Widget build(BuildContext context) {
final productsData = Provider.of<Products>(context);
final products = productsData.items;
return GridView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: products.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: products[i],
child: ProductItem(),
),
Here is where I call the GridView:
class _ProductsOverviewScreenState extends State<ProductsOverviewScreen> {
var _isIntit = true;
var _isLoading = false;
Future<void> _refreshProducts(BuildContext context) async {
await Provider.of<Products>(context).fetchAndSetProducts();
}
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
if (_isIntit) {
setState(() {
_isLoading = true;
});
Provider.of<Products>(context).fetchitems().then((_) {
setState(() {
_isLoading = false;
});
});
}
_isIntit = false;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _isLoading ? Center(child: CircularProgressIndicator(), ) : RefreshIndicator(
onRefresh: () => _refreshProducts(context),
child: ProductsGrid()),
);
}
}
final productsData = Provider.of<Products>(context, listen: false);
listen: false ensure that build will not retrigger again in the same build.
I want to search in firestore data but the list is not showing me data from firestore. I want to return my list with firestore data but it is not showing me and i want to search in list data according to my choice.
Please tell me where I am wrong?
class serachDeligat extends SearchDelegate<String> {
Firestore _firestore = Firestore.instance;
String ref = 'items';
Future<List<DocumentSnapshot>> getSearch() async =>
await _firestore.collection(ref).getDocuments().then((snaps) {
return snaps.documents;
});
List<Map> search = <Map>[];
Future getDocs() async {
search = await (await getSearch()).map((item) => item.data).toList();
return search;
}
#override
void initState() {
getDocs();
}
#override
List<Widget> buildActions(BuildContext context) {
// TODO: implement buildActions
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
print('$search');
},
),
];
}
#override
Widget buildLeading(BuildContext context) {
// TODO: implement buildLeading
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
child: Center(),
);
}
#override
Widget buildSuggestions(BuildContext context) {
return Container();
}
}
I will share what I did in one of my projects which had the same problem.I tried many ways but all of them end up with a null.Then I change my function with the help of StreamSubscription.This will change the data even after retrieving from firestore since it maintains a connection with firestore.I loaded data inside the initstate.
StreamSubscription<QuerySnapshot> dailyTaskSubscription;
ProgressDialog progressDialog;
List<ScheduleTask> mondayTaskList = List();
#override
void initState() {
super.initState();
mondayTaskList = List();
dailyTaskSubscription?.cancel();
dailyTaskSubscription = scheduleService
.getDailyTaskList(studentId, 'monday')
.listen((QuerySnapshot snapshot) {
final List<ScheduleTask> tasks = snapshot.documents
.map((documentSnapshot) =>
ScheduleTask.fromMap(documentSnapshot.data))
.toList();
});
}
here is the answer to my question
I should use the await in my onPressed
class serachDeligat extends SearchDelegate<String>{
Firestore _firestore = Firestore.instance;
String ref = 'items';
Future<List<DocumentSnapshot>> getSearch() async =>
await _firestore.collection(ref).getDocuments().then((snaps) {
return snaps.documents;
});
List<Map> search = <Map>[];
Future getDocs() async {
search=await
(await getSearch()).map((item) => item.data).toList();
return search;}
#override
void initState() {
getDocs();
super.query;
}
List<Map> searchAi = <Map>[];
#override
List<Widget> buildActions(BuildContext context) {
// TODO: implement buildActions
return[IconButton(
icon: Icon(Icons.clear),
onPressed: ()async {
searchAi=await getDocs();
print(searchAi);
},),];}
#override
Widget buildLeading(BuildContext context) {
// TODO: implement buildLeading
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
child: Center(),);}
#override
Widget buildSuggestions(BuildContext context) {
return Container();}}
I am trying to paginate in Flutter with firebase realtime databse.
I have tried this in Firestore and it works fine there but I want this with realtime database.
I am fetching data for the first time like this.
Widget buildListMessage() {
return Flexible(
child: StreamBuilder(
stream: _firebase.firebaseDB
.reference()
.child("chats")
.child("nsbcalculator")
.orderByChild('timestamp')
.limitToFirst(15)
.onValue,
builder: (context, AsyncSnapshot<Event> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor)));
} else {
if (snapshot.data.snapshot.value != null) {
listMessage = Map.from(snapshot.data.snapshot.value)
.values
.toList()
..sort(
(a, b) => a['timestamp'].compareTo(b['timestamp']));
if (lastVisible == null) {
lastVisible = listMessage.last;
listMessage.removeLast();
}
}
return ListView.builder(
...
);
}
},
),
);
}
After that to paginate I am using a listener with ScrollController
void _scrollListener() async {
if (listScrollController.position.pixels ==
listScrollController.position.maxScrollExtent) {
_fetchMore();
}
}
and finally
_fetchMore() {
_firebase.firebaseDB
.reference()
.child("chats")
.child("nsbcalculator")
.orderByChild('timestamp')
.startAt(lastVisible['timestamp'])
.limitToFirst(5)
.once()
.then((snapshot) {
List snapList = Map.from(snapshot.value).values.toList()
..sort((a, b) => a['timestamp'].compareTo(b['timestamp']));
if (snapList.isNotEmpty) {
print(snapList.length.toString());
if (!noMore) {
listMessage.removeLast();
//Problem is here.....??
setState(() {
listMessage..addAll(snapList);
});
lastVisible = snapList.last;
print(lastVisible['content']);
}
if (snapList.length < 5) {
noMore = true;
}
}
});
}
Its working fine as realtime communication but when I try to paginate in _fetchMore() setState is called but it refreshes the state of whole widget and restarts the StreamBuilder again and all data is replaced by only new query. How can I prevent this??
Calling setState will redraw your whole widget and your list view. Now, since you supplying the steam that provides the first page, after redraw it just loads it. To avoid that you could use your own stream and supply new content to it. Then your StreamBuilder will handle the update automatically.
You need to store the full list of your items as a separate variable, update it and then sink to your stream.
final _list = List<Event>();
final _listController = StreamController<List<Event>>.broadcast();
Stream<List<Event>> get listStream => _listController.stream;
#override
void initState() {
super.initState();
// Here you need to load your first page and then add to your stream
...
_list.addAll(firstPageItems);
_listController.sink.add(_list);
}
#override
void dispose() {
super.dispose();
}
Widget buildListMessage() {
return Flexible(
child: StreamBuilder(
stream: listStream
...
}
_fetchMore() {
...
// Do your fetch and then just add items to the stream
_list.addAll(snapList);
_listController.sink.add(_list);
...
}
Try this one
pagination for RealTime list
class FireStoreRepository {
final CollectionReference _chatCollectionReference =
Firestore.instance.collection('Chat');
final StreamController<List<ChatModel>> _chatController =
StreamController<List<ChatModel>>.broadcast();
List<List<ChatModel>> _allPagedResults = List<List<ChatModel>>();
static const int chatLimit = 10;
DocumentSnapshot _lastDocument;
bool _hasMoreData = true;
Stream listenToChatsRealTime() {
_requestChats();
return _chatController.stream;
}
void _requestChats() {
var pagechatQuery = _chatCollectionReference
.orderBy('timestamp', descending: true)
.limit(chatLimit);
if (_lastDocument != null) {
pagechatQuery =
pagechatQuery.startAfterDocument(_lastDocument);
}
if (!_hasMoreData) return;
var currentRequestIndex = _allPagedResults.length;
pagechatQuery.snapshots().listen(
(snapshot) {
if (snapshot.documents.isNotEmpty) {
var generalChats = snapshot.documents
.map((snapshot) => ChatModel.fromMap(snapshot.data))
.toList();
var pageExists = currentRequestIndex < _allPagedResults.length;
if (pageExists) {
_allPagedResults[currentRequestIndex] = generalChats;
} else {
_allPagedResults.add(generalChats);
}
var allChats = _allPagedResults.fold<List<ChatModel>>(
List<ChatModel>(),
(initialValue, pageItems) => initialValue..addAll(pageItems));
_chatController.add(allChats);
if (currentRequestIndex == _allPagedResults.length - 1) {
_lastDocument = snapshot.documents.last;
}
_hasMoreData = generalChats.length == chatLimit;
}
},
);
}
void requestMoreData() => _requestChats();
}
ChatListView
class ChatView extends StatefulWidget {
ChatView({Key key}) : super(key: key);
#override
_ChatViewState createState() => _ChatViewState();
}
class _ChatViewState extends State<ChatView> {
FireStoreRepository _fireStoreRepository;
final ScrollController _listScrollController = new ScrollController();
#override
void initState() {
super.initState();
_fireStoreRepository = FireStoreRepository();
_listScrollController.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Flexible(
child: StreamBuilder<List<ChatModel>>(
stream: _fireStoreRepository.listenToChatsRealTime(),
builder: (context, snapshot) {
return ListView.builder(
itemCount: snapshot.data.length,
controller: _listScrollController,
shrinkWrap: true,
reverse: true,
itemBuilder: (context, index) {
...
}
);
}
)
),
);
}
void _scrollListener() {
if (_listScrollController.offset >=
_listScrollController.position.maxScrollExtent &&
!_listScrollController.position.outOfRange) {
_fireStoreRepository.requestMoreData();
}
}
}
ChatModel Class
class ChatModel {
final String userID;
final String message;
final DateTime timeStamp;
ChatModel({this.userID, this.message, this.timeStamp});
//send
Map<String, dynamic> toMap() {
return {
'userid': userID,
'message': message,
'timestamp': timeStamp,
};
}
//fetch
static ChatModel fromMap(Map<String, dynamic> map) {
if (map == null) return null;
return ChatModel(
userID: map['userid'],
message: map['message'],
timeStamp: DateTime.fromMicrosecondsSinceEpoch(map['timestamp'] * 1000),
);
}
}