App does not navigate to a different page when user authenticates - flutter

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.

Related

I want anonymous authenticated users to be redirected to another page

I want to redirect anonymous users to "GuestHomePage" instead of "HomePage" when they launch the app. 
In my app, authenticated users have their own documents in the "users" collection in the firestore. Therefore, I thought it would be possible to transition the user to another "GuestHomePage" if the user did not have the document in the firestore. But it didn't work. Below is my code. I would like to know what improvements you would like to see.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
MobileAds.instance.initialize();
runApp((MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListenableProvider<UserState>(
create: (_) => UserState(),
builder: (context, child) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox();
}
if (snapshot.hasData) {
String uid = FirebaseAuth.instance.currentUser!.uid;
try {
FirebaseFirestore.instance
.collection("users")
.doc(uid)
.get()
.then(
(doc) {},
);
return HomePage();
} catch (e) {
return GuestHomePage();
}
}
return const UserLogin();
},
),
theme: ThemeData(primarySwatch: Colors.teal),
);
},
);
}
}
class UserState extends ChangeNotifier {
User? user;
void setUser(User newUser) {
user = newUser;
notifyListeners();
}
}
try this one...
if (snapshot.hasData) {
String uid = FirebaseAuth.instance.currentUser!.uid;
try {
FirebaseFirestore.instance.collection("users").doc(uid).get().then((doc) {
var data = doc.data();
if (data != null) {
return HomePage();
} else {
return GuestHomePage();
}
},
);
} catch (e) {
return GuestHomePage();
}
}

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));
}
},

Onboarding screen does not work correctly with auth Firebase in Flutter app

The logic of the application - onboading screen appears only once, after it goes to the authorization screen with Firebase. If the authorization is successful, we open the home page of the application.
But this doesn't work the first time you run the app. After hot restart app - the login is done and we are right back to the home page.
Is there any way to fix it? (And without onboarding this logic works)
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final pref = await SharedPreferences.getInstance();
final showHome = pref.getBool('showHome') ?? false;
runApp(MyApp(showHome: showHome));
}
class MyApp extends StatelessWidget {
bool showHome;
MyApp({Key? key, required this.showHome}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
...
child: MaterialApp(
home: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return errorMessage();
} else if (snapshot.hasData) {
return const BottomPage();
} else {
return showHome ? const LoginPage() : const Onboarding();
}
},
),
}
LoginPage()
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _loginController.text.trim(),
password: _passwordContoller.text.trim(),
);
}
Onboarding()
onTap: () async {
final pref = await SharedPreferences.getInstance();
pref.setBool('showHome', true);
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoginPage()));
},

Creating a Firebase user does not update authStateChanges method

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();
}
}

Error: Class 'String' has no instance getter 'token'. I/flutter ( 3268): Receiver: "dc9e0de8fa2eaa917657e810db06aad2458e4f65"

I have been struggling with this problem for like two days. My social media app should save its state, when signed in so that when you leave the app and come back again it should start from the home page, not the sign in page. I have found that it is possible to do this with StreamBuilder and FutureBuilder. I have tried some things with FutureBuilder and I have some errors.
Below is how my main page looks like:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => UserData(),
child: MaterialApp(
title: 'Curtain App',
debugShowCheckedModeBanner: false,
home: FutureBuilder(
future: SharedPreferencesHelper.getPrefs(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasData) {
Provider.of<UserData>(context).currentUserId =
snapshot.data.token;
return HomeScreen();
} else {
return LoginScreen();
}
},
),
),
);
}
}
class SharedPreferencesHelper {
static final String _tokenCode = "token";
static Future<String> getPrefs() async {
final SharedPreferences preferences = await SharedPreferences.getInstance();
return preferences.getString(_tokenCode) ?? "empty";
}
}
And this is my LoginPage submit btn code:
_submit() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// logging in the user w/ Firebase
//AuthService.login(_email, _password);
var user = await DatabaseService.loginUser(_username, _password);
final data = json.decode(user);
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print("Hi ${data['username']}");
print("Status ${data['status']}");
print("Token ${data['token']}");
if (data['username'] != null) {
setState(() {
_message = "Hi ${data['username']}";
sharedPreferences.setString('token', data['token']);
});
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (context) => HomeScreen(),
),
(Route<dynamic> route) => false);
}
}
}
Any ideas on how to solve this ?
Just remove the .token from the line where the error occurs. snapshot.data already is the token.