How to show loading spinner in the button when sign in using firebase - flutter

i have create a variable "isLoading" for calling the loader.
the actual problem is when i hit simply in the "signIn" button without give any value in the test field, it will start loading for delay time
how can i stop this and also catch the error when hit the "signIn" button without given any value....
this is my Repo: food delivery app
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import '../../config/constants.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
#override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final emailController = TextEditingController();
final passwordController = TextEditingController();
#override
void dispose() {
emailController.dispose();
passwordController.dispose();
super.dispose();
}
bool isLoading = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Text('login'),
htspace20,
TextField(
controller: emailController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
label: Text('Email'),
),
),
htspace20,
TextField(
controller: passwordController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
label: Text('Password'),
),
),
htspace40,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(onPressed: () {}, child: const Text('Sign up'))
],
),
SizedBox(
width: double.maxFinite,
height: 40,
child: ElevatedButton(
style: ButtonStyle(
elevation: MaterialStateProperty.all(0),
),
child: isLoading
? const SizedBox(
height: 30,
width: 30,
child: CircularProgressIndicator(
strokeWidth: 3, color: Colors.white),
)
: const Text('Sign in'),
onPressed: () {
setState(() {
isLoading = true;
});
signIn();
Future.delayed(const Duration(seconds: 3), () {
setState(() {
if (emailController.text != " " ||
passwordController.text != " ") {
isLoading = false;
} else {
isLoading = true;
}
});
});
}),
)
],
),
),
);
}
Future signIn() async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text.trim(),
password: passwordController.text.trim(),
);
} catch (e) {
print('Email or Password Incorrect');
}
}
}

Why not try it all in your singIn() function like this:
Future signIn() async {
try {
isLoading = true;
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: emailController.text.trim(),
password: passwordController.text.trim(),
);
isLoading = false;
} catch (e) {
isLoading = false;
print('Email or Password Incorrect');
}
}
This way it will stop loading if email or pass is incorrect or missing etc.
I'd also disable the login button if email and pass isn't filled out. Stop them from passing blank values.

ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20), // <-- Radius
),
),
child: isLoading
? const SizedBox(
height: 30,
width: 30,
child: CircularProgressIndicator(
strokeWidth: 3, color: Colors.white),
)
: const Text('Sign in'),
onPressed: () {
setState(() {
isLoading = true;
});
signIn();
Future.delayed(const Duration(seconds: 3), () {
setState(() {
if (emailController.text != " " ||
passwordController.text != " ") {
isLoading = false;
} else {
isLoading = true;
}
});
});
}),
it also showing circular loading on the button

Related

How to call material page in the popup dialog box on flutter?

