将 UniLinks 与 Flutter 集成(安卓 AppLinks + iOS UniversalLinks)

让我们使用 Flutter Mobile 和 Flutter Web 集成 UniLinks。

一步一步的指导!

我是 Pedro Dionísio,是葡萄牙 InspireIT 公司的 Flutter 开发人员,我写这个 UniLinks 教程的座右铭是:

  1. Firebase DynamicLinks 已被弃用,就像 Firebase 在其文档中所说,不应再实现(我正在使用它,由于它有一些错误并且已被弃用,我决定开始将这种类型的 Deeplink 迁移到 UniLinks);
  2. 这种 Deeplink 方法被 TikTok、Instagram、Facebook 等大公司使用......
  3. 我在某些特定的 Android 设备上实现它时遇到了一些问题(尝试打开并将数据传递给应用程序)。

因此,我将把所有步骤讲得一清二楚,并且解释一切,不仅适用于 Flutter Android 和 iOS,还适用于 Flutter Web 和 Firebase WebHosting,以免错过任何步骤。让我们开始吧!

Deep Linking 介绍

什么是 Deep Linking?

Deep Linking(深层链接)就像有一个指向应用程序某些部分的快捷方式。

这是一种特殊的网络链接,它不仅可以打开您的应用程序,还可以将您带到应用程序内的特定位置。就像打开一本书,直接翻到您想阅读的页面一样。

它是如何工作的?

假设您在应用程序中发现了一篇很棒的文章,并且想与朋友分享。您可以向他们发送一个特殊的链接,将他们直接带到该文章,而不是将他们发送到应用程序的主页并要求他们查找该文章。这就像给他们送了一条秘密通道。

最酷的部分是什么?

最酷的是,您还可以通过此链接发送特殊说明或代码。例如,如果应用程序中有折扣码或隐藏的惊喜,您可以将其包含在链接中。所以,你不仅能很快到达正确的地方,还能得到一些额外的好处。

如果应用程序已经打开会发生什么?

有时,当您单击深层链接时,您的应用程序可能已经打开。不用担心!当应用程序已经运行时,深度链接甚至可以工作。这就像切换到您正在阅读的书中的正确页面。

在本教程中,我将向您展示如何使用名为"uni_links"的工具使深度链接变得超级简单。

重要的是,在这种类型的深层链接中,必须在网站中分配 2 个配置文件(一个用于 Android,一个用于 iOS)。其含义是因为这些文件存储有关您的应用程序的重要信息,并且通过它们,您的网络浏览器可以准确地知道在手机内重定向到哪里。

说到这里,我将向您展示如何创建 Flutter Web 项目并将这些文件放置在正确的位置。

完全不用担心!这将很容易实施!让我们开始吧!📱🚀

为您的移动应用创建 Flutter 项目

Android 配置

转到项目的 android/app/src/main/AndroidManifest.xml 文件。

在这里,我们需要更改一些内容,首先将 android:launchMode="singleTop" 替换为 android:launchMode="singleTask" ,因为我们只希望在手机中打开 APP 的一个实例。

应该会出现这样的内容:

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application ...>
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTask" <!-- <----HERE---- -->
            ...>

之后,在同一个文件中,您需要配置您的"APP 入口",该入口将通过特定的 UniLink 进行。

例如我们希望通过这个链接打开 APP: mypage.web.app/promos/?pro... 。

因此,在 activity 内,您将添加一个 intent-filter ,如下所示:

xml 复制代码
<manifest ...>
  <application ...>
    <activity ...>
      ...

      <!-- App Links -->
      <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
          android:scheme="https"
          android:host="mypage.web.app"
          android:pathPrefix="/promos/" />
      </intent-filter>

      ...
    </activity>
  </application>
</manifest>

iOS 配置

使用相同的示例,我们希望通过此链接打开应用程序: mypage.web.app/promos/?pro... 。

