ການເຮັດ Deep Linking ສຳລັບ Flutter


ຜູ້ຂຽນໄດ້ມີໂອກາດພັດທະນາແອັບພລິເຄຊັນມືຖືໂດຍນຳໃຊ້ຟຼັດເຕີ (Flutter) ແລະສຶກສາເທັກນິກທີ່ນຳໃຊ້ຢູອາແອວ (URL) ເຊື່ອມໂຍງກັບແອັບພລິເຄຊັນທີ່ເອີ້ນວ່າ ດີບລິງຄິງ (Deep Linking) ກໍເລີຍຂຽນບົດຄວາມນີ້ເພື່ອແບ່ງປັນຄວາມຮູ້ໃຫ້ຜູ້ອ່ານນຳກັນ.

Deep Linking 101

ດີບລິງຄິງເປັນເທັກນິກທີ່ນຳໃຊ້ໃນການເປີດແອັບພລິເຄຊັນມືຖືຜ່ານຢູອາແອວເພື່ອເຂົ້າເຖິງເນື້ອຫາ ຫຼືຟັງຊັນໃດໜຶ່ງພາຍໃນແອັບນັ້ນໆ. ຜູ້ອ່ານອາດຈະເຄີຍເຫັນຄຸນລັກສະນະນີ້ຜ່ານແອັບມືຖືຢ່າງ: ແອັບອີຄອມເມີດ (E-Commerce) ໃນເວລາທີ່ເຮົາເປີດລິງສິນຄ້າຜ່ານເວັບບຣາວເຊີມືຖື ແລ້ວມີໜ້າຕ່າງແນະນຳໃຫ້ເປີດເບິ່ງຜ່ານແອັບ; ຫຼືອາດຈະເປັນແອັບອ່ານຂ່າວສານຢ່າງມີດຽມ (Medium) ທີ່ຈະເປີດລິງບົດຄວາມທີ່ເຜີຍແພ່ຜ່ານແພຼັດຟອມນີ້ຢູ່ແອັບໂດຍອັດຕະໂນມັດ ຖ້າວ່າເຮົາຕິດຕັ້ງແອັບໄວ້ແລ້ວ ເປັນຕົ້ນ.

ໂດຍທົ່ວໄປ, ດີບລິງຈະມີຢູ່ 2 ລັກສະນະດັ່ງຕໍ່ໄປນີ້:

  • ດີບລິງແບບກຳນົດສະຄີມເອງ (Custom Scheme): ເທັກນິກນີ້ເອີ້ນວ່າ Deep Link ແລະ Custom URL Scheme ສຳລັບລະບົບປະຕິບັດການແອນດຣອຍ ແລະໄອໂອເອສຕາມລຳດັບ; ໂດຍເທັກນິກນີ້, ເຮົາສາມາດກຳນົດສະຄີມຂອງຢູອາແອວໄດ້ຕາມຄວາມຕ້ອງການ. ຕົວຢ່າງ: ແອັບເຟດບຸກ (Facebook) ນຳໃຊ້ສະຄີມ fb:// ສຳລັບດີບລິງເພື່ອເຂົ້າເຖິງເນື້ອຫາຕ່າງໆພາຍໃນແພຼັດຟອມ.
  • ດີບລິງແບບນຳໃຊ້ສະຄີມ HTTP(S) (HTTP(S) Scheme): ເທັກນິກນີ້ເອີ້ນວ່າແອັບລິງ (App Link) ແລະ ຢູນິເວີແຊວລິງ (Universal Link) ສຳລັບລະບົບປະຕິບັດການແອນດຣອຍ ແລະໄອໂອເອສຕາມລຳດັບ; ໂດຍເທັກນິກນີ້ຈະນິຍົມໃຊ້ຮ່ວມກັບເວັບໄຊ ກໍຄືກຳນົດຮູບແບບໂຮສ (Host) ຂອງຢູອາແອວດີບລິງໃຫ້ກົງກັບທີ່ຢູ່ເວັບໄຊທີ່ຕ້ອງການ, ເມື່ອຜູ້ໃຊ້ກົດເປີດລິງດັ່ງກ່າວ ຖ້າຜູ້ໃຊ້ຕິດຕັ້ງແອັບໄວ້ ລະບົບກໍຈະຣັນແອັບທີ່ຮອງຮັບລິງໂດຍອັດຕະໂນມັດ, ແຕ່ຖ້າບໍ່ ລະບົບກໍຈະເປີດຜ່ານເວັບບຣາວເຊີຕາມປົກກະຕິ. ຕົວຢ່າງ: ຜູ້ອ່ານສາມາດກົດລິງວິດີໂອຢູທູບ (YouTube) ລິງນີ້ ເພື່ອທົດລອງໄດ້.

