Navigator change page on async button press - asynchronous

Very new to Flutter. How would I push a new route predicated on a condition invoked by a button press? (asynchronous sign in call).
This sort of thing: if signIn (async) = success -> Navigator.push
Cheers
Widget buildButtons() {
return new Container(
child: new Column(
children: <Widget>[
new MaterialButton(
minWidth: MediaQuery.of(context).size.width - 30, //FULL WIDTH - 30
color: Style.palette3,
padding: EdgeInsets.all(Style.padding1),
child: new Text('Sign in',
style: Style.signInBtn
),
onPressed: () {
if (LoginControl.signIn()) Navigator.push(context, MaterialPageRoute(builder: (context) => ArmDisarm()));
},
),
new FlatButton(
child: Padding(
padding: Style.paddingCreateAcc,
child: new Text(
'Create an account',
style: Style.fontSize1
),
),
),
],
),
);
}

It's better to show us the code of LoginControl.signIn().
Anyway if signIn() is a Future, then you to use async await
...
onPressed: () async {
if (await LoginControl.signIn()) {
Navigator.push(context, MaterialPageRoute(builder: (context) => ArmDisarm()));
}
},
...
Make sure that signIn() method returns true if the user is successfully signed in.

Related

How to show a loading screen when fetching data from firebase

Here is my code section in which i am signing in user using firebase, now i wanted to show a loadihng screen while we are fetching data from firebase.
Container(
width: 376,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
gradient: LinearGradient(
colors: <Color>[
Colors.black12,
Colors.blue,
Colors.black12,
]
)
),
child: TextButton(
onPressed: () async{
if(key.currentState!.validate()){
await FirebaseAuth.instance.signInWithEmailAndPassword(email: User_email_id.text, password: user_Passowrd.text)
.then((value) => {
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>Page_First())
)
}).catchError((e){
Fluttertoast.showToast(msg: e!.message);
});
}
},
child: Text(
'Sign-in',
style: TextStyle(
fontSize: 17,
color: Colors.white,
),
),
),
),
Firstly, add boolean isLoading = false to the top of you class. Then move firebase loading code to a separate Future like this:
Future<void> loadFirebase(BuildContext context) async {
await FirebaseAuth.instance.signInWithEmailAndPassword(email: User_email_id.text, password: user_Passowrd.text)
.then((value) => {
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>Page_First())
)
}).catchError((e){
Fluttertoast.showToast(msg: e!.message);
});
}
This function shall be called when the button is pressed. At the same time, isLoading shall be set to true. Finally, when building your widget, add a condition to show loading screen if isLoading is true.
isLoading == true ? LoadingContainer() : Container(content with button);

UI not updated Flutter Web

I have a Table reading data from Firestore, using StreamBuilder. I have a button Add Element below the table that when clicked opens a pop-up form. After the user fills the form and clicks the button, data is stored in Firestore, the Dialog form is closed and the user is redirected to the table. When I use only text form fields in my form, the table is updated with the new data that the user just pushed in Firestore. The problem started to occurs when I added an Upload Picture Form, which uploads the picture in Firebase Storage and pushes the download URL as a field inside the other information of the form. I fill the form and the Table doesn't update. The project is in Flutter Web so I am using image_picker_web for the upload process and the Image file is MediaInfo type. As I said it started to happen only when I added the upload picture form along with other TextFormFields.
// Upload Picture Field
InkWell(
onTap: () async {
final MediaInfo _image =
await ImagePickerWeb.getImageInfo;
setState(() {
image = _image;
});
},
child: Container(
child: Center(
child: Row(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Icon(
CupertinoIcons.cloud_upload,
color: Colors.grey[600],
),
const SizedBox(
width: 5,
),
Text(
'Upload the invoice picture',
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
),
),
//Create Invoice Button
MaterialButton(
onPressed: () async {
Invoice invoice = Invoice(
_controllerInvoiceNumber.text,
_controllerLocation.text,
invoiceDate,
_controllerAmount.text,
);
Database().addNewInvoice(
invoice,
_userId!,
image!,
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const HomePage(),
),
);
}
},
child: const Text(
'Create',
),
),
//Table
StreamBuilder(
stream: FirebaseFirestore.instance
.collection('Invoices')
.snapshots(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Column(
children: [
SizedBox(
child: PlutoGrid(
columns: editableColumns,
rows: _createRows(snapshot.data),
),
),
Padding(
padding: const EdgeInsets.all(10),
child: Center(
child: MaterialButton(
child: const Text(
'Add Invoice',
style: TextStyle(color: Colors.white),
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return const AddInvoiceForm();
},
);
},
),
),
),
],
);
},
),

