Creating a Firebase user does not update authStateChanges method - flutter

Quick question. Whenever I create a firebase user with email and password, the routing does not take the user to his correct page.
User user = context.watch<User>();
if (user == null) return LoginScreen();
///
rest of the code here where the user type stored in Firestore decides which page to navigate to
///
The createUserWithEmailAndPassword documentation does not say that it updates the authStateChanges method.
On the other hand, signInWithEmailAndPassword and FirebaseAuth.instance.signOut() documentations clearly states that it updates the authStateChanges method. And when I login the user is indeed taken to the correct page.
I assumed that createUserWithEmailAndPassword would do the same.
So should I navigate the user to the correct page manually (while popping all previous routes)?
Thank you in advance.
Routing Code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// final _themeProvider = Provider.of<ThemeProvider>(context);
return MultiProvider(
providers: [
ChangeNotifierProvider<ThemeProvider>(create: (context) => ThemeProvider()),
Provider<CurrentUser>(create: (context) => CurrentUser()),
StreamProvider(create: (context) => context.read<CurrentUser>().authStateChanges),
],
child: MaterialApp(
home: LandingRouting(),
),
);
}
}
class LandingRouting extends StatelessWidget {
const LandingRouting({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
logger.w('Landing Routing ...');
User user = context.watch<User>();
if (user == null) return LoginScreen();
return FutureBuilder(
future: FirebaseFirestore.instance.collection('users').doc(user.uid).get(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return Loading();
if (snapshot.hasError)
return AlertDialog(
title: Text('Error loading user'),
content: Text('${snapshot.error.toString}'),
);
else {
Map<String, dynamic> userData = snapshot.data.data();
assert(userData['userType'] != null, 'User Type cannot be null');
if (userData['userType'] == 'baqala') {
return BaqalaUserScreen();
} else {
return UserHomeScreen();
}
}
},
);
}
}
Sign up code:
class BaqalaAuth {
static void registerBaqala({
#required BuildContext context,
#required String baqalaName,
#required String license,
#required String landlineNumber,
#required String mobileNumber,
#required String email,
#required String password,
#required double lat,
#required double long,
}) async {
try {
FirebaseAuth.instance.createUserWithEmailAndPassword(email: email, password: password).then(
(userCred) async {
BaqalaInit.initBaqala(
context: context,
user: userCred.user,
landlineNumber: landlineNumber,
mobileNumber: mobileNumber,
licenseNumber: license,
baqalaName: baqalaName,
lat: lat,
long: long,
);
await userCred.user.updateProfile(displayName: baqalaName);
},
);
.
.
.
Login Code
class Auth {
static void login({
#required BuildContext context,
#required String email,
#required String password,
}) async {
try {
FocusScope.of(context).unfocus();
await FirebaseAuth.instance.signInWithEmailAndPassword(email: email, password: password);
} on PlatformException catch (e) {
logger.i('Login: Firebase Auth Exception');
_loginException(context, 'Error: ${e.code}\n\n${e.message}');
} on FirebaseAuthException catch (e) {
logger.i('Login: Firebase Auth Exception');
_loginException(context, 'Error: ${e.code}\n\n${e.message}');
} catch (e) {
_loginException(context, 'Login: Error: ${e.toString}');
}
}
static Future<void> logout() async {
await FirebaseAuth.instance.signOut();
}
}

Related

Checking for Firebase uID then routing

So my main.dart looking like this, I just want to check if the user already loggedIn or not. If true then route him directly to Homescreen and passing the UID else to the SignIn screen.
But somehow im getting a black screen without any error. Why? the debug print statements are working...
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
//User logged in?
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null) {
print('User is currently signed out!');
MaterialPageRoute(builder: (context) => const SignIn());
} else {
String myUid = user.uid;
MaterialPageRoute(builder: (context) => HomeScreen(userId: myUid));
print('User is signed in!');
}
});
return const SizedBox.shrink(); //<-----here
}
}
Well my Code looking now like this:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
//User logged in?
final FirebaseAuth auth = FirebaseAuth.instance;
//The stream for auth changee
Future<User?> data() async {
return FirebaseAuth.instance.currentUser;
}
final User? user = auth.currentUser;
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance
.authStateChanges(), //FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
if (snapshot.connectionState == ConnectionState.active) {
if (user == null) {
print('User is currently signed out!');
Navigator.push(context,
MaterialPageRoute(builder: (context) => const SignIn()));
} else {
String myUid = user!.uid;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(
userId: myUid,
)));
}
}
return const CircularProgressIndicator();
});
}
}
Navigator operation requested with a context that does not include a Navigator.
The relevant error-causing widget was
StreamBuilder<User?>
You can't just insert a stream listener in the build method like that. The easiest way to do this, is to use a StreamBuilder which handles the stream for you. Similar to the example in the documentation on listening for Firestore updates that'd be something like:
StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
if (user == null) {
print('User is currently signed out!');
return MaterialPageRoute(builder: (context) => const SignIn());
} else {
String myUid = user.uid;
return MaterialPageRoute(builder: (context) => HomeScreen(userId: myUid));
}
},

