| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:go_router/go_router.dart'; |
| |
| // This scenario demonstrates how to use redirect to handle a asynchronous |
| // sign-in flow. |
| // |
| // The `StreamAuth` is a mock of google_sign_in. This example wraps it with an |
| // InheritedNotifier, StreamAuthScope, and relies on |
| // `dependOnInheritedWidgetOfExactType` to create a dependency between the |
| // notifier and go_router's parsing pipeline. When StreamAuth broadcasts new |
| // event, the dependency will cause the go_router to reparse the current url |
| // which will also trigger the redirect. |
| |
| void main() => runApp(StreamAuthScope(child: App())); |
| |
| /// The main app. |
| class App extends StatelessWidget { |
| /// Creates an [App]. |
| App({super.key}); |
| |
| /// The title of the app. |
| static const String title = 'GoRouter Example: Redirection'; |
| |
| // add the login info into the tree as app state that can change over time |
| @override |
| Widget build(BuildContext context) => MaterialApp.router( |
| routerConfig: _router, |
| title: title, |
| debugShowCheckedModeBanner: false, |
| ); |
| |
| late final GoRouter _router = GoRouter( |
| routes: <GoRoute>[ |
| GoRoute( |
| path: '/', |
| builder: (BuildContext context, GoRouterState state) => |
| const HomeScreen(), |
| ), |
| GoRoute( |
| path: '/login', |
| builder: (BuildContext context, GoRouterState state) => |
| const LoginScreen(), |
| ), |
| ], |
| |
| // redirect to the login page if the user is not logged in |
| redirect: (BuildContext context, GoRouterState state) async { |
| // Using `of` method creates a dependency of StreamAuthScope. It will |
| // cause go_router to reparse current route if StreamAuth has new sign-in |
| // information. |
| final bool loggedIn = await StreamAuthScope.of(context).isSignedIn(); |
| final bool loggingIn = state.matchedLocation == '/login'; |
| if (!loggedIn) { |
| return '/login'; |
| } |
| |
| // if the user is logged in but still on the login page, send them to |
| // the home page |
| if (loggingIn) { |
| return '/'; |
| } |
| |
| // no need to redirect at all |
| return null; |
| }, |
| ); |
| } |
| |
| /// The login screen. |
| class LoginScreen extends StatefulWidget { |
| /// Creates a [LoginScreen]. |
| const LoginScreen({super.key}); |
| |
| @override |
| State<LoginScreen> createState() => _LoginScreenState(); |
| } |
| |
| class _LoginScreenState extends State<LoginScreen> |
| with TickerProviderStateMixin { |
| bool loggingIn = false; |
| late final AnimationController controller; |
| |
| @override |
| void initState() { |
| super.initState(); |
| controller = AnimationController( |
| vsync: this, |
| duration: const Duration(seconds: 1), |
| )..addListener(() { |
| setState(() {}); |
| }); |
| controller.repeat(); |
| } |
| |
| @override |
| void dispose() { |
| controller.dispose(); |
| super.dispose(); |
| } |
| |
| @override |
| Widget build(BuildContext context) => Scaffold( |
| appBar: AppBar(title: const Text(App.title)), |
| body: Center( |
| child: Column( |
| mainAxisAlignment: MainAxisAlignment.center, |
| children: <Widget>[ |
| if (loggingIn) CircularProgressIndicator(value: controller.value), |
| if (!loggingIn) |
| ElevatedButton( |
| onPressed: () { |
| StreamAuthScope.of(context).signIn('test-user'); |
| setState(() { |
| loggingIn = true; |
| }); |
| }, |
| child: const Text('Login'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| /// The home screen. |
| class HomeScreen extends StatelessWidget { |
| /// Creates a [HomeScreen]. |
| const HomeScreen({super.key}); |
| |
| @override |
| Widget build(BuildContext context) { |
| final StreamAuth info = StreamAuthScope.of(context); |
| |
| return Scaffold( |
| appBar: AppBar( |
| title: const Text(App.title), |
| actions: <Widget>[ |
| IconButton( |
| onPressed: () => info.signOut(), |
| tooltip: 'Logout: ${info.currentUser}', |
| icon: const Icon(Icons.logout), |
| ) |
| ], |
| ), |
| body: const Center( |
| child: Text('HomeScreen'), |
| ), |
| ); |
| } |
| } |
| |
| /// A scope that provides [StreamAuth] for the subtree. |
| class StreamAuthScope extends InheritedNotifier<StreamAuthNotifier> { |
| /// Creates a [StreamAuthScope] sign in scope. |
| StreamAuthScope({ |
| super.key, |
| required super.child, |
| }) : super( |
| notifier: StreamAuthNotifier(), |
| ); |
| |
| /// Gets the [StreamAuth]. |
| static StreamAuth of(BuildContext context) { |
| return context |
| .dependOnInheritedWidgetOfExactType<StreamAuthScope>()! |
| .notifier! |
| .streamAuth; |
| } |
| } |
| |
| /// A class that converts [StreamAuth] into a [ChangeNotifier]. |
| class StreamAuthNotifier extends ChangeNotifier { |
| /// Creates a [StreamAuthNotifier]. |
| StreamAuthNotifier() : streamAuth = StreamAuth() { |
| streamAuth.onCurrentUserChanged.listen((String? string) { |
| notifyListeners(); |
| }); |
| } |
| |
| /// The stream auth client. |
| final StreamAuth streamAuth; |
| } |
| |
| /// An asynchronous log in services mock with stream similar to google_sign_in. |
| /// |
| /// This class adds an artificial delay of 3 second when logging in an user, and |
| /// will automatically clear the login session after [refreshInterval]. |
| class StreamAuth { |
| /// Creates an [StreamAuth] that clear the current user session in |
| /// [refeshInterval] second. |
| StreamAuth({this.refreshInterval = 20}) |
| : _userStreamController = StreamController<String?>.broadcast() { |
| _userStreamController.stream.listen((String? currentUser) { |
| _currentUser = currentUser; |
| }); |
| } |
| |
| /// The current user. |
| String? get currentUser => _currentUser; |
| String? _currentUser; |
| |
| /// Checks whether current user is signed in with an artificial delay to mimic |
| /// async operation. |
| Future<bool> isSignedIn() async { |
| await Future<void>.delayed(const Duration(seconds: 1)); |
| return _currentUser != null; |
| } |
| |
| /// A stream that notifies when current user has changed. |
| Stream<String?> get onCurrentUserChanged => _userStreamController.stream; |
| final StreamController<String?> _userStreamController; |
| |
| /// The interval that automatically signs out the user. |
| final int refreshInterval; |
| |
| Timer? _timer; |
| Timer _createRefreshTimer() { |
| return Timer(Duration(seconds: refreshInterval), () { |
| _userStreamController.add(null); |
| _timer = null; |
| }); |
| } |
| |
| /// Signs in a user with an artificial delay to mimic async operation. |
| Future<void> signIn(String newUserName) async { |
| await Future<void>.delayed(const Duration(seconds: 3)); |
| _userStreamController.add(newUserName); |
| _timer?.cancel(); |
| _timer = _createRefreshTimer(); |
| } |
| |
| /// Signs out the current user. |
| Future<void> signOut() async { |
| _timer?.cancel(); |
| _timer = null; |
| _userStreamController.add(null); |
| } |
| } |