I tried to call the material page getting as a method in the popup dialog box.
my output
dialog box code..
RecorderDialogScreen(BuildContext context) {
Dialogs.materialDialog(
msg: 'Are you sure ? you can\'t undo this',
color: Colors.white,
context: context,
onClose: (value) => print("returned value is '$value'"),
actions: [
Expanded(
flex: 2,
child: recordingScreen12(),
),
IconsOutlineButton(
onPressed: () {
Navigator.of(context).pop(['Test', 'List']);
},
text: 'Cancel',
iconData: Icons.cancel_outlined,
textStyle: TextStyle(color: Colors.grey),
iconColor: Colors.grey,
),
]);
}
recordingScreen12() method screen code
class recordingScreen12 extends StatefulWidget {
const recordingScreen12({Key? key}) : super(key: key);
#override
State<recordingScreen12> createState() => _recordingScreen12State();
}
class _recordingScreen12State extends State<recordingScreen12> {
String? downloadURL;
List<Reference> references = [];
#override
void initState() {
super.initState();
_onUploadComplete();
}
//snack bar for showing error
showSnackBar(String snackText, Duration d) {
final snackBar = SnackBar(content: Text(snackText), duration: d);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SizedBox(
height: 50,
width: 50,
child: Expanded(
flex: 2,
child: FeatureButtonsView(
onUploadComplete: _onUploadComplete,
),
),
)
],
),
);
}
Future<void> _onUploadComplete() async {
final sp = context.read<SignInProvider>();
FirebaseStorage firebaseStorage = FirebaseStorage.instance;
ListResult listResult = await firebaseStorage
.ref()
.child("${sp.uid}/records")
.child("voices")
.list();
setState(() {
references = listResult.items;
});
}
}
FeatureButtonsView() method page code
class FeatureButtonsView extends StatefulWidget {
final Function onUploadComplete;
FeatureButtonsView({
Key? key,
required this.onUploadComplete,
}) : super(key: key);
#override
_FeatureButtonsViewState createState() => _FeatureButtonsViewState();
String? userId;
}
class _FeatureButtonsViewState extends State<FeatureButtonsView> {
late bool _isPlaying;
late bool _isUploading;
late bool _isRecorded;
late bool _isRecording;
late AudioPlayer _audioPlayer;
late String _filePath;
late FlutterAudioRecorder2 _audioRecorder;
Future getData() async {
final sp = context.read<SignInProvider>();
sp.getDataFromSharedPreferences();
}
String downloadUrl = '';
Future<void> onsend() async {
//uploading to cloudfirestore
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
final sp = context.read<SignInProvider>();
await firebaseFirestore
.collection("users")
.doc("${sp.uid}")
.collection("reco")
.add({'downloadURL': downloadUrl}).whenComplete(() =>
showSnackBar("Voice uploaded successful", Duration(seconds: 2)));
}
//snackbar for showing error
showSnackBar(String snackText, Duration d) {
final snackBar = SnackBar(content: Text(snackText), duration: d);
}
#override
void initState() {
super.initState();
_isPlaying = false;
_isUploading = false;
_isRecorded = false;
_isRecording = false;
_audioPlayer = AudioPlayer();
final sp = context.read<SignInProvider>();
FirebaseFirestore.instance
.collection("users")
.doc(sp.uid)
.get()
.then((value) {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Center(
child: _isRecorded
? _isUploading
? Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Padding(
padding: EdgeInsets.symmetric(horizontal: 20),
child: LinearProgressIndicator()),
Text('Uploading to Firebase'),
],
)
: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding:
const EdgeInsets.only(top: 20, left: 1, right: 5),
child: SizedBox(
width: 110.0,
height: 25.0,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.red),
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: const BorderSide(
color: Colors.red,
),
),
),
),
onPressed: _onRecordAgainButtonPressed,
child: const Text(
'delete',
style: TextStyle(fontSize: 10),
),
),
),
),
// IconButton(
// icon: Icon(Icons.replay),
// onPressed: _onRecordAgainButtonPressed,
// ),
IconButton(
icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow),
onPressed: _onPlayButtonPressed,
),
Padding(
padding:
const EdgeInsets.only(top: 20, left: 1, right: 20),
child: SizedBox(
width: 110.0,
height: 25.0,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.green),
shape: MaterialStateProperty.all<
RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
side: const BorderSide(
color: Colors.green,
),
),
),
),
onPressed: _onFileUploadButtonPressed,
child: const Text(
"add",
style: TextStyle(fontSize: 10),
),
),
),
),
// IconButton(
// icon: Icon(Icons.upload_file, color: Colors.green),
// onPressed: _onFileUploadButtonPressed,
// ),
],
)
: IconButton(
icon: _isRecording
? Column(
children: const [
Icon(
Icons.pause,
size: 50,
),
LinearProgressIndicator(
semanticsLabel: 'Linear progress indicator'),
],
)
: Column(
children: const [
Icon(Icons.mic, size: 30, color: Colors.redAccent),
],
),
onPressed: _onRecordButtonPressed,
),
);
}
Future<void> _onFileUploadButtonPressed() async {
FirebaseStorage firebaseStorage = FirebaseStorage.instance;
setState(() {
_isUploading = true;
});
try {
final sp = context.read<SignInProvider>();
Reference ref = firebaseStorage.ref().child('${sp.uid}/records1').child(
_filePath.substring(_filePath.lastIndexOf('/'), _filePath.length));
TaskSnapshot uploadedFile = await ref.putFile(File(_filePath));
if (uploadedFile.state == TaskState.success) {
downloadUrl = await ref.getDownloadURL();
}
widget.onUploadComplete();
onsend(); //send downloadURL after get it
} catch (error) {
print('Error occured while uplaoding to Firebase ${error.toString()}');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Error occured while uplaoding'),
),
);
} finally {
setState(() {
_isUploading = false;
});
}
}
void _onRecordAgainButtonPressed() {
setState(() {
_isRecorded = false;
});
}
Future<void> _onRecordButtonPressed() async {
if (_isRecording) {
_audioRecorder.stop();
_isRecording = false;
_isRecorded = true;
} else {
_isRecorded = false;
_isRecording = true;
await _startRecording();
}
setState(() {});
}
void _onPlayButtonPressed() {
if (!_isPlaying) {
_isPlaying = true;
_audioPlayer.play(_filePath, isLocal: true);
_audioPlayer.onPlayerCompletion.listen((duration) {
setState(() {
_isPlaying = false;
});
});
} else {
_audioPlayer.pause();
_isPlaying = false;
}
setState(() {});
}
Future<void> _startRecording() async {
final bool? hasRecordingPermission =
await FlutterAudioRecorder2.hasPermissions;
if (hasRecordingPermission ?? false) {
Directory directory = await getApplicationDocumentsDirectory();
String filepath =
'${directory.path}/${DateTime.now().millisecondsSinceEpoch}.aac';
_audioRecorder =
FlutterAudioRecorder2(filepath, audioFormat: AudioFormat.AAC);
await _audioRecorder.initialized;
_audioRecorder.start();
_filePath = filepath;
setState(() {});
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Center(child: Text('Please enable recording permission'))));
}
}
}
I mean like this output
I want the dialogue box to be on and display the recording and the pause button and when I click the pause button I want to open the other dialog box and play the voice and upload it without openingĀ anotherĀ page.

