Flutter & Supabase can't redirect user back to splash screen after Google Auth w/ deeplink & go_router

636 Views Asked by At

Hi I have been trying to solve this problem of mine where after the user authenticates and is redirected back to my app through deeplink they are redirected back to whatever screen they were on (signin page in this case) instead of going into the matched path route in go_router. I want to redirect to my initial route of "/" and my deeplink redirect url i receive after authentication is "io.supabase.launchlabnus://login-callback/#access_token=eyJhbGciOiJ...." it seems that my go router is not matching my initial route with the returned deeplink this could be from the fragment (#) returned by the supabase api how should i approach this problem?

app-router.js

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:launchlab/src/domain/user/models/accomplishment_entity.dart';
import 'package:launchlab/src/domain/user/models/experience_entity.dart';
import 'package:launchlab/src/presentation/authentication/screens/signin_page.dart';
import 'package:launchlab/src/presentation/chat/screens/chat_page.dart';
import 'package:launchlab/src/presentation/common/screens/protected_screen_page.dart';
import 'package:launchlab/src/presentation/common/screens/splash_screen_page.dart';
import 'package:launchlab/src/presentation/common/screens/unprotected_screen_page.dart';
import 'package:launchlab/src/presentation/common/widgets/scaffold_with_bottom_nav.dart';
import 'package:launchlab/src/presentation/search/screens/discover_page.dart';
import 'package:launchlab/src/presentation/team/screens/team_home_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_add_accomplishment_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_add_experience_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_edit_accomplishment_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_edit_experience_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_finish_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_step1_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_step2_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_step3_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_step4_page.dart';
import 'package:launchlab/src/presentation/user/screens/onboarding_welcome_page.dart';
import 'package:launchlab/src/presentation/user/screens/profile_page.dart';
import 'package:launchlab/src/presentation/user/widgets/onboarding_container.dart';
import 'package:launchlab/src/presentation/user/widgets/onboarding_steps_layout.dart';
import 'package:launchlab/src/presentation/team/screens/create_team_page.dart';
import 'package:launchlab/src/presentation/team/screens/edit_team_page.dart';
import 'package:launchlab/src/presentation/search/screens/external_team_page.dart';
import 'package:launchlab/src/presentation/team/screens/team_page.dart';

/// A file to configure the routing of the application

// private navigator keys
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _protectedShellNavigatorKey = GlobalKey<NavigatorState>();
final _unprotectedShellNavigatorKey = GlobalKey<NavigatorState>();

final _mainShellKey = GlobalKey<NavigatorState>();
final _onboardingShellKey = GlobalKey<NavigatorState>();
final _nestedOnboardingShellKey = GlobalKey<NavigatorState>();

final GoRouter appRouter = GoRouter(
  debugLogDiagnostics: true,
  initialLocation: "/",
  navigatorKey: _rootNavigatorKey,
  routes: [
    GoRoute(
      parentNavigatorKey: _rootNavigatorKey,
      path: "/",
      builder: (context, state) => const SplashScreenPage(),
    ),
    ShellRoute(
      navigatorKey: _unprotectedShellNavigatorKey,
      builder: (context, state, child) {
        return UnprotectedScreenPage(child: child);
      },
      routes: [
        GoRoute(
          parentNavigatorKey: _unprotectedShellNavigatorKey,
          path: "/signin",
          pageBuilder: (context, state) =>
              const NoTransitionPage(child: SigninPage()),
        ),
      ],
    ),
    ShellRoute(
      navigatorKey: _protectedShellNavigatorKey,
      builder: (context, state, child) {
        return ProtectedScreenPage(child: child);
      },
      routes: [
        ShellRoute(
          navigatorKey: _onboardingShellKey,
          builder: (context, state, child) => OnboardingContainer(child: child),
          routes: [
            GoRoute(
              parentNavigatorKey: _onboardingShellKey,
              path: "/onboard",
              pageBuilder: (context, state) {
                return const NoTransitionPage(
                  child: OnboardingWelcomePage(),
                );
              },
            ),
            ShellRoute(
              navigatorKey: _nestedOnboardingShellKey,
              builder: (context, state, child) =>
                  OnboardingStepsLayout(child: child),
              routes: [
                GoRoute(
                  path: "/onboard/step-1",
                  builder: (context, state) {
                    return const OnboardingStep1Page();
                  },
                ),
                GoRoute(
                  path: "/onboard/step-2",
                  builder: (context, state) {
                    return const OnboardingStep2Page();
                  },
                ),
                GoRoute(
                  parentNavigatorKey: _nestedOnboardingShellKey,
                  path: "/onboard/step-3",
                  builder: (context, state) {
                    return const OnboardingStep3Page();
                  },
                ),
                GoRoute(
                  path: "/onboard/step-4",
                  builder: (context, state) {
                    return const OnboardingStep4Page();
                  },
                ),
              ],
            ),
            GoRoute(
              parentNavigatorKey: _onboardingShellKey,
              path: "/onboard-success",
              builder: (context, state) => const OnboardingFinishPage(),
            ),
            GoRoute(
              parentNavigatorKey: _onboardingShellKey,
              path: "/onboard-add-experience",
              builder: (context, state) => const OnboardingAddExperiencePage(),
            ),
            GoRoute(
              parentNavigatorKey: _onboardingShellKey,
              path: "/onboard-edit-experience",
              builder: (context, state) => OnboardingEditExperiencePage(
                experience: state.extra as ExperienceEntity,
              ),
            ),
            GoRoute(
              parentNavigatorKey: _onboardingShellKey,
              path: "/onboard-add-accomplishment",
              builder: (context, state) =>
                  const OnboardingAddAccomplishmentPage(),
            ),
            GoRoute(
              parentNavigatorKey: _onboardingShellKey,
              path: "/onboard-edit-accomplishment",
              builder: (context, state) => OnboardingEditAccomplishmentPage(
                accomplishment: state.extra as AccomplishmentEntity,
              ),
            ),
          ],
        ),
        ShellRoute(
          navigatorKey: _mainShellKey,
          builder: (context, state, child) =>
              ScaffoldWithBottomNav(child: child),
          routes: [
            GoRoute(
                path: "/team-home",
                pageBuilder: (context, state) {
                  return const NoTransitionPage(child: TeamHomePage());
                }),
            GoRoute(
              path: "/chats",
              pageBuilder: (context, state) =>
                  const NoTransitionPage(child: ChatPage()),
            ),
            GoRoute(
              path: "/discover",
              pageBuilder: (context, state) =>
                  const NoTransitionPage(child: DiscoverPage()),
            ),
            GoRoute(
              path: "/profile",
              pageBuilder: (context, state) =>
                  const NoTransitionPage(child: ProfilePage()),
            ),
            GoRoute(
              path: "/teams",
              builder: (context, state) => TeamPage(state.extra as List),
            ),
            GoRoute(
              path: "/create_teams",
              pageBuilder: (context, state) => NoTransitionPage(
                  child: CreateTeamPage(userId: state.extra as String)),
            ),
            GoRoute(
              path: "/edit_teams",
              pageBuilder: (context, state) => NoTransitionPage(
                  child: EditTeamPage(teamId: state.extra as String)),
            ),
            GoRoute(
              path: "/external_teams",
              pageBuilder: (context, state) => NoTransitionPage(
                  child:
                      ExternalTeamPage(teamIdUserIdData: state.extra as List)),
            ),
          ],
        ),
      ],
    ),
  ],
);

auth_repository.dart

  @override
  Future<void> signinWithGoogle() async {
    var res = await _supabase.client.auth.signInWithOAuth(
      Provider.google,
      redirectTo: kIsWeb ? null : 'io.supabase.launchlabnus://login-callback/',
    );

    if (!res) {
      throw const Failure.badRequest();
    }
  }

main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:launchlab/src/config/app_config.dart';
import 'package:launchlab/src/config/app_router.dart';
import 'package:launchlab/src/config/app_theme.dart';
import 'package:launchlab/src/data/authentication/repository/auth_repository.dart';
import 'package:launchlab/src/presentation/common/cubits/app_root_cubit.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await Supabase.initialize(
    url: supabaseUrl,
    anonKey: supabaseToken,
    authCallbackUrlHostname: 'login-callback',
  );
  SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
    statusBarColor: Colors.transparent,
    statusBarIconBrightness: Brightness.dark,
    systemNavigationBarColor: Colors.transparent,
  ));

  runApp(const RootApp());
}

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

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) => AppRootCubit(AuthRepository(Supabase.instance)),
      child: const RootAppContent(),
    );
  }
}