转到项目的 ios/Runner/Runner.entitlements 文件并添加以下 key 和 array 标记:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  ...

  <key>com.apple.developer.associated-domains</key>
  <array>
    <string>applinks:mypage.web.app</string>
  </array>

  ...
</dict>
</plist>

您不需要这样做,但如果您愿意,您也可以通过 XCode 进行此配置:

  • 双击 ios/Runner.xcworkspace 文件打开 Xcode;
  • 转到项目导航器 (Cmd+1) 并选择最顶部的 Runner 根项目;
  • 选择 Runner 目标,然后选择 Signing & Capabilities 选项卡;
  • 单击 + Capability (加号)按钮添加新功能;
  • 输入 associated domains 并选择该项目;
  • 双击域列表中的第一项,将其从 webcredentials:example.com 更改为:applinks:mypage.web.app;
  • 将创建一个名为 Runner.entitlements 的文件并将其添加到项目中。

Flutter 实现

我通常使用模块化的方法来组织一切,但对于这个示例项目,我将进行混合,使一切变得简单直观。

让我们首先在此处获取最新版本的 uni_links 包:pub.dev/packages/un... 并将其粘贴到项目的 pubspec.yaml 文件中,如下所示:

yaml 复制代码
---
dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  uni_links: ^0.5.1 # <----------------

保存并执行 flutter pun get 以更新您的项目依赖项。

然后添加三个用户界面文件: 主屏幕、绿色宣传屏幕和红色宣传屏幕。

主屏幕文件 lib/screens/home_screen.dart :

dart 复制代码
import 'package:flutter/material.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
        child: const Text(
          "Home Screen",
          style: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }
}

绿色促销屏幕文件 lib/screens/green_promo_screen.dart :

dart 复制代码
import 'package:flutter/material.dart';
import 'package:unilinkproject/common/uni_links/core/services/uni_links_service.dart';

class GreenPromoScreen extends StatelessWidget {
  const GreenPromoScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            colors: [
              Colors.green,
              Colors.greenAccent,
            ],
            begin: Alignment.topRight,
            end: Alignment.bottomLeft,
          ),
        ),
        child: Text(
          "!!! Green Promo !!!\nCode: ${UniLinksService.promoId}",
          textAlign: TextAlign.center,
          style: const TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }
}

红色促销屏幕 lib/screens/red_promo_screen.dart :

dart 复制代码
import 'package:flutter/material.dart';
import 'package:unilinkproject/common/uni_links/core/services/uni_links_service.dart';

class RedPromoScreen extends StatelessWidget {
  const RedPromoScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            colors: [
              Colors.red,
              Colors.redAccent,
            ],
            begin: Alignment.topRight,
            end: Alignment.bottomLeft,
          ),
        ),
        child: Text(
          "!!! Red Promo !!!\nCode: ${UniLinksService.promoId}",
          textAlign: TextAlign.center,
          style: const TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }
}

为什么是 3 个屏幕?这是因为我们要测试 3 种情况:

现在让我们添加一个我在项目中经常使用的重要实用程序文件。有了它我们就可以在 APP 的任何地方访问最新的 BuildContext 。

添加此文件 lib/common/global_context/utils/contect_utility.dart :

dart 复制代码
import 'package:flutter/material.dart';

class ContextUtility {
  static final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>(debugLabel: 'ContextUtilityNavigatorKey');
  static GlobalKey<NavigatorState> get navigatorKey => _navigatorKey;

  static bool get hasNavigator => navigatorKey.currentState != null;
  static NavigatorState? get navigator => navigatorKey.currentState;

  static bool get hasContext => navigator?.overlay?.context != null;
  static BuildContext? get context => navigator?.overlay?.context;
}

接下来我们添加负责处理 UniLinks lib/common/global_context/utils/context_utility.dart 的文件:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:uni_links/uni_links.dart';
import 'package:unilinkproject/common/global_context/utils/context_utility.dart';
import 'package:unilinkproject/screens/green_promo_screen.dart';
import 'package:unilinkproject/screens/red_promo_screen.dart';

