Flutter onAuthStateChanged stream not updating child when registering or signing in - flutter

I wrapped my app inside a StreamProvider.value, where the value is an onAuthStateChanged stream, and the child is the main MaterialApp. My main wrapper listens to this value using Provider.of. This works fine for signing out; once I sign out, the StreamProvider notifies my main wrapper, and the app is redirected to the welcome screen.
But when I try to create an account or sign in, it seems the StreamProvider is not being notified of the onAuthStateChanged stream. This is why it's so weird, if the stream wasn't working, then signing out should also not work. Also, when I hot restart, I do get redirected to the home screen. I've scattered the internet: no luck. Here's the appropriate code:
In main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<FirebaseUser>.value(
value: AuthService().userStream,
child: MaterialApp(
home: Wrapper(),
theme: ThemeData.light().copyWith(
appBarTheme: AppBarTheme(color: Colors.lightBlue),
scaffoldBackgroundColor: Colors.white,
),
),
);
}
}
In wrapper.dart
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('Rebuilding');
final user = Provider.of<FirebaseUser>(context);
if (user == null) {
return Welcome();
} else {
return Home();
}
}
}
In auth_service.dart
class AuthService {
FirebaseAuth _auth = FirebaseAuth.instance;
Stream<FirebaseUser> get userStream {
return _auth.onAuthStateChanged;
}
Future registerWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
return user;
} catch (e) {
print(e.toString());
return null;
}
}
Future signInWithEmailAndPassword(String email, String password) async {
try {
AuthResult result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
return user;
} catch (e) {
print(e.toString());
return null;
}
}
void signOut() {
try {
_auth.signOut();
} catch (e) {
print(e);
}
}
}
Also, inside my welcome.dart, I have this set up as a test:
onPressed: () {
FirebaseAuth.instance.onAuthStateChanged
.listen((event) {
print('$event from .listen');
});
}
And this does notify my everytime I sign in, out or register, this tells me that there is nothing wrong with onAuthStateChanged, or the way I set it up.
If you need any more information, let me know. I don't want to make the question any longer. Thank you for your help!

Related

How to get user credential while moving to welcome screen in flutter

I have created simple home screen for login and register,
Here I have taken readymade code from a channel, and now I need to change little bit..
code is simple, so no more details to explain
just I want to pass Usercredential to my welcome screen...
here is my code
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context,snapshot){
if(snapshot.hasData)
{
print(snapshot.data);
return WelcomePage(usercredential:
//how to get usercredential,
);
}
else
{
return AuthPage();
}
},
),
);
}
}
here is my login page's login code
Future signin() async {
UserCredential? usercredential;
try {
usercredential=await FirebaseAuth.instance.signInWithEmailAndPassword(
email: txtemailcontroller.text, password: txtpasswordcontroller.text);
} on FirebaseAuthException catch (e) {
print("Error is =" + e.toString());
}
}
and register page's register code
Future signup() async {
UserCredential? usercredential;
try {
if (txtconfirmpasswordcontroller.text.trim() ==
txtpasswordcontroller.text.trim()) {
usercredential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: txtemailcontroller.text,
password: txtpasswordcontroller.text);
} else {
print("passwrod does not match");
}
} on FirebaseAuthException catch (e) {
print('Error while register' + e.toString());
}
if (usercredential != null) {
String userid = usercredential.user!.uid;
UserModel newuser = UserModel(
email: txtemailcontroller.text,
userid: userid,
fullname:
txtfirstnamecontroller.text + ' ' + txtlastnamecontroller.text,
profilepicture: '');
}
}
so far I know, user credential generated while createuserwithemailandpassword and signinwithemailandpassword method, but how to get it here....where I need...
Add this In your Welcome page:
User? currentUser=FirebaseAuth.instance.currentUser;
Then you can call it any where and get user details like email & id & display name.
for example:
currentUser.email
or
currentUser.uid

Page not disposed when signed up: Flutter

I am trying to implement provider package to signUp/signIn/signOut using Firebase Auth.
My ChangeNotifier class is-
import 'package:e_shoppie/db/authentication.dart';
import 'package:e_shoppie/db/user_services.dart';
import 'package:e_shoppie/structure/constants.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
//User can only be one of these states
/**
* uninitialized: User just opened the app (just opening)
* unauthenticated: Show the login screen to the user
* authenticating: Show a circular indicator the user
* authenticated: User is looged into the app
*/
enum Status { uninitialized, authenticated, authenticating, unauthenticated }
class UserProvider with ChangeNotifier {
FirebaseAuth _auth;
Auth _userAuth = Auth();
UserServices userServices = UserServices();
User? _user;
GoogleSignIn _googleSignIn = GoogleSignIn();
Status _status =
Status.uninitialized; //when the instance of the class is created
UserProvider.initialize() : _auth = FirebaseAuth.instance {
//subscribing to stream to listen to changes in user status
_auth.authStateChanges().listen(
(user) {
_onStatusChanged(user);
},
);
}
Status get status => _status;
User? get user => _user;
Future<bool> signUp(String username, String email, String password) async {
try {
//change the status of the user
_status = Status.authenticating;
//notify the listeners
notifyListeners();
// UserCredential credential =
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
Map<String, dynamic> values = {
'name': username,
'email': email,
'id': user!.uid,
};
userServices.createUser(values);
_status = Status.authenticated;
notifyListeners();
return true;
} catch (e) {
_status = Status.unauthenticated;
notifyListeners();
print(e.toString());
return false;
}
}
Future signOut() async {
print('entered signOut');
await _auth.signOut();
_status = Status.unauthenticated;
notifyListeners();
print('Exiting signOut');
return Future.delayed(Duration
.zero); //duration to return is set to zero (can work without it)
}
Future<void> _onStatusChanged(User? user) async {
if (user == null) {
_status = Status.unauthenticated;
} else {
_user = user;
_status = Status.authenticated;
}
notifyListeners();
}
}
The way I am navigating on state change is-
class ScreenController extends StatelessWidget {
const ScreenController({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<UserProvider>(context);
switch (user.status) {
case Status.uninitialized:
return SplashScreen();
case Status.unauthenticated:
return LoginScreen();
case Status.authenticating:
return LoadingScreen();
case Status.authenticated:
return HomePage();
default:
return LoginScreen();
}
}
}
Problem: in my SignUp page, I call the signUp method of the UserProvider class to signUp the user.
I expect the signup page gets destroyed and home page appears when user is created and sign up procedure is complete.
What I get: Home Page is built but the sign up page is not destroyed and remains on the screen unless I press the back button.
Sign Up button -
// minWidth: MediaQuery.of(context).size.width.,
child: Text(
'Sign Up and Register',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
onPressed: () async {
if (!await provider.signUp(
_nameTextController.text,
_emailTextController.text,
_passwordTextController.text)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Sign In Failed')));
}
// Navigator.pop(context);
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (context) => HomePage()));
},
),
Also- my Sign Up class is wrapped with Consumer.
I am using the provider package for navigation. The problem I am facing is: that Debug mode shows that the login page is disposed of when the user logs in and Home Page appears. But when I sign in from the Sign Up page, the page is not disposed and Home Page is constructed below it.
Please help!!
You have to use Navigator.popAndPushNamed(context) or Navigator.pushReplacementNamed(context). This is work perfect in your scenario both have the same output difference is only animations. For signUp/signIn/signOut this is ideal way.

Is setState() ignored by try & catch?

I use Firebase Auth to allow users to sign up.
If the user registers the correct email address and a sufficiently secure password, they will be registered with Firebase Auth.
I can register, but when I fail to sign up, I don't get an error.
String _state = ""; //global
Future signUp(String email, String password) async {
try {
UserCredential userCredential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
setState(() {
_state = ('The password provided is too weak.');
});
} else if (e.code == 'email-already-in-use') {
setState(() {
_state = ('The account already exists for that email.');
});
}
} catch (e) {
setState(() {
_state = e.toString();
});
}
}
Referred here.
This code executes createUserWithEmailAndPassword() by passing the email address and password as arguments.
I'm trying to display on the screen the cause of a sign-in failure with try & catch statement.
But for some reason setState() doesn't change the Text() that has global _state.
#immutable
class signUp extends StatefulWidget {
static String route = '/signup';
const signUp({Key? key}) : super(key: key);
#override
_signUp createState() => _signUp();
}
class _signUp extends State<signUp> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: myAppBar(context), //custom appBar. ignore this.
body: const Center(
child: Text(
_state
),
));
}
}
I declared Text() in StatefulWidget so that it can be updated with setState().
But for some reason setState() is ignored and Text(_state) is not executed.
I feel that the cause of this problem is in the try & catch statement, but I don't know what to do.
What should I do to display the sign-up results as text?
Thank you.
I changed code like this; this solved my issue.
String stateCode = "";
try {
UserCredential userCredential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
stateCode = ('The password provided is too weak.');
} else if (e.code == 'email-already-in-use') {
stateCode = ('The account already exists for that email.');
} else {
stateCode = "error: " + e.code;
}
} catch (e) {
stateCode = "error: " + e.toString();
}
setState(() {
_state = (stateCode);
});
All I had to do was display the e.code when an exception occurred.
I can register, but when I fail to sign up, I don't get an error.
Could you check if your sign-in really fails? Checking your code, signUp is a Future<void>. How are you handling the UserCredential being returned by FirebaseAuth.instance.createUserWithEmailAndPassword?
This block catches an Exception, not a successful login.
catch (e) {
setState(() {
_state = "Succeeded!";
});
}
You can also check for UserCredential after the login request to debug.
UserCredential userCredential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
debugPrint(uid: ${userCredential?.user?.uid}

dart - How can I resolve 'The argument type 'Null' can't be assigned to the parameter type 'MyUser'.'

I am trying to implement a simple signup page. The following is my main.dart code:
main.dart
void main() async {
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<MyUser>.value(
value: AuthService().user,
initialData: null,
child: MaterialApp(
home: Wrapper(),
debugShowCheckedModeBanner: false,
)
);
}
}
I am seeing this error on the initialData: null, line:
The argument type 'Null' can't be assigned to the parameter type 'MyUser'.
This is my MyUser class:
user.dart
class MyUser {
final String uid;
MyUser({this.uid});
}
It is showing me the following error on uid in line 3:
The parameter 'uid' can't have a value of 'null' because of its type, but the implicit default value is 'null'.
Try adding either an explicit non-'null' default value or the 'required' modifier.
I am fairly new to flutter development so I'm not sure what this means and how to resolve it. I wasn't able to find any relevant help online. Any help will be appreciated.
EDIT 1:
The entire auth.dart file:
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// create user obj based on firebase user
MyUser? _userFromFirebaseUser(User user) {
// ignore: unnecessary_null_comparison
return user != null ? MyUser(uid: user.uid) : null;
}
// auth change user stream
Stream<MyUser> get user {
return _auth.authStateChanges()
//.map((FirebaseUser user) => _userFromFirebaseUser(user));
.map(_userFromFirebaseUser);
}
// sign in with email and password
Future signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user!);
} catch (error) {
print(error.toString());
return null;
}
}
// register with email and password
Future registerWithEmailAndPassword(String email, String password) async {
try {
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user!);
} catch (error) {
print(error.toString()+"oollala");
return null;
}
}
// sign out
Future signOut() async {
try {
return await _auth.signOut();
} catch (error) {
print(error.toString());
return null;
}
}
}
Error in:
Stream<MyUser> get user {
return _auth.authStateChanges()
.map(_userFromFirebaseUser);
}
It is showing me an error in _userFromFirebaseUser
The argument type 'MyUser? Function(User)' can't be assigned to the parameter type 'MyUser Function(User?)'.
You defined your property uid as a non-nullable String but it is declared as an optional value in your constructor because of the {} which means that it would have the default value null if not assigned.
To fix this error you either need to make uid non optional in your constructor:
MyUser(this.uid);
Or to make it a required parameter:
MyUser({required this.uid});
If it is intended that your uid can be null then you need to declare your variable like this:
final String? uid; // this is a nullable String variable
make sure you have provider 5.0.0
and then change your main.dart to
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return StreamProvider<MyUser?>.value(
initialData: null,
value: AuthService().user,
child: MaterialApp(
home: Wrapper(
),
),
);
}
}