Deep Linking in Flutter

ຟຼັດເຕີຮອງຮັບການເຮັດດີບລິງເທິງລະບົບປະຕິບັດການແອນດຣອຍ ແລະໄອໂອເອສ, ໂດຍວິທີການພັດທະນາຟັງຊັນດີບລິງໃນຟຼັດເຕີແມ່ນເຮັດໄດ້ສອງແບບດັ່ງຕໍ່ໄປນີ້:

  • ໃຫ້ເນວິເກຊັນຈັດການດີບລິງ (Navigation-based): ສຳລັບວິທີນີ້, ຟຼັດເຕີຈະຈັດການດີບລິງທີ່ຜູ້ໃຊ້ກົດຜ່ານເຣົາເຕີທີ່ເຮົາກຳນົດສຳລັບແອັບ ກໍຄືຈະພາຜູ້ໃຊ້ໄປຫາໜ້າທີ່ກຳນົດພາດ (Path) ຕາມພາດຂອງຢູອາແອວນັ້ນ. ຕົວຢ່າງ: ເມື່ອຜູ້ໃຊ້ກົດລິງ https://example.com/about, ມັນກໍຈະເປີດໜ້າແອັບທີ່ໃຊ້ພາດ /about ເປັນຕົ້ນ.
  • ນຳໃຊ້ປຼັກອິນ (Plugin-based): ສຳລັບວິທີນີ້, ເຮົາຈະຈັດການລິງທີ່ຜູ້ໃຊ້ກົດເຂົ້າມາຜ່ານປຼັກອິນ, ເຊິ່ງປຼັກອິນດັ່ງກ່າວແມ່ນເຮັດວຽກໂດຍການສົ່ງລິງຈາກແພຼັດຟອມມາຍັງຟຼັດເຕີຜ່ານເມທອດແຊແນວ (Method Channel), ໂດຍເຮົາສາມາດກຳນົດການເຮັດວຽກຕໍ່ຈາກນັ້ນໄດ້ຕາມຕ້ອງການ.

ສຳລັບການຕັ້ງຄ່າເພື່ອໃຫ້ແອັບຂອງເຮົາຮອງຮັບດີບລິງນັ້ນ ເຮົາຕ້ອງໄດ້ກຳນົດຄ່າຕ່າງໆເຊັ່ນ: ສະຄີມ, ໂຮສ, ພາດທີ່ຈະອະນຸຍາດໃຫ້ແອັບເປີດຢູ່ຟາຍຕັ້ງຄ່າຂອງແຕ່ແພຼັດຟອມ ກໍຄືຟາຍ AndroidManifest.xml ແລະ info.plist ສຳລັບລະບົບປະຕິບັດການແອນດຣອຍ ແລະໄອໂອເອສຕາມລຳດັບ; ໂດຍລາຍລະອຽດແມ່ນຈະໄດ້ນຳສະເໜີໃນຫົວຂໍ້ຕໍ່ໄປ.

Demo Project

ເພື່ອເຮັດໃຫ້ຜູ້ອ່ານເຫັນພາບກ່ຽວກັບການປັບໃຊ້ຟັງຊັນດີບລິງເຂົ້າໃນແອັບ, ຜູ້ຂຽນໄດ້ສ້າງໂປຣເຈັກໜຶ່ງຂຶ້ນມາເພື່ອສາທິດການປັບໃຊ້ ແລະການເຮັດວຽກຂອງດີບລິງແບບໃຊ້ເນວິເກຊັນ; ໂດຍໂປຣເຈັກນີ້ຈະຈຳລອງເປັນແອັບຂອງຮ້ານກາເຟແຫ່ງໜຶ່ງທີ່ມີເມນູໃຫ້ເລືອກຫຼາກຫຼາຍລາຍການ ແລະຮອງຮັບດີບລິງທີ່ສາມາດນຳພາຜູ້ໃຊ້ໄປຫາໜ້າເມນູທີ່ຕ້ອງການໄດ້. ຖ້າຫາກຜູ້ອ່ານທ່ານໃດຕ້ອງການເບິ່ງໂຄດຂອງໂປຣເຈັກກໍສາມາດເຂົ້າໄປເບິ່ງໄດ້ທີ່ລິງລຸ່ມນີ້.