class UniLinksService {
  static String _promoId = '';
  static String get promoId => _promoId;
  static bool get hasPromoId => _promoId.isNotEmpty;

  static void reset() => _promoId = '';

  static Future<void> init({checkActualVersion = false}) async {
    // 这用于以下情况:应用程序未运行,用户单击链接。
    try {
      final Uri? uri = await getInitialUri();
      _uniLinkHandler(uri: uri);
    } on PlatformException {
      if (kDebugMode) print("(PlatformException) Failed to receive initial uri.");
    } on FormatException catch (error) {
      if (kDebugMode) print("(FormatException) Malformed Initial URI received. Error: $error");
    }

    // 这用于以下情况:应用程序已经在运行,用户单击链接。
    uriLinkStream.listen((Uri? uri) async {
      _uniLinkHandler(uri: uri);
    }, onError: (error) {
      if (kDebugMode) print('UniLinks onUriLink error: $error');
    });
  }

  static Future<void> _uniLinkHandler({required Uri? uri}) async {
    if (uri == null || uri.queryParameters.isEmpty) return;
    Map<String, String> params = uri.queryParameters;

    String receivedPromoId = params['promo-id'] ?? '';
    if (receivedPromoId.isEmpty) return;
    _promoId = receivedPromoId;

    if (_promoId == 'ABC1') {
      ContextUtility.navigator?.push(
        MaterialPageRoute(builder: (_) => const GreenPromoScreen()),
      );
    }

    if (_promoId == 'ABC2') {
      ContextUtility.navigator?.push(
        MaterialPageRoute(builder: (_) => const RedPromoScreen()),
      );
    }
  }
}

最后我们将 main.dart 文件更改为:

dart 复制代码
import 'package:flutter/material.dart';
import 'package:unilinkproject/common/uni_links/core/services/uni_links_service.dart';
import 'package:unilinkproject/common/global_context/utils/context_utility.dart';
import 'package:unilinkproject/screens/green_promo_screen.dart';
import 'package:unilinkproject/screens/home_screen.dart';
import 'package:unilinkproject/screens/red_promo_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await UniLinksService.init();

  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: ContextUtility.navigatorKey,
      debugShowCheckedModeBanner: false,
      title: 'UniLinks Project',
      routes: {
        '/': (_) => const HomeScreen(),
        '/green-promo': (_) => const GreenPromoScreen(),
        '/red-promo': (_) => const RedPromoScreen(),
      },
    );
  }
}

我们就完成了!

您可以测试正常打开 APP,查看是否出现主屏幕。


原文:medium.com/@pedrostick...

相关推荐
2601_949970699 小时前
HumanizeDuration 跨端三方库仓颉适配全流程
前端·javascript·flutter
传奇开心果编程10 小时前
【Flutter踩坑排雷】第3课:const 误用坑|const 错误使用引发 UI 异常与性能损耗
flutter·移动开发·跨平台·dart·编程语言理论·编程框架·声明式ui
程序员老刘12 小时前
你的Flutter项目要不要紧急升级3.47.8?
flutter·客户端
Talyra4214 小时前
不想开会员,我写了一个记账 App
flutter
2601_9495758619 小时前
JSON5 跨端三方库仓颉适配全流程
前端·javascript·flutter
法的空间19 小时前
AI 编程时代,为什么 Dart 分析会拖慢大型项目的开发
android·flutter·ios
恋猫de小郭21 小时前
Flutter 紧急发布 3.47.8 安全升级版本,建议都升级
android·前端·flutter
好的佩奇2 天前
Dart 之模式
android·flutter·dart
淡淡的香烟2 天前
Flutter实现多语言切换
android·flutter·1024程序员节
事圆则缓2 天前
从零搭建一套 Flutter 架构:先跑通数据流
flutter