How to use Firebase Cloud Messaging - firebase

I couldn't find any documents about the new version. Versions 7 and 6 have a large number of documents, while 9 is almost nonexistent. Not only me but most people couldn't find it.
I just wanted to send simple notifications to the background. I would be very happy if anyone shared a document about the new version.
Or should I use the old version?

I suppose that you know how to add firebase to your App. If not: https://firebase.google.com/docs/flutter/setup?platform=android
After adding firebase to the App, this is what I do :
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
routes: {
'/': (context) => AppStarter(),
'/message': (context) => NotificationDetails(),
},
),
);
}
class AppStarter extends StatefulWidget{
#override
_AppStarterState createState() => _AppStarterState();
}
class _AppStarterState extends State<AppStarter>
{
FirebaseMessaging messaging = FirebaseMessaging.instance;
Future<void> showMeMyToken()
async {
var myToken = await messaging.getToken();
print("My Token is: " + myToken.toString());
}
#override
void initState() {
super.initState();
showMeMyToken();
FirebaseMessaging.instance.getInitialMessage().then((value) {
if(value != null)
{
Navigator.push(context,
MaterialPageRoute(
builder: (context){return NotificationDetails();},
settings: RouteSettings(arguments: value.data,),
),
);
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
print('Message on Foreground: ${message.notification}');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message)
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {return NotificationDetails();},
settings: RouteSettings(arguments: message.data,)
),
);
});
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Just a Test',
home: AppHome(),
);
}
}
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print("Handling a background message :-): ${message.data}");
//Here you can do what you want with the message :-)
}

I created a sample app showcasing how to implement the notification system with FCM on version 9.
You can refer to this project and if you need more informations, I'll edit this answer !

Related

Cannot Login using Firebase

I'm having issues with logging into my app using Firebase Authentication. I got it to work before, but I can't figure out what changes I've made that ended up breaking the code.
Below are the relevant classes-main for controlling what screens are loaded and LoginScreen to sign the user in. Creating new users work, and I can access MyHomePage() after a new user is created. The StreamBuilder part of the code in main also works since the if(userSnapshot.hasData) of code is reached and executed. The screen however just stays on the LoginScreen. There are no platform exception errors either and the console prints out:
Update The issue was because I implemented signOut() incorrectly. I navigated the app screen to the LoginScreen before auth.FirebaseAuth.instance.signOut(). Since StreamBuilder is used to listen for authStateChanges, that navigation to the LoginScreen isn't necessary.
D/FirebaseAuth(31169): Notifying id token listeners about user ( fK9nC6AFGmew1GLsXgj6FxK1zIg2 ). D/FirebaseAuth(31169): Notifying auth state listeners about user ( fK9nC6AFGmew1GLsXgj6FxK1zIg2 ).
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => MyUser(),
),
ChangeNotifierProvider(
create: (context) => Cities(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TestApp',
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (ctx, userSnapshot) {
if (userSnapshot.hasData) {
return MyHomePage();
}
return LoginScreen();
},
),
),
);
}
}
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _auth = auth.FirebaseAuth.instance;
var message = '';
void _login(User myUser, BuildContext ctx) async {
try {
await _auth.signInWithEmailAndPassword(
email: myUser.userEmail,
password: myUser.userPassword,
);
} on PlatformException catch (err) {
if (err.message != null) {
message = err.message;
}
} catch (err) {
print(err);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(body: InputForm(_login));
}
}
There's no appear to be error in login
Just add LoginScreen as default in Home like this
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TestApp',
home: LoginScreen(),
),
and then in your logic, after you call signInWithEmailAndPassword firebase method use the Navigator method like this
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyHomePage()),
);
For more details read this Flutter navigation
Your code seems to work fine except that you're not doing anything (except assigning the value to the message variable which isn't doing anything here) with the PlatformExceptions when you catch them in the block of code below.
on PlatformException catch (err) {
if (err.message != null) {
message = err.message;
}
}
So even if you have a PlatformException, you will not see anything in the console which means the sign in can fail silently.
Solution:
If you want the final catch block to handle all exceptions, you can use the catch block directly.
try {
await _auth.signInWithEmailAndPassword(
email: myUser.userEmail,
password: myUser.userPassword,
);
} catch (err) {
print(err);
}
If you want to print the PlatformException, you can add a print statement like you did in the catch block:
on PlatformException catch (err) {
print(err);
}

