الفرق بين المراجعتين ل"ReactNative/native modules android"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
سطر 1: سطر 1:
 
<noinclude>{{DISPLAYTITLE: الوحدات الأصيلة (Android) في React Native}}</noinclude>
 
<noinclude>{{DISPLAYTITLE: الوحدات الأصيلة (Android) في React Native}}</noinclude>
تحتاج التطبيقات أحيانًا إلى الوصول إلى واجهة برمجة (API) منصةٍ ما، وReact Native لا يحتوي على وحدة ملائمة لهذا الغرض حتى الآن. قد ترغب في إعادة استخدام بعض شيفراتJava ‎  الموجودة مسبقا دون الحاجة إلى إعادة كتابتها بلغة JavaScript أو كتابة بعض الشيفرات عالية الأداء أو متعددة السلاسل (multi-threaded) مثل معالجة الصور أو قاعدة بيانات أو أي عدد من الإضافات المتقدمة.  
+
يُرجى الاطّلاع أولًا على صفحة [[ReactNative/native modules intro|مدخل إلى الوحدات الأصيلة Native Modules]] للتعرّف على الوحدات الأصيلة.  
  
صُمِّم React Native بحيث يمكنك كتابة شيفرة أصيلة حقيقية مع التحكم الكامل بالمنصة. هذه ميزة أكثر تقدمًا ولا نتوقع أن تكون جزءًا من عملية التطوير المعتادة، ولكنّ وجودها ضروري. إذا كان React Native لا يدعم ميزة أصيلة تحتاجها، يجب أن تكون قادرًا على بنائها بنفسك.  
+
== إنشاء وحدة التقويم الأصيلة كمثال Calendar Native Module ==
 +
سننشئ وحدة أصيلة هي الوحدة <code>CalendarModule</code> التي ستسمح بالوصول إلى واجهات تقويم Android البرمجية من شيفرة JavaScript، وستتمكّن في النهاية من استدعاء التابع <code>CalendarModule.createCalendarEvent('Dinner Party', 'My House');‎</code> من شيفرة JavaScript ، أي ستستدعي تابعًا أصيلًا ينشئ حدث التقويم.<blockquote>يعمل فريق React Native حاليًا على إعادة بناء نظام الوحدات الأصيلة، ويُطلَق على هذا النظام الجديد اسم TurboModules الذي سيساعد في تسهيل إنشاء اتصال أكثر كفاءة ومن النوع الآمن بين شيفرة JavaScript والشيفرة الأصيلة، دون الاعتماد على جسر React Native، وسيفعّل هذا النظام الجديد أيضًا ملحقات جديدة لم تكن ممكنة مع نظام الوحدات الأصيلة القديم (يمكنك قراءة المزيد عنه من هنا). أضفنا في هذا التوثيق ملاحظات حول أجزاء من الوحدات الأصيلة التي ستتغير في إصدار TurboModules وكيفية الاستعداد الأفضل للترقية إلى نظام TurboModules بسلاسة.</blockquote>
 +
 
 +
=== الإعداد ===
 +
افتح أولًا مشروع Android داخل تطبيق React Native الخاص بك في Android Studio. يمكنك العثور على مشروع Android الخاص بك داخل تطبيق React Native كما في الشكل التالي:
 +
[[ملف:native-modules-android-open-project.png|بديل=native modules android open project|مركز|تصغير|337x337بك|مكان العثور على مشروع Android الخاص بك]]
 +
نوصيك باستخدام Android Studio لكتابة شيفرتك الأصيلة، فإن Android studio بيئة تطوير IDE مصمَّمة لتطوير تطبيقات Android، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة.
 +
 
 +