class RootAppContent extends StatefulWidget {
  const RootAppContent({super.key});

  @override
  State<RootAppContent> createState() => _RootAppContentState();
}

class _RootAppContentState extends State<RootAppContent> {
  late AppRootCubit _appRootCubit;

  @override
  void initState() {
    super.initState();
    _appRootCubit = BlocProvider.of<AppRootCubit>(context);
    _appRootCubit.handleAuthListener();
  }

  @override
  void dispose() {
    _appRootCubit.handleStopAuthListener();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      theme: appThemeData,
      routerConfig: appRouter,
    );
  }
}

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.launchlab_nus_main_2">
    <!-- Required to fetch data from the internet. -->
    <uses-permission android:name="android.permission.INTERNET" />
   <application
        android:label="launchlab_nus_main_2"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

            <meta-data 
                android:name="flutter_deeplinking_enabled"
                android:value="true" />
                
            <!-- Deep Links -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <!-- Accepts URIs that begin with YOUR_SCHEME://YOUR_HOST -->
                <data
                android:scheme="io.supabase.launchlabnus" />
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

Update: I tried changing the Goroute path for the splash screen page from "/" to "/splash" and the redirect url into "io.supabase.launchlabnus://login-callback/splash/" and now interestingly when user goes back to the app through deeplink redirection after their google auth, I get a "Match error found during build phase Exception: no routes for location: /splash/#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjg3Mjg2Mjc5LCJzdWIiOiJjOTZkYmMyOC1kNzZmLTQwOTYtYTYyYi1kZjY4NWIwODcyZjYiLCJlbWFpbCI6Imptc2FuZGllZ28wNzEwQGdtY......"

enter image description here

0

There are 0 best solutions below