29
[Flutter] Firebase authentication : Dynamic routing by AuthStateChanges π₯
Today, dynamic routing with Firebase by AuthStateChanges.
What we want ? We want routing on
LoginPage
or HomePage
automatically when user state changed. If user is connected and suddenly token expired (or is disconnected), we want redirect to
LoginPage
.First of all, we starting by initialized Firebase app.
(In this example, no catch, but think to catch errors).
(In this example, no catch, but think to catch errors).
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// TODO: Handle error for Firebase.initializeApp
await Firebase.initializeApp();
runApp(MyApp());
}
Create two pages
HomePage
and LoginPage
. And add it to onGenerateRoute
of the MyApp
widget.UnknownPage
for all others pages. πThis page will disconnect our user.
Think to add a simple loader for example while requesting.
Think to add a simple loader for example while requesting.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.all(32),
children: [
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signOut();
},
child: Text('Sign out'),
)
],
));
}
}
This page will connect our user. We'll use
signInAnonymously
for the example. But use what you need ! πclass LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.all(32),
children: [
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signInAnonymously();
},
child: Text('Sign In'),
)
],
));
}
}
Just a basic red Container
class UnknownPage extends StatefulWidget {
const UnknownPage({Key? key}) : super(key: key);
_UnknownPageState createState() => _UnknownPageState();
}
class _UnknownPageState extends State<UnknownPage> {
Widget build(BuildContext context) {
return Container(color: Colors.red);
}
}
Add a stateful widget
MyApp
with onGenerateRoute
.Handle with a
And return
switch
the settings.name
route.And return
MaterialPageRoute
with all right pages.Add also
initialRoute
.class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget build(BuildContext context) {
return MaterialApp(
title: 'HelloWorld',
// ----- Check if user exists => First page app
initialRoute:
FirebaseAuth.instance.currentUser == null ? 'login' : 'home',
onGenerateRoute: (settings) {
switch (settings.name) {
case 'home':
return MaterialPageRoute(
settings: settings,
builder: (_) => HomePage(),
);
case 'login':
return MaterialPageRoute(
settings: settings,
builder: (_) => LoginPage(),
);
default:
return MaterialPageRoute(
settings: settings,
builder: (_) => UnknownPage(),
);
}
},
);
}
}
final _navigatorKey = GlobalKey<NavigatorState>();
This is the key, π
All we want, all we need, is this one.
All we want, all we need, is this one.
With this
GlobalKey
, we have the control to navigate in our app.class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
Widget build(BuildContext context) {
return MaterialApp(
title: 'HelloWorld',
navigatorKey: _navigatorKey, // <===== HERE
onGenerateRoute: (settings) {
// ...
Add the
initState
function to listen FirebaseAuth
changes.late StreamSubscription<User?> _sub;
void initState() {
super.initState();
_sub = FirebaseAuth.instance.authStateChanges().listen((event) {
_navigatorKey.currentState!.pushReplacementNamed(
event != null ? 'home' : 'login',
);
});
}
βοΈ Don't forget to dispose your last subscription.
void dispose() {
_sub.cancel();
super.dispose();
}
Now all code available here :
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late StreamSubscription<User?> _sub;
final _navigatorKey = new GlobalKey<NavigatorState>();
void initState() {
super.initState();
_sub = FirebaseAuth.instance.userChanges().listen((event) {
_navigatorKey.currentState!.pushReplacementNamed(
event != null ? 'home' : 'login',
);
});
}
void dispose() {
_sub.cancel();
super.dispose();
}
Widget build(BuildContext context) {
return MaterialApp(
title: 'HelloWorld',
navigatorKey: _navigatorKey,
initialRoute:
FirebaseAuth.instance.currentUser == null ? 'login' : 'home',
onGenerateRoute: (settings) {
switch (settings.name) {
case 'home':
return MaterialPageRoute(
settings: settings,
builder: (_) => HomePage(),
);
case 'login':
return MaterialPageRoute(
settings: settings,
builder: (_) => LoginPage(),
);
default:
return MaterialPageRoute(
settings: settings,
builder: (_) => UnknownPage(),
);
}
},
);
}
}
class UnknownPage extends StatefulWidget {
const UnknownPage({Key? key}) : super(key: key);
_UnknownPageState createState() => _UnknownPageState();
}
class _UnknownPageState extends State<UnknownPage> {
Widget build(BuildContext context) {
return Container(color: Colors.red);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.all(32),
children: [
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signOut();
},
child: Text('Sign Out'),
)
],
));
}
}
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.all(32),
children: [
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signInAnonymously();
},
child: Text('Sign In'),
)
],
));
}
}
There is many level of details for auth changes.
/// Notifies about changes to the user's sign-in state (such as sign-in or
/// sign-out).
Stream<User?> authStateChanges() =>
_pipeStreamChanges(_delegate.authStateChanges());
/// Notifies about changes to the user's sign-in state (such as sign-in or
/// sign-out) and also token refresh events.
Stream<User?> idTokenChanges() =>
_pipeStreamChanges(_delegate.idTokenChanges());
/// 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.
Stream<User?> userChanges() => _pipeStreamChanges(_delegate.userChanges());
29