نوصيك أيضًا بتفعيل [https://docs.gradle.org/2.9/userguide/gradle_daemon.html Gradle Daemon] لتسريع عمليات البناء أثناء التكرار على شيفرة Java.
 +
 
 +
=== إنشاء ملفات الوحدة الأصيلة المخصَّصة ===
 +
تتمثل الخطوة الأولى في إنشاء ملف جافا CalendarModule.java في المجلد <code>android/app/src/main/java/com/your-app-name/‎</code>، حيث سيحتوي هذا الملف على صنف جافا للوحدة الأصيلة.
 +
[[ملف:native-modules-android-add-class.png|بديل=native modules android add class|مركز|تصغير|325x325بك|كيفية إضافة صنف CalendarModule]]
 +
وأضِف ما يلي إلى هذا الملف:<syntaxhighlight lang="javascript">
 +
package com.your-app-name; // ‫استبدل com.your-app-name باسم تطبيقك
 +
import com.facebook.react.bridge.NativeModule;
 +
import com.facebook.react.bridge.ReactApplicationContext;
 +
import com.facebook.react.bridge.ReactContext;
 +
import com.facebook.react.bridge.ReactContextBaseJavaModule;
 +
import com.facebook.react.bridge.ReactMethod;
 +
import java.util.Map;
 +
import java.util.HashMap;
 +
 
 +
public class CalendarModule extends ReactContextBaseJavaModule {
 +
  CalendarModule(ReactApplicationContext context) {
 +
      super(context);
 +
  }
 +
}
 +
</syntaxhighlight>كما ترى ، يرث الصنف <code>CalendarModule</code> الصنف <code>ReactContextBaseJavaModule</code>.  تُكتَب وحدات جافا الأصيلة بالنسبة لنظام التشغيل Android كأصناف classes ترث <code>ReactContextBaseJavaModule</code> وتنفّذ الدوال المطلوبة في شيفرة JavaScript. <blockquote>تجدر الإشارة إلى أن أصناف جافا تحتاج فقط إلى أن ترث الصنف <code>BaseJavaModule</code> أو أن تنفّذ الواجهة <code>NativeModule</code> ليعدَّها React Native وحدة أصيلة.</blockquote>ولكن نوصي باستخدام الصنف <code>ReactContextBaseJavaModule</code>، كما هو موضح أعلاه، الذي يتيح الوصول إلى <code>ReactApplicationContext</code> (اختصارًا RAC) المفيد للوحدات الأصيلة التي تحتاج إلى استخدام خطّاف لتوابع دورة حياة الأنشطة. سيسهّل استخدام <code>ReactContextBaseJavaModule</code> جعل الوحدة الأصيلة آمنة النوع مستقبلًا. سيُطرَح أمان نوع الوحدة الأصيلة في الإصدارات المستقبلية، حيث يبحث React Native في مواصفات JavaScript لكل وحدة أصيلة ويُنشئ صنفًا أساسيًا مجردًا يرث <code>ReactContextBaseJavaModule</code>.
 +
 
 +
=== اسم الوحدة ===
 +
تحتاج جميع وحدات Java الأصيلة في Android إلى تنفيذ التابع <code>getName()‎</code>، التي تعيد سلسلة نصية تمثّل اسم الوحدة الأصيلة. يمكن بعد ذلك الوصول إلى الوحدة الأصيلة في JavaScript باستخدام اسمها. يعيد التابع <code>getName()‎</code> اسم الوحدة الأصيلة <code>"CalendarModule"</code> على سبيل المثال في جزء الشيفرة التالي:<syntaxhighlight lang="java">
 +
// ‫أضِف ما يلي إلى الملف CalendarModule.java
 +
@Override
 +
public String getName() {
 +
  return "CalendarModule";
 +
}
 +
</syntaxhighlight>ثم يمكن الوصول إلى الوحدة الأصيلة في شيفرة JS كما يلي: <syntaxhighlight lang="javascript">
 +
const { CalendarModule } = ReactNative.NativeModules;
 +
</syntaxhighlight>   
  
 
==إعداد الوحدات الأصيلة==
 
==إعداد الوحدات الأصيلة==

مراجعة 17:31، 4 يوليو 2021

يُرجى الاطّلاع أولًا على صفحة مدخل إلى الوحدات الأصيلة Native Modules للتعرّف على الوحدات الأصيلة.

إنشاء وحدة التقويم الأصيلة كمثال Calendar Native Module

سننشئ وحدة أصيلة هي الوحدة CalendarModule التي ستسمح بالوصول إلى واجهات تقويم Android البرمجية من شيفرة JavaScript، وستتمكّن في النهاية من استدعاء التابع CalendarModule.createCalendarEvent('Dinner Party', 'My House');‎ من شيفرة JavaScript ، أي ستستدعي تابعًا أصيلًا ينشئ حدث التقويم.

يعمل فريق React Native حاليًا على إعادة بناء نظام الوحدات الأصيلة، ويُطلَق على هذا النظام الجديد اسم TurboModules الذي سيساعد في تسهيل إنشاء اتصال أكثر كفاءة ومن النوع الآمن بين شيفرة JavaScript والشيفرة الأصيلة، دون الاعتماد على جسر React Native، وسيفعّل هذا النظام الجديد أيضًا ملحقات جديدة لم تكن ممكنة مع نظام الوحدات الأصيلة القديم (يمكنك قراءة المزيد عنه من هنا). أضفنا في هذا التوثيق ملاحظات حول أجزاء من الوحدات الأصيلة التي ستتغير في إصدار TurboModules وكيفية الاستعداد الأفضل للترقية إلى نظام TurboModules بسلاسة.

الإعداد

افتح أولًا مشروع Android داخل تطبيق React Native الخاص بك في Android Studio. يمكنك العثور على مشروع Android الخاص بك داخل تطبيق React Native كما في الشكل التالي:

native modules android open project
مكان العثور على مشروع Android الخاص بك

نوصيك باستخدام Android Studio لكتابة شيفرتك الأصيلة، فإن Android studio بيئة تطوير IDE مصمَّمة لتطوير تطبيقات Android، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة.

نوصيك أيضًا بتفعيل Gradle Daemon لتسريع عمليات البناء أثناء التكرار على شيفرة Java.

إنشاء ملفات الوحدة الأصيلة المخصَّصة

تتمثل الخطوة الأولى في إنشاء ملف جافا CalendarModule.java في المجلد android/app/src/main/java/com/your-app-name/‎، حيث سيحتوي هذا الملف على صنف جافا للوحدة الأصيلة.

native modules android add class
كيفية إضافة صنف CalendarModule

وأضِف ما يلي إلى هذا الملف:

package com.your-app-name; // ‫استبدل com.your-app-name باسم تطبيقك
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;

public class CalendarModule extends ReactContextBaseJavaModule {
   CalendarModule(ReactApplicationContext context) {
       super(context);
   }
}

كما ترى ، يرث الصنف CalendarModule الصنف ReactContextBaseJavaModule. تُكتَب وحدات جافا الأصيلة بالنسبة لنظام التشغيل Android كأصناف classes ترث ReactContextBaseJavaModule وتنفّذ الدوال المطلوبة في شيفرة JavaScript.

تجدر الإشارة إلى أن أصناف جافا تحتاج فقط إلى أن ترث الصنف BaseJavaModule أو أن تنفّذ الواجهة NativeModule ليعدَّها React Native وحدة أصيلة.

ولكن نوصي باستخدام الصنف ReactContextBaseJavaModule، كما هو موضح أعلاه، الذي يتيح الوصول إلى ReactApplicationContext (اختصارًا RAC) المفيد للوحدات الأصيلة التي تحتاج إلى استخدام خطّاف لتوابع دورة حياة الأنشطة. سيسهّل استخدام ReactContextBaseJavaModule جعل الوحدة الأصيلة آمنة النوع مستقبلًا. سيُطرَح أمان نوع الوحدة الأصيلة في الإصدارات المستقبلية، حيث يبحث React Native في مواصفات JavaScript لكل وحدة أصيلة ويُنشئ صنفًا أساسيًا مجردًا يرث ReactContextBaseJavaModule.

اسم الوحدة

تحتاج جميع وحدات Java الأصيلة في Android إلى تنفيذ التابع getName()‎، التي تعيد سلسلة نصية تمثّل اسم الوحدة الأصيلة. يمكن بعد ذلك الوصول إلى الوحدة الأصيلة في JavaScript باستخدام اسمها. يعيد التابع getName()‎ اسم الوحدة الأصيلة "CalendarModule" على سبيل المثال في جزء الشيفرة التالي:

// ‫أضِف ما يلي إلى الملف CalendarModule.java
@Override
public String getName() {
   return "CalendarModule";
}

ثم يمكن الوصول إلى الوحدة الأصيلة في شيفرة JS كما يلي:

const { CalendarModule } = ReactNative.NativeModules;

إعداد الوحدات الأصيلة

عادةً ما تُوزّع الوحدات الأصيلة كحزم npm، لكن بخلاف ملفات و موارد JavaScript النموذجية، فستحتوي على مشروع مكتبة Android. من منظور NPM، هذا المشروع مثل أي أصول وسائط (media asset) أخرى، أي لا شيء مميز بها من وجهة النظر هذه. للحصول على السقالات (scaffolding) الأساسية، تأكد من قراءة دليل إعداد الوحدات الأصيلة أولاً.

تمكين Gradle

إذا كنت تخطط لإجراء تغييرات في شيفرة Java، فإننا نوصي بتمكين Gradle Daemon لتسريع عمليات البناء.

وحدة Toast

سيستخدم هذا الدليل وحدة Toast كمثال. لنقُل أننا نود إنشاء رسالة Toast من JavaScript.

نبدأ بإنشاء وحدة أصيلة. الوحدة الأصيلة هي صنف Java عادةً ما يُوسِّع صنف ReactContextBaseJavaModule ويُجري (implements) الوظيفة المطلوبة في JavaScript. هدفنا هنا هو أن نكون قادرين على كتابة ‎‎ToastExample.show('Awesome', ToastExample.SHORT); ‎‎ من JavaScript لعرض رسالة Toast قصيرةٍ على الشاشة.

أنشئ صنف Java جديدٍ باسم ToastModule.java داخل ملف ‎‎android/app/src/main/java/com/your-app-name/‎‎ بالمحتوى التالي (مع استبدال ‎‎your-app-name‎‎ باسم تطبيقك):

// ToastModule.java

package com.your-app-name;

import android.widget.Toast;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

import java.util.Map;
import java.util.HashMap;

public class ToastModule extends ReactContextBaseJavaModule {

  private static final String DURATION_SHORT_KEY = "SHORT";
  private static final String DURATION_LONG_KEY = "LONG";

  public ToastModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }
}

