supabase / supabase-flutter

Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products.
https://supabase.com/
MIT License
656 stars 154 forks source link

How to make deep linking play nicely with `go_router` #901

Closed hld0 closed 1 month ago

hld0 commented 1 month ago

Hi!

I am not sure if this is a bug or just me having wrongly configured go_router so I would like to apologize in advance if this is wrong. Still, it kept me busy for a few days now and I am unsure how to proceed from here.

I have implemented the reset password functionality in my Flutter app. My issue is that if i put in the associated URL as redirectTo it opens the URL in the browser only and not the application itself. (Although it does show the button to open the page in the app). As clicking links directly with the associated domain usually works without problems I assume that this is expected behavior by iOS.

Therefore, I entered my custom app scheme like this:

ElevatedButton(
                    onPressed: () {
                      if (_formKey.currentState!.validate()) {
                        supabase.auth
                            .resetPasswordForEmail(_emailController.text,
                                redirectTo:
                                    kIsWeb ? null : '<my-app-scheme>://restore_password')
                            .then((value) => Navigator.push(
                                context,
                                MaterialPageRoute(
                                    builder: (context) =>
                                        const CheckEmailPage())));
                      }
                    },
                    child: const Text('Send Reset Link')),

The redirect works and opens the app successfully, but it cannot navigate to the restore_password page as it gives the following error:

======== Exception caught by foundation library ====================================================
The following StateError was thrown while dispatching notifications for GoRouteInformationProvider:
Bad state: Origin is only applicable to schemes http and https: <my-app-scheme>://restore_password [...]

When the exception was thrown, this was the stack: 
#0      _SimpleUri.origin (dart:core/uri.dart:4502:7)
#1      GoRouteInformationParser.parseRouteInformationWithDependencies (package:go_router/src/parser.dart:83:47)
#2      _RouterState._processRouteInformation (package:flutter/src/widgets/router.dart:749:8)
#3      _RouterState._handleRouteInformationProviderNotification (package:flutter/src/widgets/router.dart:767:5)
#4      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:433:24)
#5      GoRouteInformationProvider.notifyListeners (package:go_router/src/information_provider.dart:134:11)
#6      GoRouteInformationProvider._platformReportsNewRouteInformation (package:go_router/src/information_provider.dart:235:5)
#7      GoRouteInformationProvider.didPushRouteInformation (package:go_router/src/information_provider.dart:279:5)
#8      WidgetsBinding._handlePushRouteInformation (package:flutter/src/widgets/binding.dart:777:26)
<asynchronous suspension>
#9      MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:571:42)
<asynchronous suspension>
#10     _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:603:22)
<asynchronous suspension>
The GoRouteInformationProvider sending notification was: Instance of 'GoRouteInformationProvider'

Everything should be configured correctly, my Info.plist contains the following:

        <key>CFBundleURLTypes</key>
        <array>
            <dict>
                <key>CFBundleTypeRole</key>
                <string>Editor</string>
                <key>CFBundleURLName</key>
                <string><my-app-url></string>
                <key>CFBundleURLSchemes</key>
                <array>
                    <string><my-app-scheme></string>
                </array>
            </dict>
        </array>
    <key>CFBundleVersion</key>
    <string>16</string>
    <key>FlutterDeepLinkingEnabled</key>
    <true/>

As well as having the same url in my redirect URLs in my Supabase dashboard.

A possible workaround which lets me navigate to the desired page is to listen to AuthChangeEvents in my initState() like so:

  @override
  void initState() {
    super.initState();

    supabase.auth.onAuthStateChange.listen((data) {
      if (data.event == AuthChangeEvent.passwordRecovery) {
        Navigator.push(
            context,
            MaterialPageRoute(
                builder: (context) => const UpdatePasswordPage()));
      }
    });
  }

It still throws the same error, but at least in this case navigation does work. Is there any better way to make this work? My App is configured as follows in my main.dart:

  final router = GoRouter(
    observers: [routeObserver],
    routes: [
      GoRoute(
        path: '/',
        builder: (context, state) => const RedirectPage(),
      ), 
      GoRoute(
          path: '/restore_password',
          builder: (context, state) => const UpdatePasswordPage()),
.... // remaining routes
   ]
);

runApp(MyApp(prefs: prefs, router: router));

final RouteObserver<ModalRoute> routeObserver = RouteObserver<ModalRoute>();

class MyApp extends StatelessWidget {
  final SharedPreferences _prefs;
  final GoRouter _router;

  const MyApp(
      {super.key, required SharedPreferences prefs, required GoRouter router})
      : _prefs = prefs,
        _router = router;

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
          routerConfig: _router,
          debugShowCheckedModeBanner: false,
            ...
        ),
      ),
    );
  }

To Reproduce Steps to reproduce the behavior:

  1. Call resetPasswordForEmail with a custom scheme as redirect like so: supabase.auth.resetPasswordForEmail(emailAddress, redirectTo: '<my-app-scheme>//restore_password')
  2. Open the reset password link received via mail

Expected behavior Should navigate to the specified screen in the URI.

Package information

├── supabase_flutter 2.5.1
│   ├── supabase 2.1.1
│   │   ├── functions_client 2.0.0
│   │   ├── gotrue 2.6.0
│   │   ├── postgrest 2.1.1
│   │   ├── realtime_client 2.0.4
│   │   ├── storage_client 2.0.1
dshukertjr commented 1 month ago

You are not specifying the path of the route. Try something like this:<my-app-scheme>://callback/restore-password.

Another tip here is that underscores don't work well as URLs, so generally should be avoided.

hld0 commented 1 month ago

Thank you @dshukertjr! This actually solved the issue. I always thought that the /callback/ part was not necessary.

I also removed all the underscores from my other URLs, thank you for that tip.