State not triggering while clicking login button

Every thing is working fine except that the page is not navigating while I press login button. The user is logged in and The screen is changing once I reload it. But the screen has to change when I click the login button. I used the setState fuction But still not working.Please help me solve this. Thanks in advance.
This is my root page
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:login/main.dart';
import 'package:login/services/auth_service.dart';
class Root extends StatefulWidget {
const Root({Key? key}) : super(key: key);
#override
State<Root> createState() => _RootState();
}
enum AuthState { signedIn, signedOut }
class _RootState extends State<Root> {
AuthState _authState = AuthState.signedOut;
AuthService auth = AuthService();
String authuser = "ghnfgjy";
void signOut() async {
await auth.signOut();
setState(() {
_authState = AuthState.signedOut;
});
}
void signIn() async {
setState(() {
_authState = AuthState.signedIn;
});
}
#override
void initState() {
super.initState();
auth.currentUser().then((user) {
print("check check $user ");
setState(() {
_authState = user == null ? AuthState.signedOut : AuthState.signedIn;
});
});
}
#override
Widget build(BuildContext context) {
if (_authState == AuthState.signedOut) {
return Login();
} else {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(
height: 69,
),
Text("Helllloooooooo $authuser"),
SizedBox(
height: 45,
),
FlatButton(
onPressed: () {
signOut();
},
child: Text("Sign out"))
],
),
));
}
}
}
This is my login.dart page
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:login/main.dart';
import 'package:login/services/auth_service.dart';
import 'package:login/share/constant.dart';
class Login extends StatefulWidget {
const Login({Key? key}) : super(key: key);
#override
_LoginState createState() => _LoginState();
}
enum FormType {
login,
register,
}
class _LoginState extends State<Login> {
dynamic _email;
dynamic _password;
final formkey = GlobalKey<FormState>();
FormType _formType = FormType.login;
dynamic result;
AuthService auth = AuthService();
bool validateandsave() {
final form = formkey.currentState;
if (form!.validate()) {
form.save();
print('form is valid');
print('$_email $_password');
return true;
} else {
print('form is not valid');
return false;
}
}
Future validateandsumit() async {
if (validateandsave()) {
if (_formType == FormType.register) {
auth.register(_email, _password);
setState(() {
_formType = FormType.login;
});
} else {
auth.signIn(_email, _password);
}
}
}
void moveToRegister() {
formkey.currentState!.reset();
setState(() {
_formType = FormType.register;
});
}
void moveToLogin() {
formkey.currentState!.reset();
setState(() {
_formType = FormType.login;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Colors.blueAccent,
Colors.purple,
],
),
),
padding: EdgeInsets.all(16),
child: Form(
key: formkey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: openText() + inputFields() + buttons(),
),
),
),
);
}
List<Widget> openText() {
if (_formType == FormType.register) {
return [
Text(
'Please Register to continue',
style: TextStyle(
fontSize: 35, color: Colors.white, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
SizedBox(
height: 40,
),
];
} else {
return [
Text('Please Login to continue',
style: TextStyle(
fontSize: 35, color: Colors.white, fontWeight: FontWeight.bold),
textAlign: TextAlign.center),
SizedBox(
height: 40,
),
];
}
}
List<Widget> inputFields() {
return [
TextFormField(
decoration: inputDecoration.copyWith(
labelText: 'Email Address',
),
style: TextStyle(color: Colors.black),
validator: (val) => val!.isEmpty ? 'Enter the email address' : null,
onSaved: (val) => _email = val),
SizedBox(
height: 20,
),
TextFormField(
obscureText: true,
decoration: inputDecoration.copyWith(
labelText: 'Password',
),
style: TextStyle(color: Colors.black),
validator: (val) => val!.isEmpty ? 'Enter the password' : null,
onSaved: (val) => _password = val),
SizedBox(
height: 60,
),
];
}
List<Widget> buttons() {
if (_formType == FormType.register) {
return [
FlatButton(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 60),
onPressed: validateandsumit,
child: Text(
'Register',
style: TextStyle(fontSize: 20, color: Colors.white),
),
color: Colors.pinkAccent,
),
SizedBox(
height: 20,
),
FlatButton(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 60),
onPressed: moveToLogin,
child: Text(
'Have an account? Login',
style: TextStyle(fontSize: 20, color: Colors.white),
),
color: Colors.pinkAccent,
),
];
} else {
return [
FlatButton(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 60),
onPressed: validateandsumit,
child: Text(
'Login',
style: TextStyle(fontSize: 20, color: Colors.white),
),
color: Colors.pinkAccent,
),
SizedBox(
height: 20,
),
FlatButton(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 60),
onPressed: moveToRegister,
child: Text(
'Register',
style: TextStyle(fontSize: 20, color: Colors.white),
),
color: Colors.pinkAccent,
),
];
}
}
}
This is my authservice file
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class AuthService {
FirebaseAuth _auth = FirebaseAuth.instance;
//Stream <User>? get authStateChanges => _auth.authStateChanges();
Stream<User?> get authStateChanges => _auth.authStateChanges();
CollectionReference users = FirebaseFirestore.instance.collection('users');
late User user;
//register in with email and password
Future register(email, password) async {
try {
UserCredential userCredential = await _auth
.createUserWithEmailAndPassword(email: email, password: password);
user = userCredential.user!;
print('Registered ${user.uid}');
var userdata = {
'email': email,
'password': password,
'role': 'user',
};
users.doc(user.uid).get().then((doc) {
if (doc.exists) {
doc.reference.update(userdata);
} else {
users.doc(user.uid).set(userdata);
}
});
} catch (e) {
print(e);
}
}
//sign in with email and password
Future signIn(email, password) async {
try {
UserCredential userCredential = await _auth.signInWithEmailAndPassword(
email: email, password: password);
user = userCredential.user!;
print('logged in ${user.uid}');
var userdata = {
'email': email,
'password': password,
'role': 'user',
};
users.doc(user.uid).get().then((doc) {
if (doc.exists) {
doc.reference.update(userdata);
} else {
users.doc(user.uid).set(userdata);
}
});
} catch (e) {
print(e);
}
}
Future currentUser() async {
try {
User user = await _auth.currentUser!;
return user.uid;
} catch (e) {
print(e);
}
}
//sign out
Future signOut() async {
try {
await _auth.signOut();
} catch (e) {
print(e);
}
}
}
I think the problem is that the build function in _RootState does not rebuild when you login.
One possible solution is to use StreamBuilder to rebuild whenever auth changes (login/logout).
Add this function to your AuthService class:
class AuthService {
final _auth = FirebaseAuth.instance;
...
Stream<User?> authStream() => _auth.authStateChanges(); // add this line
}
Then write your build function (in _RootState) as below:
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: auth.authStream(), // auth here is AuthService.
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
// StreamBuilder will automatically rebuild anytime you log in or log
// logout. No need for your initState or signout & signin functions.
if (snapshot.hasError) return const Text('Something went wrong');
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.data == null) return Login();
// snapshot.data is User.
// print(snapshot.data?.email); print(snapshot.data?.displayName);
return Scaffold(
body: Center(
child: Column(
children: [
const SizedBox(height: 69),
Text('Hello ${snapshot.data?.email}'),
const SizedBox(height: 45),
FlatButton(
onPressed: () async {
// no need to call sign out here.
await auth.signOut();
},
child: const Text('Sign out'),
)
],
),
),
);
},
);
// you can delete your initState, signin and signout functions in _RootState

