🫥
アプリ起動時にログイン画面が表示される?
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