يتطلب ReactContextBaseJavaModule إجراء (implement) تابعٍ يسمى getName. الغرض منه هو إعادة الاسم النصيّ للوحدة الأصيلة (‎‎NativeModule‎‎) الذي يمثل هذا الصنف في JavaScript. لذلك سنُسمّي هذا ToastExample لنصل إليه من خلال React.NativeModules.ToastExample في JavaScript.

  @Override
  public String getName() {
    return "ToastExample";
  }

يُعيد تابع اختياري يسمى getConstants القيم الثابتة الموفَّرة للغة JavScript. إجراؤه (implementation) ليس مطلوبًا ولكنه مفيد جدًا لإدخال قيم محددة مسبقًا والتي تحتاج إلى نقلها من JavaScript إلى Java في المزامنة.

  @Override
  public Map<String, Object> getConstants() {
    final Map<String, Object> constants = new HashMap<>();
    constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
    constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
    return constants;
  }

لتمكين JavaScript من الوصول لتابع ما، يجب توصيف (annotate) تابعِ Java باستخدام ‎‎@ReactMethod‎‎. النوع المعاد لتوابع الجسر يكون دائمًا النوعَ void. جسر React Native غير متزامن (asynchronous)، وبالتالي فإن الطريقة الوحيدة لتمرير نتيجة ما إلى JavaScript هي باستخدام دوال رد النداء أو بعث الأحداث (انظر أدناه).

  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }

أنواع المعاملات

أنواع المعاملات التالية مدعومة للتوابع الموصوفة باستخدام ‎‎@ReactMethod‎‎ وترتبط مباشرةً بنظيراتها في JavaScript.

Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array

اقرأ المزيد عن ReadableMap وReadableArray.

تسجيل الوحدة

الخطوة الأخيرة داخل Java هي تسجيل الوحدة؛ يحدث هذا في createNativeModules الخاصة بحزمة تطبيقاتك. إذا لم تُسجَّل وحدة ما، فلن تكون متاحة من جهة JavaScript.

أنشئ صنف Java جديد باسم CustomToastPackage.java داخل مجلّد ‎‎android/app/src/main/java/com/your-app-name/‎‎ بما يلي:

// CustomToastPackage.java

package com.your-app-name;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CustomToastPackage implements ReactPackage {

  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Collections.emptyList();
  }

  @Override
  public List<NativeModule> createNativeModules(
                              ReactApplicationContext reactContext) {
    List<NativeModule> modules = new ArrayList<>();

    modules.add(new ToastModule(reactContext));

    return modules;
  }

}

يجب توفير الحزمة في تابع getPackages الخاص بملفّ MainApplication.java. يوجد هذا الملف ضمن مجلد android في مجلّد تطبيق react-native الخاص بك. مسار هذا الملف هو: ‎‎android/app/src/main/java/com/your-app-name/MainApplication.java‎‎.

