Trying to use some nested streambuilders, listview builders and expansion tiles to display data from Firestore, app freezes until terminated when I try to expand the tiles.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_troubleshooting/models/class1.dart';
import 'package:firestore_troubleshooting/services/auth_service.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'models/class2.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await AuthService().getOrCreateUser();
runApp(const MyApp());
}
late var class1Data;
late var class2Data;
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
backgroundColor: Colors.grey[800],
textTheme: Theme.of(context)
.textTheme
.apply(bodyColor: Colors.white, displayColor: Colors.white),
splashColor: Colors.grey[800],
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Firestore Troubleshooting'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TextEditingController textController1 = TextEditingController();
final Stream<QuerySnapshot> class1Stream = FirebaseFirestore.instance
.collection('users')
.doc(AuthService().currentUser?.uid)
.collection('Class 1 Objects')
.snapshots();
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[800],
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(children: [
Expanded(
child: TextField(
controller: textController1,
),
),
Expanded(
child: ElevatedButton(
onPressed: () {
createClass1Object(textController1.text);
textController1.clear();
setState(() {});
},
child: Text('Add Object')))
]),
StreamBuilder(
stream: class1Stream,
builder: (context, class1Snapshot) {
if (class1Snapshot.hasError) {
return Text('client snapshot has error');
}
if (class1Snapshot.connectionState ==
ConnectionState.waiting) {
return CircularProgressIndicator();
}
class1Data = class1Snapshot.requireData;
return ListView.builder(
shrinkWrap: true,
itemCount: class1Data.size,
itemBuilder: (context, class1_index) {
final Stream<QuerySnapshot> class2Stream =
FirebaseFirestore.instance
.collection('users')
.doc(AuthService().currentUser?.uid)
.collection('Class 1 Objects')
.doc(class1Data.docs[class1_index]['docID'])
.collection('Class 2 Objects')
.snapshots();
return class1Data.size > 0
? ExpansionTile(
initiallyExpanded: true,
title:
Text(class1Data.docs[class1_index]['name']),
children: [
Row(children: [
Expanded(
child: TextField(
controller: textController1,
),
),
Expanded(
child: ElevatedButton(
onPressed: () {
createClass2Object(
textController1.text,
class1_index);
textController1.clear();
setState(() {});
},
child: Text('Add Object')))
]),
StreamBuilder(
stream: class2Stream,
builder: (context, class2Snapshot) {
if (class2Snapshot.hasError) {
return Text(
'client snapshot has error');
}
if (class2Snapshot.connectionState ==
ConnectionState.waiting) {
return CircularProgressIndicator();
}
class2Data = class2Snapshot.requireData;
return ListView.builder(
shrinkWrap: true,
itemCount: class2Data.size,
itemBuilder:
(context, class2_index) {
return ExpansionTile(
initiallyExpanded: false,
title: Text('expansion tile 2'),
children: [
ListView.builder(
shrinkWrap: true,
itemBuilder:
(context, index3) {
return ListTile(
title:
Text('List tile'),
);
})
],
);
});
})
],
)
: Text('no data');
});
}),
ElevatedButton(
onPressed: () {
setState(() {});
},
child: Text('Set State'))
],
),
),
);
}
}
Here are the model classes:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_troubleshooting/services/auth_service.dart';
class Class1 {
late var name;
late var docID;
Class1({required this.name, required this.docID});
Map<String, dynamic> toJson() => {'name': name, 'docID': docID};
Class1 fromJson(Map<String, dynamic> json) =>
Class1(name: ['name'], docID: ['docID']);
}
Future createClass1Object(name) async {
final class1_ref = FirebaseFirestore.instance
.collection('users')
.doc(AuthService().currentUser?.uid)
.collection('Class 1 Objects')
.doc();
final class1Object = Class1(name: name, docID: class1_ref.id);
final json = class1Object.toJson();
await class1_ref.set(json);
}
class2:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_troubleshooting/main.dart';
import 'package:firestore_troubleshooting/services/auth_service.dart';
class Class2 {
late var name;
late var docID;
Class2({required this.name, required this.docID});
Map<String, dynamic> toJson() => {'name': name, 'docID': docID};
Class2 fromJson(Map<String, dynamic> json) =>
Class2(name: ['name'], docID: ['docID']);
}
Future createClass2Object(name, class1_index) async {
final Class2_ref = FirebaseFirestore.instance
.collection('users')
.doc(AuthService().currentUser?.uid)
.collection('Class 1 Objects')
.doc(class1Data.docs[class1_index]['docID'])
.collection('Class 2 Objects')
.doc();
final Class2Object = Class2(name: name, docID: Class2_ref.id);
final json = Class2Object.toJson();
await Class2_ref.set(json);
}
auth service:
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
User? get currentUser => _firebaseAuth.currentUser;
Future<User?> getOrCreateUser() async {
if (currentUser == null) {
await _firebaseAuth.signInAnonymously();
}
return currentUser;
}
}
Sorry, I know it's a lot of code. I'm trying to isolate the a problem I'm having in a larger project and this was the most minimal way I could recreate.
Related
Does anyone have a example for using the 'where' in the snapshot for FirebaseFirestore?
As I want to listen to the documents where one of the fields are equal to a specific email.
This I want then to use to update my Scaffold to display the current settings.
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
class Test2 extends StatelessWidget {
const Test2({Key? key}) : super(key: key);
static const String id = 'test_2';
final TextEditingController _deviceName = TextEditingController();
final TextEditingController _onHour = TextEditingController();
#override
var firebaseUser = FirebaseAuth.instance.currentUser?.email;
final CollectionReference _device =
FirebaseFirestore.instance.collection('devices').where('email', isEqualTo: '$firebaseUser') as CollectionReference<Object?>;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(leading: null,
actions: [
IconButton(
onPressed: () {},
icon: Icon(Icons.close))
],
title: const Text('Device Selection page'),),
body: StreamBuilder(
stream: _device.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot = streamSnapshot.data!
.docs[index];
return Card(
margin: const EdgeInsets.all(10),
child: ListTile(
title: Text(documentSnapshot['deviceName']),
subtitle: Text(documentSnapshot['startHour'].toString()),
trailing: SizedBox(
width: 100,
child: Row(
children: [IconButton(
onPressed: () {}, icon: const Icon(Icons.edit)),
IconButton(onPressed: () {},
icon: const Icon(Icons.delete))
],
),
),
),
);
},);
};
return Container(
child: Row(
children: [Text('Test')],
),
);
},
)
);
}
}
Hi Im trying to use List views to make the UI more interactive and appealing, however I cant figure out a way to pass the TextEditingController() pass through to other pages. I've attached the relevant pages of code below.
admin_exercise.dart
this creates both list views this is then passed to admin_screen.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
final _firestore = Firestore.instance;
late FirebaseUser users;
class ExerciseView extends StatefulWidget {
const ExerciseView({Key? key}) : super(key: key);
#override
State<ExerciseView> createState() => _ExerciseViewState();
}
class _ExerciseViewState extends State<ExerciseView> {
void getUsers() {
users.email.toString();
}
var selectedUser;
bool setDefaultUser = true;
var selectedExercise;
bool setDefaultExercise = true;
int set1 = 0;
List<Widget> _cardList = [];
void _addCardWidget() {
setState(() {
_cardList.add(SetCard(
setNo: (set1 + 1),
exercise: selectedExercise,
));
});
}
void _deleteCardWidget() {
setState(() {
_cardList.removeLast();
});
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Center(
child: StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('exercises')
.orderBy('Title')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return Container();
if (setDefaultExercise) {
selectedExercise = snapshot.data?.documents[0]['Title'];
}
return DropdownButton(
isExpanded: false,
value: selectedExercise,
items: snapshot.data?.documents.map((value2) {
return DropdownMenuItem(
value: value2['Title'],
child: Text('${value2['Title']}'),
);
}).toList(),
onChanged: (value2) {
setState(
() {
// Selected value will be stored
selectedExercise = value2;
// Default dropdown value won't be displayed anymore
setDefaultExercise = false;
},
);
},
);
},
),
),
ElevatedButton(
onPressed: () {
setState(() {
_addCardWidget();
set1++;
});
},
child: Icon(Icons.add)),
Text('Sets: $set1'),
ElevatedButton(
onPressed: () {
_deleteCardWidget();
setState(() {
set1--;
});
},
child: Icon(Icons.remove))
],
),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _cardList.length,
itemBuilder: (context, index) {
return _cardList[index];
}),
],
);
}
}
class SetCard extends StatelessWidget {
SetCard({required this.setNo, required this.exercise});
late int setNo;
late String exercise;
final repTextController = TextEditingController();
final notesTextController = TextEditingController();
Future<void> insertData(final reps) async {
_firestore.collection("plans").add(reps).then((DocumentReference document) {
print(document.documentID);
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// added in exercise here to help to understand how to write to firebase
Text('Set $setNo, $exercise'),
Row(
children: [
Expanded(
flex: 1,
child: TextFormField(
controller: repTextController,
decoration: InputDecoration(hintText: 'Reps...'),
keyboardType: TextInputType.number,
),
),
SizedBox(
width: 10,
),
Expanded(
flex: 4,
child: TextFormField(
controller: notesTextController,
decoration: InputDecoration(hintText: 'Notes...'),
keyboardType: TextInputType.number,
),
),
],
),
],
);
}
}
admin_screen.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:intl/intl.dart';
import 'package:fitness_guide/components/admin_exercise.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class AdminScreen extends StatefulWidget {
static const String id = 'AdminScreen';
const AdminScreen({Key? key}) : super(key: key);
#override
State<AdminScreen> createState() => _AdminScreenState();
}
class _AdminScreenState extends State<AdminScreen> {
TextEditingController dateinput = TextEditingController();
final _auth = FirebaseAuth.instance;
final _firestore = Firestore.instance;
late FirebaseUser users;
void getUsers() {
users.email.toString();
}
var selectedUser;
bool setDefaultUser = true;
var selectedExercise;
bool setDefaultExercise = true;
int set1 = 0;
#override
void initState() {
dateinput.text = ""; //set the initial value of text field
super.initState();
}
List<Widget> _exerciseList = [];
void _addExerciseWidget() {
setState(() {
_exerciseList.add(ExerciseView());
});
}
void _deleteExerciseWidget() {
setState(() {
_exerciseList.removeLast();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
//submit to fbase here
},
label: const Text('Submit'),
icon: const Icon(Icons.thumb_up),
backgroundColor: Color(0xff75D6F2),
),
body: ListView(
physics: BouncingScrollPhysics(),
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(
height: 20,
),
Container(
padding: EdgeInsets.symmetric(horizontal: 15),
height: 50,
child: Center(
child: TextField(
controller:
dateinput, //editing controller of this TextField
decoration: InputDecoration(
icon: Icon(Icons.calendar_today), //icon of text field
labelText: "Enter Date" //label text of field
),
readOnly:
true, //set it true, so that user will not able to edit text
onTap: () async {
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(
2000), //DateTime.now() - not to allow to choose before today.
lastDate: DateTime(2101));
if (pickedDate != null) {
print(
pickedDate); //pickedDate output format => 2021-03-10 00:00:00.000
String formattedDate =
DateFormat('yyyy-MM-dd').format(pickedDate);
print(
formattedDate); //formatted date output using intl package => 2021-03-16
//you can implement different kind of Date Format here according to your requirement
setState(() {
dateinput.text =
formattedDate; //set output date to TextField value.
});
} else {
print("Date is not selected");
}
},
))),
Center(
child: StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('users')
.orderBy('email')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
// Safety check to ensure that snapshot contains data
// without this safety check, StreamBuilder dirty state warnings will be thrown
if (!snapshot.hasData) return Container();
// Set this value for default,
// setDefault will change if an item was selected
// First item from the List will be displayed
if (setDefaultUser) {
selectedUser = snapshot.data?.documents[0]['email'];
}
return DropdownButton(
isExpanded: false,
value: selectedUser,
items: snapshot.data?.documents.map((value1) {
return DropdownMenuItem(
value: value1['email'],
child: Text('${value1['email']}'),
);
}).toList(),
onChanged: (value1) {
setState(
() {
// Selected value will be stored
selectedUser = value1;
// Default dropdown value won't be displayed anymore
setDefaultUser = false;
},
);
},
);
},
),
),
//Row below for the first exercise sets etc
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _exerciseList.length,
itemBuilder: (context, index) {
return _exerciseList[index];
}),
Center(
child: Text('Add another Exercise...'),
),
ElevatedButton(
onPressed: () {
_addExerciseWidget();
},
child: Icon(Icons.add),
),
],
),
],
),
);
}
}```
p.s. sorry for any spagetti code
[Image shows the layout UI I'm trying to achieve][1]
[1]: https://i.stack.imgur.com/d3qxg.png
I have a string error which I can't seem to debug I think its related to how I'm trying to retrieve data when using Future.wait I get the above error. If I only build a single future and don't use Future.wait and retrieve data using snapshot.data!.data()!["prediction"] I don't get any errors.
Code below for my future.wait
Any help appreciated!
class CorrectMood extends StatefulWidget {
const CorrectMood({Key? key}) : super(key: key);
#override
_CorrectMoodState createState() => _CorrectMoodState();
}
class _CorrectMoodState extends State<CorrectMood> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([getData(), getLatestMood()]),
builder: (context, AsyncSnapshot<List> snapshot) {
snapshot.data![0]; //getLatestMood
snapshot.data![1]; //getData
if (snapshot.hasData) {
return Scaffold(
appBar: AppBar(
title: const Text('Display the Picture'),
backgroundColor: kPrimaryColor,
),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
body: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.fromLTRB(8.0, 20.0, 8.0, 8.0),
child: Column(
children: [
Center(
child:
Text(
"${snapshot.data![0]["firstName"]}"
// "${snapshot.data![0].prediction}"
//"${snapshot.data!.data()!["firstName"]} \n\n "
"We have predicted your mood as:\n\n "
//"${DatabaseService.getMood()}\n\n"
"Please select a reason associated to your mood",
style: const TextStyle(
color: Colors.black, fontSize: 15),
textAlign: TextAlign.center,
),
),
countDocuments(),
],
),
),
],
),
),
);
}else {
return CircularProgressIndicator();
}
},
);
}
}
Future<DocumentSnapshot<Map<String, dynamic>>> getData() async {
var currentUser = FirebaseAuth.instance.currentUser;
return await FirebaseFirestore.instance
.collection('USER_TABLE')
.doc(currentUser!.uid)
.get();
}
countDocuments() async {
var currentUser = FirebaseAuth.instance.currentUser;
QuerySnapshot _myDoc = await FirebaseFirestore.instance
.collection('userMoods')
.doc('useerID')
.collection(currentUser!.uid)
.get();
List<DocumentSnapshot> _myDocCount = _myDoc.docs;
return (_myDocCount.length);
}
Future<DocumentSnapshot<Map<String, dynamic>>> getLatestMood() async {
var currentUser = FirebaseAuth.instance.currentUser;
var latestMoodDoc = countDocuments();
return await FirebaseFirestore.instance
.collection('userMoods')
.doc('useerID') // make this the userID
.collection('UbcNaFtJwXWoId9J5RLuVBVPhpN2') // make this increment every time
.doc('2') //this can be system generated name don't care about it
.get();
}
I am trying to load more data once I reach the end of the stream builder, everytime I reach the end, the stream builder reloads and bounces back to the beginning, and it does not allow me to scroll down ( keep reload and bounce back to the top).
What I tried is once I reach the end, increase the limit from the firebase, but I am not sure what the problem is.. anyone can help me with this?
Here is the sample code for what I want to do
_onEndScroll(ScrollMetrics metrics) {
//loadToTrue();
setState(() {
documentLimit = documentLimit + 10;
});
}
StreamBuilder(
initialData: cache,
stream: FirebaseFirestore.instance
.collection("timeline")
.doc(widget.currentUser.id)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> streamSnapshot) {
items = streamSnapshot.data != null &&
streamSnapshot.data.docs != null
? streamSnapshot.data.docs
: [];
List<Post> posts =
items.map((doc) => Post.fromDocument(doc)).toList();
cache = streamSnapshot.data;
return !streamSnapshot.hasData ||
streamSnapshot.connectionState ==
ConnectionState.waiting
? Center(
child: CircularProgressIndicator(),
)
: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification) {
_onEndScroll(scrollNotification.metrics);
}
},
child: Container(
height: MediaQuery.of(context).size.height - 200,
margin: EdgeInsets.only(bottom: 1),
child: ListView.builder(
physics: BouncingScrollPhysics(
parent: ScrollPhysics()),
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (_, i) =>
timelineDecision == "follow"
? posts[i]
: postsLocal[i])));
})
and this is code for all
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/pages/home.dart';
import 'package:fluttershare/models/user.dart';
import 'package:fluttershare/pages/search.dart';
import 'package:fluttershare/pages/upload.dart';
import 'package:fluttershare/pages/upload_limit.dart';
import 'package:fluttershare/widgets/post.dart';
import 'package:latlong/latlong.dart';
final usersRef = FirebaseFirestore.instance.collection('users');
class Timeline extends StatefulWidget {
final User currentUser;
Timeline({this.currentUser});
#override
_TimelineState createState() => _TimelineState();
}
class _TimelineState extends State<Timeline> {
QuerySnapshot cache;
List<Post> posts;
List<Post> postsLocal;
List<DocumentSnapshot> items;
List<String> followingList = [];
String timelineDecision = "local";
String address = "Norman";
ScrollController listScrollController;
int documentLimit = 5;
void initState() {
super.initState();
getFollowing();
}
getFollowing() async {
QuerySnapshot snapshot = await followingRef
.doc(currentUser.id)
.collection('userFollowing')
.get();
if (mounted) {
setState(() {
followingList = snapshot.docs.map((doc) => doc.id).toList();
});
}
}
setTimeline(String timelineDecision) {
setState(() {
this.timelineDecision = timelineDecision;
});
}
buildToggleTimeline() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
TextButton(
onPressed: () => setTimeline("follow"),
child: Text('Following',
style: TextStyle(
color: timelineDecision == 'follow'
? Theme.of(context).primaryColor
: Colors.grey)),
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 20),
),
),
TextButton(
onPressed: () => setTimeline("local"),
child: Text('Local',
style: TextStyle(
color: timelineDecision == 'local'
? Theme.of(context).primaryColor
: Colors.grey)),
style: TextButton.styleFrom(
textStyle: const TextStyle(fontSize: 20),
)),
],
);
}
_onEndScroll(ScrollMetrics metrics) {
//loadToTrue();
setState(() {
documentLimit = documentLimit + 10;
});
}
#override
Widget build(context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Entango'),
actions: <Widget>[
PopupMenuButton(
icon: const Icon(Icons.add),
onSelected: choiceAction,
itemBuilder: (context) => [
PopupMenuItem(
child: Text("Timelimit post"),
value: 1,
),
PopupMenuItem(
child: Text("Normal post"),
value: 2,
)
]),
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Show Snackbar',
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (context) => Search())),
)
],
),
body: ListView(
children: <Widget>[
buildToggleTimeline(),
Divider(
height: 0.0,
),
//buildSlider(),
StreamBuilder(
initialData: cache,
stream: timelineDecision == "follow"
? FirebaseFirestore.instance
.collection("timeline")
.doc(widget.currentUser.id)
.collection('timelinePosts')
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.snapshots()
: FirebaseFirestore.instance
.collection("postsLocalRef")
.doc(address)
.collection("userPosts")
.orderBy('timestamp', descending: true)
.limit(documentLimit)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> streamSnapshot) {
items = streamSnapshot.data != null &&
streamSnapshot.data.docs != null
? streamSnapshot.data.docs
: [];
List<Post> posts =
items.map((doc) => Post.fromDocument(doc)).toList();
List<Post> postsLocal =
items.map((doc) => Post.fromDocument(doc)).toList();
cache = streamSnapshot.data;
return !streamSnapshot.hasData ||
streamSnapshot.connectionState ==
ConnectionState.waiting
? Center(
child: CircularProgressIndicator(),
)
: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification) {
_onEndScroll(scrollNotification.metrics);
}
},
child: Container(
height: MediaQuery.of(context).size.height - 200,
margin: EdgeInsets.only(bottom: 1),
child: ListView.builder(
physics: BouncingScrollPhysics(
parent: ScrollPhysics()),
shrinkWrap: true,
itemCount: items.length,
itemBuilder: (_, i) =>
timelineDecision == "follow"
? posts[i]
: postsLocal[i])));
})
],
),
);
}
void choiceAction(int value) {
if (value == 1) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Upload_limit(
currentUser: currentUser,
)));
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Upload(
currentUser: currentUser,
)));
}
}
}
There are few issues I could find. One of them is using StreamBuilder to fetch paged data. While it might be great in theory but it won't work in case of Firebase as Firebase is providing Stream. So every-time, setState is called, a new steam will be created. I have wrote sample app for fetching data from firestore in paginated way.
//main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const PAGE_SIZE = 30;
bool _allFetched = false;
bool _isLoading = false;
List<ColorDetails> _data = [];
DocumentSnapshot? _lastDocument;
#override
void initState() {
super.initState();
_fetchFirebaseData();
}
Future<void> _fetchFirebaseData() async {
if (_isLoading) {
return;
}
setState(() {
_isLoading = true;
});
Query _query = FirebaseFirestore.instance
.collection("sample_data")
.orderBy('color_label');
if (_lastDocument != null) {
_query = _query.startAfterDocument(_lastDocument!).limit(PAGE_SIZE);
} else {
_query = _query.limit(PAGE_SIZE);
}
final List<ColorDetails> pagedData = await _query.get().then((value) {
if (value.docs.isNotEmpty) {
_lastDocument = value.docs.last;
} else {
_lastDocument = null;
}
return value.docs
.map((e) => ColorDetails.fromMap(e.data() as Map<String, dynamic>))
.toList();
});
setState(() {
_data.addAll(pagedData);
if (pagedData.length < PAGE_SIZE) {
_allFetched = true;
}
_isLoading = false;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: NotificationListener<ScrollEndNotification>(
child: ListView.builder(
itemBuilder: (context, index) {
if (index == _data.length) {
return Container(
key: ValueKey('Loader'),
width: double.infinity,
height: 60,
child: Center(
child: CircularProgressIndicator(),
),
);
}
final item = _data[index];
return ListTile(
key: ValueKey(
item,
),
tileColor: Color(item.code | 0xFF000000),
title: Text(
item.label,
style: TextStyle(color: Colors.white),
),
);
},
itemCount: _data.length + (_allFetched ? 0 : 1),
),
onNotification: (scrollEnd) {
if (scrollEnd.metrics.atEdge && scrollEnd.metrics.pixels > 0) {
_fetchFirebaseData();
}
return true;
},
),
);
}
}
class ColorDetails {
final String label;
final int code;
ColorDetails(this.code, this.label);
factory ColorDetails.fromMap(Map<String, dynamic> json) {
return ColorDetails(json['color_code'], json['color_label']);
}
Map toJson() {
return {
'color_code': code,
'color_label': label,
};
}
}
In case you are interested, you can checkout the article I have written as well at https://blog.litedevs.com/infinite-scroll-list-using-flutter-firebase-firestore
I tried many things but couldn't get any workable solution, please help.
Even wit the FutureBuilder it doesn't seem to work and without it, I get the result when I hot reload. I don't know how to change the code to make it work. Probably there is something small missing, but I couldn't figure yet what it is or how to solve it.
"""import 'package:flutter/material.dart';
import 'package:list/screens/add_new_item_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
final _firestore = Firestore.instance;
FirebaseUser loggedinUser;
Future<void> _fetchdata;
FirebaseAuth _auth = FirebaseAuth.instance;
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
void initState() {
super.initState();
_fetchdata = getCurrentUser();
}
Future<void> getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedinUser = user;
// print(loggedinUser.email);
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(
builder: (context) => Addnewitem()));
},
child: Icon(Icons.add),
),
appBar: AppBar(
leading: Container(),
title: Text("Shopping List"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
// messagesStream();
_auth.signOut();
Navigator.pop(context);
})
],
),
body: SafeArea(child:
MessagesStream(),
),
);
}
}
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
FutureBuilder(
future: _fetchdata,
builder: (context, myFuture){
if (myFuture.connectionState == ConnectionState.done && !myFuture.hasError && myFuture.hasData) {
if (myFuture.data != null) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('users')
.document(loggedinUser.uid)
.collection('items')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.hasError || snapshot.data == null || snapshot.connectionState == ConnectionState.waiting || loggedinUser.email == null) {
return (Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent)));
}
final items = snapshot.data.documents.reversed;
List<MessageBubble> messageBubbles = [];
for (var message in items) {
final item = message.data['item'];
final quant = message.data['quant'];
final id = message.data['id'];
final boli = message.data['bool'];
// final currentUser = loggedinUser.email;
final messageBubble = MessageBubble(
text: item,
quant: quant,
documentReference: message.reference,
);
messageBubbles.add(messageBubble);
}
try {
return Expanded(
child: ListView(
// reverse: true,
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 10),
children: messageBubbles,
),
);
} catch (e) {
return Container();
}
});
}else {
return Container();
}
} else {
return CircularProgressIndicator();
}
});
}
}
class MessageBubble extends StatelessWidget {
MessageBubble({this.text, this.quant, this.documentReference});
final String text;
final String quant;
final DocumentReference documentReference;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: Colors.tealAccent,
child: FlatButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
text,
style: TextStyle(color: Colors.black, fontSize: 20),
),
Text(quant,
style: TextStyle(color: Colors.black, fontSize: 20))
],
),
onPressed: () {
documentReference.delete();
}),
)
],
),
),
);
}
}"""
Flutter doctor:
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 11.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 3.5)
[✓] VS Code (version 1.44.0)
[✓] Connected device (3 available)
• No issues found!
You need to add return keyword when using the build function since it returns a Widget:
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _fetchdata,
builder: (context, myFuture){
if (myFuture.connectionState == ConnectionState.done && !myFuture.hasError && myFuture.hasData) {
if (myFuture.data != null) {
return StreamBuilder<QuerySnapshot>(
For people having the same issue - here is my full code! Hope it helps
import 'package:flutter/material.dart';
import 'package:list/screens/add_new_item_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
final _firestore = Firestore.instance;
FirebaseUser loggedinUser;
Future<void> _fetchdata;
FirebaseAuth _auth = FirebaseAuth.instance;
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
void initState() {
super.initState();
_fetchdata = getCurrentUser();
}
Future<void> getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedinUser = user;
// print(loggedinUser.email);
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => Addnewitem()));
},
child: Icon(Icons.add),
),
appBar: AppBar(
leading: Container(),
title: Text("Shopping List"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
// messagesStream();
_auth.signOut();
Navigator.pop(context);
})
],
),
body: SafeArea(
child: MessagesStream(),
),
);
}
}
class MessagesStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _fetchdata,
builder: (context, myFuture) {
if (myFuture.connectionState == ConnectionState.done &&
!myFuture.hasError) {
return StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('users')
.document(loggedinUser.uid)
.collection('items')
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData ||
snapshot.hasError ||
snapshot.data == null ||
snapshot.connectionState == ConnectionState.waiting ||
loggedinUser.email == null) {
return (Center(
child: CircularProgressIndicator(
backgroundColor: Colors.lightBlueAccent)));
}
final items = snapshot.data.documents.reversed;
List<MessageBubble> messageBubbles = [];
for (var message in items) {
final item = message.data['item'];
final quant = message.data['quant'];
final id = message.data['id'];
final boli = message.data['bool'];
// final currentUser = loggedinUser.email;
final messageBubble = MessageBubble(
text: item,
quant: quant,
documentReference: message.reference,
);
messageBubbles.add(messageBubble);
}
try {
return Expanded(
child: ListView(
// reverse: true,
padding:
EdgeInsets.symmetric(horizontal: 10, vertical: 10),
children: messageBubbles,
),
);
} catch (e) {
return Container();
}
});
} else {
return CircularProgressIndicator();
}
});
}
}
class MessageBubble extends StatelessWidget {
MessageBubble({this.text, this.quant, this.documentReference});
final String text;
final String quant;
final DocumentReference documentReference;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Expanded(
flex: 1,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
color: Colors.tealAccent,
child: FlatButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
text,
style: TextStyle(color: Colors.black, fontSize: 20),
),
Text(quant,
style: TextStyle(color: Colors.black, fontSize: 20))
],
),
onPressed: () {
documentReference.delete();
}),
)
],
),
),
);
}
}