Flutter/Firebase: Dynamic homepage depending on user loginStatus using MultiProvider issues - firebase

I want to check if the user is already logged in and show him page depending on that.
Here is my main.dart:
...
import 'firebase/authentication_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthenticationService>(
create: (_) => AuthenticationService(FirebaseAuth.instance),
),
StreamProvider(
create: (context) =>
context.read<AuthenticationService>().authStateChanges,
initialData: null,
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
backgroundColor: Colors.transparent,
primaryColor: Color(0xff4d629f),
buttonBarTheme:
ButtonBarThemeData(alignment: MainAxisAlignment.center)),
home: AuthenticationWrapper(),
),
);
}
}
class AuthenticationWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
if (firebaseUser != null) {
//If the user is successfully Logged-In.
return HomePage();
} else {
//If the user is not Logged-In.
return LoginPage();
}
}
}
And here is my Authentication_service.dart:
class AuthenticationService {
final FirebaseAuth _firebaseAuth;
UserModel userModel = UserModel.empty();
final userRef = FirebaseFirestore.instance.collection('users');
AuthenticationService(this._firebaseAuth);
Stream<User?> get authStateChanges => _firebaseAuth.authStateChanges();
Future<String> signIn(
{required String email, required String password}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
return "Signed in";
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found')
return "There is no user for that e-mail";
else if (e.code == 'wrong-password')
return "Entered wrong Password";
else
return "Something went wrong: $e";
}
}
...
And there are errors:
The following ProviderNotFoundException was thrown building AuthenticationWrapper(dirty):
Error: Could not find the correct Provider above this AuthenticationWrapper Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that AuthenticationWrapper is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
Unfortunately it didn't help or i just can't implement it in a right way.

#SumerSingh solution worked, i just changed it a bit for my use.
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _initialized = false;
bool _error = false;
void initializeFlutterFire() async {
try {
await Firebase.initializeApp();
setState(() {
_initialized = true;
});
} catch (e) {
setState(() {
_error = true;
});
}
}
#override
void initState() {
initializeFlutterFire();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App',
debugShowCheckedModeBanner: false,
home: Scaffold(
body: _error
? splashScreen()
: !_initialized
? splashScreen()
: SplashScreen()));
}
}
class SplashScreen extends StatefulWidget {
SplashScreen({Key? key}) : super(key: key);
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
final FirebaseAuth _auth = FirebaseAuth.instance;
var currentUser;
AuthenticationService _authService =
new AuthenticationService(FirebaseAuth.instance);
late final UserModel userModel;
bool isAuthinticated = false;
_isUserSignedin() async {
currentUser = _auth.currentUser;
userModel = await _authService.getUserFromDB(uid: _auth.currentUser!.uid);
setState(() {
currentUser != null ? isAuthinticated = true : isAuthinticated = false;
});
}
#override
void initState() {
super.initState();
_isUserSignedin();
startTime();
}
startTime() async {
var _duration = new Duration(seconds: 4);
return new Timer(_duration, navigationPage);
}
Widget userAuthState() {
if (!isAuthinticated)
return LoginPage();
else if (userModel.type == 'Attendant')
return AttendantMainPage();
else
return SeniorMainPage();
}
void navigationPage() {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (BuildContext context) => userAuthState()),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(body: splashScreen());
}
}
Widget splashScreen() {
return Container(
height: double.maxFinite,
width: double.maxFinite,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: [
CircleAvatar(
radius: 80.0, child: Image.asset('assets/logo.png')),
Text("APP NAME",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
CircularProgressIndicator()
]),
);
}
It works well, thank you for help!

Related

Check authentication state using stream not working in Flutter

