Hi there 👋
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
.
Initialize app
First of all, we starting by initialized Firebase app.
(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());
}
Define our pages and routes
Create two pages HomePage
and LoginPage
. And add it to onGenerateRoute
of the MyApp
widget.
UnknownPage
for all others pages. 😎
1. HomePage
This page will disconnect our user.
Think to add a simple loader for example while requesting.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.all(32),
children: [
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signOut();
},
child: Text('Sign out'),
)
],
));
}
}
2. LoginPage
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);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.all(32),
children: [
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signInAnonymously();
},
child: Text('Sign In'),
)
],
));
}
}
3. UnknownPage
Just a basic red Container
class UnknownPage extends StatefulWidget {
const UnknownPage({Key? key}) : super(key: key);
@override
_UnknownPageState createState() => _UnknownPageState();
}
class _UnknownPageState extends State<UnknownPage> {
@override
Widget build(BuildContext context) {
return Container(color: Colors.red);
}
}
4. MyApp
Add a stateful widget MyApp
with onGenerateRoute
.
Handle with a switch
the settings.name
route.
And return MaterialPageRoute
with all right pages.
Add also initialRoute
.
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
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(),
);
}
},
);
}
}
Time to be reactive
1. GlobalKey
final _navigatorKey = GlobalKey<NavigatorState>();
This is the key, 🔑
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>();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HelloWorld',
navigatorKey: _navigatorKey, // <===== HERE
onGenerateRoute: (settings) {
// ...
2. InitState
Add the initState
function to listen FirebaseAuth
changes.
late StreamSubscription<User?> _sub;
@override
void initState() {
super.initState();
_sub = FirebaseAuth.instance.authStateChanges().listen((event) {
_navigatorKey.currentState!.pushReplacementNamed(
event != null ? 'home' : 'login',
);
});
}
3. Dispose
⛔️ Don't forget to dispose your last subscription.
@override
void dispose() {
_sub.cancel();
super.dispose();
}
Done ✅
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 {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late StreamSubscription<User?> _sub;
final _navigatorKey = new GlobalKey<NavigatorState>();
@override
void initState() {
super.initState();
_sub = FirebaseAuth.instance.userChanges().listen((event) {
_navigatorKey.currentState!.pushReplacementNamed(
event != null ? 'home' : 'login',
);
});
}
@override
void dispose() {
_sub.cancel();
super.dispose();
}
@override
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);
@override
_UnknownPageState createState() => _UnknownPageState();
}
class _UnknownPageState extends State<UnknownPage> {
@override
Widget build(BuildContext context) {
return Container(color: Colors.red);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
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);
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
padding: EdgeInsets.all(32),
children: [
ElevatedButton(
onPressed: () {
FirebaseAuth.instance.signInAnonymously();
},
child: Text('Sign In'),
)
],
));
}
}
One more thing
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());
Top comments (1)
Thank you