Related
I've created a login screen and once the email and password got verified user will be pushed to the dashboard. So what I need to do is, show a loading indicator only if there are no exceptions that get thrown otherwise it should show the alert which I've implemented with in a try-catch.
log-in button -
SizedBox(
width: MediaQuery.of(context).size.width,
child: TextButton(
onPressed: signIn,
style: ButtonStyle(
padding: MaterialStateProperty.all<EdgeInsets>(
const EdgeInsets.fromLTRB(0, 20, 0, 20),
),
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0))),
backgroundColor:
MaterialStateProperty.all(primaryColor),
),
child: const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontFamily: 'InterBold',
),
),
),
),
Validation class -
Future signIn() async {
if (_key.currentState!.validate()) {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text.trim(),
password: passwordCrontroller.text.trim());
errorMessage = '';
} on FirebaseAuthException catch (e) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text(
'Error',
style: TextStyle(color: mainText),
),
content: Text(
e.message!,
style: const TextStyle(color: secondaryText),
),
contentPadding:
const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 0.0),
backgroundColor: Colors.white,
actions: [
TextButton(
onPressed: () {
navigatorKey.currentState!.pop();
},
child: const Text(
'Close',
style: TextStyle(color: primaryColor),
))
],
));
}
}
}
String? validateEmail(String? formEmail) {
String pattern = r'\w+#\w+\.\w+';
RegExp regex = RegExp(pattern);
if (formEmail == null || formEmail.isEmpty || !regex.hasMatch(formEmail)) {
return '';
}
return null;
}
String? validatePassword(String? formPassword) {
String pattern =
r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!##\$&*~]).{6,}$';
RegExp regex = RegExp(pattern);
if (formPassword == null ||
formPassword.isEmpty ||
!regex.hasMatch(formPassword)) {
return '';
}
return null;
}
}
Loading indicator -
showLoadingIndicatorDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const Center(
child: CircularProgressIndicator(
color: primaryColor,
),
));
}
So what you can do is the following :
declare the bool value _isLoading =false;
you can use the setstate to change the state.
So what does the below code do:
Initially _isLoading will be false so it will show you the text widget.
when you hit the signin button it will make it true so the circular progress indicator will be appearing.
then when the api call is complete then resetting it back to false and that we can see the text widget.
And if there comes any error en the exception making the _isLoading to false so that it will be the text widget and there will be dialog shown with error message.
I have taken your code and just made the changes check the use of bool value and use it as per you state management needs, I have elaborated with a simple setState.
class App extends StatelessWidget {
bool _isLoading =false;
#override
Widget build(BuildContext context) {
// TODO: implement build
return SizedBox(
width: MediaQuery.of(context).size.width,
child: TextButton(
onPressed: signIn,
style: ButtonStyle(
padding: MaterialStateProperty.all<EdgeInsets>(
const EdgeInsets.fromLTRB(0, 20, 0, 20),
),
shape:
MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0))),
// backgroundColor:
// MaterialStateProperty.all(primaryColor),
),
child:
_isLoading ?
const CircularProgressIndicator():
const Text(
'Login',
style: TextStyle(
color: Colors.white,
fontSize: 15,
fontFamily: 'InterBold',
),
),
),
);
}
Future signIn() async {
if (_key.currentState!.validate()) {
try {
setState({
_isLoading =true;
});
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text.trim(),
password: passwordCrontroller.text.trim());
errorMessage = '';
setState({
_isLoading =false;
});
} on FirebaseAuthException catch (e) {
setState({
_isLoading =false;
});
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text(
'Error',
style: TextStyle(color: mainText),
),
content: Text(
e.message!,
style: const TextStyle(color: secondaryText),
),
contentPadding:
const EdgeInsets.fromLTRB(24.0, 20.0, 24.0, 0.0),
backgroundColor: Colors.white,
actions: [
TextButton(
onPressed: () {
navigatorKey.currentState!.pop();
},
child: const Text(
'Close',
style: TextStyle(color: primaryColor),
))
],
));
}
}
}
}
Let me know if it works
Flutter fellow today I have a problem with the Firebase Authentication service.
Well then let me explain the situation. I have a page that let you select a type of user, if you select "Guest" you will push through the app's dashboard page Nav() without sign-in but if select "Sign-in" app will push you to the login page Login().
class SelectUser extends StatelessWidget {
const SelectUser({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF20348F),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: const EdgeInsets.all(15.0),
margin: const EdgeInsets.all(15.0),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: BorderRadius.circular(45),
),
child: TextButton(
child: const Text('Guest'),
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) => const Nav(),
),
(route) => false,
);
},
style: TextButton.styleFrom(
primary: const Color(0xFF20348F),
textStyle: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
)),
),
),
Container(
padding: const EdgeInsets.all(15.0),
margin: const EdgeInsets.all(15.0),
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: BorderRadius.circular(45),
),
child: TextButton(
child: const Text('Sign-In'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (contex) {
return const Login();
}));
},
style: TextButton.styleFrom(
primary: const Color(0xFF20348F),
textStyle: const TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
)),
),
),
],
),
),
);
}
}
In Nav() I use BottomNavigationBarItem and have a several widget included Proflie()
inside.
I already test the sign-up, sign-out and registration function everything is perfectly fine but when I try to be tricky in Proflie() and I face...let's said the weird problem. First let's me show you the code in the Proflie().
class Profile extends StatefulWidget {
const Profile({Key? key}) : super(key: key);
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
String? _email;
DateTime? _creationTime;
DateTime? _lastSignIn;
bool? _status;
#override
Widget build(BuildContext context) {
userStatus();
return userForm();
}
userStatus() {
FirebaseAuth.instance.authStateChanges().listen((event) {
if (event == null) {
_status = false;
} else {
_status = true;
_email = FirebaseAuth.instance.currentUser!.email;
_creationTime =
FirebaseAuth.instance.currentUser!.metadata.creationTime;
_lastSignIn =
FirebaseAuth.instance.currentUser!.metadata.lastSignInTime;
}
});
}
Widget userForm() {
if (_status == true) {
return Scaffold(
appBar: defaultAppBar('Profile'),
body: Center(
child: ListView(
padding: const EdgeInsets.all(20),
children: [
Container(
margin: const EdgeInsets.all(30),
child: Text(
'Email: $_email',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
),
Container(
margin: const EdgeInsets.all(30),
child: Text(
'Creation Time: $_creationTime',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
),
Container(
margin: const EdgeInsets.all(30),
child: Text(
'Last Sign-In: $_lastSignIn',
style: const TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
),
const SizedBox(height: 45),
button('Change password', 1),
const SizedBox(height: 18),
button('Change email', 2),
const SizedBox(height: 18),
button('Sign-out', 0),
],
),
),
);
} else {
return Center(
child: ListView(
padding: const EdgeInsets.all(14),
children: <Widget>[
const SizedBox(height: 100),
Container(
margin: const EdgeInsets.all(30),
child: const Center(
child: Text(
'Please sign-in to use this Feature',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
),
const SizedBox(height: 50),
button('Login', 3),
const SizedBox(height: 18),
button('Register', 4),
],
),
);
}
}
Widget button(String txt, int _nav) {
Color? _color = const Color(0xFF20348F);
if (_nav == 0) {
_color = Colors.red;
}
return TextButton(
style: TextButton.styleFrom(
primary: _color,
textStyle: const TextStyle(
fontSize: 20,
),
),
child: Text(txt),
onPressed: () {
if (_nav == 0) {
FirebaseAuth.instance.signOut();
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) => const SelectUser(),
),
(route) => false,
);
} else {
Navigator.push(context, MaterialPageRoute(builder: (contex) {
if (_nav == 1) {
return const EditPass();
} else if (_nav == 2) {
return const EditEmail();
} else if (_nav == 3) {
return const Login();
} else {
return const Register();
}
}));
}
},
);
}
}
As you can see I'm trying to use userStatus() to identify the "User" and "Guest" and that function I use FirebaseAuth.instance.authStateChanges().listen((event) to check user state in Firebase Authentication(Not really sure am I doing the right way if not please teach me how to check user state) if event == null that mean no user sign-in right now. I'm just going to set _status = false so the "Guest" should found the else case in userForm(). Otherwise _status = true this mean user is signed-in and userForm() should go to if (_status == true) case.
Now the problem is when I success to sign-in on the Login(). In Profile() I ended up got the else case of userForm() instead but that not all! When I hot reloaded the IDE it turn out now I'm in the if (_status == true) case. Yeah like said it a weird problem the first time that app loaded the page it go to false case but when hot reloaded it turn to true case. I'm not so sure the error is from Profile() or Login(). I'm just going leave the Login() then.
class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key);
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
final _formKey = GlobalKey<FormState>();
var _email = "";
var _password = "";
final emailController = TextEditingController();
final passwordController = TextEditingController();
bool _passwordVisible = true;
#override
void dispose() {
emailController.dispose();
passwordController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return formLogin();
}
Widget formLogin() {
return Scaffold(
appBar: defaultAppBar('Sign-In'),
body: Center(
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(32),
child: ListView(
children: [
buildEmail(),
const SizedBox(height: 24),
buildPassword('Password', 'Your password...'),
const SizedBox(height: 50),
button('Sign-in', 0),
const SizedBox(height: 24),
button('Sign-up', 1),
],
),
),
),
),
);
}
userLogin() async {
try {
await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password);
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (BuildContext context) => const Nav(),
),
(route) => false,
);
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.amber,
content: Text(
"Email not found",
style: TextStyle(fontSize: 16.0, color: Colors.black),
),
),
);
} else if (e.code == 'wrong-password') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.amber,
content: Text(
"Password incorrect",
style: TextStyle(fontSize: 16.0, color: Colors.black),
),
),
);
}
}
}
TextButton button(String txt, int nav) {
return TextButton(
style: TextButton.styleFrom(
primary: const Color(0xFF20348F),
textStyle: const TextStyle(
fontSize: 20,
),
),
child: Text(txt),
onPressed: () {
if (nav == 0) {
if (_formKey.currentState!.validate()) {
setState(() {
_email = emailController.text;
_password = passwordController.text;
});
userLogin();
}
} else {
_formKey.currentState!.reset();
Navigator.push(context, MaterialPageRoute(builder: (contex) {
return const Register();
}));
}
},
);
}
Widget buildEmail() {
return TextFormField(
decoration: const InputDecoration(
labelText: 'Email',
hintText: 'name#example.com',
prefixIcon: Icon(Icons.mail_outline),
border: OutlineInputBorder(),
errorStyle: TextStyle(color: Colors.redAccent, fontSize: 15),
),
controller: emailController,
validator: MultiValidator([
RequiredValidator(errorText: "Email is required"),
EmailValidator(errorText: "The format of email is incorrect")
]),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.done,
);
}
Widget buildPassword(String _txt1, String _txt2) {
return TextFormField(
obscureText: _passwordVisible,
decoration: InputDecoration(
labelText: _txt1,
hintText: _txt2,
suffixIcon: GestureDetector(
onTap: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
child: Icon(
_passwordVisible ? Icons.visibility : Icons.visibility_off,
),
),
prefixIcon: const Icon(Icons.vpn_key_outlined),
border: const OutlineInputBorder(),
errorStyle: const TextStyle(color: Colors.redAccent, fontSize: 15),
),
controller: passwordController,
validator: RequiredValidator(errorText: "Password is required"),
textInputAction: TextInputAction.done,
);
}
}
If you guy could help me I'd gladly appreciated.
If you can create a .gist i would be of help to u, also in your profile page i can see issue's that u r listening to the user changes in the build, its very unpractical to add it in the build because flutter will call the build method multiple times , build should be clean, also potential memory leaks are there since u r not disposing the streams.
I placing on the interface a message to provide information to users on errors in logging or signing up.
The error does not provide a bug and the application continues to run.
However, the message of error or the status is not passed on to the widget _showAlert which does not appear.
_signup() async {
AuthNotifier authNotifier = Provider.of<AuthNotifier>(context, listen: false);
{
setState(() {
});
final status =
await signup(_user, authNotifier);
if (status == AuthResultStatus.successful) {
// Navigate to success screen
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
(r) => false);
} else {
final errorMsg = AuthExceptionHandler.generateExceptionMessage(status);
_showAlert(errorMsg);
}
}
}
_showAlert(errorMsg) {
if (errorMsg != null) {
return Container(
color: Colors.amberAccent,
width: double.infinity,
padding: EdgeInsets.all(8.0),
child: Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(Icons.error_outline),
),
Expanded(
child: AutoSizeText(
errorMsg,
maxLines: 3,
),
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: IconButton(
icon: Icon(Icons.close),
onPressed: () {
setState(() {
errorMsg = null;
});
},
),
)
],
),
);
}
return SizedBox(
height: 0,
);
}
You are returning a Widget not showing a Dialog. To show a Dialog try this
_showAlert(errorMsg, BuildContext context) {
if (errorMsg != null) {
showDialog(
context: context,
builder: (BuildContext context) =>
Dialog(
child: Container(
// ...
),
),
);
}
}
Running my App on Android and iOS I get the Error shown below. What I want to do, give every Data that get's added a Author, the Author should be the Mail Adress the User Registers with, so by Inviting other Users get Access to this Data.
Another Problem is that in Line 302 the String "user" is unused.
Do i really need to use this String?
Error
════════ Exception caught by widgets library ═══════════════════════════════════
The following NoSuchMethodError was thrown building FutureBuilder<FirebaseUser>(dirty, state: _FutureBuilderState<FirebaseUser>#7f7ed):
The getter 'iterator' was called on null.
Receiver: null
Tried calling: iterator
The relevant error-causing widget was
FutureBuilder<FirebaseUser>
lib/…/MealPlan/mealTile.dart:92
When the exception was thrown, this was the stack
Code
import 'package:flutter/material.dart';
import 'package:mealapp/models/Widgets/whenAndWhatToEat.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:intl/intl.dart';
import 'package:mealapp/models/global.dart';
import 'package:status_alert/status_alert.dart';
import 'package:firebase_auth/firebase_auth.dart';
class MealTile extends StatefulWidget {
final MealsAndWhen mealsAndWhen;
MealTile({this.mealsAndWhen});
#override
MealTileState createState() {
return MealTileState();
}
}
class MealTileState extends State<MealTile> {
String id;
final db = Firestore.instance;
String mail;
List<String> authors = [];
DateTime selectedDate = DateTime.now();
Future pickDate() async {
DateTime datepick = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime.now().add(Duration(days: 0)),
lastDate: new DateTime.now().add(Duration(days: 365)));
if (datepick != null)
setState(() {
selectedDate = datepick;
});
}
Future<String> inputData() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.uid : null;
}
Future<String> inputDataMail() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.email : null;
}
String userId;
void _getUserId() {
inputData().then((value) => setState(() {
userId = value;
}));
}
String currentMail;
void _getMail(doc) {
inputDataMail().then((value) => setState(() {
currentMail = value;
}));
}
/*void _getAuthors(DocumentSnapshot doc) async {
authors = [];
//if (await FirebaseAuth.instance.currentUser() != null) {
authors = List.from(doc.data['Authors']);
print(doc.data['authors']);
//authors.insert(0, currentMail);
//}
}*/
Widget buildItem(DocumentSnapshot doc) {
DateTime now = doc.data['Date'].toDate();
DateFormat formatter = DateFormat('dd-MM-yyyy');
String formatted = formatter.format(now);
_getUserId();
_getMail(doc);
if (doc.data['Authors'] != null) {
//_getAuthors(doc);
//print('Current mail: ' + currentMail + authors.toString() + doc.data['Author'] + doc.data['Meal']);
}
if (now.day == DateTime.now().day) { // If the Date of the meal is today
deleteData(doc, false); // Delete it!
}
// You could also change ".day" to ".hour".
// Example: if (now.day == DateTime.now().day && now.hour == DateTime.hour())
// So, if a meal is set for 2PM, it will delete at 2PM
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData && snapshot != null) {
return Container(
margin: const EdgeInsets.all(8.0),
child: currentMail == doc.data['Author'] || // If the current mail is the author
List.from(doc.data['Authors']).contains(currentMail) // Or if the current mail is part of the authors
? Column( // then if true, show a Column
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'Meal:',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Text(
'${doc.data['Meal']}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
SizedBox(height: 20),
Text(
'When:',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () => updateData(doc),
color: lightBlueColor,
icon: Icon(Icons.calendar_today,
color: Colors.white),
tooltip: 'Update Date',
),
Text(
formatted,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SizedBox(width: 8),
FlatButton(
color: Colors.red,
onPressed: () => deleteData(doc, true),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Delete',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.delete_forever, color: Colors.white),
]),
),
SizedBox(width: 8),
FlatButton(
color: Colors.blue,
onPressed: () => [
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
child: invite(doc),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(12)),
),
);
})
],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Invite',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.share, color: Colors.white),
]),
),
],
),
],
)
: Text(''), // if false, show an empty text widget
decoration: BoxDecoration(
color: lightBlueColor,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
);
}
else {
return CircularProgressIndicator();
}
/*Navigator.pop(context);
return HomePage();*/
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: darkGreyColor,
body: ListView(
padding: EdgeInsets.only(top: 220),
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: db
.collection('mealList')
.orderBy('Date', descending: false) // Order by Date, not descending
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents
.map((doc) => buildItem(doc))
.toList());
} else {
return Container();
}
},
),
],
),
);
}
/*share(BuildContext context, DocumentSnapshot doc) {
final RenderBox box = context.findRenderObject();
final dynamic date = timeago.format(doc['Date'].toDate());
Share.share(
"${doc['Meal']} - $date",
subject: doc['Meal'],
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size,
);
}*/
Widget invite(DocumentSnapshot doc) {
final _formKey = GlobalKey<FormState>();
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Center(
child: Text(
"Invite someone by mail",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
)),
SizedBox(
height: 24,
),
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
labelText: 'Enter the email address'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter an email address';
}
return null;
},
onSaved: (value) => mail = value,
),
FlatButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
saveInviteToFirestore(doc, mail);
}
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
child: Text("Save"),
color: redColor,
textColor: Colors.white,
),
]),
),
);
}
Future<String> getCurrentUser() async {
return await FirebaseAuth.instance.currentUser().then((value) => value.uid);
}
void saveInviteToFirestore(DocumentSnapshot doc, String email) async {
final String user = await getCurrentUser();
var list = List<String>();
list.add(email);
Firestore.instance
.collection('mealList')
.document(doc.documentID)
.updateData({"Authors": FieldValue.arrayUnion(list)});
//setState(() => id = doc.documentID);
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Added',
subtitle: 'You have Added your and the Date to your List',
configuration: IconConfiguration(icon: Icons.done),
);
//Navigator.pop(context);
}
void deleteData(DocumentSnapshot doc, bool showMessage) async {
await db.collection('mealList').document(doc.documentID).delete();
setState(() => id = null);
if (showMessage) {
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Deleted',
subtitle: 'You have Deleted your Meal',
configuration: IconConfiguration(icon: Icons.delete),
);
}
}
void updateData(DocumentSnapshot doc) async {
await pickDate();
await db
.collection('mealList')
.document(doc.documentID)
.updateData({'Date': selectedDate});
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Updated',
subtitle: 'You have updated your Meal Date',
configuration: IconConfiguration(icon: Icons.done),
);
}
}
Currently i develop a Meal and Shopping App. In this App you can Add what you want to Eat next and have the secound Tab, Shopping where you can Add your Items you want to buy next. Created is that a User can invite another User to edit together the List.
I get the Error shown below. I can't figure out how to return the Container. At the void saveInviteToFirestore the user is not used do I need that it used?
Code
import 'package:flutter/material.dart';
import 'package:mealapp/models/Widgets/whenAndWhatToEat.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:intl/intl.dart';
import 'package:mealapp/models/global.dart';
import 'package:status_alert/status_alert.dart';
import 'package:firebase_auth/firebase_auth.dart';
class MealTile extends StatefulWidget {
final MealsAndWhen mealsAndWhen;
MealTile({this.mealsAndWhen});
#override
MealTileState createState() {
return MealTileState();
}
}
class MealTileState extends State<MealTile> {
String id;
final db = Firestore.instance;
String mail;
List<String> authors = [];
DateTime selectedDate = DateTime.now();
Future pickDate() async {
DateTime datepick = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime.now().add(Duration(days: -0)),
lastDate: new DateTime.now().add(Duration(days: 365)));
if (datepick != null)
setState(() {
selectedDate = datepick;
});
}
Future<String> inputData() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.uid : null;
}
Future<String> inputDataMail() async {
final FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user != null ? user.email : null;
}
String userId;
void _getUserId() {
inputData().then((value) => setState(() {
userId = value;
}));
}
String currentMail;
void _getMail(doc) {
inputDataMail().then((value) => setState(() {
currentMail = value;
}));
}
/*void _getAuthors(DocumentSnapshot doc) async {
authors = [];
//if (await FirebaseAuth.instance.currentUser() != null) {
authors = List.from(doc.data['Authors']);
print(doc.data['authors']);
//authors.insert(0, currentMail);
//}
}*/
Widget buildItem(DocumentSnapshot doc) {
DateTime now = doc.data['Date'].toDate();
DateFormat formatter = DateFormat('dd-MM-yyyy');
String formatted = formatter.format(now);
_getUserId();
_getMail(doc);
if (doc.data['Authors'] != null) {
//_getAuthors(doc);
//print('Current mail: ' + currentMail + authors.toString() + doc.data['Author'] + doc.data['Meal']);
}
if (now.day == DateTime.now().day) { // If the Date of the meal is today
deleteData(doc, false); // Delete it!
}
// You could also change ".day" to ".hour".
// Example: if (now.day == DateTime.now().day && now.hour == DateTime.hour())
// So, if a meal is set for 2PM, it will delete at 2PM
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData && snapshot != null) {
return Container(
margin: const EdgeInsets.all(8.0),
child: currentMail == doc.data['Author'] || // If the current mail is the author
List.from(doc.data['Authors']).contains(currentMail) // Or if the current mail is part of the authors
? Column( // then if true, show a Column
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'Meal:',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Text(
'${doc.data['Meal']}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
SizedBox(height: 20),
Text(
'When:',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
IconButton(
onPressed: () => updateData(doc),
color: lightBlueColor,
icon: Icon(Icons.calendar_today,
color: Colors.white),
tooltip: 'Update Date',
),
Text(
formatted,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white),
textAlign: TextAlign.center,
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SizedBox(width: 8),
FlatButton(
color: Colors.red,
onPressed: () => deleteData(doc, true),
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Delete',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.delete_forever, color: Colors.white),
]),
),
SizedBox(width: 8),
FlatButton(
color: Colors.blue,
onPressed: () => [
showDialog(
context: context,
builder: (BuildContext context) {
return Dialog(
child: invite(doc),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(12)),
),
);
})
],
shape: RoundedRectangleBorder(
borderRadius:
BorderRadiusDirectional.circular(12)),
child: Row(children: <Widget>[
Text('Invite',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white)),
Icon(Icons.share, color: Colors.white),
]),
),
],
),
],
)
: Text(''), // if false, show an empty text widget
decoration: BoxDecoration(
color: lightBlueColor,
borderRadius: BorderRadius.all(Radius.circular(12)),
),
);
}
/*Navigator.pop(context);
return HomePage();*/
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: darkGreyColor,
body: ListView(
padding: EdgeInsets.only(top: 220),
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: db
.collection('mealList')
.orderBy('Date', descending: false) // Order by Date, not descending
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents
.map((doc) => buildItem(doc))
.toList());
} else {
return Container();
}
},
),
],
),
);
}
/*share(BuildContext context, DocumentSnapshot doc) {
final RenderBox box = context.findRenderObject();
final dynamic date = timeago.format(doc['Date'].toDate());
Share.share(
"${doc['Meal']} - $date",
subject: doc['Meal'],
sharePositionOrigin: box.localToGlobal(Offset.zero) & box.size,
);
}*/
Widget invite(DocumentSnapshot doc) {
final _formKey = GlobalKey<FormState>();
return Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Center(
child: Text(
"Invite someone by mail",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
)),
SizedBox(
height: 24,
),
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
labelText: 'Enter the email address'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter an email address';
}
return null;
},
onSaved: (value) => mail = value,
),
FlatButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
saveInviteToFirestore(doc, mail);
}
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))),
child: Text("Save"),
color: redColor,
textColor: Colors.white,
),
]),
),
);
}
Future<String> getCurrentUser() async {
return await FirebaseAuth.instance.currentUser().then((value) => value.uid);
}
void saveInviteToFirestore(DocumentSnapshot doc, String email) async {
final String user = await getCurrentUser();
var list = List<String>();
list.add(email);
Firestore.instance
.collection('mealList')
.document(doc.documentID)
.updateData({"Authors": FieldValue.arrayUnion(list)});
//setState(() => id = doc.documentID);
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Added',
subtitle: 'You have Added your and the Date to your List',
configuration: IconConfiguration(icon: Icons.done),
);
//Navigator.pop(context);
}
void deleteData(DocumentSnapshot doc, bool showMessage) async {
await db.collection('mealList').document(doc.documentID).delete();
setState(() => id = null);
if (showMessage) {
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Deleted',
subtitle: 'You have Deleted your Meal',
configuration: IconConfiguration(icon: Icons.delete),
);
}
}
void updateData(DocumentSnapshot doc) async {
await pickDate();
await db
.collection('mealList')
.document(doc.documentID)
.updateData({'Date': selectedDate});
StatusAlert.show(
context,
duration: Duration(seconds: 2),
title: 'Updated',
subtitle: 'You have updated your Meal Date',
configuration: IconConfiguration(icon: Icons.done),
);
}
}
Error
The following assertion was thrown building FutureBuilder<FirebaseUser>(dirty, state: _FutureBuilderState<FirebaseUser>#a4504):
A build function returned null.
The offending widget is: FutureBuilder<FirebaseUser>
Build functions must never return null.
To return an empty space that causes the building widget to fill available room, return "Container()". To return an empty space that takes as little room as possible, return "Container(width: 0.0, height: 0.0)".
The relevant error-causing widget was
FutureBuilder<FirebaseUser>
lib/…/MealPlan/mealTile.dart:92
When the exception was thrown, this was the stack
#0 debugWidgetBuilderValue.<anonymous closure>
package:flutter/…/widgets/debug.dart:276
In your FutureBuilder you are not returning anything when the Future hasn't completed yet. A widget always needs to be returned whether there is data or not.
Example fix for your code:
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.hasData && snapshot != null) {
return Container(
...
);
}
//ADDED ELSE BLOCK
else {
return Container();
}
}
);
Or as #stacker suggested, you can return a CircularProgressIndicator().