Linking في React Native
خاصٌّ بالمشاريع المكتوبة بشفرة Native تطبّق هذه المقالة على المشاريع المكتوبة بشفرة Native فقط؛ أمّا عند استخدام سير العملexpo-cli
، فيمكن الاطّلاع على الدّليل Linking ضمن توثيق Expo لإيجاد البديل الملائم.
تُقدّم Linking
واجهةً عامّةً للتّفاعل مع الرّوابط الواردة، والصّادرة للتّطبيقات، ولكل رابط (URL) مخطّط URL
، مثل: البادئة 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 | فتح متصفح الويب مثل: 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;
التوابع
constructor()
constructor();
addEventListener()
addEventListener(type, handler);
يضيف معالجًا لتغيّرات الرّبط، بالتّنصّت على الحدث من نوع url
، وتزويد المعالج.
removeEventListener()
removeEventListener(type, handler);
يزيل المعالج بتمرير الحدث من نوع url
، والمعالج المطلوب.
openURL()
openURL(url);
يحاول فتح عنوان url
المعطى بواحدٍ من التّطبيقات الموجودة.
يمكن استخدام عناوين URLs أخرى كموقع (مثل: "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 بسبب عدم وجود وصولٍ إلى تطبيق الاتصال.
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
دومًا، ويمكن تصفير الحدّ المسموح عن طريق إعادة تنصيب التّطبيق، أو تحديثه.
openSettings()
openSettings();
فتح تطبيق الإعدادات (Settings) لإظهار الإعدادات الشّخصيّة للتّطبيقات إن وُجدت.
getInitialURL()
getInitialURL();
يعيد هذا التابع عنوان الرابط url
، إذا أطلِق التّطبيق عن طريق رابطه، وإلا سيعيد null
.
ملاحظة: لدعم الرّبط العميق على منصّة Android، يرجى زيارة deep-linking، على developer.android.
sendIntent()
sendIntent(action: string, extras?: Array<{key: string, value: string | number | boolean}>)
يعمل على منصّة Android فقط، ويستخدم لإطلاق نيّة الفعل (intent) مع الإضافات (اختيارية).