Stream Builder load more data - firebase

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

Related

Flutter Firebase - App Becomes Unresponsive When Expanding Exoansion Tiles

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.

How do I write to firebase using a TextFormField in a ListView in flutter

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

Implement Search With FirestoreQueryBuilder FlutterFire [FireStore UI]

i'm new with firebase and i try to Implement Search With FirestoreQueryBuilder FlutterFire [FireStore UI] , how i Implement it works but i want to know the right way, i'm not sure this is a right way or not how i did.
below this how i implement it, thank in advance :)
code example how i implemented:
...........................................................................................................................................
class ProductController {
final categoryCollection = FirebaseFirestore.instance.collection('category');
//Note: get data time and allow user for pagination
Query<ProductModel> searchProduct({required String? searchText}) {
return productCollection
.where('keySearch', arrayContains: searchText)
.withConverter<ProductModel>(
fromFirestore: (snapshot, _) {
Map<String, dynamic> _tempSnapShot = snapshot.data()!;
_tempSnapShot['id'] = snapshot.id;
return ProductModel.fromJson(_tempSnapShot);
},
toFirestore: (product, _) => product.toJson());
}
Query<ProductModel> getProduct() {
return productCollection.withConverter<ProductModel>(
fromFirestore: (snapshot, _) {
Map<String, dynamic> _tempSnapShot = snapshot.data()!;
_tempSnapShot['id'] = snapshot.id;
return ProductModel.fromJson(_tempSnapShot);
},
toFirestore: (product, _) => product.toJson());
}
}
class Product extends StatefulWidget {
const Product({Key? key}) : super(key: key);
#override
State<Product> createState() => _ProductState();
}
class _ProductState extends State<Product> {
String? searchText = '';
#override
Widget build(BuildContext context) {
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: FormBuilderTextField(
name: 'search',
decoration: CustomDecoration.formFieldDecoration(label: 'Search'),
onChanged: (String? text) => setState(() {
searchText = text;
})),
),
Expanded(
child: FirestoreQueryBuilder<ProductModel>(
query: searchText!.isNotEmpty
? ProductController().searchProduct(searchText: searchText)
: ProductController().getProduct(),
builder: (context, snapshot, _) {
if (snapshot.isFetching) {
return const Align(
alignment: Alignment.bottomCenter,
child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Text('Error : ${snapshot.error.toString()}');
} else {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.docs.length,
itemBuilder: ((context, index) {
ProductModel product = snapshot.docs[index].data();
// if we reached the end of the currently obtained items, we try to
final hasEndReached = snapshot.hasMore &&
index + 1 == snapshot.docs.length &&
!snapshot.isFetchingMore;
// obtain more items
if (hasEndReached) {
snapshot.fetchMore();
}
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(product.images != null
? product.images![0].filePath!
: 'https://images.assetsdelivery.com/compings_v2/yehorlisnyi/yehorlisnyi2104/yehorlisnyi210400016.jpg'),
),
title: Text('${product.name} (${product.price} \$)'),
subtitle: Text('Expire Date : '
'${DateFormat('dd/MM/yyyy').format(product.expireDate)}'),
trailing: Wrap(
children: [
IconButton(
onPressed: () => Navigator.pushNamed(
context, route.editProductScreen,
arguments: product),
color: Colors.orangeAccent,
icon: const Icon(Icons.edit),
),
IconButton(
onPressed: () {
if (product.images != null) {
FireBaseStorageMethods()
.removeImage(
images: product.images!,
context: context)
.whenComplete(
() => ProductController()
.removeProduct(
id: product.id!,
context: context),
);
} else {
ProductController().removeProduct(
id: product.id!, context: context);
}
},
color: Colors.redAccent,
icon: const Icon(Icons.delete),
)
],
),
);
}),
);
}
})
),
],
);
}

How do you update a FutureBuilder when the Sqlite db it gets data from gets new data?

