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

让我们使用 Flutter Mobile 和 Flutter Web 集成 UniLinks。
一步一步的指导!
我是 Pedro Dionísio,是葡萄牙 InspireIT 公司的 Flutter 开发人员,我写这个 UniLinks 教程的座右铭是:
- Firebase DynamicLinks 已被弃用,就像 Firebase 在其文档中所说,不应再实现(我正在使用它,由于它有一些错误并且已被弃用,我决定开始将这种类型的 Deeplink 迁移到 UniLinks);
- 这种 Deeplink 方法被 TikTok、Instagram、Facebook 等大公司使用……
- 我在某些特定的 Android 设备上实现它时遇到了一些问题(尝试打开并将数据传递给应用程序)。
因此,我将把所有步骤讲得一清二楚,并且解释一切,不仅适用于 Flutter Android 和 iOS,还适用于 Flutter Web 和 Firebase WebHosting,以免错过任何步骤。让我们开始吧!

Deep Linking 介绍
什么是 Deep Linking?
Deep Linking(深层链接)就像有一个指向应用程序某些部分的快捷方式。
这是一种特殊的网络链接,它不仅可以打开您的应用程序,还可以将您带到应用程序内的特定位置。就像打开一本书,直接翻到您想阅读的页面一样。
它是如何工作的?
假设您在应用程序中发现了一篇很棒的文章,并且想与朋友分享。您可以向他们发送一个特殊的链接,将他们直接带到该文章,而不是将他们发送到应用程序的主页并要求他们查找该文章。这就像给他们送了一条秘密通道。
最酷的部分是什么?
最酷的是,您还可以通过此链接发送特殊说明或代码。例如,如果应用程序中有折扣码或隐藏的惊喜,您可以将其包含在链接中。所以,你不仅能很快到达正确的地方,还能得到一些额外的好处。
如果应用程序已经打开会发生什么?
有时,当您单击深层链接时,您的应用程序可能已经打开。不用担心!当应用程序已经运行时,深度链接甚至可以工作。这就像切换到您正在阅读的书中的正确页面。
关于 UniLinks 的一些最后说明
在本教程中,我将向您展示如何使用名为“uni_links”的工具使深度链接变得超级简单。
重要的是,在这种类型的深层链接中,必须在网站中分配 2 个配置文件(一个用于 Android,一个用于 iOS)。其含义是因为这些文件存储有关您的应用程序的重要信息,并且通过它们,您的网络浏览器可以准确地知道在手机内重定向到哪里。
说到这里,我将向您展示如何创建 Flutter Web 项目并将这些文件放置在正确的位置。
完全不用担心!这将很容易实施!让我们开始吧!📱🚀

为您的移动应用创建 Flutter 项目
Android 配置
转到项目的 android/app/src/main/AndroidManifest.xml 文件。
在这里,我们需要更改一些内容,首先将 android:launchMode="singleTop" 替换为 android:launchMode="singleTask" ,因为我们只希望在手机中打开 APP 的一个实例。
应该会出现这样的内容:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><application ...><activityandroid:name=".MainActivity"android:exported="true"android:launchMode="singleTask" <!-- <----HERE---- -->...>
之后,在同一个文件中,您需要配置您的“APP 入口”,该入口将通过特定的 UniLink 进行。
例如我们希望通过这个链接打开 APP: https://mypage.web.app/promos/?promo-id=ABC1 。
因此,在 activity 内,您将添加一个 intent-filter ,如下所示:
<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" /><dataandroid:scheme="https"android:host="mypage.web.app"android:pathPrefix="/promos/" /></intent-filter>...</activity></application>
</manifest>
iOS 配置
使用相同的示例,我们希望通过此链接打开应用程序: https://mypage.web.app/promos/?promo-id=ABC1 。
转到项目的 ios/Runner/Runner.entitlements 文件并添加以下 key 和 array 标记:
<?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 包:https://pub.dev/packages/uni_links 并将其粘贴到项目的 pubspec.yaml 文件中,如下所示:
---
dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.2uni_links: ^0.5.1 # <----------------
保存并执行 flutter pun get 以更新您的项目依赖项。
然后添加三个用户界面文件: 主屏幕、绿色宣传屏幕和红色宣传屏幕。
主屏幕文件 lib/screens/home_screen.dart :
import 'package:flutter/material.dart';class HomeScreen extends StatelessWidget {const HomeScreen({super.key});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 :
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});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 :
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});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 正常打开时显示主屏幕;
- 当我们收到 Unilink https://mypage.web.app/promos/?promo-id=ABC1 时,会显示绿色促销屏幕;
- 当我们收到 UniLink https://mypage.web.app/promos/?promo-id=ABC2 时,会显示红色促销屏幕。
现在让我们添加一个我在项目中经常使用的重要实用程序文件。有了它我们就可以在 APP 的任何地方访问最新的 BuildContext 。
添加此文件 lib/common/global_context/utils/contect_utility.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 的文件:
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 文件更改为:
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});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,查看是否出现主屏幕。

