EduFlow

الوحدة 4 · البيانات والجهاز

الأذونات: الطلب في اللحظة المناسبة

17 دقراءة 3 دبيئة تجريب الكود

الدرس في لمحة

اطلب الوصول إلى الموقع والكاميرا في اللحظة التي يكون فيها الطلب منطقيًا، وتعامل مع كل حالات الإذن بما فيها الرفض الدائم، واكتب أوصاف الاستخدام التي يشترطها iOS وتشترطها مراجعة المتاجر.

  1. 1الحالات التي يمكن أن يكون فيها الإذن
  2. 2اسأل في السياق، بعد أن تشرح
  3. 3أوصاف الاستخدام والـ config plugins
  4. 4المقدّمة أولًا، والخلفية فقط عند الضرورة

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

أفضل ميزة في Trailhead هي التتبّع الحيّ: ابدأ رحلة، فيسجّل التطبيق مسارك. يحتاج ذلك إلى موقع الهاتف، والهاتف لن يعطيه دون أن يسأل المستخدم. وهذا الطلب أكثر لحظة هشاشة في التطبيق. اسأل بطريقة سيئة فتخسر الميزة لدى ذلك المستخدم، وأحيانًا إلى الأبد.

الحالات التي يمكن أن يكون فيها الإذن

كل وحدة في Expo تحتاج إلى إذن (الموقع، الكاميرا، مكتبة الوسائط، الإشعارات) تُرجع شكل الاستجابة نفسه:

TypeScript
{
  status: 'undetermined' | 'granted' | 'denied',
  granted: boolean,
  canAskAgain: boolean,
  expires: 'never' | number,
}
  • undetermined: لم تسأل قط. والطلب سيعرض نافذة النظام.
  • granted: تابع. وفي إصدارات iOS وAndroid الحديثة، قد يمنح المستخدمون الوصول مرة واحدة فقط (لهذه الجلسة)، أو الموقع التقريبي فقط، فكن مستعدًا لأن تسأل مرة أخرى في يوم آخر، وأن تتلقّى مواقع أقل دقّة.
  • denied مع canAskAgain: true: Android بعد رفض واحد. يمكنك أن تسأل مرة أخرى، ويُفضَّل بعد شرح أفضل.
  • denied مع canAskAgain: false: لن يعرض النظام النافذة مرة أخرى. على iOS هذه هي الحالة مباشرة بعد أول رفض؛ وعلى Android بعد أن يرفض المستخدم مرتين. ولا يستطيع تغييرها الآن إلا تطبيق الإعدادات.
من حالة undetermined، يشرح التطبيق ثم يطلب. يمنح المستخدم الإذن أو يرفضه. المنح يقود إلى الميزة. والرفض مع canAskAgain بقيمة true يسمح بالطلب مجددًا؛ أما الرفض مع canAskAgain بقيمة false فلا يُغيَّر إلا بفتح الإعدادات. undetermined اشرح، ثم request…() granted: الميزة تعمل denied canAskAgain? نعم لا: افتح الإعدادات
إلى أين تقود كل حالة إذن

اسأل في السياق، بعد أن تشرح

أسوأ نمط هو الذي تطلقه تطبيقات كثيرة: سلسلة من نوافذ النظام عند أول تشغيل، قبل أن يعرف المستخدم ما يفعله التطبيق. الناس يرفضون ما لا يفهمونه، وعلى iOS يكون ذلك الـ “Don’t Allow” الأول دائمًا ما لم ينقّبوا في الإعدادات.

يطلب Trailhead الموقع حين ينقر المستخدم على Start tracking، وهي أول لحظة يرتبط فيها الطلب بوضوح بشيء يريده. وقبل نافذة النظام، يعرض شرحه القصير الخاص: “Trailhead records your route while you hike. Your location stays on this phone unless you sync.” ثم يُطلق زرّ Continue الطلب الحقيقي. شاشتك أنت يمكن إغلاقها دون عواقب؛ أما نافذة النظام فلا.

والقاعدة نفسها تنطبق على كل إذن في التطبيق. اطلب الكاميرا حين ينقر المستخدم على Add photo، والإشعارات حين يفعّل التذكيرات، لا قبل ذلك. عندها يصل كل طلب وسببه ظاهر على الشاشة أصلًا، وترتفع نسب القبول بشكل ملحوظ.

src/hooks/useLocationPermission.ts
import * as Location from 'expo-location';
import { useFocusEffect } from 'expo-router';
import { useCallback, useState } from 'react';
import { Linking } from 'react-native';

export function useLocationPermission() {
  const [permission, setPermission] = useState<Location.LocationPermissionResponse | null>(null);

  // Re-check whenever the screen regains focus: the user may have
  // changed the setting in the Settings app while Trailhead was in the background.
  useFocusEffect(
    useCallback(() => {
      Location.getForegroundPermissionsAsync().then(setPermission);
    }, []),
  );

  const request = useCallback(async () => {
    const result = await Location.requestForegroundPermissionsAsync();
    setPermission(result);
    return result.granted;
  }, []);

  return { permission, request, openSettings: () => Linking.openSettings() };
}

ثم تقرأ شاشة التتبّع permission وتختار ما ترسمه: مؤشّر تحميل ما دامت null، والشرح مع زرّ Continue ما دام الطلب ممكنًا، ورسالة “Location is off for Trailhead” مع زرّ Open Settings بمجرد أن تصبح canAskAgain قيمتها false، والخريطة الحيّة بمجرد منح الإذن. وتحويل الاستجابة إلى واحد من هذه الأفعال الأربعة دالة نقية صغيرة، وهي تمرين هذا الدرس.