Null value comes from singleton structure

I am developing an application with flutter. I set up a singleton structure to access the data in shared preferences. I made a password change area in the settings section of the application. And I wrote the service with node js, everything works fine, but the email value is null.
how can i fix the problem
data_store (singleton)
class DataStore {
static DataStore? _dataStore;
static DataStore? getInstance() {
if (_dataStore == null) {
// keep local instance till it is fully initialized.
var data = DataStore._();
_dataStore = data;
}
return _dataStore;
}
DataStore._();
UserModel user = UserModel();
bool? putUser(UserModel user) {
this.user = user;
return null;
}
UserModel? getUser() {
return user;
}
}
change password page
import 'package:flutter/material.dart';
import 'package:meetdy/Constants/Utils/app_shared_preferences.dart';
import 'package:meetdy/Constants/colors.dart';
import 'package:meetdy/Constants/utils/data_store.dart';
import 'package:meetdy/Models/auth_models/change_passsword_models/change_password_req_model.dart';
import 'package:meetdy/Models/auth_models/user_model.dart';
import 'package:meetdy/Services/auth_services/change_password_services/change_password_services.dart';
import 'package:provider/provider.dart';
import 'package:sizer/sizer.dart';
class ChangePasswordScreen extends StatefulWidget {
const ChangePasswordScreen({Key? key}) : super(key: key);
#override
State<ChangePasswordScreen> createState() => _ChangePasswordScreenState();
}
class _ChangePasswordScreenState extends State<ChangePasswordScreen> {
late Size size = MediaQuery.of(context).size;
bool _isObscure = true;
bool isLoading = false;
final _formKey = GlobalKey<FormState>();
final _passwordController = TextEditingController();
final DataStore? _dataStore = DataStore.getInstance();
UserModel? user;
#override
void initState() {
user = _dataStore?.getUser();
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
},
child: Scaffold(
appBar: buildAppBar(context),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 3.h, vertical: 1.h),
child: Column(
children: [
SizedBox(height: 2.h),
// Login TextFields
Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: [
Form(
key: _formKey,
child: TextFormField(
obscureText: _isObscure,
controller: _passwordController,
cursorColor: AppColors.lightBlack,
decoration: InputDecoration(
suffixIcon: IconButton(
icon: Icon(
_isObscure
? Icons.visibility
: Icons.visibility_off,
color: Colors.grey,
),
onPressed: () {
setState(() {
_isObscure = !_isObscure;
});
}),
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
labelText: 'Password',
labelStyle: TextStyle(
color: AppColors.lightBlack,
),
),
validator: (value) {
if (value!.isEmpty) {
return 'Required';
} else if (value.length < 6) {
return 'Password must be at least 6 characters';
}
return null;
},
onSaved: (value) {
_passwordController.text = value!;
},
),
),
SizedBox(height: 4.h),
Consumer<ChangePasswordService>(
builder: (context, provider, child) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
"Update",
style: TextStyle(
fontSize: 22.sp,
color: AppColors.black,
fontWeight: FontWeight.w300),
),
Padding(
padding:
EdgeInsets.symmetric(horizontal: 1.0.h)),
isLoading
? CircularProgressIndicator(
color: AppColors.orange)
: FloatingActionButton(
backgroundColor: AppColors.orange,
onPressed: () async {
final form = _formKey.currentState;
if (!form!.validate()) {
return;
}
setState(() {
isLoading = true;
});
String password =
_passwordController.text.trim();
String? email = user!.email;
ChangePasswordReqModel passwordData =
ChangePasswordReqModel(
email: email,
password: password);
var newPassword = await provider
.changePassword(passwordData);
setState(() {
isLoading = false;
});
if (newPassword?.error != null) {
var snackBar = SnackBar(
content: Text(
newPassword?.error?.message ??
""),
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20)),
clipBehavior: Clip.hardEdge,
);
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
} else {
var snackBar = SnackBar(
content: Text(
"Your password has been successfully changed"),
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20)),
clipBehavior: Clip.hardEdge,
);
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
}
_passwordController.clear();
},
child: Icon(
Icons.keyboard_arrow_right_sharp))
]);
})
],
),
),
],
),
),
),
),
);
}
AppBar buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
centerTitle: true,
title: Text(
"Change Password",
style: TextStyle(
color: AppColors.lightBlack,
),
),
leading: IconButton(
icon: Icon(Icons.arrow_back, color: AppColors.lightBlack),
onPressed: () => Navigator.of(context).pop(),
),
);
}
}