Flutter Web/Firebase - Pressing back in browser bypasses verification process

When my user signs up I direct them to a page to inform them that they need to verify their email before continuing:
Here is my verification screen code:
class VerifyScreen extends StatefulWidget {
#override
_VerifyScreenState createState() => _VerifyScreenState();
}
class _VerifyScreenState extends State<VerifyScreen> {
final auth = FirebaseAuth.instance;
User user;
Timer timer;
#override
void initState() {
user = auth.currentUser;
user.sendEmailVerification();
timer = Timer.periodic(
Duration(seconds: 5),
(timer) {
checkEmailVerified();
},
);
super.initState();
}
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
height: MediaQuery.of(context).size.height * 0.8,
width: MediaQuery.of(context).size.width * 0.8,
child: Text(
"An email has been sent to ${user.email} please verify before proceeding"),
),
),
);
}
Future<void> checkEmailVerified() async {
user = auth.currentUser;
await user.reload();
if (user.emailVerified) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => OurHomePage(),
),
);
timer.cancel();
}
}
}
Problem Statement: When I press the back arrow on my chrome browser:
I get returned to my homepage with the user signed in which I don't want. I would like my user to verify their email before being able to continue. Here's the drawer on my homepage after I press the back button without verifying the email:
I use provider to pass my user object around my app. Here is my main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
Provider(
create: (_) => FirebaseAuthService(),
),
StreamProvider<OurUser>(
create: (context) =>
context.read<FirebaseAuthService>().onAuthStateChanged),
],
child: MaterialApp(theme: OurTheme().buildTheme(), home: OurHomePage()),
),
);
}
I then user Consumer to consume that provider on my Homepage:
class OurHomePage extends StatefulWidget {
#override
_OurHomePageState createState() => _OurHomePageState();
}
class _OurHomePageState extends State<OurHomePage> {
#override
Widget build(BuildContext context) {
return Consumer<OurUser>(
builder: (_, user, __) {
return ChangeNotifierProvider<SignInViewModel>(
create: (_) => SignInViewModel(context.read),
builder: (_, child) {
return Scaffold(appBar: AppBar(title: Text("My Homepage")));
},
);
},
);
}
}
Can anyone help me resolve the issue I'm facing? Thanks in advance.
On homepage check if user is logged in and when is, check if he has verified email. If he has, let him in, otherwise show him some message.

Firebase Messaging packages is not working after Package Update?