أوصاف الاستخدام والـ config plugins

يعرض iOS جملة من تطبيقك في كل نافذة إذن، ويرفض الـ App Store التطبيقات التي تكون أوصافها مفقودة أو مبهمة (“This app needs your location”). قل ما الذي تفعله الميزة بالبيانات. تضبط هذه النصوص عبر الـ config plugin الخاص بكل وحدة في app.json:

app.json
{
  "expo": {
    "plugins": [
      [
        "expo-location",
        { "locationWhenInUsePermission": "Trailhead records your route on the map while you track a hike." }
      ],
      [
        "expo-image-picker",
        {
          "photosPermission": "Trailhead lets you attach photos from your library to a hike.",
          "cameraPermission": "Trailhead lets you take trail photos to attach to a hike."
        }
      ]
    ]
  }
}

تكتب الـ config plugins الإعدادات الأصلية (ملف Info.plist في iOS، والـ manifest في Android) حين يُبنى التطبيق. وهذا يجعلها تغييرات أصلية: لن يطبّقها Fast Refresh، فأعِد بناء الـ development build بعد تعديلها. ويعرض Expo Go نصوصه العامة الخاصة، وهذا سبب إضافي لانتقال Trailhead الآن إلى development build.

المقدّمة أولًا، والخلفية فقط عند الضرورة

تتبّع رحلة والشاشة مطفأة يحتاج إلى موقع في الخلفية (background)، وهو إذن منفصل وأكثر حساسية، له متطلّبات إضافية من المنصّتين ومراجعة أدقّ في المتجرين. ابدأ بالوصول في المقدّمة (“while using the app”)، الذي يغطّي التتبّع ما دام Trailhead مفتوحًا. وأضف الوصول في الخلفية فقط حين يطلبه المستخدمون، مع شرح واضح للسبب، وتوقّع أن تطرح عليك المراجعة السؤال نفسه.

في الدرس التالي ستستخدم هذه الأذونات فعلًا: موقع المتنزّه، وصور الدروب، وإشعار تذكير.

أهم النقاط

  • استجابات الأذونات لها status قيمتها undetermined أو granted أو denied، إضافةً إلى canAskAgain، التي تخبرك هل ما زال يمكن أن تظهر نافذة النظام.
  • اطلب الإذن حين يبدأ المستخدم الميزة التي تحتاج إليه، بعد شرح قصير في واجهتك أنت، ولا تطلق سيلًا من النوافذ عند التشغيل أبدًا.
  • بمجرد أن تصبح canAskAgain قيمتها false، لا يستطيع تغيير الإجابة إلا تطبيق الإعدادات في النظام، فقدّم زرًّا يستدعي Linking.openSettings().
  • يشترط iOS وصفًا للاستخدام لكل إذن؛ اضبطها عبر خيارات الـ config plugins في app.json وأعد البناء.
  • أعِد التحقّق من الأذونات حين تستعيد الشاشة التركيز، لأن المستخدمين يغيّرونها في الإعدادات بينما تطبيقك في الخلفية.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

يطلب Trailhead الموقع في أول تشغيل للتطبيق، قبل أن يرى المستخدم أي شيء. على iOS، ينقر كثير من المستخدمين على Don’t Allow. لماذا يهمّ هذا إلى هذا الحد؟

السؤال 2 من 3

استجابة الإذن هي { status: 'denied', granted: false, canAskAgain: false }. ماذا يجب أن يفعل زرّ Start tracking؟

السؤال 3 من 3

غيّرت وصف استخدام الموقع في app.json، وأعدت التحميل بـ Fast Refresh، وما زال النص القديم يظهر في النافذة. لماذا؟

تدريب

بيئة تجريب الكود

حوّل استجابة الإذن إلى شاشة

تستدعي شاشة التتبّع في Trailhead الدالة permissionAction(response) لتقرّر ما ترسمه. response هو الكائن القادم من getForegroundPermissionsAsync()، أو null ما دام التحميل جاريًا. أرجع:

  • 'wait' ما دامت الاستجابة null أو undefined (اعرض مؤشّر تحميل)،
  • 'proceed' حين يُمنح الوصول (اعرض الخريطة)،
  • 'ask' حين يمكن أن تظهر نافذة النظام: لم يُسأل قط، أو رُفض مع canAskAgain: true (اعرض الشرح وزرّ Continue)،
  • 'open-settings' حين يُرفض وتكون canAskAgain قيمتها false (اعرض زرّ Open Settings).
// Decide what the tracking screen shows for a permission response.
// Returns one of: 'wait' | 'proceed' | 'ask' | 'open-settings'
function permissionAction(response) {
  if (response.status === 'granted') return 'proceed';
  return 'ask';
}

console.log(permissionAction({ status: 'denied', granted: false, canAskAgain: false, expires: 'never' }));
جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

الـ console

(لا مخرجات)

الفحوص

  • الاستجابة المفقودة تعني الانتظار
  • الإذن الممنوح يتابع
  • الإذن الذي لم يُطلب قط يقود إلى الطلب
  • الرفض الذي يمكن الطلب بعده مجددًا يقود إلى الطلب
  • الرفض الدائم يقود إلى الإعدادات