I'm using GoogleSignIn.
On my splash screen, I'm using StreamBuilder to check whether the user is logged in or not, and based on that data I'm trying to show the Login Screen or Home Screen.
When I uninstall and reinstall the app it shows the Login Screen for the first time. And then after it always shows me the Home Screen even if I logged out from the app.
Below is my code for Splash Screen:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
print(snapshot.hasData);
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: HomeScreen(),
splash: kLogoImage,
duration: 800,
);
} else {
print(snapshot.hasData);
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: LoginScreen(),
splash: kLogoImage,
duration: 800,
);
}
},
);
}
}
Below is my code for Home Screen:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final GoogleSignIn googleSignIn = GoogleSignIn();
User firebaseUser;
signOut() async {
await googleSignIn.signOut();
Navigator.pushReplacementNamed(context, MyRoutes.loginScreen);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text('Hello'),
),
MaterialButton(
child: Text('Log out'),
color: Colors.red,
onPressed: () {
signOut();
})
],
),
);
}
}
Could it be that GoogleSignIn is not the same as Firebase?
Try replacing googleSignIn.signOut() with FirebaseAuth.instance.signOut() on your home screen.
I don't know why it doesn't,t work I think your code is correct but I have another way to do that you can use stream provider instead of stream builder
wrap the stream provider above your material app or cupirtinoapp :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider.value(
value: firebaseauht.inistance.onauthchange,
child: MaterialApp(
home: Wholescreen(),
),
);
}
}
then call the provider in the wholescreen :
class Wholescreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
var provider = Provider.of<User>(context);
if (provider!=null) {
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: HomeScreen(),
splash: kLogoImage,
duration: 800,
} else {
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: LoginScreen(),
splash: kLogoImage,
duration: 800,
);
}
}
}
Change this:
signOut() async {
await googleSignIn.signOut();
Navigator.pushReplacementNamed(context, MyRoutes.loginScreen);
}
Into this:
signOut() async {
await googleSignIn.disconnect();
Navigator.pushReplacementNamed(context, MyRoutes.loginScreen);
}
According to the docs the disconnect method is what revokes the authentication/signs the user out.

FirebaseAuth.instance.userChanges stream does not emit the signed in user after page reload (web)

On Flutter Web, the userChanges stream from a FirebaseAuth instance never emits the signed in user after a page reload. Instead, it only emits null. With the example below, the app gets stuck on the loading page.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/loading',
onGenerateRoute: (settings) {
switch (settings.name) {
case '/error':
return MaterialPageRoute(builder: (_) => ErrorScreen());
case '/loading':
return MaterialPageRoute(builder: (_) => LoadingScreen());
case '/signin':
return MaterialPageRoute(builder: (_) => SignInScreen());
case '/welcome':
return MaterialPageRoute(builder: (_) => WelcomeScreen());
default:
return MaterialPageRoute(
builder: (_) => Center(child: Text('Unknown route')));
}
},
);
}
}
class ErrorScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: Text('An error occurred.'),
),
);
}
}
class LoadingScreen extends StatefulWidget {
#override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
#override
void initState() {
init();
super.initState();
}
init() async {
try {
await Firebase.initializeApp();
if (kDebugMode) {
await FirebaseAuth.instance.useEmulator('http://localhost:1001');
}
final preferences = await SharedPreferences.getInstance();
bool signedIn = preferences.getBool('IS_SIGNED_IN') ?? false;
String landingPath = '/signin';
if (signedIn) {
landingPath = '/welcome';
// Wait for the userChanges to emit a non-null element.
await FirebaseAuth.instance
.userChanges()
.firstWhere((user) => user != null);
}
Navigator.of(context).pushReplacementNamed(landingPath);
} catch (error) {
print(error);
Navigator.of(context).pushReplacementNamed('/error');
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: Text('Loading...'),
),
);
}
}
class SignInScreen extends StatelessWidget {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
TextField(
controller: _emailController,
decoration: InputDecoration(labelText: 'Email address'),
),
TextField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
),
ElevatedButton(
onPressed: () => _submit(context),
child: Text('SIGN IN'),
),
],
),
),
);
}
void _submit(BuildContext context) async {
final email = _emailController.text;
final password = _passwordController.text;
FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password)
.then((credential) async {
final preferences = await SharedPreferences.getInstance();
await preferences.setBool('IS_SIGNED_IN', true);
Navigator.of(context).pushReplacementNamed('/welcome');
});
}
}
class WelcomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Text('Welcome!'),
ElevatedButton(
onPressed: () => _signOut(context),
child: Text('SIGN OUT'),
)
],
),
),
);
}
void _signOut(BuildContext context) {
FirebaseAuth.instance.signOut().then((_) async {
final preferences = await SharedPreferences.getInstance();
await preferences.setBool('IS_SIGNED_IN', false);
Navigator.of(context).pushReplacementNamed('/signin');
});
}
}
If it helps, I'm using version 8.3.0 of the Firebase Javascript libraries in my index.html file.
Am I missing something here?
It turns out the code above is fine. The issue is the emulator. Commenting out these lines of code makes the app behave as expected:
if (kDebugMode) {
FirebaseAuth.instance.useEmulator('http://localhost:1001');
}
I've filed a bug report on FlutterFire's repo about this emulator issue.

