🫥

アプリ起動時にログイン画面が表示される?

2024/08/01に公開

Tips💡

仕事で、GoRouterを使ってリダイレクトの機能を実装しているのですが、アプリ起動時に、一瞬だけログイン画面が表示されてしまう問題がありました。

SwiftUIとFirebaseで認証機能を実装していたときも同じような課題がありました。

🤔何が原因なのか?

起動時に一瞬だけログイン画面が表示されるのは、アプリの初期化や認証状態の確認が完了する前に、GoRouterが初期ルートとしてログイン画面を表示しているためです。この問題を解決するためには、アプリの初期化や認証状態の確認が完了するまで、ローディング画面を表示するようにします。

ローディングを表示するUIを作成。標準のインジケーターでも良いしアニメーションのパッケージを使っても良いです。

import 'package:flutter/material.dart';
import 'package:workstream_prod/presentation/component/indicator_component.dart';

class LoadingScreen extends StatelessWidget {
  const LoadingScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndicatorComponent(),
    );
  }
}

ルートにパスを追加する。staticな定数で私はクラスを定義してます。

// go routerで使用するpathを定義する。継承しないクラスなので、final key wordをつける
final class RouterPath {
  static const String LOADING = '/loading';
  static const String SIGN_IN = '/sign_in';
  static const String SIGNUP = 'signup';
}

final isLoading = authState.isLoadingという変数を定義します。内部実装を見てみたのですが、isLoadingというゲッターがありこのメソッドを使うと、非同期処理をおこなうときに、ローディングをおこなうことができるようです。

if文で分岐処理を追加して、ローディング処理をreturnで返すようにすると、一瞬だけ、ログイン画面が表示される問題が解決されたようです。

if (isLoading) {
        return RouterPath.LOADING;
      }
解決策
final GlobalKey<NavigatorState> _rootNavigatorKey =
    GlobalKey<NavigatorState>(debugLabel: 'root');

final GlobalKey<NavigatorState> _sectionANavigatorKey =
    GlobalKey<NavigatorState>(debugLabel: 'sectionANav');


GoRouter router(RouterRef ref) {
  final authState = ref.watch(authStateProvider);
  final isLoading = authState.isLoading;

  return GoRouter(
    // firebase analyticsを使用するためのobserver
    // observerを使用することで、画面遷移のイベントをfirebase analyticsに送信することができる
    observers: [
      FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance)
    ],
    navigatorKey: _rootNavigatorKey,
    initialLocation: RouterPath.SIGN_IN,
    redirect: (context, state) async {
      // アプリの初期化や認証状態の確認が完了するまで、ローディング画面を表示
      if (isLoading) {
        return RouterPath.LOADING;
      }

      // ログインしているかどうかを確認するプロパティ
      final loggedIn = authState.value?.session?.user != null;
      // JWTトークンがExpiredしているか確認するプロパティ
      final isExpired = authState.value?.session?.isExpired;

      switch (state.matchedLocation) {
        case RouterPath.SIGN_IN:
          if (loggedIn && !(isExpired ?? true)) {
            return RouterPath.HOME;
          } else {
            return RouterPath.SIGN_IN;
          }
        default:
          return null;
      }
    },
    routes: [
      /// BottomNavigationBarのルート
      StatefulShellRoute.indexedStack(
        builder: (
          context,
          state,
          navigationShell,
        ) {
          return ScaffoldWithNavBar(navigationShell: navigationShell);
        },
        branches: <StatefulShellBranch>[
          StatefulShellBranch(
            navigatorKey: _sectionANavigatorKey,
            routes: <RouteBase>[
              GoRoute(
                path: RouterPath.HOME,
                builder: (context, state) => const HomePage(),
                routes: [
                  GoRoute(
                      path: RouterPath.APP_INFO,
                      name: RouterPath.APP_INFO,
                      builder: (context, state) {
                        return const AppInfo();
                      }),
                  GoRoute(
                    path: RouterPath.MY_PAGE,
                    name: RouterPath.MY_PAGE,
                    builder: (context, state) {
                      return const MyPage();
                    },
                    routes: [
                      GoRoute(
                        path: RouterPath.INVITATION,
                        name: RouterPath.INVITATION,
                        builder: (context, state) {
                          return const InvitationPage();
                        },
                        routes: [
                          GoRoute(
                            path: RouterPath.INVITATION_CODE,
                            name: RouterPath.INVITATION_CODE,
                            builder: (context, state) {
                              return const InvitationCodePage();
                            },
                          ),
                          GoRoute(
                            path: RouterPath.CREATE_GOAL,
                            name: RouterPath.CREATE_GOAL,
                            builder: (context, state) {
                              return const CreateGoalPage();
                            },
                          ),
                        ],
                      ),
                      GoRoute(
                        path: RouterPath.INPUT_MY_PAGE,
                        name: RouterPath.INPUT_MY_PAGE,
                        builder: (context, state) {
                          return const InputMyPage();
                        },
                      ),
                    ],
                  ),
                ],
              ),
            ],
          ),
          StatefulShellBranch(
            routes: <RouteBase>[
              GoRoute(
                path: RouterPath.MY_Task,
                name: RouterPath.MY_Task,
                builder: (context, state) => const MyTaskPage(),
              ),
            ],
          ),
          StatefulShellBranch(
            routes: <RouteBase>[
              GoRoute(
                path: RouterPath.NOTIFICATION,
                builder: (context, state) => const NotificationPage(),
              ),
            ],
          ),
        ],
      ),

      /// 通常のルート
      GoRoute(
        path: RouterPath.SIGN_IN,
        name: RouterPath.SIGN_IN,
        builder: (context, state) => const SignInPage(),
        routes: [
          // 新規ページへのルーティング
          GoRoute(
            path: RouterPath.SIGNUP,
            name: RouterPath.SIGNUP,
            builder: (context, state) {
              return const SignUpPage();
            },
          ),
          // パスワードリセットページへのルーティング
          GoRoute(
            path: RouterPath.FORGOT,
            name: RouterPath.FORGOT,
            builder: (context, state) {
              return const ForgotPage();
            },
          ),
        ],
      ),
    ],
    // 404ページを指定
    errorPageBuilder: (context, state) {
      return const MaterialPage(
        child: Scaffold(
          body: Center(
            child: Text('Page not found'),
          ),
        ),
      );
    },
  );
}

まとめ

今回は、GoRouterk, Riverpod, Supabase Authを使用したときにハマったエラーの解決策について記事を書いてみました。

Discussion