// MainApplication.java

...
import com.your-app-name.CustomToastPackage; // <-- Add this line with your package name.
...

protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new CustomToastPackage()); // <-- أضف هذا السطر مع اسم حزمتك
}

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

أنشئ ملف JavaScript جديد باسم ToastExample.js بالمحتوى أدناه:

/**
 * هذا يوفِّر الوصول إلى وحدة
 * ToastExample
 * الأصيلة كوحدةِ جافاسكريبت. وتحتوي على دالّةٍ باسم
 * 'show'
 * والتي تأخذ ما يلي من معاملات
 *
 * 1. String message: سلسلة نصية تمثّل الرسالة
 * 2. int duration:مدّة الرّسالة، إمّا 
 *    ToastExample.SHORT أو ToastExample.LONG
 */
import {NativeModules} from 'react-native';
module.exports = NativeModules.ToastExample;

الآن، من ملف JavaScript الآخر الخاص بك، يمكنك استدعاء التابع هكذا:

import ToastExample from './ToastExample';

ToastExample.show('Awesome', ToastExample.SHORT);

يرجى التأكد من أن ملف JavaScript هذا يوجد في نفس التسلسل الهرمي (hierarchy) الخاص بالملفّ ToastExample.js.

ما بعد وحدة Toast

دوال رد النداء (Callbacks)

تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات، وهو دوال رد النداء. والتي تُستخدم في معظم الحالات لتوفير نتيجة استدعاء دالةٍ إلى JavaScript.

import com.facebook.react.bridge.Callback;

public class UIManagerModule extends ReactContextBaseJavaModule {

...

