Deep Link, IOs Universal Links, Custom URL Schemes Handler for Flutter

app_links

Android App Links, Deep Links, iOS Universal Links and Custom URL schemes handler (desktop included).

This plugin allows you to:

  • catch HTTPS URLs to open your app instead of the browser (App Link / Universal Link).
  • catch custom schemes to open your app (Deep Link / Custom URL scheme).

Getting Started

Before using the plugin, you'll need to setup each platforms you target.

All those configurations below are accessible in the example project.

Android

iOS

Windows

How to setup

Don't be afraid, this is just copy/paste commands to follow.
But yes, it we will be a bit painful...

Declare this method in <PROJECT_DIR>\windows\runner\win32_window.h as private method.

  // Dispatches link if any.
  // This method enables our app to be with a single instance too.
  // This is mandatory if you want to catch further links in same app.
  bool SendAppLinkToInstance(const std::wstring& title);

Add this inclusion in <PROJECT_DIR>\windows\runner\win32_window.cpp

#include "app_links/app_links_plugin_c_api.h"

Add this method in <PROJECT_DIR>\windows\runner\win32_window.cpp

bool Win32Window::SendAppLinkToInstance(const std::wstring& title) {
  // Find our exact window
  HWND hwnd = ::FindWindow(kWindowClassName, title.c_str());
  
  if (hwnd) {
    // Dispatch new link to current window
    SendAppLink(hwnd);

    // (Optional) Restore our window to front in same state
    WINDOWPLACEMENT place = { sizeof(WINDOWPLACEMENT) };
    GetWindowPlacement(hwnd, &place);

    switch(place.showCmd) {
      case SW_SHOWMAXIMIZED:
          ShowWindow(hwnd, SW_SHOWMAXIMIZED);
          break;
      case SW_SHOWMINIMIZED:
          ShowWindow(hwnd, SW_RESTORE);
          break;
      default:
          ShowWindow(hwnd, SW_NORMAL);
          break;
    }

    SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
    SetForegroundWindow(hwnd);
    // END Restore

    // Window has been found, don't create another one.
    return true;
  }

  return false;
}

Add the call to the previous method in CreateAndShow