Return error message if password or user email wrong in flutter login form

Hy Guys I have connected flutter to my firebase project and used AUTH feature in Firebase but what I would do is to show an error message when a wrong password or Email user is wrong.
this is my code:
Widget submitButton (){
return Container(
child: SizedBox(
width: 220,
height: 40,
child: MaterialButton(
elevation: 5,
onPressed: () async {
setState(() {
showProgress = true;
});
try {
final newUser = await _auth.signInWithEmailAndPassword(email: email, password: password);
print(newUser.toString());
if (newUser != null) {
Navigator.push(context, PageTransition(
type: PageTransitionType.fade,
child: WelcomeScreen(),
));
setState(() {
showProgress = false;
});
}
} catch (e) {}
},
child: Text('Accedi',style: TextStyle(color: Colors.black),),
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.black12,width: 1)),
),
),
);
}
First you need to catch the error, you can do that by using catchError():
final newUser = await _auth.signInWithEmailAndPassword(email: email, password: password)
.catchError((err) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Error"),
content: Text(err.message),
actions: [
FlatButton(
child: Text("Ok"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
});
Then the showDialog will have err.message which will contain the error received from Firebase Authentication.
You have two options:
1.Use an alert dialog
void _showAlertDialog(String message) async {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(message),
actions: <Widget>[
FlatButton(
child: Text('Ok'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
full example
Use errorText property in InputDecoration
TextField(
decoration: InputDecoration(
errorText: this.errorText,
),
)
full example
You can use a Form() with TextFormField()
Something like this:
Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (value.isEmpty || !value.contains('#'))
return 'Please enter a valid Email address';
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: 'Email address'),
onSaved: (value) {
_userEmail = value;
},
),
It's just a dummy, but as you see there's some validation code in the validator attribute.
So what validator does is, if you return null everything is well and good, but if something is wrong, just return the String and it'll be shown below the text form field as an error message.
And to validate you can call a method like this to activate the method while form submission
void _trySubmit() {
final bool isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
widget.submitFn(
_userEmail.trim(),
_userName.trim(),
_userPassword.trim(),
_isLogin,
context,
);
}}
The method for getting isValid will invoke validation function of all TextFormFields, if all of them are okay, it'll return true else it'll return false.
All the _name are used to store the values in state variables using the onSaved attribute.
Let me know if you have anymore doubts.
Hope this helpsāœŒ

Logout with Firebase not working after navigating to a particular screen

Logout works fine from the home screen and even when navigating to other screens and then returning to the home screen to logout, however, when starting the assessment from the home screen which takes you to 'assessment_list' and you return to the home screen, logout doesn't work. How would I fix this?
Home Screen
actions: [
...
items: [
DropdownMenuItem(
child: Container(
child: Row(
children: [
const Icon(Icons.exit_to_app),
const Text('Logout'),
const SizedBox(width: 8),
],
),
),
value: 'logout',
),
],
onChanged: (itemIdentifier) {
if (itemIdentifier == 'logout') {
FirebaseAuth.instance.signOut();
}
...
RaisedButton(
child: Text(
'Start Assessment'
),
onPressed: () {
navigateToAssessmentList(context);
},
),
...
Future navigateToAssessmentList(context) async {
Navigator.push(
context, MaterialPageRoute(builder: (context) => AssessmentList()));
}
assessment_list
child: RaisedButton(
padding: EdgeInsets.all(10),
color: const Color(0xfff4f4f4),
child: Text(
'Back To Home',
),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => BottomNavBarController()));
}),
You are pushing the same page again in the stack. Please replace the following line
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => BottomNavBarController()));
with this
Navigator.pop(context)

StreamBuilder not updating after an item is removed Flutter