App does not navigate to a different page when user authenticates

The issue is that my app does not navigate to another page automatically when user logs in or out.
class MyApp extends StatelessWidget {
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _initialization,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(home: Wrapper()),
);
}
return Center(child: CircularProgressIndicator());
},
);
}
}
class Wrapper extends StatelessWidget {
const Wrapper({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user != null) {
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => Home(),
'/profile': (context) => Profile()
});
}
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => Welcome(),
'/signup': (context) => SignUp(),
'/signin': (context) => SignIn()
});
}
}
When the app starts it does show the Welcome() page. Then i am able to navigate to the signup page by pressing a signup button as such
onPressed: () {Navigator.pushNamed(context, "/signup");}),
but then when the user signs up, the app doesn't automatically navigate to Home()
class AuthService {
FirebaseAuth auth = FirebaseAuth.instance;
User _userFromFirebaseUser(User user) {
return user != null ? User(id: user.uid) : null;
}
Stream<User> get user {
return auth.authStateChanges().map(_userFromFirebaseUser);
}
Future<String> signUp(email, password) async {
try {
UserCredential user = await auth.createUserWithEmailAndPassword(
email: email, password: password);
await FirebaseFirestore.instance
.collection('users')
.doc(user.user.uid)
.set({'name': email, 'email': email});
_userFromFirebaseUser(user.user);
} on FirebaseAuthException catch (e) {
return e.code;
} catch (e) {
return e;
}
return "";
}
}
I am not sure what the issue is. Any help is appreciated.
First of all you need 1 MaterialApp not 3, then try to debug signUp method maybe there is an erorr for instance signUp returns Future<String> but in catch block you are returning an Exception and finally I suggest you to use Cubit whenever you need to listen state changes to navigate.

How to build different UI based on ProfileType in Flutter?