https://github.com/mrarty55/valentine_cafe_app

ໝາຍເຫດ: ຜູ້ຂຽນຈະບໍ່ພາຜູ້ອ່ານທົດລອງເຮັດໂປຣເຈັກ ແຕ່ຈະອະທິບາຍສ່ວນທີ່ສຳຄັນຂອງການເຮັດດີບລິງຢູ່ໃນໂປຣເຈັກຕົວຢ່າງແທນ.

Configuration

ຢ່າງທຳອິດທີ່ເຮົາຕ້ອງເຮັດກໍຄື ການຕັ້ງຄ່າເພື່ອໃຫ້ແອັບຂອງເຮົາຮອງຮັບດີບລິງໃນແຕ່ລະແພຼັດຟອມ; ສຳລັບແອນດຣອຍແມ່ນໃຫ້ເຂົ້າໄປແກ້ໄຂຟາຍ AndroidManifest.xml ທີ່ຢູ່ໃນໄດເຣັກໂທຣີ android/src/main ໂດຍຂຽນໂຄດຕື່ມໃສ່ພາຍໃນແທັກ activity ດັ່ງຕົວຢ່າງລຸ່ມນີ້:

<!-- Add following lines of code inside activity tag -->
<!-- Add this line if you're implementing navigation-base deep linking-->
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
<!-- Custom scheme URL a.k.a. deep link -->
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="YOUR_SCHEME" android:host="YOUR_HOST" />
</intent-filter>
<!-- HTTPS web-based scheme a.k.a. app link -->
<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="YOUR_HOST" />
</intent-filter>

ສຳລັບສ່ວນການຕັ້ງຄ່າຂອງແອັບລິງນັ້ນ, ສັງເກດວ່າຈະມີແອັດທຣິບິວ android:autoVerify ຢູ່ນຳ, ໂດຍແອັດທຣິບິວນີ້ຈະເປັນໂຕບົ່ງບອກໃຫ້ແພຼັດຟອມຮູ້ວ່າ ທີ່ຢູ່ຢູອາແອວທີ່ກຳນົດນັ້ນຂຶ້ນກັບແອັບຂອງເຮົາ. ນອກຈາກນີ້, ເຮົາຕ້ອງໄດ້ໂຮສຟາຍດິຈິຕອນແອັດເຊສລິງ (Digital Asset Link) ໄວ້ໃນທີ່ຢູ່ດັ່ງນີ້: https://yourdomainname.com/.well-known/assetlinks.json, ເພື່ອໃຫ້ເຊີວິດຂອງກູເກິລຮູ້ວ່າເວັບ ແລະແອັບນັ້ນກ່ຽວພັນກັນ (Associated).

ຕົວຢ່າງຂອງຟາຍ assetlinks.json:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target" : { "namespace": "your_app_name", "package_name": "your_app_package_name",
               "sha256_cert_fingerprints": ["hash_of_app_certificate"] }
}]

ໃນສ່ວນຂອງໄອໂອເອສແມ່ນເຂົ້າໄປແກ້ໄຂຟາຍ Info.plist ທີ່ຢູ່ໃນໄດເຣັກໂທຣີ ios/Runner ໂດຍຂຽນໂຄດຕື່ມໃສ່ດັ່ງຕົວຢ່າງຕໍ່ໄປນີ້:

<!-- Add this line if you're implementing navigation-base deep linking-->
<key>FlutterDeepLinkingEnabled</key>
<true/>
<!-- Config your preferred link here-->
<key>CFBundleURLTypes</key>
<array>
    <dict>
    <key>CFBundleTypeRole</key>
    <string>Editor</string>
    <key>CFBundleURLName</key>
    <string>YOUR_HOST</string>
    <key>CFBundleURLSchemes</key>
    <array>
    <string>YOUR_SCHEME</string>
    </array>
    </dict>
</array>

ອີກວິທີໜຶ່ງແມ່ນແກ້ໄຂຜ່ານ Xcode ໂດຍມີຂັ້ນຕອນດັ່ງຕໍ່ໄປນີ້:

  • ເລືອກເມນູ File > Open ຫຼືກົດ Command + O ແລ້ວເລືອກ Runner.xcworkspace ທີ່ຢູ່ໃນໄດເຣັກໂທຣີ ios
  • ເລືອກ Runner > Info ແລ້ວເພີ່ມພຣອບເພີຕີ (Property)CFBundleURLTypes ໃນໝວດ Custom iOS Target Properties.
  • ຈາກນັ້ນ ເຮົາຈະເຫັນພຣອບທີ່ຊື່ວ່າ “URL Types”, ແລ້ວເຮົາກໍເພີ່ມຄ່າດັ່ງຮູບຕົວຢ່າງລຸ່ມນີ້:
  • ຖ້າເຮົາຈະເຮັດດີບລິງແບບນຳໃຊ້ເນວີເກຊັນແມ່ນໃຫ້ເພີ່ມພຣອບເພີຕີ FlutterDeepLinkingEnabled ແລ້ວກຳນົດຄ່າໃຫ້ເປັນ true.

ສຳລັບຢູນິເວີແຊວລິງນັ້ນ, ເຮົາຕ້ອງໄດ້ເພີ່ມ Associated Domains ກໍຄືຊື່ໂດເມນທີ່ເຮົາຈະໃຊ້ກັບດີບລິງຂອງເຮົາ ເຊິ່ງສາມາດເຮັດໄດ້ໂດຍການສ້າງຟາຍ Runner.entitlements ຢູ່ໃນໄດເຣັກໂທຣີ ios/Runner ແລ້ວຂຽນໂຄດດັ່ງຕົວຢ່າງລຸ່ມນີ້:

<?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:YOUR_HOST</string>
 </array>
</dict>
</plist>

ອີກວິທີໜຶ່ງແມ່ນສ້າງຢູ່ Xcode ໂດຍເລືອກແທັບ Signing & Capabilities ແລ້ວກົດປຸ່ມ “+ Capability” ແລ້ວເລືອກ “Associated Domain”, ຫຼັງນັ້ນກໍຈະຂຶ້ນບຼັອກໃຫ້ເຮົາແກ້ໄຂຄ່າດັ່ງຮູບຕົວຢ່າງລຸ່ມນີ້:

ນອກຈາກນີ້, ເຮົາຕ້ອງໄດ້ໂຮສຟາຍ apple-app-site-association ໃນລັກສະນະດຽວກັນກັບຂອງແອນດຣອຍຄື: https://yourdomainname.com/.well-known/apple-app-site-association ໂດຍຟາຍດັ່ງກ່າວຈະຢູ່ໃນຮູບແບບເຈຊັນ (JSON) ດັ່ງຕົວຢ່າງລຸ່ມນີ້:


{
  "applinks": {
      "details": [
           {
             "appIDs": [ "YOUR_TEAM_ID.your.app.bundleId", "YOUR_TEAM_ID.your.app.bundleId2" ],
             "components": [
               {
                  "#": "no_universal_links",
                  "exclude": true,
                  "comment": "Matches any URL with a fragment that equals no_universal_links and instructs the system not to open it as a universal link."
               },
               {
                  "/": "/buy/*",
                  "comment": "Matches any URL with a path that starts with /buy/."
               },
               {
                  "/": "/help/website/*",
                  "exclude": true,
                  "comment": "Matches any URL with a path that starts with /help/website/ and instructs the system not to open it as a universal link."
               },
               {
                  "/": "/help/*",
                  "?": { "articleNumber": "????" },
                  "comment": "Matches any URL with a path that starts with /help/ and that has a query item with name 'articleNumber' and a value of exactly four characters."
               }
             ]
           }
       ]
   },
}

Implementation

ຈາກໂປຣເຈັກຕົວຢ່າງ, ຜູ້ຂຽນໄດ້ນຳໃຊ້ go_router ໃນສ່ວນຂອງເນວີເກຊັນ ເຊິ່ງໄລບຣານີ້ຮອງຮັບການເຮັດດີບລິງ. ຢູ່ໃນແອັບຈະມີຢູ່ສອງໜ້າ (ດັບເບິ້ນໜ້າ) ກໍຄືໜ້າຫຼັກ HomeScreen ທີ່ສະແດງເມນູຕ່າງໆທີ່ມີຢູ່ພາຍໃນຮ້ານ ແລະໜ້າສະແດງລາຍລະອຽດ CoffeeScreen ທີ່ສະແດງລາຍລະອຽດຂອງເມນູທີ່ເຮົາເລືອກ ໂດຍກວດຈາກອາກິວເມນ (Argument) id ດັ່ງຕົວຢ່າງລຸ່ມນີ້:

import 'package:go_router/go_router.dart';

import '../screens/screens.dart';

const String homeRoute = '/';

const String coffeeRoute = '/coffee/:id';

/// Router configuration for this app
final routerConfig = GoRouter(
  initialLocation: homeRoute,
  routes: <GoRoute>[
    GoRoute(
      path: homeRoute,
      builder: (context, state) => const HomeScreen(),
    ),
    GoRoute(
      path: coffeeRoute,
      builder: (context, state) =>
          CoffeeScreen(idParam: state.params['id'] ?? ""),
    ),
  ],
  errorBuilder: (context, state) => const ErrorScreen(),
);

ເຮົາສາມາດທົດສອບການເຮັດວຽກຂອງດີບລິງໄດ້ໂດຍການໃຊ້ຄຳສັ່ງ adb ແລະ xcrun simctl ສຳລັບແອນດຣອຍ ແລະໄອໂອເອສຕາມລຳດັບ. ຕົວຢ່າງ:

# Test deep link on Android
# Replace YOUR_SCHEME and YOUR_HOST with yours
adb shell 'am start -W -a android.intent.action.VIEW \
 -c android.intent.category.BROWSABLE \
 -d "YOUR_SCHEME://YOUR_HOST/path/to/test"' \
 your.app.package.name
# Test deep link on iOS simulator
# Assume Simulator.app is already running
# Replace YOUR_SCHEME and YOUR_HOST with yours
xcrun simctl openurl booted "YOUR_SCHEME://YOUR_HOST/path/to/test"

Bonus: Plugin-based Deep Link

ໃນບາງກໍລະນີ, ດີບລິງແບບທີ່ໃຊ້ເນວິເກຊັນອາດຈະບໍ່ຕອບໂຈດຂອງແອັບເຮົາເຊັ່ນ: ຕ້ອງການໃຫ້ດີບລິງທຣິກເກີຟັງຊັນໃດໜຶ່ງພາຍໃນແອັບ; ກໍລະນີນີ້ກໍສາມາດນຳໃຊ້ປຼັກອິນແທນໄດ້. ໂດຍສ່ວນຕົວ, ຜູ້ຂຽນໄດ້ນຳໃຊ້ປຼັກອິນ uni_links ສຳລັບຈັດການດີບລິງ ໂດຍມີວິທີນຳໃຊ້ດັ່ງຕໍ່ໄປນີ້:

  • ກໍລະນີທີ່ເປີດແອັບເທື່ອທຳອິດ (ບໍ່ໄດ້ຣັນຢູ່ເບື້ອງຫຼັງ) ແມ່ນຈະເອີ້ນໃຊ້ຟັງຊັນ getInitialUri() ເພື່ອຮັບຄ່າລິງ ເຊິ່ງຟັງຊັນນີ້ຈະຖືກເອີ້ນໃຊ້ພຽງເທື່ອດຽວເທົ່ານັ້ນ.
  • ກໍລະນີທີ່ແອັບຣັນຢູ່ບໍ່ວ່າຈະຢູ່ເບື້ອງໜ້າຫຼືເບື້ອງຫຼັງ ແມ່ນຈະໃຊ້uniLinkStreamທີ່ເປັນStreamSubscriptionເພື່ອຕິດຕາມ ແລະຈັດການລິງທີ່ຮັບເຂົ້າມາ.

ຕົວຢ່າງການນຳໃຊ້ປຼັກອິນສຳລັບພັດທະນາດີບລິງ:

class DeepLinkService {
  StreamSubscription? streamSubscription;
  
  Future<Uri?> getInitLink() async {
    try {
      final initialLink = await getInitialUri();
      return initialLink;
    } catch (e) {
      rethrow;
    }
  }

  void linkListener() {
    streamSubscription = uriLinkStream.listen((Uri? link) async {
      if (link != null) {
          // Handle incoming link here
       }
    }, onError: (error, stackTrace) {
        // Handle error
    });
  }
}
void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final deepLinkService = DeepLinkService();

  var initialLink = await deepLinkService.getInitLink();

  deepLinkService.linkListener();

  // Pass an initial link to your app
  runApp(YourApp(initialLink: initialLink));
}

Further Resources

https://docs.flutter.dev/development/ui/navigation/deep-linking

https://developer.android.com/training/app-links

https://developer.apple.com/documentation/xcode/allowing-apps-and-websites-to-link-to-your-content

Flutter Deep Linking Mobile Development

support by: LAOITDEV