_firebaseMessaging.*configure*( onMessage: (Map<String, dynamic> message) async {
print("\n\n on1Message: ${message.toString()}");
Map<String, dynamic> object = json.decode(
message['data']['notification'].toString());
print(
'\n\n Object==${message['data']}\n\n object===$object');
object['work'] = 'updateCount';
Stream<Map<String, dynamic>> stream =
Stream.value(object);
streamController.addStream(stream);
print("\n\n object ---> ${object}");
Error in Console Log----------------------------------------------------------------
722:24: Error: The method 'configure' isn't defined for the class 'FirebaseMessaging'.
'FirebaseMessaging' is from 'package:firebase_messaging/firebase_messaging.dart' ('/E:/flutterSDK/flutter/.pub-cache/hosted/pub.dartlang.org/firebase_messaging-9.1.0/lib/firebase_messaging.dart').
Try correcting the name to the name of an existing method, or defining a method named 'configure'.
_firebaseMessaging.configure
( ^^^^^^^^^
configure() method is not working after the update of the Package of Firebase Cloud Messaging. I tried different solution from the stack overflow but nothing works.
What Should I do in my case.
The new FirebaseMessaging is a little bit different.
Here are two interesting links:
https://firebase.google.com/docs/flutter/setup?platform=android
https://firebase.flutter.dev/docs/messaging/usage/
After adding firebase to the App, this is what I do (NotificationDetails is a class I wrote to show the details of the Notification. You can write you own class.):
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
routes: {
'/': (context) => AppStarter(),
'/message': (context) => NotificationDetails(),
},
),
);
}
class AppStarter extends StatefulWidget{
#override
_AppStarterState createState() => _AppStarterState();
}
class _AppStarterState extends State<AppStarter>
{
FirebaseMessaging messaging = FirebaseMessaging.instance;
Future<void> showMeMyToken()
async {
var myToken = await messaging.getToken();
print("My Token is: " + myToken.toString());
}
#override
void initState() {
super.initState();
showMeMyToken();
FirebaseMessaging.instance.getInitialMessage().then((value) {
if(value != null)
{
Navigator.push(context,
MaterialPageRoute(
builder: (context){return NotificationDetails();},
settings: RouteSettings(arguments: value.data,),
),
);
}
});
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
if (message.notification != null) {
print('Message on Foreground: ${message.notification}');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message)
{
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {return NotificationDetails();},
settings: RouteSettings(arguments: message.data,)
),
);
});
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Just a Test',
home: AppHome(),
);
}
}
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print("Handling a background message :-): ${message.data}");
//Here you can do what you want with the message :-)
}

Persist user Auth Flutter Firebase