snapshot.data.emailVerified not changing the screen in realtime after the email has been verified by user flutter firebase

I want to navigate to Homescreen in real time after the email is verified by the user but after
the user has verified their email it still remains on VerifyEmailScreen.
Till now i am able to navigate to LoginScreen and HomeScreen in realtime only when user signout or signin
I tried to reload to the user after 3 but it is still at EmailVerification Screen
.....please help
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Timer timer;
#override
void initState() {
timer = Timer(Duration(seconds: 3), () {
FirebaseAuth.instance.currentUser.reload();
});
super.initState();
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MultiProvider(
child: MaterialApp(
darkTheme: ThemeData(
brightness: Brightness.dark,
),
themeMode: ThemeMode.dark,
debugShowCheckedModeBanner: false,
home: LandingPage()
),
providers: [
ChangeNotifierProvider(create: (_)=>FirebaseMethods()),
ChangeNotifierProvider(create: (_)=>ProfileScreenUtils())
],
);
}
}
class LandingPage extends StatelessWidget {
//final FirebaseRepository _repository = FirebaseRepository();
#override
Widget build(BuildContext context) {
return StreamBuilder<User>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
// print("USER ID IF USER IS NOT NULL : " + user.uid);
if(snapshot.data.emailVerified)
{
return HomeScreen();
}
else
{
return VerifyEmailScreen();
}
}
else if(snapshot.hasError)
{
return CircularProgressIndicator();
}
else {
//print("USER ID IF THE USER IS NULL : " + user.uid);
return LoginScreen();
}
},
);
}
}
When the user verifies the email, authStateChanges is not called.
You should call FirebaseAuth.instance.currentUser.reload(); to get the refreshed data of the user.
You can do it using a polling request, every minute for example:
Timer timer = Timer(Duration(seconds: 1), () {
FirebaseAuth.instance.currentUser.reload();
});
Or you can add a button, where the user clicks when the email is verified.

How to use keys in stateful widgets?

