skip to Main Content

I’m trying to implement deep linking with go_router. Everything is working fine on Android, but on iOS opening the deep link URL opens the app to the correct screen only if the app is open and it is in the background. If the app is closed, the deep link URL only opens the login screen.

Here is my setup

Info.plist

<key>CFBundleURLTypes</key>
<array>
<dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLSchemes</key>
    <array>
        <string>appscheme</string>
        <string>${GOOGLE_REVERSED_CLIENT_ID}</string>
    </array>
</dict>
</array>

app.dart

MaterialApp.router(
  debugShowCheckedModeBanner: false,
  routerConfig: GoRouter(
    routes: [ 
      GoRoute( 
        path: '/',
        builder: (context, state) => const LoginPage( key: Key('loginPage',),),
      ), 
      GoRoute( 
        path: '/sign-up',
        builder: (context, state) => const SignUpPage( key: Key('signUpPage'), ), 
      ),
    ],
  ),
);

To invoke deeplink
xcrun simctl openurl booted "appscheme:/sign-up"

2

Answers


  1. Let’s see how you can handle deeplink on iOS:

    Swift Code in AppDelegate.swift:

    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
      
      private var methodChannel: FlutterMethodChannel?
      private var eventChannel: FlutterEventChannel?
      
      private let linkStreamHandler = LinkStreamHandler()
      
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        
        let controller = window.rootViewController as! FlutterViewController
        
        // Create a MethodChannel for invoking methods from Dart to Swift
        methodChannel = FlutterMethodChannel(name: "deeplink.flutter.dev/channel", binaryMessenger: controller.binaryMessenger)
        
        // Create an EventChannel to listen for deep link events from Swift to Dart
        eventChannel = FlutterEventChannel(name: "deeplink.flutter.dev/events", binaryMessenger: controller.binaryMessenger)
    
        // Set up a method call handler for incoming method calls from Dart
        methodChannel?.setMethodCallHandler({ (call: FlutterMethodCall, result: FlutterResult) in
          guard call.method == "initialLink" else {
            result(FlutterMethodNotImplemented)
            return
          }
        })
    
        // Register Flutter plugins
        GeneratedPluginRegistrant.register(with: self)
        
        // Set the stream handler for the EventChannel to handle incoming deep links
        eventChannel?.setStreamHandler(linkStreamHandler)
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
      
      // Handle deep link events when the app is already open
      override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        // Set the stream handler for the EventChannel to handle the deep link
        eventChannel?.setStreamHandler(linkStreamHandler)
        return linkStreamHandler.handleLink(url.absoluteString)
      }
    }
    
    // Create a class to handle incoming deep links
    class LinkStreamHandler: NSObject, FlutterStreamHandler {
      
      var eventSink: FlutterEventSink?
      
      // Store links in a queue until the sink is ready to process them
      var queuedLinks = [String]()
      
      func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
        self.eventSink = events
        
        // Send any queued links to the sink
        queuedLinks.forEach({ events($0) })
        queuedLinks.removeAll()
        
        return nil
      }
      
      func onCancel(withArguments arguments: Any?) -> FlutterError? {
        self.eventSink = nil
        return nil
      }
      
      func handleLink(_ link: String) -> Bool {
        guard let eventSink = eventSink else {
          // Queue the link if the event sink is not ready
          queuedLinks.append(link)
          return false
        }
        // Send the link to the event sink
        eventSink(link)
        return true
      }
    }
    

    Explanation for AppDelegate.swift:

    • In AppDelegate.swift, we set up deep link handling for a Flutter app running on iOS.
    • We create a MethodChannel named "deeplink.flutter.dev/channel" to allow Dart to invoke methods in Swift.
    • An EventChannel named "deeplink.flutter.dev/events" is created to listen for deep link events from Swift to Dart.
    • We set a method call handler to handle method calls from Dart, specifically the "initialLink" method.
    • Flutter plugins are registered with GeneratedPluginRegistrant.register(with: self).
    • The LinkStreamHandler class handles incoming deep links and sends them to the Flutter app.

    Now, let’s explain the Dart code in main.dart:

    Dart Code in main.dart:

    final navigatorKey = GlobalKey<NavigatorState>();
    void main() {
      WidgetsFlutterBinding.ensureInitialized();
    
      // Event Channel creation
      const stream = EventChannel('deeplink.flutter.dev/events');
    
      // Method channel creation
      const platform = MethodChannel('deeplink.flutter.dev/channel');
    
      Future<String?> startUri() async {
        try {
          return platform.invokeMethod('initialLink');
        } on PlatformException catch (e) {
          return "Failed to Invoke: '${e.message}'.";
        }
      }
    
      onRedirected(String? uri) {
        debugPrint("uri: $uri");
        if (uri == "deeplink://login") {
          GoRouter.of(navigatorKey.currentContext!).push('/login');
        } else if (uri == "deeplink://sign-up") {
          GoRouter.of(navigatorKey.currentContext!).push('/sign-up');
        }
      }
    
      stream.receiveBroadcastStream().listen((d) {
        // Checking application start by deep link
        startUri().then(onRedirected);
        // Checking broadcast stream if a deep link was clicked in an opened application
        stream.receiveBroadcastStream().listen((d) => onRedirected(d));
      });
    
      runApp(MaterialApp.router(routerConfig: router));
    }
    
    // Define your GoRouter routes and pages
    final router = GoRouter(
      debugLogDiagnostics: true,
      initialLocation: "/login",
      navigatorKey: navigatorKey,
      routes: [
        GoRoute(
          path: '/login',
          builder: (context, state) => const LoginPage(
            key: Key('loginPage'),
          ),
        ),
        GoRoute(
          path: '/sign-up',
          builder: (context, state) => const SignUpPage(
            key: Key('signUpPage'),
          ),
        ),
      ],
    );
    
    // Define your Flutter pages
    class LoginPage extends StatelessWidget {
      const LoginPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text("Login")),
          body: const Center(child: Text("Login Page")),
        );
      }
    }
    
    class SignUpPage extends StatelessWidget {
      const SignUpPage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text("Sign Up")),
          body: const Center(child: Text("Sign Up Page")),
        );
      }
    }
    

    Explanation for main.dart:

    • In main.dart, we set up deep link handling

    for a Flutter app.

    • We create an EventChannel named "deeplink.flutter.dev/events" to listen for deep link events from Swift to Dart.
    • A MethodChannel named "deeplink.flutter.dev/channel" is created to invoke methods in Swift.
    • startUri is a function that attempts to invoke the "initialLink" method from Swift and handles exceptions.
    • onRedirected is a function that processes deep link URIs, pushing corresponding routes using the GoRouter package.
    • We listen to the broadcast stream to check for deep links both when the app starts and while it’s running.
    • The router object defines routes and pages for the GoRouter package.

    These code segments together enable seamless handling of deep links between Flutter and native iOS code, allowing control over deep link transitions within the Flutter app on iOS using Swift. While the provided Swift code works well, the uni_links package offers a more convenient and feature-rich solution for deep link handling in Flutter applications. You can find more information about the uni_links package here.

    For more details, you can refer to the article here.

    Login or Signup to reply.
  2. I think the issue is with the URL you’re trying to pass. Instead of passing the path after the scheme, add a host.

    xcrun simctl openurl booted "appscheme://somehost.com/sign-up"
    

    Here’s a link to the GitHub issue which demonstrates the same behaviour.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search