  @ReactMethod
  public void measureLayout(
      int tag,
      int ancestorTag,
      Callback errorCallback,
      Callback successCallback) {
    try {
      measureLayout(tag, ancestorTag, mMeasureBuffer);
      float relativeX = PixelUtil.toDIPFromPixel(mMeasureBuffer[0]);
      float relativeY = PixelUtil.toDIPFromPixel(mMeasureBuffer[1]);
      float width = PixelUtil.toDIPFromPixel(mMeasureBuffer[2]);
      float height = PixelUtil.toDIPFromPixel(mMeasureBuffer[3]);
      successCallback.invoke(relativeX, relativeY, width, height);
    } catch (IllegalViewOperationException e) {
      errorCallback.invoke(e.getMessage());
    }
  }

...

يُمكن الوصول إلى هذا التابع في JavaScript باستخدام:

UIManager.measureLayout(
  100,
  100,
  (msg) => {
    console.log(msg);
  },
  (x, y, width, height) => {
    console.log(x + ':' + y + ':' + width + ':' + height);
  },
);

من المفترض أن تستدعي الوحدةُ الأصيلة دالةَ رد النداء الخاصة بها مرة واحدة فقط. لكن يمكنها تخزين دالة رد النداء واستدعائها لاحقًا.

من المهم إدراك أن دالة رد النداء لن تُستدعى فور اكتمال الدالة الأصيلة - تذكر أن التواصل عبر الجسر غير متزامن، وهذا مرتبط أيضًا بحلقة التشغيل (run loop).

الوعود (Promises)

يمكن للوحدات الأصيلة أيضًا الوفاء بالوعود، ما يمكن أن يبسط شيفرتك، خاصة عند استخدام بنية ‎‎async/await‎‎ الجديدة في نسخة ES2016. عندما يكون Promise آخر معاملٍ من معاملات التابع الأصيل الموصَل عن طريق الجسر، فسيُعيد تابع JavaScript الخاص به والمقابل له كائنَ Promise في JavaScript.

ستبدو إعادة تصميم الشيفرة أعلاه لاستخدام وعدٍ بدلاً من دوال رد النداء كما يلي:

import com.facebook.react.bridge.Promise;

public class UIManagerModule extends ReactContextBaseJavaModule {

...
  private static final String E_LAYOUT_ERROR = "E_LAYOUT_ERROR";
  @ReactMethod
  public void measureLayout(
      int tag,
      int ancestorTag,
      Promise promise) {
    try {
      measureLayout(tag, ancestorTag, mMeasureBuffer);

      WritableMap map = Arguments.createMap();

      map.putDouble("relativeX", PixelUtil.toDIPFromPixel(mMeasureBuffer[0]));
      map.putDouble("relativeY", PixelUtil.toDIPFromPixel(mMeasureBuffer[1]));
      map.putDouble("width", PixelUtil.toDIPFromPixel(mMeasureBuffer[2]));
      map.putDouble("height", PixelUtil.toDIPFromPixel(mMeasureBuffer[3]));

      promise.resolve(map);
    } catch (IllegalViewOperationException e) {
      promise.reject(E_LAYOUT_ERROR, e);
    }
  }

...

يقوم نظير JavaScript الخاص بهذا التابع بإرجاع وعد (أي كائن ‎‎Promise‎‎). هذا يعني أنه يمكنك استخدام الكلمة المفتاحيّة await داخل دالة غير متزامنة (async function) لاستدعائها وانتظار نتيجتها:

async function measureLayout() {
  try {
    var {relativeX, relativeY, width, height} = await UIManager.measureLayout(
      100,
      100,
    );

    console.log(relativeX + ':' + relativeY + ':' + width + ':' + height);
  } catch (e) {
    console.error(e);
  }
}

measureLayout();

التسلسل Threading

لا ينبغي أن تَفترِض الوحدة الأصيلة ماهية السلسلة التي يتم استدعاؤها عليها، لأن ذلك قد يتغيَّر في المستقبل. إذا كانت هناك حاجة إلى استدعاءٍ مُوقِف (blocking call)، فيجب إرسال العمل طويل الأمد إلى سلسلةِ عاملٍ مُدارة داخليًا (internally managed worker thread) ويجب توزيع أي دوال رد نداء من هناك.

إرسال الأحداث إلى JavaScript

يمكن أن تشير الوحدات الأصيلة إلى الأحداث (signal events) للغة JavaScript دون استدعائها مباشرة. أسهل طريقة للقيام بذلك هي استخدام RCTDeviceEventEmitter والذي يمكن الحصول عليه من ReactContext كما في الشيفرة أدناه:

...
private void sendEvent(ReactContext reactContext,
                       String eventName,
                       @Nullable WritableMap params) {
  reactContext
      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, params);
}
...
WritableMap params = Arguments.createMap();
...
sendEvent(reactContext, "keyboardWillShow", params);

يمكن لوحدات JavaScript التسجيل لتلقي الأحداث عن طريق addListenerOn باستخدام مخلوط Subscribable.

import { DeviceEventEmitter } from 'react-native';
...

var ScrollResponderMixin = {
  mixins: [Subscribable.Mixin],


  componentDidMount() {
    ...
    this.addListenerOn(DeviceEventEmitter,
                       'keyboardWillShow',
                       this.scrollResponderKeyboardWillShow);
    ...
  },
  scrollResponderKeyboardWillShow:function(e: Event) {
    this.keyboardWillOpenTo = e;
    this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
  },

يمكنك أيضًا استخدام وحدة DeviceEventEmitter مباشرةً للاستماع للأحداث.

...
componentDidMount() {
  this.subscription = DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    // handle event
  });
}

