Related
I have a piece of code here that is supposed to get some data from firebase firestore and add that data to a list which is then in turn is used by listview.builder to update the ui with the list of items. But somehow, the same data keeps getting added to the list over and over again. I put a print statement and i can see that the code inside the "then" function keeps executing over and over. How do i stop this from happening?
Thanks in advance
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import '../models/reportmodel.dart';
import 'package:flutter/material.dart';
class AllReports extends StatefulWidget {
#override
_AllReportsState createState() => _AllReportsState();
}
class _AllReportsState extends State<AllReports> {
List<Report> reportList = [];
bool isLoading = true;
#override
Widget build(BuildContext context) {
getData();
return (isLoading)
? buildLoading()
: ListView.builder(
padding: EdgeInsets.all(20),
itemBuilder: (context, index) {
return Container(
width: double.infinity,
height: 140,
child: Column(
children: [
Text(
"Complaint ID: " + reportList[index].getComplaintHash(),
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(
"Name: " + reportList[index].getComplainantName(),
),
Text("Time: " + reportList[index].getComplaintTime()),
],
),
);
},
itemCount: reportList.length);
}
Widget buildLoading() => Stack(
fit: StackFit.expand,
children: [
Center(child: CircularProgressIndicator()),
],
);
void getData() async {
final user = FirebaseAuth.instance.currentUser;
final snapshot = await FirebaseFirestore.instance
.collection(user.email)
.getDocuments()
.then((snapshot) {
for (int i = 0; i < snapshot.documents.length; i++) {
reportList.add(Report.addData(
snapshot.documents[i].id.toString(),
snapshot.documents[i].data()["name"].toString(),
snapshot.documents[i].data()["contact"].toString(),
snapshot.documents[i].data()["time"].toString(),
snapshot.documents[i].data()["description"].toString(),
snapshot.documents[i].data()["additional_info"].toString()));
}
this.setState(() {
isLoading = false;
});
});
}
}
It's getting executed over and over again, because you are calling the method getData() inside the build(), and everytime you call setState then it issues another build.Therefore, the method getData() will keep executing and retrieving the data.
You should use FutureBuilder widget to handle asynchronous operations.
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Hello Im very to the flutter framework, so please let me know if im going wrong anywhere and the appropriate way of doing the things.
this is a drawerPage.dar file
In this file im trying to call a function getData for retrieving the data from firebase,this fucntion is in Database.dart file.
Database.dart
In the Database.dart file i wrote the getData function inside which im retrieving a particular record from the firebase and storing in a global variable. And then im trying to print the global variable in the drawerPage.dart file.But here when ever i run the program, for the first time the variable is having a null value and upon hot reload the actual value is getting stored in the variable.Please let me know how can i get rid of this problem.
output
drawerPageOutput
drawerPage.dart
import 'package:attendee/constants.dart';
import 'package:attendee/models/userdeails.dart';
import 'package:attendee/pages/profile.dart';
import 'package:attendee/services/authentication_service.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:attendee/services/database.dart';
import 'package:provider/provider.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:attendee/pages/userdetails.dart';
class StudentDashboard extends StatefulWidget {
#override
_StudentDashboardState createState() => _StudentDashboardState();
}
class _StudentDashboardState extends State<StudentDashboard> {
userdetails userdetail;
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
final AuthenticationService _auth = AuthenticationService();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
DatabaseService().getData('email');
final drawerHeader = UserAccountsDrawerHeader(
accountName: Text(userName),
accountEmail: Text('${result}'),
currentAccountPicture
: CircleAvatar(
child: FlutterLogo(size: 42.0),
backgroundColor: Colors.white,
);
final drawerItems = ListView(
children: <Widget>[
drawerHeader,
ListTile(
title: Row(
children: <Widget>[
Icon(Icons.perm_identity_outlined),
Text(' Profile'),
],
),
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (context)=>Profile())),
),
ListTile(
title: Text('To page 2'),
onTap: () => Navigator.of(context).push(_NewPage(2)),
),
ListTile(
title:Row(
children: <Widget>[
Icon(Icons.exit_to_app_rounded),
Text(' Logout'),
],
),
onTap: () async {
await _auth.signOut();
Navigator.of(context).pushNamed('/homepage');
},
),
],
);
return StreamProvider<List<userdetails>>.value(
value: DatabaseService().students,
initialData: [],
child: SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightGreen,
title: Text('Student Welcome'),
actions: <Widget>[
TextButton.icon(
onPressed: () async {
await _auth.signOut();
Navigator.of(context).pushNamed('/homepage');
},
icon: Icon(Icons.person),
label: Text('Logout'))
],
),
body:
UserDetails(),
drawer: GestureDetector(
onTap: display,
child: Drawer(
child: drawerItems,
),
),
),
),
);
}
display() async{
await DatabaseService().getData('email');
}
}
// <Null> means this route returns nothing.
class _NewPage extends MaterialPageRoute<Null> {
_NewPage(int id)
: super(builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page $id'),
elevation: 1.0,
),
body: Center(
child: Text('Page $id'),
),
);
});
}
database.dart
import 'package:attendee/models/userdeails.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_icons/flutter_icons.dart';
import '../constants.dart';
class DatabaseService{
final String uid;
DatabaseService({this.uid});
//collection reference
final CollectionReference user_details=FirebaseFirestore.instance.collection('users');`
final CollectionReference tutor_details` `=FirebaseFirestore.instance.collection("tutors");`
Future updateStudentData(String fullname,String mobilenumber,String `email,String rollno,String tutorid,String role) async {`
return await user_details.doc(uid).set({
'fullname' : fullname,
'mobilenumber': mobilenumber,
'email' : email,
'rollno': rollno,
'tutorid': tutorid,
'role' : role,//FMERT series
});
}
Future updateTutorData(String fullname,String mobilenumber,String `email,String rollno,String tutorid,String role) async {`
return await tutor_details.doc(uid).set({
'fullname' : fullname,
'mobilenumber': mobilenumber,
'email' : email,
'rollno': rollno,
'tutorid': tutorid,
'role' : role,//FMERT series
});
}
//studentDetails from snapshot
List<userdetails> _studentDetailsFromSnapshot(QuerySnapshot snapshot){
return snapshot.docs.map((doc){
return userdetails(
fullname: doc.data()['fullname'] ?? '',
mobilenumber: doc.data()['mobilenumber'] ?? '',
email: doc.data()['email'] ?? '',
rollno: doc.data()['rollno'] ?? '',
tutorid: doc.data()['tutorid'] ?? '',
//role: doc.data()['role'] ?? '',
);
}).toList();
}
//get students stream
Stream<List<userdetails>> get students {
return user_details.snapshots()
.map(_studentDetailsFromSnapshot);
}
//tutorsDetails from snapshot
List<userdetails> _tutorDetailsFromSnapshot(QuerySnapshot snapshot){
return snapshot.docs.map((doc){
return userdetails(
fullname: doc.data()['fullname'] ?? '',
mobilenumber: doc.data()['mobilenumber'] ?? '',
email: doc.data()['email'] ?? '',
rollno: doc.data()['rollno'] ?? '',
tutorid: doc.data()['tutorid'] ?? '',
);
}).toList();
}
//get tutors stream
Stream<List<userdetails>> get tutors {
return user_details.snapshots()
.map(_studentDetailsFromSnapshot);
}
void display() {
tutor_details.get().then((querySnapshot) {
querySnapshot.docs.forEach((result) {
print(result.data());
});
});
}
getData (String string) async{
String userId = await FirebaseAuth.instance.currentUser.uid;
final document = isTutor ? `FirebaseFirestore.instance.doc('tutors/$userId') :`
await FirebaseFirestore.instance.doc('users/$userId');
document.get().then((DocumentSnapshot) async {
if(string =='role') {
checkRole = DocumentSnapshot.data()[string].toString();
print('$checkRole inside getData Function');
//return checkRole;
print(checkRole);
}
else {
print(result);
result = await DocumentSnapshot.data()[string].toString();
print('${DocumentSnapshot.data()[string].toString()} in the `database else block');`
//return result;
}
//print(document("name"));
});
}
}
After changes
terminaloutput
draweroutput
""when ever i run the program, for the first time the variable is having a null value and upon hot reload the actual value is getting stored in the variable""
When we try to get data from http / https request, it takes some time. Meanwhile the page gets loaded and you get null values.
You can use Provider package to resolve this issue, or try the below code. Please add the below code in your drawerPage.dart.
What I have done below is made getData() return type. Only on receiving a value from this function, _loadOnce will change to false & final screen will be shown.
Database.dart
Future<bool> getData (String string) async{
String userId = await FirebaseAuth.instance.currentUser.uid;
final document = isTutor ? `FirebaseFirestore.instance.doc('tutors/$userId') :`
await FirebaseFirestore.instance.doc('users/$userId');
document.get().then((DocumentSnapshot) async {
if(string =='role') {
checkRole = DocumentSnapshot.data()[string].toString();
print('$checkRole inside getData Function');
//return checkRole;
print(checkRole);
return true;
}
else {
print(result);
result = await DocumentSnapshot.data()[string].toString();
print('${DocumentSnapshot.data()[string].toString()} in the `database else block');`
//return result;
return false;
}
//print(document("name"));
});
}
}
/// create a new variable.
bool _loadOnce = true;
/// shift your code `DatabaseService().getData('email');`
#override
void didChangeDependencies() {
if(_loadOnce == true) {
DatabaseService().getData('email').then((value) {
if(value == true){
setState(() {
_loadOnce = false;
});
} else {
/// you can write your code here
setState(() {
_loadOnce = false;
});
}
)}
}
super.didChangeDependencies();
}
Below code will show a spinner till the time all the code gets executed and values are retreived.
/// in your main page under Scaffold
body: _loadOnce == true
? Center(
child: CircularProgressIndicator(
backgroundColor: Theme.of(context).primaryColor,
),
)
: UserDetails(),
I'm very new in flutter, and are trying to write the app that will take value from sqlite. From what I tried, it needs to use FutureBuilder widget.
But the following code I wrote, the FutureBuilder widget seems to get data from sqlite, but the "builder" property was never called:
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:flutterapp/dbHelper.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
routes:{
"/": (context) =>Test()
}
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool nameCheck = false; // Use to check name textfield has correctly be inputed
TextEditingController nameController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: <Widget>[
SizedBox(height:300),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: nameController,
)
),
IconButton(
icon: Icon(Icons.check_circle),
onPressed:(){
return FutureBuilder(
future: getData(nameController.text),
builder: (BuildContext context, AsyncSnapshot<List<Map>>snapshot) {
print("Start future"); // never get printed
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
Builder(
builder: (BuildContext context) {
print("got data");
final result = snapshot.data;
print(result) ; // never get printed
setState(() {
nameCheck = true;
});
return Container();
})
];
} else {
children = <Widget>[
AlertDialog(
content: SpinKitCircle(
color: Colors.white,
size: 80.0,
))
];
}
return Center(
child: Container(
color: Colors.blue,
child: Column(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment:
CrossAxisAlignment
.center,
children: children,
)));
});}
)
],
),
Builder(
builder: (BuildContext context){
if (nameCheck == true){
return Text("test");
}
return Container();
}
)
],
)
);
}
}
Future<List<Map>> getData(String input_name) async{
final dbHelper = DBHelper.instance;
await dbHelper.database;
final result = await dbHelper.query("SELECT * FROM Guest WHERE Name = \"$input_name\"");
print(result); // This get printed
return result;
}
The DBHelper code is as follow, basically it just set up a sqlite database and some database operation:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
class DBHelper {
static final _databaseName = "MonetaryDB.db";
static final _databaseVersion = 1;
static final create_table_Test = "CREATE TABLE \"Guest\" (\"Name\" TEXT NOT NULL PRIMARY KEY, \"Money\" INTEGER, \"Person\" INTEGER)";
static final String insert_guest = "INSERT INTO Guest (Name, Money, Person) VALUES (\"testname\", 1000, 1)";
DBHelper._privateConstructor();
static final DBHelper instance = DBHelper._privateConstructor();
static Database _database;
Future<Database> get database async {
if (_database != null) {
return _database;}
else{
_database = await _initDatabase();
return _database;}
}
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate,
);
}
Future _onCreate(Database db, int version) async {
await db.execute(create_table_Test);
await db.rawInsert(insert_guest);
}
Future<int> insert(String statement) async {
Database db = await instance.database;
return await db.rawInsert(statement);
}
Future<List<Map>> query(String statement) async {
Database db = await instance.database;
return await db.rawQuery(statement);
}
Future<int> update(String statement) async{
Database db = await instance.database;
return await db.rawUpdate(statement);
}
Future<int> delete(String statement) async{
Database db = await instance.database;
return db.rawDelete(statement);
}
}
If I simply change the IconButton onPressed function into setState((){nameCheck = true}), The Text("test") widget will show, so the problem must be the FutureBuilder. Also, the getData() function can get the correct result from the sqlite database
I have no idea why the FutureBuilder doesn't get build, did someone have any idea of it?
Thanks!
So with the help in the comment section, I change the code and it worked:
...
Widget build(BuildContext context) {
return Scaffold(
body:Column(
children: <Widget>[
SizedBox(height:300),
Row(
children: <Widget>[
Expanded(
child: TextField(
controller: nameController,
)
),
IconButton(
icon: Icon(Icons.check_circle),
onPressed:(){
return showDialog(
context: context,
builder: (BuildContext context){
return AlertDialog(
actions: <Widget>[
SizedBox(
width: 300,
height: 300,
child: FutureBuilder(
future: getData(nameController.text),
builder: (BuildContext context, AsyncSnapshot<List<Map>>snapshot) {
if (snapshot.hasData) {
final result = snapshot.data;
SchedulerBinding.instance.addPostFrameCallback((_) => setState(() {
nameCheck = true;
}));
Navigator.pop(context, true);
}
else {
return Container(
child: SpinKitCircle(
color: Colors.white,
size: 80.0,
));
}
return Container(color: Colors.blue);
})
),
],
);
}
);
}
)
],
),
Builder(
builder: (BuildContext context){
if (nameCheck == true){
return Text("test");
}
return Container();
}
)
],
)
);
}
...
The other codes are still the same.
Like the comment section above had suggested, the main problem is that there will be no place to build for the widget the FutureBuilder that is going to built. To solve this problem, I place the FutureBuilder widget into the AlertDialog widget, since that I still want to keep the SpinKitCircle widget when loading.
I also gave up the Column widget at the end of the FutureBuilder widget, and deleted the Builder widget at the beginning of the FutureBuilder widget, which it was no longer needed when there was no Column.
The above codes still throw an acceptable exception:"setState() or markNeedsBuild() called during build." But the whole things still can run, so I will try to fix that the other day.
Thanks for the suggestion in the comment section.
I'm trying to make a chat between two people using Flutter and Firebase but I'm facing an error when I connect into my app using Firebase's signInWithEmailAndPassword, it tells me:
The getter 'email' was called on null. Receiver: null Tried calling:
email
And Flutter also tells me the error come from the MaterialApp widget from my main.dart which doesn't help me to find the error..
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:social/responsive/size_config.dart';
import 'settings.dart';
import 'home.dart';
final _firestore = Firestore.instance;
FirebaseUser loggedInUser;
class ActivityFeed extends StatefulWidget {
static const String id = 'activity_feed_screen';
#override
_ActivityFeedState createState() => _ActivityFeedState();
}
class _ActivityFeedState extends State<ActivityFeed> {
final _auth = FirebaseAuth.instance;
#override
void initState() {
super.initState();
getCurrentUser();
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Container(
color: Colors.red,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
UserInfo(),
FlatButton(
child: Icon(
Icons.settings,
color: Colors.white,
),
onPressed: () {
Navigator.pushNamed(context, Settings.id);
},
),
FlatButton(
child: Icon(
Icons.not_interested,
color: Colors.white,
),
onPressed: () {
_auth.signOut();
Navigator.pushNamed(context, Home.id);
},
),
],
),
),
)
],
),
);
}
}
class UserInfo extends StatefulWidget {
#override
_UserInfoState createState() => _UserInfoState();
}
class _UserInfoState extends State<UserInfo> {
String email = loggedInUser.email;
String username;
#override
void initState() {
super.initState();
getUsername();
}
void getUsername() async {
DocumentReference docRef = _firestore.collection('users').document(email);
docRef.get().then((snapshot) {
if (snapshot.exists) {
username = snapshot.data['username'];
}
});
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Column(
children: <Widget>[
Text(
'Welcome $username',
style: TextStyle(
fontFamily: 'Amatic',
fontSize: SizeConfig.safeBlockHorizontal * 10),
),
],
)
;
}
}
So basically what I am just trying to display 'Welcome' + the username, but it doesn't want,
my Firebase Database have one collection named 'users', where the document names are the email the user used when he created his account.
All the Register/Log-In process seems to work fine.
If someone has a clue of what is happening that would be awesome, thanks.
You initialize username as null. You can either check it on the widget tree
username != null ? Text(
'Welcome $username',
style: TextStyle(
fontFamily: 'Amatic',
fontSize: SizeConfig.safeBlockHorizontal * 10),
) : CircularProgressIndicator()
and set it via setState()
setState(() {
username = snapshot.data['username'];
});
Another solution is to change
String username;
to
String username = '';
You should use a state management method by the way. setState() is very primitive and you will end up having sphagetti code if you use it as a state management solution.
I am developing a Flutter app and I am using the cloud_firestore plugin. I have a collection of submissions and I am using the StreamBuilder to display them (which I am assuming will update when the stream changes). I literally took the example from the plugin examples as there is not much documentation on how to do things using the plugin. When I added a record, the list of documents that I am displaying gets longer, but it seems to be copying one of the submissions instead of inserting the new submission. The new submission does not show after it is added. Here is the code for how I am displaying the list:
// At the top of the class home.dart.
final submissions = Firestore.instance.collection('submissions');
// This is in submission-list.dart and the above submissions
// is passed in to the contructor
Widget build(BuildContext context) {
return new StreamBuilder<QuerySnapshot>(
stream: submissions
.where('owner_uid', isEqualTo: this.user.uid)
.orderBy('timestamp', descending: true)
.snapshots,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
var date = _formatDate(document['timestamp']);
String body = _constructCardBody(document['weight'],
bodyFat: document['bodyFat']);
String id = document.documentID;
return new SubmissionCard(id: id, title: date, body: body, submissions: submissions);
}).toList(),
);
},
);
}
Here is submission-card.dart in full:
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import '../utils/logger.dart';
import './block-button.dart';
class SubmissionCard extends StatefulWidget {
final String id;
final String title;
final String body;
final CollectionReference submissions;
SubmissionCard({this.id, this.title, this.body, this.submissions});
#override
State<StatefulWidget> createState() =>
new _SubmissionCardState(id: this.id, title: this.title, body: this.body, submissions: this.submissions);
}
class _SubmissionCardState extends State<SubmissionCard> {
final String id;
final String title;
final String body;
bool showActionButtons = false;
final CollectionReference submissions;
_SubmissionCardState({this.id, this.title, this.body, this.submissions});
void _showEditScreen() {}
void _showActionButtons() {
setState(() {
showActionButtons = true;
});
}
void _hideActionButtons() {
setState(() {
showActionButtons = false;
});
}
Future<Null> _deleteSubmission() async {
try {
await submissions.document(id).delete();
await Logger.log('error', 'stackTrace');
} catch (error, stackTrace) {
await Logger.log(error, stackTrace);
}
}
void _closeDialog() {
Navigator.of(context).pop();
_hideActionButtons();
}
Future<Null> _warnAboutDeletion() async {
return showDialog(
context: context,
child: new SimpleDialog(
title: new Text('Are you sure?'),
children: <Widget>[
new SimpleDialogOption(
onPressed: () {
this._deleteSubmission();
this._closeDialog();
},
child: new Text("I'm sure. Delete it."),
),
new SimpleDialogOption(
onPressed: _closeDialog,
child: new Text("Nope. Take me back."),
),
],
)
);
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: _showActionButtons,
onTap: _hideActionButtons,
child: new Card(
elevation: showActionButtons ? 8.0 : 2.0,
key: new GlobalKey(),
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
trailing: showActionButtons
? new Row(
children: <Widget>[
new IconButton(
padding: const EdgeInsets.all(0.0),
icon: const Icon(Icons.edit),
onPressed: _showEditScreen,
color: Colors.black12,
splashColor: Colors.black26,
highlightColor: Colors.black12,
),
new IconButton(
padding: const EdgeInsets.all(0.0),
icon: const Icon(Icons.delete),
onPressed: _warnAboutDeletion,
color: Colors.redAccent,
splashColor: Colors.black26,
highlightColor: Colors.black12,
),
],
)
: new Container(),
isThreeLine: true,
title: new Text(title),
subtitle: new Text(
body,
style: new TextStyle(height: 3.0),
),
),
],
),
),
);
}
}
Link to repo: https://github.com/dericgw/bodwatch
Before, when I have worked with Firebase, this collection would automatically update. I have never seen this weird behavior before. Now, I am new to Flutter and Dart, so I could be missing something for sure.
You need to add the indexing in firebase console.
In your case, you need to a multiple indexes.
1. owner_uid, ascending
2. timestamp, descending
And the problem should solve.