I am using a FutureBuilder to display data from a sqlite database. I can add data to that database using a drawer that is also on that page. When I add new data I want the FutureBuilder to automatically update so the page will display the new data that was just added to the sqlite database. How could I properly go about doing this? Thanks!
The page where the data is displayed using the FutureBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[200],
appBar: PreferredSize(
preferredSize: Size.fromHeight(95.0),
child: AppBar(
automaticallyImplyLeading: false, // hides leading widget
flexibleSpace: DataAppBar(),
),
),
body: FutureBuilder<dynamic>(
future: DataDBProvider.dataDB.getData(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('none');
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.active:
return Text('');
case ConnectionState.done:
if (snapshot.hasError) {
print(
'${snapshot.error}',
);
}
}
List data = snapshot.data;
return ListView.builder(
itemCount: data.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 1.0, horizontal: 4.0),
child: Card(
color: (index % 2 == 0) ? greycolor : Colors.white,
child: Container(
height: 60,
padding: EdgeInsets.fromLTRB(0, 20, 0, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Flexible(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 4),
child: Text(data[index].date,
style: TextStyle(fontSize: 14),
textAlign: TextAlign.left),
),
],
),
),
Expanded(
flex: 5,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(data[index].title,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black,
fontFamily: 'Montserrat'),
textAlign: TextAlign.center)
],
)),
],
),),
Expanded(
flex: 3,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text('\$${data[index].amount}',
style: TextStyle(fontSize: 17,
color: Colors.black),
textAlign: TextAlign.right),
],
),
),
],
)),
),
);
},
);
}
));
}
You can copy paste run full code below
To achieve automatically update so the page will display the new data
In this case, you can use package https://pub.dev/packages/sqlbrite and StreamBuilder
sqlbrite is Streaming sqflite, The BriteDatabase.createQuery method is similar to Database.query. Listen to the returned Stream<Query> which will immediately notify with a Query to run.
And the page will automatically display the new data
code snippet
class AppDb {
...
final _dbFuture = _open().then((db) => BriteDatabase(db));
Stream<List<Item>> getAllItems() async* {
final db = await _dbFuture;
yield* db
.createQuery(_tableItems, orderBy: 'createdAt DESC')
.mapToList((json) => Item.fromJson(json));
}
Future<bool> insert(Item item) async {
final db = await _dbFuture;
final id = await db.insert(
_tableItems,
item.toJson(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
return id != -1;
}
Future<bool> remove(Item item) async {
...
}
Future<bool> update(Item item) async {
...
}
}
...
StreamBuilder<List<Item>>(
stream: AppDb.getInstance().getAllItems(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final items = snapshot.data;
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
working demo
full code
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:sqlbrite/sqlbrite.dart';
import 'dart:math';
const _tableItems = 'items';
Future<Database> _open() async {
final directory = await getApplicationDocumentsDirectory();
final path = join(directory.path, 'example.db');
return await openDatabase(
path,
version: 1,
onCreate: (Database db, int version) async {
await db.execute(
'''
CREATE TABLE $_tableItems(
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
content TEXT NOT NULL,
createdAt TEXT NOT NULL
)
''',
);
final batch = db.batch();
for (int i = 0; i < 10; i++) {
batch.insert(
_tableItems,
Item(
null,
contents.random(),
DateTime.now(),
).toJson(),
);
}
final list = await batch.commit(
continueOnError: true,
noResult: false,
);
print('Batch result: $list');
},
);
}
class AppDb {
static AppDb _singleton;
AppDb._();
factory AppDb.getInstance() => _singleton ??= AppDb._();
final _dbFuture = _open().then((db) => BriteDatabase(db));
Stream<List<Item>> getAllItems() async* {
final db = await _dbFuture;
yield* db
.createQuery(_tableItems, orderBy: 'createdAt DESC')
.mapToList((json) => Item.fromJson(json));
}
Future<bool> insert(Item item) async {
final db = await _dbFuture;
final id = await db.insert(
_tableItems,
item.toJson(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
return id != -1;
}
Future<bool> remove(Item item) async {
final db = await _dbFuture;
final rows = await db.delete(
_tableItems,
where: 'id = ?',
whereArgs: [item.id],
);
return rows > 0;
}
Future<bool> update(Item item) async {
final db = await _dbFuture;
final rows = await db.update(
_tableItems,
item.toJson(),
where: 'id = ?',
whereArgs: [item.id],
conflictAlgorithm: ConflictAlgorithm.replace,
);
return rows > 0;
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData.dark(),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final _dateFormatter = DateFormat.Hms().add_yMMMd();
MyHomePage({Key key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('sqlbrite example'),
),
body: Container(
constraints: BoxConstraints.expand(),
child: StreamBuilder<List<Item>>(
stream: AppDb.getInstance().getAllItems(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
final items = snapshot.data;
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
title: Text(item.content),
subtitle:
Text('Created: ${_dateFormatter.format(item.createdAt)}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.remove_circle),
onPressed: () => _remove(item),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _update(item),
),
],
),
);
},
);
},
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _add,
),
);
}
void _add() async {
final item = Item(
null,
contents.random(),
DateTime.now(),
);
final success = await AppDb.getInstance().insert(item);
print('Add: $success');
}
void _remove(Item item) async {
final success = await AppDb.getInstance().remove(item);
print('Remove: $success');
}
void _update(Item item) async {
final success = await AppDb.getInstance().update(
item.copyWith(
contents.random(),
),
);
print('Update: $success');
}
}
const contents = [
'Aaren',
'Aarika',
'Abagael',
'Abagail',
'Abbe',
'Abbey',
'Abbi',
'Abbie',
'Abby',
'Abbye',
'Abigael',
'Abigail',
'Abigale',
'Abra',
'Ada',
'Adah',
'Adaline',
'Adan',
'Adara',
'Adda',
'Addi',
'Addia',
'Addie',
'Addy',
'Adel',
'Adela',
'Adelaida',
'Adelaide',
'Adele',
'Adelheid',
'Adelice',
'Adelina',
'Adelind',
'Adeline',
'Adella',
'Adelle',
'Adena',
'Adey',
'Adi',
'Adiana',
'Adina',
'Adora',
'Adore',
'Adoree',
'Adorne',
'Adrea',
'Adria',
'Adriaens',
'Adrian',
'Adriana',
'Adriane',
'Adrianna',
'Adrianne',
'Adriena',
'Adrienne',
'Aeriel',
'Aeriela',
'Aeriell',
'Afton',
'Ag',
'Agace',
'Agata',
'Agatha',
'Agathe',
'Aggi',
'Aggie',
'Aggy',
'Agna',
'Agnella',
'Agnes',
'Agnes',
];
extension RandomElementExtension<T> on List<T> {
T random() {
final index = Random().nextInt(length);
return this[index];
}
}
class Item {
final int id;
final String content;
final DateTime createdAt;
const Item(
this.id,
this.content,
this.createdAt,
);
factory Item.fromJson(Map<String, dynamic> json) {
return Item(
json['id'],
json['content'],
DateTime.parse(json['createdAt']),
);
}
Map<String, dynamic> toJson() {
return {
if (id != null) 'id': id,
'content': content,
'createdAt': createdAt.toIso8601String(),
};
}
Item copyWith(String content) => Item(id, content, createdAt);
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is Item &&
runtimeType == other.runtimeType &&
id == other.id &&
content == other.content &&
createdAt == other.createdAt;
#override
int get hashCode => id.hashCode ^ content.hashCode ^ createdAt.hashCode;
#override
String toString() =>
'Item{id: $id, content: $content, createdAt: $createdAt}';
}

A build function returned null. Flutter Firebase

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();
}),
)
],
),
),
);
}
}

Resources