bool Win32Window::CreateAndShow(const std::wstring& title,
                                const Point& origin,
                                const Size& size) {
if (SendAppLinkToInstance(title)) {
    return false;
}

Destroy();

...

Great!

Now you can register your own scheme.
On Windows, URL protocols are setup in the Windows registry.

This package won't do it for you (and will never sorry).

You can achieve it with url_protocol inside you app.

But... The most relevant solution is to include those registry modifications into your installer to allow the unregistration.

Mac OS

How to setup

Add this XML chapter in your macos/Runner/Info.plist inside <plist version="1.0"><dict> chapter:

<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <!-- abstract name for this URL type (you can leave it blank) -->
        <string>sample_name</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <!-- your schemes -->
            <string>sample</string>
        </array>
    </dict>
</array>

Done!


AppLinks usage

Simpliest usage with a single stream

final _appLinks = AppLinks();

// Subscribe to all events when app is started.
// (Use allStringLinkStream to get it as [String])
_appLinks.allUriLinkStream.listen((uri) {
    // Do something (navigation, ...)
});

Decomposed usage

final _appLinks = AppLinks();

// Get the initial/first link.
// This is useful when app was terminated (i.e. not started)
final uri = await _appLinks.getInitialAppLink();
// Do something (navigation, ...)

// Subscribe to further events when app is started.
// (Use stringLinkStream to get it as [String])
_linkSubscription = _appLinks.uriLinkStream.listen((uri) {
    // Do something (navigation, ...)
});

...

// Maybe later. Get the latest link.
final uri = await _appLinks.getLatestAppLink();

Android notes:

  • By default, flutter Activity is set with android:launchMode="singleTop". This is perfectly fine and expected, but this launches another instance of your app, specifically for the requested view.
    If you don't want this behaviour, you can set android:launchMode="singleInstance" in your AndroidManifest.xml and avoid another flutter warmup.

Tests

The following commands will help you to test links.

Test on Android

adb shell am start -a android.intent.action.VIEW \
  -d "sample://open.my.app/#/book/hello-world"

For App Links, you can also test it from Android Studio: Documentation.

Test on iOS

/usr/bin/xcrun simctl openurl booted "app://www.example.com/#/book/hello-world"

Test on windows & macOS

Open your browser and type in your address bar:

sample://foo/#/book/hello-world2

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add app_links

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
  app_links: ^3.4.3

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:app_links/app_links.dart';

example/lib/main.dart

// ignore_for_file: avoid_print

import 'dart:async';
import 'dart:io';

import 'package:app_links/app_links.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'web_url_protocol.dart'
    if (dart.library.io) 'package:url_protocol/url_protocol.dart';

///////////////////////////////////////////////////////////////////////////////
/// Please make sure to follow the setup instructions below
///
/// Please take a look at:
/// - example/android/app/main/AndroidManifest.xml for Android.
///
/// - example/ios/Runner/Runner.entitlements for Universal Link sample.
/// - example/ios/Runner/Info.plist for Custom URL scheme sample.
///
/// You can launch an intent on an Android Emulator like this:
///    adb shell am start -a android.intent.action.VIEW \
///     -d "sample://open.my.app/#/book/hello-world"
///
///
/// On windows & macOS:
///   The simpliest way to test it is by
///   opening your browser and type: sample://foo/#/book/hello-world2
///////////////////////////////////////////////////////////////////////////////

const kWindowsScheme = 'sample';

void main() {
  // Register our protocol only on Windows platform
  _registerWindowsProtocol();

  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _navigatorKey = GlobalKey<NavigatorState>();
  late AppLinks _appLinks;
  StreamSubscription<Uri>? _linkSubscription;

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

    initDeepLinks();
  }

  @override
  void dispose() {
    _linkSubscription?.cancel();

    super.dispose();
  }

  Future<void> initDeepLinks() async {
    _appLinks = AppLinks();

    // Check initial link if app was in cold state (terminated)
    final appLink = await _appLinks.getInitialAppLink();
    if (appLink != null) {
      print('getInitialAppLink: $appLink');
      openAppLink(appLink);
    }

    // Handle link when app is in warm state (front or background)
    _linkSubscription = _appLinks.uriLinkStream.listen((uri) {
      print('onAppLink: $uri');
      openAppLink(uri);
    });
  }

  void openAppLink(Uri uri) {
    _navigatorKey.currentState?.pushNamed(uri.fragment);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: _navigatorKey,
      initialRoute: "/",
      onGenerateRoute: (RouteSettings settings) {
        Widget routeWidget = defaultScreen();

        // Mimic web routing
        final routeName = settings.name;
        if (routeName != null) {
          if (routeName.startsWith('/book/')) {
            // Navigated to /book/:id
            routeWidget = customScreen(
              routeName.substring(routeName.indexOf('/book/')),
            );
          } else if (routeName == '/book') {
            // Navigated to /book without other parameters
            routeWidget = customScreen("None");
          }
        }

        return MaterialPageRoute(
          builder: (context) => routeWidget,
          settings: settings,
          fullscreenDialog: true,
        );
      },
    );
  }

  Widget defaultScreen() {
    return Scaffold(
      appBar: AppBar(title: const Text('Default Screen')),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const SelectableText('''
            Launch an intent to get to the second screen.

            On web:
            http://localhost:<port>/#/book/1 for example.

            On windows & macOS, open your browser:
            sample://foo/#/book/hello-deep-linking

            This example code triggers new page from URL fragment.
            '''),
            const SizedBox(height: 20),
            buildWindowsUnregisterBtn(),
          ],
        ),
      ),
    );
  }

  Widget customScreen(String bookId) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Screen')),
      body: Center(child: Text('Opened with parameter: ' + bookId)),
    );
  }

  Widget buildWindowsUnregisterBtn() {
    if (!kIsWeb) {
      if (Platform.isWindows) {
        return TextButton(
            onPressed: () => unregisterProtocolHandler(kWindowsScheme),
            child: const Text('Remove Windows protocol registration'));
      }
    }

    return const SizedBox.shrink();
  }
}

void _registerWindowsProtocol() {
  // Register our protocol only on Windows platform
  if (!kIsWeb) {
    if (Platform.isWindows) {
      registerProtocolHandler(kWindowsScheme);
    }
  }
}

Download details:

Author: openapi4j.org

Source: https://github.com/llfbandit/app_links

#flutter #android #ios #web-development #mobile #url 

Deep Link, IOs Universal Links, Custom URL Schemes Handler for Flutter
1.05 GEEK