I am new to Flutter and this is my first time asking a question on Stackoverflow. I apologize for any misunderstanding. I will try my best to make it clear.
I am using sqflite for storing user's favorites and populating a list from the DB on a page, named Favorites screen. This Favorites page is one of the items on my bottom navbar.
My issue is that when I tap on an item from the favorites list which takes me to a screen where I can unfavorite that item. I double-checked that it is really removed from the DB by logging the rows count. But when I go back to the Favorites page, that item is still on the list. If I go to one of the pages from the bottom navbar and go back to the Favorites screen, the item isn't there. I understand that the page is being rebuilt again this time but my intention was the Stream will constantly listen for a change.
I have also implemented a slide to dismiss feature on the fav screen, which works as intended. But I am using the same logic on both.
StreamBuilder code in Favorite screen
StreamBuilder<List<WeekMezmurList>>(
stream: favBloc.favStream,
builder: (context, AsyncSnapshot<List<WeekMezmurList>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Text(
"Loading Favorites...",
style: TextStyle(fontSize: 20),
),
);
} else if (snapshot.data == null) {
return Center(
child: Text(
"No Favorites yet!",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
);
} else {
return ListView.builder(
physics: BouncingScrollPhysics(),
padding: const EdgeInsets.fromLTRB(5.0, 10.0, 5.0, 10.0),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return new GestureDetector(
onTap: () =>
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AudioPlayerScreen(
mezmurName: snapshot.data[index].mezmurName,
),
),
),
child: Slidable(
key: new Key(snapshot.data[index].mezmurName),
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.25,
// closes other active slidable if there is any
controller: slidableController,
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Share',
color: Colors.indigo,
icon: Icons.share,
onTap: () =>
_share(snapshot
.data[index]),
),
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () =>
_swipeDelete(
context, snapshot.data[index].mezmurName),
),
],
child: Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.symmetric(
vertical: 15.0,
horizontal: 10.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Row(
children: <Widget>[
_misbakChapter(
snapshot.data[index].misbakChapters),
SizedBox(width: 15),
_displayFavoritesMisbakLines(
snapshot.data[index], index),
],
)
],
),
),
),
),
);
},
);
}
},
);
slide to delete code in Favorites screen
// deletes the specific favorite from the sqflite db
Future<void> _swipeDelete(BuildContext context, String mezmurName) async {
try {
favBloc.delete(mezmurName);
} catch (e) {
CupertinoAlertDialog(
content: Text("Something went wrong. Please try again."),
actions: <Widget>[
CupertinoDialogAction(
child: Text(
"Ok",
),
onPressed: () => Navigator.of(context).pop(),
),
],
);
}
}
I have the same logic in the second screen, the screen I get when I tap on one of the items from the Fav list.
favBloc.delete(widget.mezmurName);
BLoC code, I got the concepts from this Medium article
class FavoritesBloc{
FavoritesBloc(){
getFavorites();
}
final databaseHelper = DatabaseHelper.instance;
// broadcast makes it to start listening to events
final _controller = StreamController<List<WeekMezmurList>>.broadcast();
get favStream => _controller.stream;
void dispose() {
_controller.close();
}
getFavorites () async{
_controller.sink.add(await databaseHelper.getFavorites());
}
insert(WeekMezmurList fav){
databaseHelper.insertToDb(fav);
getFavorites();
}
delete(String mezmurName){
databaseHelper.delete(mezmurName: mezmurName);
getFavorites();
}
}
Delete method in the DB class
// deleting a value from the db
delete({String mezmurName}) async {
var dbClient = await getDb;
try {
await dbClient
.delete(TABLE, where: '$MEZMUR_NAME = ?', whereArgs: [mezmurName]);
} catch (e) {
}
}
I have tried to research this issue but all I have found were for remote databases.
Just to make it more clear, I took a screen record.
Thank you in advance!
The reason why StreamBuilder on the first screen doesn't update with the changes made is because it uses a different instance of FavoritesBloc(). If you'd like for the bloc to be globally accessible with a single instance, you can declare it as
final favBloc = FavoritesBloc();
Otherwise, you can follow what has been suggested in the comments and pass FavoritesBloc as an argument between screens.

Resources