Flutter: StreamBuilder Snapshot -- No Data

I am just learning Flutter and am trying to use a StreamBuilder to display a Login / Register page if the user is logged out, or a Profile page if the user is logged in. My code is below:
Auth Service:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthUser {
AuthUser({#required this.uid, #required this.email});
final String uid;
final String email;
}
abstract class AuthBase {
Future<AuthUser> currentUser();
Future<AuthUser> signIn({String email, String pw});
Future<AuthUser> registerUser({String email, String pw});
Stream<AuthUser> get onAuthStateChanged;
Future<void> signOut();
}
class Auth implements AuthBase {
final _firebaseAuth = FirebaseAuth.instance;
AuthUser _userFromFirebase(FirebaseUser user) {
if (user != null) {
return AuthUser(uid: user.uid, email: user.email);
} else {
return null;
}
}
#override
Stream<AuthUser> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
#override
Future<AuthUser> currentUser() async {
final user = await _firebaseAuth.currentUser();
return _userFromFirebase(user);
}
#override
Future<AuthUser> signIn({String email, String pw}) async {
final authResult = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
#override
Future<AuthUser> registerUser({String email, String pw}) async {
final authResult = await _firebaseAuth.createUserWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
#override
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
}
StreamBuilder:
class WelcomeScreen extends StatelessWidget {
WelcomeScreen({#required this.auth});
static const String id = '/';
final AuthBase auth;
#override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) {
AuthUser user = snapshot.data;
if (user == null) {
return displayLoginOrRegPage(context);
} else {
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
It was my understanding the stream would begin emitting 'null' once it was initialized, and would continue doing so until it fired off an Auth state change...
But the snapshot continually reports "No Data" and thus my code is stuck on the CircularProgressIndicator.
BTW, if I display the log-in screen in place of the progress indicator, the code works. So I'm clearly not understanding the whole stream initialization process.
Can somebody kindly explain to me where I have gone wrong here? Thanks a million in advance.
As you mentioned, when stream initialises it emits null, but when the user is not logged in, it still emits null, which stream considers as no data i.e null that's the reason for the error.
You can use Streambuilder's connection state to differentiate between no user null and null after initialisation.
I hope following code helps you.
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data == null) {
return displayLoginOrRegPage(context);
} else {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
Per #VirenVVarasadiya, it was definitely a case of checking the ConnectionState. Here is the final working code. Thank you!
#override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data != null) {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
} else {
return displayLoginOrRegPage(context);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
Try to change WelcomeScreen to state full Widget.