I have two files, in one file, i have auth class, which handles the authentication.
Error handling is also in auth class, all i have to do is pass the errors to the login screen,
Here is my auth class
Future<FirebaseUser> handleSignInEmail(String email, String password) async {
FirebaseUser user;
var errorMessage;
try {
AuthResult result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
user = result.user;
} catch (error) {
switch (error.code) {
case "ERROR_INVALID_EMAIL":
errorMessage = "Your email address appears to be malformed.";
break;
case "ERROR_WRONG_PASSWORD":
errorMessage = "Your password is wrong.";
break;
case "ERROR_USER_NOT_FOUND":
errorMessage = "User with this email doesn't exist.";
break;
case "ERROR_USER_DISABLED":
errorMessage = "User with this email has been disabled.";
break;
case "ERROR_TOO_MANY_REQUESTS":
errorMessage = "Too many requests. Try again later.";
break;
case "ERROR_OPERATION_NOT_ALLOWED":
errorMessage = "Signing in with Email and Password is not enabled.";
break;
default:
errorMessage = "An undefined Error happened.";
}
}
if (errorMessage != null) {
Body(
key: errorMessage,// the error occurs here :(
);
return Future.error(errorMessage);
}
return user;
}
Here is my body class, into which i have to pass the error.
class Body extends StatefulWidget {
final errorMess;
Body({Key key, this.errorMess}) : super(key: key);
#override
_BodyState createState() => _BodyState();
}
In my Bodystate, i have this function,
geterrorMesage() {
print(widget.errorMess);
}
When i try to call this function, an Exception occurs-
The argument type 'String' can't be assigned to the parameter type 'Key'.
how to resolve this issue?
You can copy paste run full code below
You can pass _key to Body and use _key.currentState to call geterrorMesage
code snippet
GlobalKey _key = GlobalKey();
...
final _BodyState _bodyState = _key.currentState;
_bodyState.geterrorMesage("this is test");
...
Body(
key: _key,
errorMess: "no error",
),
...
geterrorMesage(String errorMess) {
print(errorMess);
setState(() {
_errorMess = errorMess;
});
}
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
GlobalKey _key = GlobalKey();
void _incrementCounter() {
final _BodyState _bodyState = _key.currentState;
_bodyState.geterrorMesage("this is test");
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
Body(
key: _key,
errorMess: "no error",
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class Body extends StatefulWidget {
final errorMess;
Body({Key key, this.errorMess}) : super(key: key);
#override
_BodyState createState() => _BodyState();
}
class _BodyState extends State<Body> {
String _errorMess = "";
geterrorMesage(String errorMess) {
print(errorMess);
setState(() {
_errorMess = errorMess;
});
}
#override
void initState() {
super.initState();
_errorMess = widget.errorMess;
}
#override
Widget build(BuildContext context) {
return Text("${_errorMess}");
}
}

authentification Firebase with Provider flutter

I tried to implements this Authentification here Firebase with Porvider here the issue is I want to save the state of variable loggedIn even I close and reponning the app so I used this code but dont work
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AuthenticationService>(builder: (_, auth, __) {
if (auth.getCurrentUser() != null)
return HomeView();
else
return TermsView();
});
}
}
here method to get current user
Future getCurrentUser() async {
FirebaseUser _user ;
_user = await FirebaseAuth.instance.currentUser();
return _user ;
}
Is there a way to check signing in inside Consumer?
You need to use FirebaseAuth.currentUser to check if user is signed in or not, I am using FutureBuilder inside Consumer for this work.
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ChangeNotifierProvider<Auth>(
create: (_) => Auth(),
child: MaterialApp(home: MyApp()),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<Auth>(
builder: (_, auth, __) {
return FutureBuilder<FirebaseUser>(
future: auth.currentUser,
builder: (context, snapshot) {
if (snapshot.hasData) return HomeScreen();
return LoginScreen();
},
);
},
);
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF4C3E13),
appBar: AppBar(title: Text('Home Screen')),
floatingActionButton: FloatingActionButton.extended(
label: Text('Sign out'),
onPressed: () async {
final auth = Provider.of<Auth>(context, listen: false);
await auth.signOut();
},
),
);
}
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Login Screen')),
body: Center(
child: RaisedButton(
onPressed: () async {
final auth = Provider.of<Auth>(context, listen: false);
await auth.signIn(email: 'user#user.com', password: 'user1234');
},
child: Text('Sign In'),
),
),
);
}
}
class Auth with ChangeNotifier {
final _auth = FirebaseAuth.instance;
Future<void> signOut() async {
await _auth.signOut();
notifyListeners();
}
Future<void> signIn({#required String email, #required String password}) async {
await _auth.signInWithEmailAndPassword(email: email, password: password);
notifyListeners();
}
Future<FirebaseUser> get currentUser async {
return await _auth.currentUser();
}
}

Resources