Integrate UniLinks with Flutter (Android AppLinks + iOS UniversalLinks)

Integrate UniLinks with Flutter (Android AppLinks + iOS UniversalLinks)

Let’s integrate UniLinks using Flutter Mobile and Flutter Web.

Step by step guide!

I’m Pedro Dionísio, a Flutter developer at InspireIT in Portugal, and my motto for writing this UniLinks tutorial is:

  1. Firebase DynamicLinks is deprecated, as Firebase says in its documentation and should not be implemented anymore (I'm using it and since it had some bugs and was deprecated, I decided to start migrating this type of Deeplink to UniLinks);
  2. This Deeplink method is used by big companies like TikTok, Instagram, Facebook, etc…
  3. I'm having some issues implementing it on some specific Android devices (trying to open and pass data to the app).

So, I will make all the steps crystal clear and explain everything, not only for Flutter Android and iOS, but also for Flutter Web and Firebase WebHosting, so that you don’t miss any steps. Let’s get started!

Introduction to Deep Linking

What is Deep Linking?

Deep Linking is like having a shortcut to certain parts of your app.

This is a special web link that not only opens your app, but takes you to a specific location within the app. Just like opening a book and going straight to the page you want to read.

How does it work?

Let's say you found a great article in the app and you want to share it with a friend. Instead of sending them to the app's homepage and asking them to look for the article, you can send them a special link that takes them directly to the article. It's like sending them a secret passage.

The coolest part?

The cool thing is that you can also send special instructions or codes through this link. For example, if there is a discount code or hidden surprise in the app, you can include it in the link. So, not only do you get to the right place quickly, but you also get some extra benefits.

What happens if the app is already open?

Sometimes, your app might already be open when you click a deep link. Don't worry! Deep links even work when the app is already running. It's like switching to the right page in the book you're reading.

Some final notes about UniLinks

In this tutorial, I’ll show you how to make deep linking super easy using a tool called “uni_links”.

It is important that in this type of deep linking, 2 profiles must be assigned in the website (one for Android and one for iOS). The meaning is because these files store important information about your application and through them your web browser knows exactly where to redirect inside the phone.

With that said, I’ll show you how to create a Flutter Web project and put these files in the right place.

Don't worry! It will be easy to implement! Let's get started! 📱🚀

Create a Flutter project for your mobile app

Android Configuration

Go to your project's android/app/src/main/AndroidManifest.xml file.

Here, we need to change some things, first replace android:launchMode="singleTop" with android:launchMode="singleTask" because we only want to open one instance of the APP in the phone.

It should look like this:

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

After that, in the same file, you need to configure your "APP Entry", which will be through a specific UniLink.

For example, we want to open the APP through this link: https://mypage.web.app/promos/?promo-id=ABC1.

So, inside your activity you would add an intent-filter like this:

 <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 Configuration

Using the same example, we want the app to open via this link: https://mypage.web.app/promos/?promo-id=ABC1 .

Go to your project's ios/Runner/Runner.entitlements file and add the following key and array tags:

 <?xml versinotallow="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>

You don't need to do this, but you can also configure this through XCode if you prefer:

  • Double-click the ios/Runner.xcworkspace file to open Xcode.
  • Go to the project navigator (Cmd+1) and select the top-most Runner root project;
  • Select the Runner target, then select the Signing & Capabilities tab;
  • Click the + Capability button to add a new capability.
  • Enter associated domains and select the project;
  • Double-click the first item in the Domains list and change it from webcredentials:example.com to applinks:mypage.web.app.
  • A file named Runner.entitlements is created and added to the project.

Flutter Implementation

I usually use a modular approach to organize everything, but for this example project I'm going to mix it up and keep everything simple and intuitive.

Let’s first get the latest version of the uni_links package here: https://pub.dev/packages/uni_links and paste it into your project’s pubspec.yaml file like this:

 --- dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 uni_links: ^0.5.1 # <----------------

Save and execute flutter pun get to update your project dependencies.

Then add three user interface files: the main screen, the green teaser screen, and the red teaser screen.

Home screen file lib/screens/home_screen.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, ), ), ), ); } }

Green promo screen file 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}); @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, ), ), ), ); } }

Red promo screen 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}); @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, ), ), ), ); } }

Why 3 screens? This is because we want to test 3 situations:

  • The main screen is displayed when the APP is opened normally;
  • When we receive Unilink https://mypage.web.app/promos/?promo-id=ABC1, a green promotion screen will be displayed;
  • When we receive UniLink https://mypage.web.app/promos/?promo-id=ABC2, a red promotion screen will be displayed.

Now let's add an important utility file that I use frequently in my projects. With it we can access the latest BuildContext anywhere in the app.

Add this file 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; }

Next we add the file responsible for processing 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()), ); } } }

Finally we change our main.dart file to:

 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(), }, ); } }

And we are done!

You can test by opening the app normally to see if the home screen appears.

picture

Original article: https://medium.com/@pedrostick3/integrate-unilinks-with-flutter-android-applinks-ios-universallinks-c9a1542d6625

<<:  How did Instagram reach 14 million users with only 3 engineers?

>>:  Exploration and practice of intelligent film performance optimization

Recommend

How did the last population of mammoths disappear from the earth?

As a species that "just" became extinct...

Data analysis from the perspective of product operation

What is Data Analytics? Data analysis is a goal, ...

After watching 1,000 videos, we summarized 10 ways to make money on Douyin!

In the past two days, I have watched more than 1,...

Which one is more suitable for merchants, Baidu Ai Purchasing or Baidu Bidding?

Both Aicaigou and Baidu Bidding can be displayed ...

Mobile Internet Channel Promotion Methodology (with case studies)

Friends often ask me that they spent a lot of mon...

CES Observation: Female users become the new favorite of smart hardware industry

Recently, CES 2016 closed successfully under the ...

No flowers to enjoy in May? Learn more about peonies

As the saying goes, "See peonies in the thre...

Imagination and Testin partner to establish testing center

Recently, Imagination Technologies announced that...

Baidu bidding promotion, how to optimize keyword quality?

In order to optimize the quality of keywords, you...