原文:https://medium.com/@pedrostick3/integrate-unilinks-with-flutter-android-applinks-ios-universallinks-c9a1542d6625
相关文章:
将 UniLinks 与 Flutter 集成(安卓 AppLinks + iOS UniversalLinks)
让我们使用 Flutter Mobile 和 Flutter Web 集成 UniLinks。 一步一步的指导! 我是 Pedro Dionsio,是葡萄牙 InspireIT 公司的 Flutter 开发人员,我写这个 UniLinks 教程的座右铭是: Firebase DynamicLinks 已被弃用࿰…...
Spring-Spring 之底层架构核心概念解析
BeanDefinition BeanDefinition表示Bean定义,BeanDefinition中存在很多属性用来描述一个Bean的特点。比如: class,表示Bean类型scope,表示Bean作用域,单例或原型等lazyInit:表示Bean是否是懒加载initMeth…...
电脑版WPS怎么将更新目录加到快速访问栏
效果 步骤 开启首页的“标签”、快速访问、在最近置顶展示然后选择好目录点击右边的设置 》 添加标签选中“快速访问”,确定并关闭完成 相关 WPS怎样设置快速访问工具栏 WPS怎样设置快速访问工具栏-百度经验WPS怎样设置快速访问工具栏,WPS怎样设置快速访问工具栏…...
保障效率与可用,分析Kafka的消费者组与Rebalance机制
系列文章目录 上手第一关,手把手教你安装kafka与可视化工具kafka-eagle Kafka是什么,以及如何使用SpringBoot对接Kafka 架构必备能力——kafka的选型对比及应用场景 Kafka存取原理与实现分析,打破面试难关 防止消息丢失与消息重复——Kafka可…...
“1-5-15”原则:中国联通数字化监控平台可观测稳定性保障实践
一分钟精华速览 “只知道系统有问题,但是找不到问题到底出在哪里”,这几乎是大家都面临过、或正在面临的问题。用户在投诉,但是我的指标都是正常的,到底是哪一环出问题了? 本文详细介绍了中国联通在智能运维领域的应用…...
LinkedList详解-Deque接口链表实现方案
简介 LinkedList实现了List和Deque接口。List是一个有序的集合,可以包含重复元素,它提供了在列表的头部、尾部或指定位置进行插入、删除和查找等操作的方法。Deque是双端队列,提供了在列表的头部和尾部进行插入和删除操作的方法。通过实现这…...
【考研数据结构代码题1】二叉搜索树的插入与查找
题目:请用C语言写出二叉树的二叉链表结构,并编写一个函数在二叉搜索树中可以搜索给定的关键字 难度:★ 二叉树的二叉链表结构 #include<stdio.h> #include<stdlib.h> //二叉树的结点结构 typedef struct Node{int data;//存放结…...
世微 平均电流型降压恒流驱动器 电动摩托车LED灯小钢炮驱动IC AP5218
1,来源:深圳市世微半导体有限公司 2,产品描述 AP5218 是一款 PWM工作模式, 高效率、外 围简单、内置功率管,适用于5V~100V输入的高 精度降压 LED 恒流驱动芯片。输出最大功率可达 15W,最大电流 1.5A。AP5…...
docker 下安装mysql8.0
在docker中查询mysql镜像 PS C:\Users\admin> docker search mysql NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql MySQL is a widely used, open-source relation……...
Android MVI架构的深入解析与对比
什么是MVI? M:model,此处的model并不是传统的数据模块,它是指用来存储视图状态UI State的一个模块 。比如请求数据时的loading、请求失败的提示页面等UI层面的变化状态。 V:view,视图模块 I:…...
达梦数据库表空间管理常用SQL
达梦数据库表空间管理常用SQL 表空间容量分析表空间创建与扩容 查看数据库状态: select name,instance_name,status$,mode$ from v$instance; --mode$显示Primary为主库select name,status$,role$ from v$database; --status$:1 启动,2 启动…...
Flutter 组件集录 | InheritedNotifier 内置状态管理组件
theme: cyanosis 1. 前言 在上一篇 《Flutter 知识集锦 | 监听与通知 ChangeNotifier》 中,我们介绍了 ChangeNotifier 对象通知监听者的能力。并通过一个简单的模拟下载进度案例,介绍了它的使用方式: | 案例演示 | 监听-通知关系 | | --- | …...
NOIP2023模拟10联测31 涂鸦
题目大意 有一面由 n m n\times m nm个格子组成的墙,每个格子要么是黑色,要么是白色。你每次将会进行这样的操作:等概率随机选择一个位置 ( x , y ) (x,y) (x,y)和一个颜色 c c c(黑色或白色),࿰…...
【Python基础知识一】基本语法、常用数据类型等
Python基础知识: 1 标识符(Identifier)2 关键字/保留字(Keyword)3 引号4 编码5 输入输出6 行与缩进7 多行语句8 注释9 数据类型9.1 数字(Number)类型9.2 变量(variate)9.3 字符串(St…...
听听ChatGPT对IT行业的发展和就业前景的看法
🌈个人主页: Aileen_0v0🔥系列专栏:PYTHON学习系列专栏💫"没有罗马,那就自己创造罗马~" 目录 (1)判断素数 写法1: 写法2: (2)计算1-100的偶数之和 写法1: 写法2: (3)计算1-100的奇数之和 (4)多层循环 IT行业哪个方向比较…...
〖程序员的自我修养 - 认知剖析篇⑤〗- 选择前端还是后端?
人之所以会觉得迷茫,本质上是欠缺对自己的一个控制力、识别庞杂信息、去伪存真的独立思考与认知能力。 说明:该文属于 程序员的自我修养 专栏,购买任意白宝书体系化专栏可加入易编程社区,早鸟价订阅模式除外。福利:加入社区的小伙伴们,除了可以获取博主所有付费专栏的阅读…...
Rust语言初步
文章目录 安装与测试变量条件语句和函数数组和元组循环 安装与测试 可以从官网直接下载。下载rustup-init并运行之后,会打开命令行,选1默认安装,然后不出意外就安装完了。 安装完成后按照惯例查看一下版本,如不报错就算成功。 …...
BIMILLC算法源码解析
论文链接:https://arxiv.org/abs/1607.02533 源码出处:https://github.com/Harry24k/adversarial-attacks-pytorch/tree/master 源码 import torch import torch.nn as nnfrom ..attack import Attackclass BIM(Attack):r"""BIM or iter…...
Android STR研究之五
前言: 在前四篇中初步介绍了开机流程,STR流程,唤醒流程,这里讲下STR的问题点 Android STR研究之一-CSDN博客 Android STR研究之二-CSDN博客 Android STR研究之三-CSDN博客 Android STR研究之四-CSDN博客 问题1:进入STR…...
python3+requests接口自动化测试实例详细操作
前段时间由于公司测试方向的转型,由原来的web页面功能测试转变成接口测试,之前大多都是手工进行,利用postman和jmeter进行的接口测试,后来,组内有人讲原先web自动化的测试框架移驾成接口的自动化框架,使用的…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
