الواجهة Linking في React Native

من موسوعة حسوب
< ReactNative
مراجعة 14:13، 9 أكتوبر 2021 بواسطة جميل-بيلوني (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)
اذهب إلى التنقل اذهب إلى البحث

خاصٌ بالمشاريع المكتوبة بشفرة Native

تطبّق هذه المقالة على المشاريع المكتوبة بشفرة Native فقط؛ أمّا عند استخدام سير العمل expo-cli، فيمكن الاطّلاع على الدّليل Linking ضمن توثيق Expo لإيجاد البديل الملائم.

تُقدّم الواجهة Linking واجهةً عامّةً للتّفاعل مع الرّوابط الواردة، والصّادرة للتّطبيقات، ولكل رابط (URL) نوع يحدد ببادئة الرابط (يطلق عليه أحيانًا تخطيط العنوان أو URL schema)، مثل: البادئة http، أو https في عنوان الموقع، كما يوجد رابط mailto، الذي يقوم نظام التّشغيل -عند فتح رابطٍ يحويه- بفتح تطبيق البريد الإكترونيّ المنصّب على الجهاز، كما توجد روابط للاتّصال الهاتفيّ، وإرسال الرّسائل النّصّيّة، ستُشرح أنواع الرّوابط الضّمنيّة built-in URL لاحقًا بالتّفصيل.

يمكن الرّبط مع التّطبيقات الأخرى -بشكلٍ مماثلٍ للرابط mailto- باستخدام نوع URL المخصصة، مثلًا: عند استلام البريد الإلكتروني Magic Link من تطبيق Slack سيكون المفتاح Launch Slack هو وسم الربط (anchor tag)، والذي يتضمّن رابطًا مرجعيّا فائقًا (href) له الشّكل التّالي: slack://secret/magic-login/other-secret يمكننا إخبار نظام التّشغيل بأننا نريد معالجة مخطط مخصّص كما في Slack، عند فتح تطبيق Slack فإنّه يستقبل الرّابط المُستخدَم في فتحه، ويمكن بنفس الطّريقة ربط أيّ تطبيقٍ باستخدام المخطّطات المخصّصة، وهو ما يسمى بالرّبط العميق (deep linking) والذي سيُشرح لاحقًا بالتّفصيل.

روابط URL المخصصة ليست الطّريقة الوحيدة لفتح التّطبيقات على الهاتف المحمول، حيث يفضّل عدم استخدمها مع روابط البريد الإلكتروني لأن ذلك سيؤدّي إلى تعطيلها على سطح المكتب، لكن يمكن استخدامها مع روابط https الاعتياديّة، مثل: https://www.myapp.io/records/1234546 يستخدم هذا الرّابط لفتح تطبيقٍ ما على الهاتف المحمول، وتدعى هذه الرّوابط بالروابط العميقة (Deep Links) على منصّة Android؛ أمّا على منصّة iOS فتدعى بالرّوابط الشّاملة (Universal Links).

مخططات الروابط الضمنية

إنّ هذه المخطّطات من الوظائف الأساسيّة، وتتوافر في الأنظمة كلّها، وأكثرها استخدامًا:

نوع الرابط الوصف iOS Android
mailto فتح تطبيق البريد الإلكتروني، مثل : mailto:support@expo.io
tel فتح نافذة الاتصال للاتصال بالرقم المعطى، مثل: tel:+123456789
sms فتح تطبيق الرسائل النصية، مثل: sms:+123456789
https / http فتح متصفح الويب مثل: https://expo.io

تفعيل الروابط العميقة

على منصة Android

لمعرفة كيفيّة دعم الرّبط العميق على منصّة Android يمكن زيارة تفعيل الروابط العميقة لمحتوى التطبيق - إضافة الفلاتر إليها.

عند الرّغبة باستقبال نيّة الفعل (intent) على النسخة الموجودة من MainActivity، يجب ضبط launchMode الخاصّ بنسخة MainActivity للقيمة singleTask في AndroidManifest.xml، ويمكن الاطّلاع على <activity> لمزيدٍ من المعلومات.

<activity
  android:name=".MainActivity"
  android:launchMode="singleTask">

على منصة IOS

ملاحظة: يجب ربط RCTLinking مع المشروع وفق الخطوات المشروحة هنا، ويجب إضافة السّطور البرمجيّة التاليّة للملف ‎*AppDelegate.m عند الرغبة بالتّنصّت على روابط التّطبيق الواردة أثناء فترة عمل هذا التّطبيق :

// iOS 9.x or newer
#import <React/RCTLinkingManager.h>

- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

أمّا في منصّة IOS ذات الإصدار 8.x، أو الأقدم منه، فتستخدم السّطور البرمجيّة التّالية عوضًا عن الأسطر السّابقة:

// iOS 8.x or older
#import <React/RCTLinkingManager.h>

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
  return [RCTLinkingManager application:application openURL:url
                      sourceApplication:sourceApplication annotation:annotation];
}

يجب إضافة السّطور البرمجيّة التّالية أيضًا، إذا استخدم التطبيق الرّوابط الشّاملة (Universal Links):

- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
 return [RCTLinkingManager application:application
                  continueUserActivity:userActivity
                    restorationHandler:restorationHandler];
}

معالجة الروابط العميقة

تعالَج الرّوابط URL التي تفتح التّطبيقات بطريقتين:

  • إذا كان التّطبيق مفتوحًا مسبقًا، فإنه يُجلَب للواجهة، ويُطلق حدث الربط، باستخدام التّابع ‫‪‎‏‪ ‎‏.‪Linking.addEventListener(url, callback)
  • إذا كان التطبيق يفتَح للمرّة الأولى، فإنّه يُفتح، ويُمرَّر الرّابط url كرابطٍ مبدئيٍّ InitialURL، باستخدام التّابع ()Linking.getInitialURL الذي يعيد وعداً (Promise) يُقبل كرابط url -عند وجوده-.

مثال

فتح الروابط الضمنية والعميقة (والشاملة)

إليك المثال (تجربة حية):

import React, { useCallback } from "react";
import { Alert, Button, Linking, StyleSheet, View } from "react-native";

const supportedURL = "https://google.com";

const unsupportedURL = "slack://open?team=123456";

const OpenURLButton = ({ url, children }) => {
  const handlePress = useCallback(async () => {
    // Checking if the link is supported for links with custom URL scheme.
    const supported = await Linking.canOpenURL(url);

    if (supported) {
      // Opening the link with some app, if the URL scheme is "http" the web link should be opened
      // by some browser in the mobile
      await Linking.openURL(url);
    } else {
      Alert.alert(`Don't know how to open this URL: ${url}`);
    }
  }, [url]);

  return <Button title={children} onPress={handlePress} />;
};

const App = () => {
  return (
    <View style={styles.container}>
      <OpenURLButton url={supportedURL}>Open Supported URL</OpenURLButton>
      <OpenURLButton url={unsupportedURL}>Open Unsupported URL</OpenURLButton>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});

export default App;

فتح الإعدادات الشخصية

إليك المثال (تجربة حية):

import React, { useCallback } from "react";
import { Button, Linking, StyleSheet, View } from "react-native";

const OpenSettingsButton = ({ children }) => {
  const handlePress = useCallback(async () => {
    // Open the custom settings if the app has one
    await Linking.openSettings();
  }, []);

  return <Button title={children} onPress={handlePress} />;
};

const App = () => {
  return (
    <View style={styles.container}>
      <OpenSettingsButton>Open Settings</OpenSettingsButton>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});

export default App;

الحصول على الرابط العميق

إليك المثال (تجربة حية):

import React, { useState, useEffect } from "react";
import { Linking, StyleSheet, Text, View } from "react-native";

const useMount = func => useEffect(() => func(), []);

const useInitialURL = () => {
  const [url, setUrl] = useState(null);
  const [processing, setProcessing] = useState(true);

  useMount(() => {
    const getUrlAsync = async () => {
      // Get the deep link used to open the app
      const initialUrl = await Linking.getInitialURL();

      // The setTimeout is just for testing purpose
      setTimeout(() => {
        setUrl(initialUrl);
        setProcessing(false);
      }, 1000);
    };

    getUrlAsync();
  });

  return { url, processing };
};

const App = () => {
  const { url: initialUrl, processing } = useInitialURL();

  return (
    <View style={styles.container}>
      <Text>
        {processing
          ? `Processing the initial url from a deep link`
          : `The deep link is: ${initialUrl || "None"}`}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});

export default App;

إرسال نية الفعل (intent) على منصة Android

إليك المثال (تجربة حية):

import React, { useCallback } from "react";
import { Alert, Button, Linking, StyleSheet, View } from "react-native";

const SendIntentButton = ({ action, extras, children }) => {
  const handlePress = useCallback(async () => {
    try {
      await Linking.sendIntent(action, extras);
    } catch (e) {
      Alert.alert(e.message);
    }
  }, [action, extras]);

  return <Button title={children} onPress={handlePress} />;
};

const App = () => {
  return (
    <View style={styles.container}>
      <SendIntentButton action="android.intent.action.POWER_USAGE_SUMMARY">
        Power Usage Summary
      </SendIntentButton>
      <SendIntentButton
        action="android.settings.APP_NOTIFICATION_SETTINGS"
        extras={[
          { "android.provider.extra.APP_PACKAGE": "com.facebook.katana" },
        ]}
      >
        App Notification Settings
      </SendIntentButton>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center", alignItems: "center" },
});

export default App;

التوابع

addEventListener()‎

addEventListener(type, handler);

يضيف معالجًا لتغيّرات الرّبط، بالتّنصّت على الحدث من نوع url، وتزويد المعالج.

canOpenURL()‎

canOpenURL(url);

يقرر وجود (أو عدم وجود) تطبيقٍ على الجهاز ، يكون قادرًا على معالجة عنوان url المعطى.

يعيد هذا التابع وعدًا Promise يقبل عندما يقرر إمكانيّة (أو عدم إمكانيّة) معالجة العنوان url المعطى، ويكون معامله الأول هو إمكانية فتحه، ويرفَض هذا الوعد عندما لا يكون فحص إمكانيّة فتح url ممكنًا، كما يُرفض على منصّة IOS، إذا لم يوضع مخطّطٌ محدّدٌ في المفتاح LSApplicationQueriesSchemes داخل الملف Info.plist.