Flutter Integration test case fails when run

I'm trying to run an integration test in my app. The screen is my login screen which leads to a signup flow and logged in to Home Screen. I'm using flutter integration test from the framework it self.
I've tried to run an integration test on the login screen but I get this error,
The following TestFailure object was thrown running a test:
Expected: exactly one matching node in the widget tree
Actual: _WidgetPredicateFinder:<zero widgets with widget matching predicate (Closure: (Widget) =>
bool) (ignoring offstage widgets)>
Which: means none were found but one was expected
My Login screen looks like this
class LoginScreen extends StatefulWidget {
static String tag = loginScreenRoute;
const LoginScreen({Key? key}) : super(key: key);
#override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _userLoginFormKey = GlobalKey<FormState>();
String? _userName = "";
String? _password = "";
bool _invisiblePass = false;
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool hasInterNetConnection = false;
late StreamSubscription _connectionChangeStream;
#override
initState() {
//Create instance
ConnectionUtil connectionStatus = ConnectionUtil.getInstance();
//Initialize
connectionStatus.initialize();
//Listen for connection change
_connectionChangeStream =
connectionStatus.connectionChange.listen(connectionChanged);
super.initState();
}
#override
void dispose() {
_connectionChangeStream.cancel();
super.dispose();
}
void connectionChanged(dynamic hasConnection) {
setState(() {
hasInterNetConnection = hasConnection;
//print(isOffline);
});
if (!hasInterNetConnection) {
offlineBar(context);
}
}
final loading = Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
CircularProgressIndicator(
color: lightWTextColor,
),
Text(" Login in ... Please wait")
],
);
void _showPassword() {
setState(() {
_invisiblePass = !_invisiblePass;
});
}
#override
Widget build(BuildContext context) {
//// user email ////
TextFormField userName() => TextFormField(
key: const Key('login username input'),
autofocus: false,
keyboardType: TextInputType.emailAddress,
controller: usernameController,
validator: validateEmail,
onSaved: (value) => _userName = value!.trim(),
textInputAction: TextInputAction.next,
style: AppTheme.body1WTextStyle,
decoration: buildInputDecoration(
'Enter Email',
Icons.email,
lightWTextColor.withOpacity(0.4),
),
// focusNode: _usernameFocusNode,
// onFieldSubmitted: (String val) {
// final focusNode = FocusNode();
// focusNode.unfocus();
// },
);
//// user password ////
TextFormField userPassword() => TextFormField(
key: const Key('login password input'),
obscureText: !_invisiblePass,
keyboardType: TextInputType.visiblePassword,
controller: passwordController,
validator: validatePassword,
onSaved: (value) => _password = value!.trim(),
textInputAction: TextInputAction.done,
style: AppTheme.body1WTextStyle,
decoration: buildInputDecoration(
'Enter Password',
Icons.vpn_lock,
lightWTextColor.withOpacity(0.4),
).copyWith(
suffixIcon: GestureDetector(
onTap: () {
_showPassword();
},
child: Icon(
_invisiblePass ? Icons.visibility : Icons.visibility_off,
color: Colors.black54,
),
),
),
);
final forgotLabel = Padding(
padding: const EdgeInsets.all(0.0),
child: Container(
alignment: Alignment.topRight,
child: TextButton(
child: const Text(
"Forgot password?",
style: AppTheme.body1WTextStyle,
),
onPressed: () {
Navigator.of(context).pushNamed(passwordResetScreenRoute);
},
),
),
);
final signupLabel = Padding(
padding: const EdgeInsets.all(10.0),
child: TextButton(
child: const Text(
"Sign Up for an Account",
style: AppTheme.subTitleWTextStyle,
),
onPressed: () {
Navigator.of(context).pushNamed(
userEditScreenRoute,
arguments: eProfile.addProfile,
);
},
),
);
final loginButton = ButtonWidget(
key: const Key('login button'),
text: 'LOG IN',
btnColor: accentColor,
borderColor: accentColor,
textColor: lightWTextColor,
onPressed: () {
Navigator.of(context).pushReplacementNamed(homeScreenRoute);
// _submit();
},
);
final loginForm = Form(
key: _userLoginFormKey,
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
userName(),
const SizedBox(
height: 10.0,
),
userPassword(),
forgotLabel,
const SizedBox(
height: 10.0,
),
loginButton,
const SizedBox(
height: 10.0,
),
signupLabel,
],
),
);
final mainBody = InkWell(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: wBackground(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/_logo.png',
height: 200.0,
),
Expanded(
flex: 1,
child: loginForm, //Text('this text here'),
),
],
),
),
),
),
);
return SafeArea(
child: Scaffold(
body: SingleChildScrollView(
child: mainBody,
),
),
);
}
}
and when I try to navigate to Home Screen on tap of login button, the test fails.
my test case is like this
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
//
// start.main();
login.main();
}
//
void main() {
doLoginTest();
}
void doLoginTest() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets("Login in test run", (WidgetTester tester) async {
//
pawfect.main();
await tester.pumpAndSettle(const Duration(seconds: 3));
//test here
final Finder login =
find.byWidgetPredicate((widget) => widget is LoginScreen);
expect(login, findsOneWidget);
await tester.pumpAndSettle(const Duration(seconds: 1));
//
var emailInput = find.byKey(const Key('login username input'));
await tester.tap(emailInput);
await tester.enterText(emailInput, "test#m.com");
await tester.pumpAndSettle(const Duration(seconds: 1));
//
var passwordInput = find.byKey(const Key('login password input'));
await tester.tap(passwordInput);
await tester.enterText(passwordInput, "password");
await tester.pumpAndSettle(const Duration(seconds: 1));
//
var loginButton = find.byKey(const Key('login button'));
await tester.tap(loginButton, warnIfMissed: false);
await tester.pumpAndSettle(const Duration(seconds: 3));
//
// expect(version, findsOneWidget);
// final Finder home = find.byWidget(const HomeScreen());
expect(find.byWidgetPredicate((widget) => widget is HomeScreen),
findsOneWidget);
// await tester.pumpAndSettle(const Duration(seconds: 1));
var version = find.byWidgetPredicate(
(widget) => widget is Text && widget.data!.contains("Version: 2.0"));
expect(version, findsOneWidget);
await tester.pumpAndSettle(const Duration(seconds: 3));
});
}
what am I doing wrong here? I tried to look for something helpful over the internet and in the docs but I couldn't get my hands dirty enough. Will someone please help me to write a fine Integration test move in with screen to another screen. Thank you so much in advance.
Do you still have both main methods in your test file? If so can you remove the first (I don't understand how that can even be there):
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
//
// start.main();
login.main();
}
Also try stepping through your code - add a breakpoint in your test file and with that test file still in the editor press F5 ( I am assuming here you are in VSCode like me ), find out which expect call is reporting the failure - I'm guessing it is the second:
expect(find.byWidgetPredicate((widget) => widget is HomeScreen),
findsOneWidget);
Try adding this code before that call (instead of your existing pumpAndSettle call):
await tester.pump(const Duration(milliseconds: 4000));
await tester.pumpAndSettle();
Also consider some the ideas in this answer: https://stackoverflow.com/a/70472212/491739