I am using Firebase Auth with google sign in Flutter. I am able to sign in however when I close the app(kill it), I have to sign up all over again. So is there a way to persist user authentication till specifically logged out by the user?
Here is my auth class
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class Auth {
FirebaseAuth _firebaseAuth;
FirebaseUser _user;
Auth() {
this._firebaseAuth = FirebaseAuth.instance;
}
Future<bool> isLoggedIn() async {
this._user = await _firebaseAuth.currentUser();
if (this._user == null) {
return false;
}
return true;
}
Future<bool> authenticateWithGoogle() async {
final googleSignIn = GoogleSignIn();
final GoogleSignInAccount googleUser = await googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
this._user = await _firebaseAuth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
if (this._user == null) {
return false;
}
return true;
// do something with signed-in user
}
}
Here is my start page where the auth check is called.
import 'package:flutter/material.dart';
import 'auth.dart';
import 'login_screen.dart';
import 'chat_screen.dart';
class Splash extends StatefulWidget {
#override
_Splash createState() => _Splash();
}
class _Splash extends State<Splash> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(
value: null,
),
),
);
}
#override
void initState() {
super.initState();
_handleStartScreen();
}
Future<void> _handleStartScreen() async {
Auth _auth = Auth();
if (await _auth.isLoggedIn()) {
Navigator.of(context).pushReplacementNamed("/chat");
}
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) => LoginScreen(auth: _auth,)));
}
}
I believe your problem is routing. In my apps I use FirebaseAuth and it works just as you say you wanted to, and I don't persist any login token. However, I don't know why your approach of using a getUser is not working.
Try to adjust your code to use onAuthStateChanged. EDIT: As of 2022, with Flutter 3, I noticed it worked better with userChanges instead.
Basically, on your MaterialApp, create a StreamBuilder listening to _auth.userChanges() and choose your page depending on the Auth status.
I'll copy and paste parts of my app so you can have an idea:
[...]
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> main() async {
FirebaseApp.configure(
name: '...',
options:
Platform.isIOS
? const FirebaseOptions(...)
: const FirebaseOptions(...),
);
[...]
runApp(new MaterialApp(
title: '...',
home: await getLandingPage(),
theme: ThemeData(...),
));
}
Future<Widget> getLandingPage() async {
return StreamBuilder<FirebaseUser>(
stream: _auth.userChanges(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData && (!snapshot.data!.isAnonymous)) {
return HomePage();
}
return AccountLoginPage();
},
);
}
Sorry, it was my mistake. Forgot to put the push login screen in else.
Future<void> _handleStartScreen() async {
Auth _auth = Auth();
if (await _auth.isLoggedIn()) {
Navigator.of(context).pushReplacementNamed("/chat");
}
else {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) => LoginScreen(auth: _auth,)));
}
}
void main() {
FirebaseAuth.instance.authStateChanges().listen((User user) {
if (user == null) {
runApp(MyApp(auth : false);
} else {
runApp(MyApp(auth : false);
}
});
}
class MyApp extends StatefulWidget {
final bool auth;
MyApp({this.auth});
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
return MaterialApp(
......
......
home: widget.auth ? MainScreen() : AuthScreen();
);
You can use shared_preferences to keep alive your session even when you kill the app.
Here is the documentation https://pub.dartlang.org/packages/shared_preferences.
Also I've heard that it's possible to use sqlite to persist the session.
Add this code. It should work fine.
FirebaseAuth auth = FirebaseAuth.instance;
auth.setPersistence(Persistence.SESSION);
You can use my code, You can use userChanges() instead of authStateChanges()
Notifies about changes to any user updates.
This is a superset of both [authStateChanges] and [idTokenChanges]. It provides events on all user changes, such as when credentials are linked, unlinked and when updates to the user profile are made. The purpose of this Stream is for listening to realtime updates to the user state (signed-in, signed-out, different user & token refresh) without manually having to call [reload] and then rehydrating changes to your application.
final Stream<User?> firebaseUserChanges = firebaseAuth.userChanges();
One more simple example:
Future<bool> isUserLoggedIn() async {
final User? user = FirebaseAuth.instance.currentUser;
return user != null;
}
class InitialScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<bool>(
future: isUserLoggedIn(),
builder: (_, snapshot) {
if (snapshot.hasData) {
if (snapshot.data ?? false) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => UnauthScreen()));
} else {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => HomeScreen()));
}
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
I was able to achieve it by checking the firebase instance currentUser value. if null I routed to my Signup page. If not, then I routed to my HomePage. Not sure if there is anything wrong with this implementation (its working well so far) but seems simpler than the StreamBuilder solution posted above.
home: getLandingPage(),
routes: {
(...)
}
Widget getLandingPage() {
if (_auth.currentUser == null) {
return SignupPage();
} else {
return HomePage();
}
}

Firebase Login with Flutter using onAuthStateChanged

Outside of Flutter, when I implement firebase authentication I always use the onAuthStateChanged listener provided by firebase to determine if the user is logged in or not and respond accordingly.
I am trying to do something similar using flutter, but I can find a way to access onAuthStateChanged of Firebase. I am using the firebase_auth, and google_signin Flutter plugins. I am working of example code that is included with the firebase_auth Flutter plugin. Below is the sample code. I can login successfully with google sign in, but the example is too simple, because I want to have an observer/listener to detect the user's signed in/out state.
Is there a way to detect via observer/listener using the firebase_auth/google_signin flutter plugins to determine the status of a user?
Ultimately I want the app to determine if the user is logged in (yes/no). If not then show a login screen, if yes then show my main app page.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = new GoogleSignIn();
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Firebase Auth Demo',
home: new MyHomePage(title: 'Firebase Auth Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<String> _message = new Future<String>.value('');
Future<String> _testSignInAnonymously() async {
final FirebaseUser user = await _auth.signInAnonymously();
assert(user != null);
assert(user == _auth.currentUser);
assert(user.isAnonymous);
assert(!user.isEmailVerified);
assert(await user.getToken() != null);
if (Platform.isIOS) {
// Anonymous auth doesn't show up as a provider on iOS
assert(user.providerData.isEmpty);
} else if (Platform.isAndroid) {
// Anonymous auth does show up as a provider on Android
assert(user.providerData.length == 1);
assert(user.providerData[0].providerId == 'firebase');
assert(user.providerData[0].uid != null);
assert(user.providerData[0].displayName == null);
assert(user.providerData[0].photoUrl == null);
assert(user.providerData[0].email == null);
}
return 'signInAnonymously succeeded: $user';
}
Future<String> _testSignInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final FirebaseUser user = await _auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getToken() != null);
return 'signInWithGoogle succeeded: $user';
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new MaterialButton(
child: const Text('Test signInAnonymously'),
onPressed: () {
setState(() {
_message = _testSignInAnonymously();
});
}),
new MaterialButton(
child: const Text('Test signInWithGoogle'),
onPressed: () {
setState(() {
_message = _testSignInWithGoogle();
});
}),
new FutureBuilder<String>(
future: _message,
builder: (_, AsyncSnapshot<String> snapshot) {
return new Text(snapshot.data ?? '',
style: const TextStyle(
color: const Color.fromARGB(255, 0, 155, 0)));
}),
],
),
);
}
}
Here are links to the flutter packages in question:
https://github.com/flutter/plugins/tree/master/packages/firebase_auth
https://github.com/flutter/plugins/tree/master/packages/google_sign_in
I know this question is pretty old, but here is the answer if anybody is still looking for it.
Firebase returns a Stream of FirebaseUser with it's onAuthStateChanged function. There are many ways to listen to the user's authentication state change. This is how I do it:
Solution 1
I return a StreamBuilder to my App's home page, and the StreamBuilder returns specific pages based on the auth status of the user.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Your App Name',
home: _getLandingPage()
);
}
Widget _getLandingPage() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.providerData.length == 1) { // logged in using email and password
return snapshot.data.isEmailVerified
? MainPage()
: VerifyEmailPage(user: snapshot.data);
} else { // logged in using other providers
return MainPage();
}
} else {
return LoginPage();
}
},
);
}
Solution 2
You can create a listener in your app's initState() function as well. Make sure the firebase app has been initialized before registering the listener.
#override
void initState() {
super.initState();
FirebaseAuth.instance.authStateChanges().listen((firebaseUser) {
// do whatever you want based on the firebaseUser state
});
}
Solution 3 (Update May 2021)
A simple approach with null-safety without using the provider package:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
/// State is persistent and not rebuilt, therefore [Future] is only created once.
/// If [StatelessWidget] is used, in the event where [App] is rebuilt, that
/// would re-initialize FlutterFire and makes our app re-enter the
/// loading state, which is undesired.
class _AppState extends State<App> {
final Future<FirebaseApp> _initFirebaseSdk = Firebase.initializeApp();
final _navigatorKey = new GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: _navigatorKey,
theme: theme(),
home: FutureBuilder(
future: _initFirebaseSdk,
builder: (_, snapshot) {
if (snapshot.hasError) return ErrorScreen();
if (snapshot.connectionState == ConnectionState.done) {
// Assign listener after the SDK is initialized successfully
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null)
_navigatorKey.currentState!
.pushReplacementNamed(LoginScreen.routeName);
else
_navigatorKey.currentState!
.pushReplacementNamed(HomeScreen.routeName);
});
}
return LoadingScreen();
}),
routes: routes,
);
}
}
This approach guarantees that you only use Firebase authentication FirebaseAuth.instance.authStateChanges().listen() after the SDK completes initialization. The auth change listener will be first invoked on app launch and then automatically called again after logout and login.
.pushReplacementNamed() will move to a new screen without back (no back icon on the app bar)
Null safe code (without 3rd party packages)
Screenshot:
To check if the user is signed in from anywhere in the app, use
bool signedIn = Auth.instance.isSignedIn;
To sign in, use
await Auth.instance.signIn(email: 'email', password: 'password');
To sign out, use
await Auth.instance.signOut();
Full Code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(
home: StreamBuilder<User?>(
stream: Auth.instance.authStateChange(),
builder: (_, snapshot) {
final isSignedIn = snapshot.data != null;
return isSignedIn ? HomePage() : LoginPage();
},
),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Center(
child: ElevatedButton(
onPressed: () => Auth.instance.signOut(),
child: Text('Sign out'),
),
),
);
}
}
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Auth.instance.signIn(email: 'test#test.com', password: 'test1234'),
child: Text('Sign in'),
),
),
);
}
}
class Auth {
static final instance = Auth._();
Auth._();
final FirebaseAuth _auth = FirebaseAuth.instance;
bool get isSignedIn => _auth.currentUser != null;
Stream<User?> authStateChange() => _auth.authStateChanges();
Future<void> signIn({required String email, required String password}) => _auth.signInWithEmailAndPassword(email: email, password: password);
Future<void> signOut() => _auth.signOut();
}
Same code using provider package:
Check this answer:
You can create a stream as a getter for the onAuthStateChanged inside an AuthService class. To help you manage the state, you can use the Provider package. The AuthService class will extend the ChangeNotifier class.
class AuthService extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = new GoogleSignIn();
// create a getter stream
Stream<FirebaseUser> get onAuthStateChanged => _auth.onAuthStateChanged;
//Sign in async functions here ..
}
Wrap your MaterialApp with ChangeNotifierProvider and return an instance of the AuthService class in create method like so:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => AuthService(),
child: new MaterialApp(
title: 'Firebase Auth Demo',
home: Landing(),
),
);
}
}
Now create landing page as a stateless widget. Use Provider.of(context) and a stream builder to listen to the auth changes and render the login page or home page as appropriate.
class Landing extends StatelessWidget {
#override
Widget build(BuildContext context) {
AuthService auth = Provider.of<AuthService>(context);
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
FirebaseUser user = snapshot.data;
if (user == null) {
return LogIn();
}
return Home();
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
}
You can read more about state management with provider from the official flutter documentation. Follow this link: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
The Firebase for Flutter Codelab has a much more in-depth example using Google sign in and Firebase auth.
After the final step you end up with this _ensureLoggedIn function that is used to check whether the user is signed in and if not, initiate a sign in flow.
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = googleSignIn.currentUser;
if (user == null)
user = await googleSignIn.signInSilently();
if (user == null) {
user = await googleSignIn.signIn();
analytics.logLogin();
}
if (auth.currentUser == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
}
You could modify this to check these things when your app starts up and conditionally show different views to pre-auth and post-auth users with something like:
final auth = FirebaseAuth.instance;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'MyApp',
home: (_checkLogin() == true ? new PostAuthScaffold() : new PreAuthScaffold())
);
}
}
bool _checkLogin() {
GoogleSignInAccount user = googleSignIn.currentUser;
return !(user == null && auth.currentUser == null);
}
Null safe code (using provider):
Full Code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(
home: ChangeNotifierProvider(
create: (_) => AuthModel(),
child: Consumer<AuthModel>(
builder: (_, model, __) => model.isSignedIn ? HomePage() : LoginPage(),
),
),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: Center(
child: ElevatedButton(
onPressed: () async {
final model = context.read<AuthModel>();
await model.signOut();
},
child: Text('Sign out'),
),
),
);
}
}
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () async {
final model = context.read<AuthModel>();
await model.signIn(email: 'test#test.com', password: 'test1234');
},
child: Text('Sign in'),
),
),
);
}
}
class AuthModel extends ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
bool get isSignedIn => _auth.currentUser != null;
Future<void> signIn({required String email, required String password}) async {
await _auth.signInWithEmailAndPassword(email: email, password: password);
notifyListeners();
}
Future<void> signOut() async {
await _auth.signOut();
notifyListeners();
}
}
I had the same query I used Shared preference to get the auth state changed information I also have built a project using Shared Prefrences with Firebase and flutter. Iif you wish to know you can read the blog written on the same by me :
Implementing FirebaseAuthStateListener using Shared Prefrences in Flutter.” https://medium.com/#vaibhavminiyar/implementing-firebaseauthstatelistener-using-shared-prefrences-in-flutter-b42e12f81eb2
since the last update of FlutterFire you need to do it like this
FirebaseAuth.instance.authStateChanges().listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
}
);
Firebase Auth enables you to subscribe in realtime to this state via a
Stream. Once called, the stream provides an immediate event of the
users current authentication state, and then provides subsequent
events whenever the authentication state changes.
To subscribe to these changes, call the authStateChanges() method on
your FirebaseAuth instance.
The stream returns a User class if the user is signed in, or null
if they are not. You can read more about managing your users below.
More information

Resources