componentWillUnmount() {
  // إذا أردت التوقف عن الاستماع لأحداث جديدة، فاستدعِ التابع
  // .remove()

  this.subscription.remove();
}
...

الحصول على نتيجة النشاط (activity result) من startActivityForResult

ستحتاج إلى الاستماع إلى onActivityResult إذا رغبت في الحصول على نتائجٍ من نشاطٍ بدأت به باستخدام startActivityForResult. للقيام بذلك، يجب عليك توسيع (extend) الصنف BaseActivityEventListener أو إجراء (implement) ActivityEventListener. الخيار الأول هو المفضل لأنه أكثر مقاومةً لتغييرات الواجهة البرمجية (API). ثم تحتاج إلى تسجيل المستمع في الدالة البانية (constructor) للوحدة.

reactContext.addActivityEventListener(mActivityResultListener);

يمكنك الآن الاستماع إلى onActivityResult من خلال إجراء التابع التالي:

@Override
public void onActivityResult(
  final Activity activity,
  final int requestCode,
  final int resultCode,
  final Intent intent) {
  // ضع شيفرتك هنا
}

سنُجري (implement) مُنتَقِيَ صُوَرٍ (image picker) بسيط لشرح هذا الأمر. سيوفر منتقي الصور الوصول لتابع pickImage للغة JavaScript، والذي سيعيد مسار الصورة عند استدعائه.

public class ImagePickerModule extends ReactContextBaseJavaModule {

  private static final int IMAGE_PICKER_REQUEST = 467081;
  private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
  private static final String E_PICKER_CANCELLED = "E_PICKER_CANCELLED";
  private static final String E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER";
  private static final String E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND";

  private Promise mPickerPromise;

  private final ActivityEventListener mActivityEventListener = new BaseActivityEventListener() {

    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
      if (requestCode == IMAGE_PICKER_REQUEST) {
        if (mPickerPromise != null) {
          if (resultCode == Activity.RESULT_CANCELED) {
            mPickerPromise.reject(E_PICKER_CANCELLED, "Image picker was cancelled");
          } else if (resultCode == Activity.RESULT_OK) {
            Uri uri = intent.getData();

            if (uri == null) {
              mPickerPromise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found");
            } else {
              mPickerPromise.resolve(uri.toString());
            }
          }

          mPickerPromise = null;
        }
      }
    }
  };

  public ImagePickerModule(ReactApplicationContext reactContext) {
    super(reactContext);

    // أضف المُستمِع للتابع
    // `onActivityResult`
    reactContext.addActivityEventListener(mActivityEventListener);
  }

  @Override
  public String getName() {
    return "ImagePickerModule";
  }

  @ReactMethod
  public void pickImage(final Promise promise) {
    Activity currentActivity = getCurrentActivity();

    if (currentActivity == null) {
      promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Activity doesn't exist");
      return;
    }

    // خزِّن الوعد للحلّ أو الرفض عندما يُعيد المُنتقي البيانات 
    mPickerPromise = promise;

    try {
      final Intent galleryIntent = new Intent(Intent.ACTION_PICK);

      galleryIntent.setType("image/*");

      final Intent chooserIntent = Intent.createChooser(galleryIntent, "Pick an image");

      currentActivity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST);
    } catch (Exception e) {
      mPickerPromise.reject(E_FAILED_TO_SHOW_PICKER, e);
      mPickerPromise = null;
    }
  }
}

الاستماع إلى أحداث LifeCycle

يشبه الاستماع إلى أحداث LifeCycle الخاصة بالنشاط (مثل onResume و onPause إلخ)، إجراء ActivityEventListener. يجب أن تُجريَ الوحدة LifecycleEventListener. ثم تحتاج إلى تسجيل مستمعٍ في دالّة الوحدة البانيّة:

reactContext.addLifecycleEventListener(this);

يمكنك الآن الاستماع إلى أحداث LifeCycle الخاصة بالنشاط من خلال تطبيق التوابع التالية:

@Override
public void onHostResume() {
    // Activity `onResume`
}

@Override
public void onHostPause() {
    // Activity `onPause`
}

@Override
public void onHostDestroy() {
    // Activity `onDestroy`
}

مصادر