المعاملات:

الاسم النوع مطلوب الوصف
url سلسلة نصية (string) نعم الرابط الذي سيتم فتحه

تنبيهات:

  • يجب وضع البروتوكول ("http://", "https://‎") بشكلٍ موافقٍ لعناوين URL الخاصّة بالويب.
  • في منصّة IOS ذات الإصدار 9، يجب وضع مخطّطٍ محدّدٍ في المفتاح LSApplicationQueriesSchemes داخل الملف Info.plist، وإلّا سيعيد هذا التابع القيمة false دائمًا.
  • يعمل هذا التابع بشكلٍ محدودٍ على منصّة IOS ذات الإصدار 9 وما بعده، حسب الموقع الرسميّ لـ the official Apple documentation.
  • يمكن تكرار استدعاء هذا التابع حتى 50 مرّةٍ عند ربط التطبيق على إصدارات IOS الأقدم من 9، وتشغيله على الإصدار 9، وما بعده. فإذا تجاوز عدد مرات استدعائه الحدّ المسموح به، فسيعيد القيمة false دومًا، ويمكن تصفير الحدّ المسموح عن طريق إعادة تنصيب التّطبيق، أو تحديثه.
  • بدءًا من الإصدار 9 من منصة iOS، يجب أن يوفر تطبيق المفتاح LSApplicationQueriesSchemes داخل Info.plist أو سيعيد canOpenURL()‎ القيمة false دومًا.
  • إن أردت استهداف منصة Android ذات الإصدار 11 (SDK 30)، فيجب أن تحدد البروتوكولات التي تنوي معالجتها داخل AndroidManifext.xml، ويمكن أن تجد قائمة كاملة بتلك النوايا intent الشائعة هنا. فمثلًا، إن أردت معالجة البروتوكول https، فيجب أن تضيف ما يلي ضمن البيان manifest.
<manifest ...>
    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="https"/>
        </intent>
    <queries>
</manifest>

getInitialURL()‎

getInitialURL();

يعيد هذا التابع عنوان الرابط url، إذا أطلِق التّطبيق عن طريق رابطه، وإلا سيعيد null.

ملاحظة: لدعم الرّبط العميق على منصّة Android، يرجى زيارة deep-linking، على developer.android.

ملاحظة: قد يعيد هذا التابع القيمة null إن كان وضح التنقيح debugging مفعلًا، لذا تأكد من تعطيل ذلك الوضع ليعمل عملًا صحيحًا.

openSettings()‎

openSettings();

فتح تطبيق الإعدادات (Settings) لإظهار الإعدادات الشّخصيّة للتّطبيقات إن وُجدت.

openURL()‎

openURL(url);

يحاول فتح عنوان url المعطى بواحدٍ من التّطبيقات الموجودة.

يمكن استخدام عناوين URL أخرى كموقع (مثل: "geo:37.484847،-122.148386" على منصّة Android، و"maps.apple " على منصّة iOS)، أو جهات الاتصال (contact)، أو أيّ عناوين يمكن فتحها بالتّطبيقات.

يعيد هذا التابع وعدًا Promise يقبل عند موافقة المستخدم على مربع الحوار المفتوح، أو إذا كان الرابط url يفتح آليًا، ويُرفض إذا ألغى المستخدم مربع الحوار، أو عند عدم وجود تطبيقٍ مُسجّلٍ للعنوان url.

المعاملات:

الاسم النوع مطلوب الوصف
url سلسلة نصية (string) نعم الرابط الذي سيتم فتحه

تنبيهات:

  • سيفشل هذا التابع، إذا لم يعلم كيفيّة فتح عنوان URL المحدّد، لذا يجب استخدام code canOpenURL@ عند تمرير عناوين URL ليست من نوع ‪‪‎‏‪‫‪.http(s)
  • يجب وضع البروتوكول ("http://", "https://‎") بشكلٍ موافقٍ لعناوين URL الخاصّة بالويب.
  • قد يعمل هذا التابع بطريقة مختلفة على المحاكي (simulator)، فمثلًا: لا تعالج الرّوابط ":‫tel" على محاكي IOS بسبب عدم وجود وصولٍ إلى تطبيق الاتصال.

removeEventListener()‎

removeEventListener(type, handler);

يزيل المعالج بتمرير الحدث من نوع url، والمعالج المطلوب.

تنبيه: أهمل هذا التابع، لذا استعمل التابع remove()‎ مع إشتراك الحدث الذي يعيده التابع addEventListener()‎.

sendIntent()‎

sendIntent(action: string, extras?: Array<{key: string, value: string | number | boolean}>)

يعمل على منصّة Android فقط، ويستخدم لإطلاق نيّة الفعل (intent) مع الإضافات (اختيارية).

المعاملات:

الاسم النوع مطلوب
action سلسلة نصية (string) نعم
extras مصفوفة من:

{key: string, value: string \| number \| boolean}

لا

مصادر