Flutter pressing back button pops up previous snackBar from Login page again

I have a LoginPage in Flutter. After login, it shows a small snackbar with "success" or "failure.." if password is wrong, then it navigates to the todo list.
When I now press the "back" button on an Android device, it navigates back to the login screen. However, there is still the snackbar popping up and saying "Login successful, redirecting..", and also, my textfields are not emptied and still have the values from the first login, why? That should not happen, but I cannot figure out why that is... here is my code:
import 'package:flutter/material.dart';
import 'package:todoey_flutter/components/rounded_button.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:todoey_flutter/util/file_handler.dart';
import 'package:provider/provider.dart';
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
String username;
String password;
String hashedPW;
// Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
var _nameController = TextEditingController();
var _pwController = TextEditingController();
#override
Widget build(BuildContext context) {
CryptOid cy = Provider.of<CryptOid>(context, listen: true);
FileHandler fh = Provider.of<FileHandler>(context, listen: true);
return Scaffold(
backgroundColor: Colors.white,
body: Builder(
builder: (BuildContext scaffoldBuildContext) {
return Container(
//inAsyncCall: isSpinning,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 34.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
/*
Flexible(
child: Hero(
tag: 'logo',
child: Container(
height: 200.0,
child: Image.asset('images/logo.png'),
),
),
),*/
SizedBox(
height: 48.0,
),
TextField(
controller: _nameController,
style: TextStyle(color: Colors.black54),
onChanged: (value) {
//Do something with the user input.
username = value.toLowerCase();
},
decoration: InputDecoration(
hintText: 'Enter your username',
),
),
SizedBox(
height: 8.0,
),
TextField(
controller: _pwController,
obscureText: true,
style: TextStyle(color: Colors.black54),
onChanged: (value) {
//Do something with the user input.
password = value;
},
decoration: InputDecoration(
hintText: 'Enter your password',
),
),
SizedBox(
height: 24.0,
),
RoundedButton(
title: 'Login',
colour: Colors.lightBlueAccent,
onPressed: () async {
Scaffold.of(scaffoldBuildContext).removeCurrentSnackBar();
print("user: $username, pw: $password");
if ((username != '' && username != null) && (password != '' && password != null)) {
SharedPreferences prefs = await SharedPreferences.getInstance();
// cy.test();
if ((username != '' && username != null) && prefs.containsKey(username)) {
hashedPW = prefs.getString(username);
bool decryptPW = await cy.deHash(hashedPW, password);
if (decryptPW) {
cy.setUsername(username);
fh.setUser(username);
prefs.setString('activeUser', username);
Scaffold.of(scaffoldBuildContext).showSnackBar(
SnackBar(
content: Text("Login successful! redirecting.."),
),
);
Navigator.pushNamed(context, 'taskScreen');
} else {
Scaffold.of(scaffoldBuildContext).showSnackBar(
SnackBar(
content: Text("Wrong password for user $username!"),
),
);
}
} else {
String hashedPW = await cy.hashPW(password);
prefs.setString('activeUser', username);
prefs.setString(username, hashedPW);
cy.setUsername(username);
fh.setUser(username);
Scaffold.of(scaffoldBuildContext).showSnackBar(
SnackBar(
content: Text("User created successful! redirecting.."),
),
);
Navigator.pushNamed(context, 'taskScreen');
//prefs.setString(username, hashedPW);
}
_nameController.clear();
_pwController.clear();
} else {
Scaffold.of(scaffoldBuildContext).showSnackBar(
SnackBar(
content: Text("User and password may not be empty.."),
),
);
_nameController.clear();
_pwController.clear();
return;
}
},
),
],
),
),
);
},
),
);
}
}
You should create a ScaffoldState GlobalKey then assign the to the scaffold.
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Container());
}
The use the key to showSnackBar
void _showInSnackBar(String value) {
_scaffoldKey.currentState
.showSnackBar(new SnackBar(content: new Text(value)));
}
So your full code would look like this:
import 'package:flutter/material.dart';
import 'package:todoey_flutter/components/rounded_button.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:todoey_flutter/util/file_handler.dart';
import 'package:provider/provider.dart';
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
String username;
String password;
String hashedPW;
// Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
var _nameController = TextEditingController();
var _pwController = TextEditingController();
#override
Widget build(BuildContext context) {
CryptOid cy = Provider.of<CryptOid>(context, listen: true);
FileHandler fh = Provider.of<FileHandler>(context, listen: true);
return Scaffold(
key: _scaffoldKey,
backgroundColor: Colors.white,
body: Builder(
builder: (BuildContext scaffoldBuildContext) {
return Container(
//inAsyncCall: isSpinning,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 34.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
/*
Flexible(
child: Hero(
tag: 'logo',
child: Container(
height: 200.0,
child: Image.asset('images/logo.png'),
),
),
),*/
SizedBox(
height: 48.0,
),
TextField(
controller: _nameController,
style: TextStyle(color: Colors.black54),
onChanged: (value) {
//Do something with the user input.
username = value.toLowerCase();
},
decoration: InputDecoration(
hintText: 'Enter your username',
),
),
SizedBox(
height: 8.0,
),
TextField(
controller: _pwController,
obscureText: true,
style: TextStyle(color: Colors.black54),
onChanged: (value) {
//Do something with the user input.
password = value;
},
decoration: InputDecoration(
hintText: 'Enter your password',
),
),
SizedBox(
height: 24.0,
),
RoundedButton(
title: 'Login',
colour: Colors.lightBlueAccent,
onPressed: () async {
_scaffoldKey.currentState.removeCurrentSnackBar();
print("user: $username, pw: $password");
if ((username != '' && username != null) &&
(password != '' && password != null)) {
SharedPreferences prefs =
await SharedPreferences.getInstance();
// cy.test();
if ((username != '' && username != null) &&
prefs.containsKey(username)) {
hashedPW = prefs.getString(username);
bool decryptPW = await cy.deHash(hashedPW, password);
if (decryptPW) {
cy.setUsername(username);
fh.setUser(username);
prefs.setString('activeUser', username);
_showInSnackBar("Login successful! redirecting..");
Navigator.pushNamed(context, 'taskScreen');
} else {
_showInSnackBar(
"Wrong password for user $username!");
}
} else {
String hashedPW = await cy.hashPW(password);
prefs.setString('activeUser', username);
prefs.setString(username, hashedPW);
cy.setUsername(username);
fh.setUser(username);
_showInSnackBar(
"User created successful! redirecting..");
Navigator.pushNamed(context, 'taskScreen');
//prefs.setString(username, hashedPW);
}
_nameController.clear();
_pwController.clear();
} else {
_showInSnackBar("User and password may not be empty..");
_nameController.clear();
_pwController.clear();
return;
}
},
),
],
),
),
);
},
),
);
}
void _showInSnackBar(String value) {
_scaffoldKey.currentState
.showSnackBar(new SnackBar(content: new Text(value)));
}
}