I am new to Flutter, I want to render different UI in my app based of ProfileType i.e. if a user is 'Club', it will show a '+' icon for adding a event, otherwise not. I stored userData in firestore like here:Firestore
I stored extra user info like this in main.dart :
if (isLogin) {
authResult = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} else {
authResult = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await FirebaseFirestore.instance
.collection('users')
.doc(authResult.user.uid)
.set({
'username': username,
'email': email,
'profileType': profileType,
'SignUpDate': timestamp,
});
}
}
I also extracted userId while building screens:
home: StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, userSnapshot) {
if (userSnapshot.hasData) {
var arr = userSnapshot.data.toString().split(',');
String arr1 = arr[17].split(',').toString();
String uid = arr1.split(':')[1].split(')')[0];
FirebaseFirestore.instance.collection('users').doc(uid).get().then((DocumentSnapshot doc) {
print('---Data-----');
print(doc.data());
});
return Home(uid: uid,);
}
return AuthScreen();
},
),
);
But i failed to get 'profileType' for build UI. I searched for 'profileType' with this
findProfileType() {
FirebaseFirestore.instance.collection('users').doc(uid).get().then((DocumentSnapshot doc) {
print('---Data-----');
print(doc.data());
});
}
But everytime it returned null. Any suggestion
Thanks in Advance
It looks like the findProfileType function is returning null because there is no explicit return statement inside the function. See the documentation here for more information.
Given that findProfileType is asynchronous you could use a FutureBuilder widget to build the UI. The data from the snapshot could be used to determine which UI to build.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Stream<String> _stream;
#override
Widget build(BuildContext context) {
final appBar = AppBar(
title: Text('Flutter Demo Home Page'),
);
return StreamBuilder<String>(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FloatingActionButton floatingActionButton;
if (snapshot.data == 'Club') {
floatingActionButton = FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
);
}
return Scaffold(
appBar: appBar,
body: Center(
child: Text(snapshot.data),
),
floatingActionButton: floatingActionButton,
);
} else {
return Scaffold(
appBar: appBar,
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
#override
void initState() {
super.initState();
_stream = FirebaseAuth.instance
.authStateChanges()
.asyncMap(
(user) => FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.get(),
)
.map(
(doc) => doc.data()['profileType'],
);
}
}
I would recommend taking a look at the documentation here for more information. Please do not hesitate to let me know if you have any questions or would like more information!

Unable to store value to class variable in flutter/dart when using Future

I want to store value of share preference in to class variable but it doesn't working at all, the value doesn't store to variable.
here is my code, basically i want to store to variable _uid but when i accessing it inside my UI it printing ""..
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class User with ChangeNotifier {
FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
Firestore _firestore = Firestore.instance;
bool _loggedIn = false;
bool get loggedIn => _loggedIn;
String _uid = "";
String get uid => _uid;
User() {
getPrefState().then((val) {
// do some operation
_uid = val.toString() ?? "test";
});
//init();
}
Future init() async {
//var data;
//_uid = getPrefState();
/*SharedPreferences.getInstance().then((value) => {
_uid = value.getString("uid") ?? "d",
data = "dsdasd",
});*/
/*try {
SharedPreferences prefs = await SharedPreferences.getInstance();
_uid = data;
} catch (err) {
//pass.
}*/
//var uid = prefs.getString("uid") ?? "d";
/*if (uid != null) {
_loggedIn = true;
}*/
//_uid = "dsd";
//notifyListeners();
}
Future<String> getPrefState() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
//Return String
String stringValue = prefs.getString('uid') ?? "test";
return stringValue;
}
Future<void> saveId(uid) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString("uid", uid);
}
Future<void> saveUserInDocument(String uid, String name, String sap) {
_firestore.collection("users").document(uid).setData({
"name": name,
"sap": sap,
'role': "student",
});
}
Future<FirebaseUser> getCurrentUser() async {
return await _firebaseAuth.currentUser();
}
// user signup.
Future<void> signup(
String email, String password, String name, String sap) async {
FirebaseUser user = (await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password))
.user;
if (user != null) {
saveUserInDocument(user.uid, name, sap);
saveId(user.uid);
_loggedIn = true;
notifyListeners();
}
}
/// user login
Future<void> login(String email, String password) async {
FirebaseUser user = (await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password))
.user;
//if (user != null) {
saveId(user.uid);
_loggedIn = true;
notifyListeners();
//}
}
/// User logout
Future<void> logout() async {
_firebaseAuth.signOut();
saveId(null);
_loggedIn = false;
notifyListeners();
}
/// reset user password
Future<void> resetPassword(String email) async {
await _firebaseAuth.sendPasswordResetEmail(email: email);
}
}
Here is my UI class where i want to use it
user.uid
import "package:flutter/material.dart";
import 'package:riphahwebresources/data/User.dart';
import 'package:riphahwebresources/pages/auth/login_ui.dart';
import 'package:shared_preferences/shared_preferences.dart';
class WebResourceAppDrawer extends StatefulWidget {
#override
_WebResourceAppDrawerState createState() => _WebResourceAppDrawerState();
}
class _WebResourceAppDrawerState extends State<WebResourceAppDrawer> {
User user = User();
#override
Widget build(BuildContext context) {
List<Widget> children = [];
children.add(
ListTile(
leading: Icon(Icons.home),
title: Text("Home"),
),
);
if (user.loggedIn) {
children.add(ListTile(
leading: Icon(Icons.people),
title: Text("Profile"),
onTap: () => {
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginUi()))
},
));
children.add(ListTile(
leading: Icon(Icons.people),
title: Text("Logout"),
onTap: () => {
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginUi()))
},
));
} else {
children.add(ListTile(
leading: Icon(Icons.people),
title: Text(user.uid),
onTap: () => {
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginUi()))
},
));
}
return Drawer(
child: ListView(
padding: const EdgeInsets.all(8),
children: <Widget>[
DrawerHeader(
child: Text("Menu"),
),
...children,
],
),
);
}
}
You can see from method init i try different things but idk nothing works for me.
Thanks you so much.
When using ChangeNotifier, you need to call notifyListeners to propagate the change to your UI class:
notifyListeners(). Call this method any time the model changes in a way that might change your app’s UI.
Flutter Docs on State Management
As a reminder, you need the following 3 things to get State Management working:
Setup a class that extends ChangeNotifier (which you have done, but just replace the with with extends)
Add a ChangeNotifierProvider above the widget where you require the value (i.e. above your WebResourceAppDrawer UI widget)
Now access User by wrapping your UI widget with Consumer<User>
Here's what a complete, minimal example would look like:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
body: Center(
child:
ChangeNotifierProvider(create: (_) => User(), child: UI()))),
// 'UI' can now access the newly created User() since it's a
// child of ChangeNotifierProvider
);
}
}
class UI extends StatefulWidget {
#override
_UIState createState() => _UIState();
}
class _UIState extends State<UI> {
#override
Widget build(BuildContext context) {
return Container(
child: Consumer<User>(
builder: (context, user, child) => Text("User ${user.uid}")),
// This is how user can be accessed within UI
);
}
}
class User extends ChangeNotifier {
String _uid = "(empty)";
String get uid => _uid;
User() {
getPrefState().then((val) {
_uid = val;
notifyListeners(); // this call triggers a rebuild of UI
});
}
getPrefState() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString('uid');
}
}

