[Flutter] Firebase authentication : Dynamic routing by AuthStateChanges πŸ”₯

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

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

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

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

3. UnknownPage

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

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

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

  
  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;

  
  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.


  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 {
  
  _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'),
        )
      ],
    ));
  }
}

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

See you soon 🐼

23