[Flutter] Firebase authentication : Dynamic routing by AuthStateChanges 🔥
Tags: flutter, firebase, dart
Published: 2021-07-07 11:59:44

# 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)._

```dart
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.<br>__Think to add a simple loader for example while requesting.__

```dart
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 ! 👌__

```dart
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__

```dart
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.<br>
And return `MaterialPageRoute` with all right pages.

Add also `initialRoute`.

```dart
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<NavigatorState>

```dart
final _navigatorKey = GlobalKey<NavigatorState>();
```

__This is the key, 🔑__<br>
All we want, all we need, is this one. 

With this `GlobalKey`, we have the control to navigate in our app.

```dart
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.

```dart
  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.

```dart
  @override
  void dispose() {
    _sub.cancel();
    super.dispose();
  }
```

# Done ✅

Now all code available here :


```dart
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.

```dart
  /// 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 🐼__