cannot navigate form login screen to bottom_tab_screen with provider

I'm trying to navigate from login screen to the bottom tab screen but nothing happen and now i have no error
it is the main
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: UserProvider()),
ChangeNotifierProvider.value(value: AppProvider()),
],
child:MaterialApp(
key: key,
title: 'Voyager',
debugShowCheckedModeBanner: false,
theme: AppTheme.getTheme(),
routes: routes,
),
);
}
my dialog which has two cases if success or fail to login or sign up
import 'package:flutter/material.dart';
class Dialogs {
static showErrorDialog(BuildContext context,
{#required String message, #required int code}) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
actions: <Widget>[
FlatButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Ok'),
)
],
title: Text('error $code'),
content: Text(message),
backgroundColor: Colors.white,
);
},
);
}
}
my login method and it depend on user api provider
signIn() async {
var res = await userProvider.login(
_userNameController.text, _passwordController.text);
if (res is FailedRequest) {
Dialogs.showErrorDialog(widget._context , message: res.message, code: res.code);
print('results ${res.toString()}');
} else {
print("Signing in success");
Navigator.pushReplacement(
widget._context, MaterialPageRoute(builder: (context) => BottomTabScreen()));
}
userProvider.isLoading = false;
}
and the api provider which use in the login
Future<dynamic> login(String email, String password) async {
final Map<String, dynamic> body = {'email': email, 'password': password};
_isLoading = true;
notifyListeners();
print('Starting request');
http.Response response = await http.post(Environment.userLogin,
body: json.encode(body), headers: Environment.requestHeader);
print('Completed request');
print('user login response : ${response.body}');
Map<String, dynamic> res = json.decode(response.body);
var results;
if (res['code'] == 200) {
// login successful
_user = User.fromJson(res['message']);
results = true;
} else {
// login failed;
results =
FailedRequest(code: 400, message: res['error'], status: false);
}
_isLoading = false;
notifyListeners();
return results;
}
finally the failed request class if request not done
import 'package:flutter/foundation.dart';
class FailedRequest {
String message;
int code;
bool status;
FailedRequest({
#required this.message,
#required this.code,
#required this.status,
});
}
The Issue seems to be with the res['error'] can you verify that the field error actually exists and is not null.
At this block can you print the value of res['error']
else {
print(res['error']);
// login failed;
results =
FailedRequest(code: 400, message: res['error'], status: false);
}