Skip to content

iOS deep links are not working when app comes from a terminated state using the Authenticator pre-built widget. #5731

@ramon-san

Description

@ramon-san

Description

I just finished setting up my app's deep link structure as per the Flutter documentation and it all works well except for the iOS launch of deep links when using the Authenticator pre-built widget.

The Android version of my code works just fine and even when app is terminated it opens the deep link as expected, the issue only occurs in iOS when the app comes from a terminated state. I attach two videos of what I'm talking about:

authentication_video.mov
noauthentication_video.mov

The first video uses the Authenticator pre-built widget as outlined in the code that appears next to the simulator, here you can see that deep link doesn't work when app comes from terminated state, when app is already launched it works just fine. The second video uses the bare-bone MaterialApp widget as seen in the code next to the simulator and in this case the app coming from a terminated state properly handles the deep link.

In the video that doesn't use Authenticator the first screen is different (no stores are displayed) becuase my API requires an authenticated user, not because anything else changed in the code.

In the first video it is clearly seen that the terminated app doesn't handle deep links correctly, the first time you open this it just stays in the home page (the "/" route) without navigating to the complete path. In the second video it is clear that the deep link works as intended, taking the user to the complete path (the "/help" route).

I was wondering if this is a known issue or if there is some clear problem with my implementation. The complete structure of my main.dart file is the following:

// Dart libraries
import 'dart:convert';
// External libraries
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:amplify_flutter/amplify_flutter.dart';
import 'package:amplify_api/amplify_api.dart';
import 'package:amplify_auth_cognito/amplify_auth_cognito.dart';
import 'package:amplify_analytics_pinpoint/amplify_analytics_pinpoint.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:go_router/go_router.dart';
import 'package:amplify_authenticator/amplify_authenticator.dart';
// Utilities
import 'package:pey/utils/rest_api_manager.dart';
import 'package:pey/routes.dart';
// Components
import 'package:pey/components/main/splash_screen.dart';
import 'package:pey/components/main/initialization_error.dart';
// Models
import 'package:pey/models/cart.dart';
import 'package:pey/models/store.dart';
// Configs
import 'package:pey/configs/amplify.dart';

Future<void> configureAmplify() async {
  try {
    if (Amplify.isConfigured) {
      safePrint("Amplify is already configured 🎉");
      return;
    }

    await Amplify.addPlugins([
      AmplifyAnalyticsPinpoint(),
      AmplifyAuthCognito(),
      AmplifyAPI(),
    ]);

    await Amplify.configure(jsonEncode(amplifyConfig));
    safePrint("Successfully configured Amplify 🎉");
  } on Exception catch (e) {
    safePrint('An error occurred configuring Amplify: $e');
    rethrow;
  }
}

Future<void> configureStripe() async {
  String merchantId = "merchant.com.pey";
  try {
    final publishableKey = await RestApi.fetchStripePublishableKey();
    Stripe.publishableKey = publishableKey;
    Stripe.merchantIdentifier = merchantId;
    await Stripe.instance.applySettings();
    safePrint("Successfully configured Stripe 🎉");
  } catch (e) {
    safePrint('An error occurred configuring Stripe: $e');
    rethrow;
  }
}

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const InitializationWrapper());
}

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

  @override
  State<InitializationWrapper> createState() => _InitializationWrapperState();
}

class _InitializationWrapperState extends State<InitializationWrapper> {
  late Future<void> _initializationFuture;
  final GoRouter _router = router;

  @override
  void initState() {
    super.initState();
    _initializationFuture = _initialize();
  }

  Future<void> _initialize() async {
    await configureAmplify();
    await configureStripe();
  }

  void _retryInitialization() {
    setState(() {
      _initializationFuture = _initialize();
    });
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _initializationFuture,
      builder: (context, snapshot) {
        if (snapshot.hasError) {
          return MaterialApp(
            home: InitializationError(
              error: snapshot.error?.toString(),
              onRetry: _retryInitialization,
            ),
          );
        }

        if (snapshot.connectionState == ConnectionState.done) {
          return MultiProvider(
            providers: [
              ChangeNotifierProvider(create: (context) => CartModel()),
              ChangeNotifierProvider(create: (context) => StoreModel())
            ],
            child: Authenticator(
              child: MaterialApp.router(
                routerConfig: _router,
                theme: ThemeData(
                  colorScheme: ColorScheme.fromSeed(
                    seedColor: const Color(0xFF0067F1),
                    primary: const Color(0xFF0067F1),
                  ),
                  useMaterial3: true,
                ),
                builder: Authenticator.builder(),
              ),
            ),
          );
        }

        return const MaterialApp(
          home: SplashScreen(),
        );
      },
    );
  }
}

I was trying to narrow down the causes of failure given this GitHub issue which stated that conditional statements when launching the MaterialApp caused issues; this turned out to be true and it seems the culprit right now is Authenticator. I first want to fix the Authenticator which clearly causes the issue and then see if my other conditions (SplashScreen and InitializationError) also affect the deep link behavior. In the video examples I showed above the Widget build(BuildContext context) { ... } used the simplified versions of the code that appear in the videos.

This issue in the Flutter repo goes deep into the functionality of deep links in iOS when app comes from a terminated state.

Categories

  • Analytics
  • API (REST)
  • API (GraphQL)
  • Auth
  • Authenticator
  • DataStore
  • Notifications (Push)
  • Storage

Steps to Reproduce

Already explained in the description, try to configure flutter deep links with an app that uses Amplify's pre-built Authenticator widget.

Screenshots

No response

Platforms

  • iOS
  • Android
  • Web
  • macOS
  • Windows
  • Linux

Flutter Version

3.24.3

Amplify Flutter Version

^2.0.0

Deployment Method

AWS CDK

Schema

No response

Metadata

Metadata

Assignees

Labels

AuthenticatorIssues related to the Authenticator UI ComponentbugSomething is not working; the issue has reproducible steps and has been reproducediOSIssues specific to the iOS Platform

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions