الفرق بين المراجعتين لصفحة: «ReactNative/native modules android»

من موسوعة حسوب
لا ملخص تعديل
طلا ملخص تعديل
 
(8 مراجعات متوسطة بواسطة 3 مستخدمين غير معروضة)
سطر 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، وسيفعّل هذا النظام الجديد أيضًا ملحقات جديدة لم تكن ممكنة مع نظام الوحدات الأصيلة القديم (يمكنك قراءة المزيد عنه من [https://github.com/react-native-community/discussions-and-proposals/issues/40 هنا]). أضفنا في هذا التوثيق ملاحظات حول أجزاء من الوحدات الأصيلة التي ستتغير في إصدار TurboModules وكيفية الاستعداد الأفضل للترقية إلى نظام TurboModules بسلاسة.</blockquote>


==إعداد الوحدات الأصيلة==
=== الإعداد ===
افتح أولًا مشروع Android داخل تطبيق React Native الخاص بك في Android Studio. يمكنك العثور على مشروع Android الخاص بك داخل تطبيق React Native كما في الشكل التالي:
[[ملف:native-modules-android-open-project.png|بديل=native modules android open project|مركز|تصغير|680x680px]]
نوصيك باستخدام Android Studio لكتابة شيفرتك الأصيلة، فإن Android studio بيئة تطوير IDE مصمَّمة لتطوير تطبيقات Android، وسيساعدك استخدامه على حل الأخطاء الصغيرة كالأخطاء الصياغية بسرعة.


عادةً ما تُوزّع الوحدات الأصيلة كحزم npm، لكن بخلاف ملفات و موارد JavaScript النموذجية، فستحتوي على مشروع مكتبة Android. من منظور NPM، هذا المشروع مثل أي أصول وسائط (media asset) أخرى، أي لا شيء مميز بها من وجهة النظر هذه. للحصول على السقالات (scaffolding) الأساسية، تأكد من قراءة [[ReactNative/native modules setup|دليل إعداد الوحدات الأصيلة]] أولاً.
نوصيك أيضًا بتفعيل [https://docs.gradle.org/2.9/userguide/gradle_daemon.html Gradle Daemon] لتسريع عمليات البناء أثناء التكرار على شيفرة Java.
 
===تمكين Gradle===
إذا كنت تخطط لإجراء تغييرات في شيفرة Java، فإننا نوصي بتمكين [https://docs.gradle.org/2.9/userguide/gradle_daemon.html Gradle Daemon] لتسريع عمليات البناء.
 
==وحدة Toast==
سيستخدم هذا الدليل وحدة [http://developer.android.com/reference/android/widget/Toast.html Toast] كمثال. لنقُل أننا نود إنشاء رسالة Toast من JavaScript.
 
نبدأ بإنشاء وحدة أصيلة. الوحدة الأصيلة هي صنف Java عادةً ما يُوسِّع صنف <code>ReactContextBaseJavaModule</code> ويُجري (implements) الوظيفة المطلوبة في JavaScript. هدفنا هنا هو أن نكون قادرين على كتابة ‎‎<code>ToastExample.show('Awesome', ToastExample.SHORT); </code>‎‎ من JavaScript لعرض رسالة Toast  قصيرةٍ على الشاشة.
 
أنشئ صنف Java جديدٍ باسم <code>ToastModule.java</code> داخل ملف ‎‎<code>android/app/src/main/java/com/your-app-name/</code>‎‎ بالمحتوى التالي (مع استبدال ‎‎<code>your-app-name</code>‎‎ باسم تطبيقك):
<syntaxhighlight lang="java">
// ToastModule.java
 
package com.your-app-name;
 
import android.widget.Toast;


=== إنشاء ملفات الوحدة الأصيلة المخصَّصة ===
تتمثل الخطوة الأولى في إنشاء ملف جافا CalendarModule.java ضمن المجلد <code>android/app/src/main/java/com/your-app-name/‎</code>، حيث سيحتوي هذا الملف على صنف جافا للوحدة الأصيلة.
[[ملف:native-modules-android-add-class.png|بديل=native modules android add class|مركز|تصغير|680x680px|كيفية إضافة صنف CalendarModule]]
وأضِف ما يلي إلى هذا الملف:<syntaxhighlight lang="java">
package com.your-app-name; // ‫استبدل com.your-app-name باسم تطبيقك
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactApplicationContext;
سطر 29: سطر 22:
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.Map;
import java.util.HashMap;
import java.util.HashMap;


public class ToastModule extends ReactContextBaseJavaModule {
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) المفيد للوحدات الأصيلة التي تحتاج إلى استخدام خطّاف hook لتوابع دورة حياة النشاط. سيسهّل استخدام <code>ReactContextBaseJavaModule</code> جعل الوحدة الأصيلة آمنة النوع مستقبلًا. سيُطرَح أمان نوع الوحدة الأصيلة في الإصدارات المستقبلية، حيث يبحث React Native في مواصفات JavaScript لكل وحدة أصيلة ويُنشئ صنفًا أساسيًا مجردًا يرث <code>ReactContextBaseJavaModule</code>.


  private static final String DURATION_SHORT_KEY = "SHORT";
=== اسم الوحدة ===
  private static final String DURATION_LONG_KEY = "LONG";
تحتاج جميع وحدات Java الأصيلة في Android إلى تنفيذ التابع <code>getName()‎</code>، الذي يعيد سلسلة نصية تمثّل اسم الوحدة الأصيلة. يمكن بعد ذلك الوصول إلى الوحدة الأصيلة في JavaScript باستخدام اسمها. يعيد التابع <code>getName()‎</code> اسم الوحدة الأصيلة <code>"CalendarModule"</code> على سبيل المثال في جزء الشيفرة التالي:<syntaxhighlight lang="java">
 
// ‫أضِف ما يلي إلى الملف CalendarModule.java
  public ToastModule(ReactApplicationContext reactContext) {
@Override
    super(reactContext);
public String getName() {
  }
  return "CalendarModule";
}
}
</syntaxhighlight>ثم يمكن الوصول إلى الوحدة الأصيلة في شيفرة JS كما يلي: <syntaxhighlight lang="javascript">
const { CalendarModule } = ReactNative.NativeModules;
</syntaxhighlight>
</syntaxhighlight>


يتطلب <code>ReactContextBaseJavaModule</code> إجراء (implement) تابعٍ يسمى <code>getName</code>. الغرض منه هو إعادة الاسم النصيّ للوحدة الأصيلة (‎‎<code>NativeModule</code>‎‎) الذي يمثل هذا الصنف في JavaScript. لذلك سنُسمّي هذا <code>ToastExample</code> لنصل إليه من خلال <code>React.NativeModules.ToastExample</code> في JavaScript.
=== تصدير تابع أصيل إلى شيفرة JavaScript ===
<syntaxhighlight lang="java">
ستحتاج بعد ذلك إلى إضافة تابع إلى الوحدة الأصيلة الخاصة بك الذي سينشئ أحداث التقويم ويمكن استدعاؤه في شيفرة JavaScript. يجب الإشارة إلى جميع توابع الوحدات الأصيلة التي يُراد استدعاؤها من JavaScript باستخدام <code>@ReactMethod</code>.
  @Override
  public String getName() {
    return "ToastExample";
  }
</syntaxhighlight>
يُعيد تابع اختياري يسمى <code>getConstants</code>  القيم الثابتة الموفَّرة للغة JavScript. إجراؤه (implementation) ليس مطلوبًا ولكنه مفيد جدًا لإدخال قيم محددة مسبقًا والتي تحتاج إلى نقلها من JavaScript إلى Java في المزامنة.
<syntaxhighlight lang="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;
  }
</syntaxhighlight>
لتمكين JavaScript من الوصول لتابع ما، يجب توصيف (annotate) تابعِ Java باستخدام ‎‎<code>@ReactMethod</code>‎‎. النوع المعاد لتوابع الجسر يكون دائمًا النوعَ <code>void</code>. جسر React Native  غير متزامن (asynchronous)، وبالتالي فإن الطريقة الوحيدة لتمرير نتيجة ما إلى JavaScript هي باستخدام دوال رد النداء أو بعث الأحداث (انظر أدناه).
<syntaxhighlight lang="java">
  @ReactMethod
  public void show(String message, int duration) {
    Toast.makeText(getReactApplicationContext(), message, duration).show();
  }
</syntaxhighlight>
 
===أنواع المعاملات===


أنواع المعاملات التالية مدعومة للتوابع الموصوفة باستخدام ‎‎<code>@ReactMethod</code>‎‎ وترتبط مباشرةً بنظيراتها في JavaScript.
اضبط التابع <code>createCalendarEvent()‎</code> للصنف <code>CalendarModule</code> الذي يمكن استدعاؤه في شيفرة JS بالشكل <code>CalendarModule.createCalendarEvent()‎</code>. سيأخذ هذا التابع حاليًا وسيطَي الاسم name والموقع location كسلاسل نصية (سنتكلم عن خيارات نوع الوسيط لاحقًا).<syntaxhighlight lang="java">
<syntaxhighlight lang="javascript">
@ReactMethod
Boolean -> Bool
public void createCalendarEvent(String name, String location) {
Integer -> Number
}
Double -> Number
</syntaxhighlight>أضِف سجل وحدة التحكم console log في هذا التابع، لتتمكّن من تأكيد استدعاء التابع عندما استدعيته من تطبيقك. يوضَح المثال التالي كيفية استيراد واستخدام الصنف [https://developer.android.com/reference/android/util/Log Log] من حزمة استخدام Android:<syntaxhighlight lang="java">
Float -> Number
import android.util.Log;
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
</syntaxhighlight>


اقرأ المزيد عن [https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableMap.java ReadableMap] و[https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableArray.java ReadableArray].
@ReactMethod
public void createCalendarEvent(String name, String location) {
  Log.d("CalendarModule", "Create event called with name: " + name
  + " and location: " + location);
}
</syntaxhighlight>يمكنك اتباع [https://developer.android.com/studio/debug/am-logcat.html هذه الخطوات] لعرض السجلات logs من تطبيقك، بعد الانتهاء من تنفيذ الوحدة الأصيلة واستخدام خطّاف لربطها مع شيفرة JavaScript .


===تسجيل الوحدة===
=== التوابع المتزامنة ===
الخطوة الأخيرة داخل Java هي تسجيل الوحدة؛ يحدث هذا في <code>createNativeModules</code> الخاصة بحزمة تطبيقاتك. إذا لم  تُسجَّل وحدة ما، فلن تكون متاحة من جهة JavaScript.
يمكنك تمرير <code>isBlockingSynchronousMethod = true</code> إلى تابع أصيل لتمييزه على أنه تابع متزامن.<syntaxhighlight lang="java">
@ReactMethod(isBlockingSynchronousMethod = true)
</syntaxhighlight>لا نوصي حاليًا باستخدام توابع متزامنة، لأن استدعاء التوابع المتزامنة يمكن أن يكون لها عواقب على الأداء ويمكن أن يدخل أخطاءً مرتبطة بالخيوط إلى وحداتك الأصيلة. لاحظ أيضًا أنه إذا اخترت تفعيل <code>isBlockingSynchronousMethod</code>، فلن يتمكن تطبيقك من استخدام منقّح أخطاء Google Chrome مرة أخرى، لأن التوابع المتزامنة تتطلب آلة JS الافتراضية لمشاركة الذاكرة مع التطبيق. بينما بالنسبة لمنقّح أخطاء Google Chrome، فإن React Native يعمل داخل آلة JS الافتراضية في Google Chrome، ويتواصل تواصلًا غير متزامن مع الأجهزة المتنقلة عبر WebSockets.


أنشئ صنف Java جديد باسم <code>CustomToastPackage.java</code> داخل مجلّد ‎‎<code>android/app/src/main/java/com/your-app-name/</code>‎‎  بما يلي:
=== تسجيل الوحدة (خاص بنظام Android) ===
<syntaxhighlight lang="java">
يجب تسجيل الوحدة الأصيلة بعد كتابتها في React Native، لذلك تحتاج إلى إضافة الوحدة الخاصة بك إلى الحزمة <code>ReactPackage</code> وتسجيل هذه الحزمة باستخدام React Native. سيعمل React Native أثناء التهيئة على تكرار كل الحزم ويسجّل كل وحدة أصيلة بداخل حزمتها.
// CustomToastPackage.java


package com.your-app-name;
يستدعي React Native التابع <code>createNativeModules()‎</code> في الحزمة <code>ReactPackage</code> للحصول على قائمة الوحدات الأصيلة لتسجيلها. إن لم تُنشَأ وحدة في نظام Android  ولكن أعادها التابع createNativeModules، فلن تكون متاحة من شيفرة JavaScript.


يمكنك إضافة الوحدة الخاصة بك إلى الحزمة <code>ReactPackage</code> من خلال إنشاء صنف Java جديد باسم <code>MyAppPackage.java</code> يطبّق الحزمة <code>ReactPackage</code> داخل المجلد <code>android/app/src/main/java/com/your-app-name/‎</code>، ثم أضِف ما يلي:<syntaxhighlight lang="java">
package com.your-app-name; // ‫استبدل com.your-app-name باسم تطبيقك
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.NativeModule;
سطر 103: سطر 81:
import java.util.List;
import java.util.List;


public class CustomToastPackage implements ReactPackage {
public class MyAppPackage implements ReactPackage {
 
  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
      return Collections.emptyList();
  }


  @Override
  @Override
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
  public List<NativeModule> createNativeModules(
    return Collections.emptyList();
          ReactApplicationContext reactContext) {
  }
      List<NativeModule> modules = new ArrayList<>();


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


    modules.add(new ToastModule(reactContext));
      return modules;
  }


     return modules;
}
</syntaxhighlight>يستورد هذا الملف الوحدة الأصيلة التي أنشأتها وهي <code>CalendarModule</code>، ثم ينشئ نسخة منها ضمن دالة <code>createNativeModules()‎</code> ويعيدها كقائمة وحدات <code>NativeModules</code> لتسجيلها. إذا أضفت المزيد من الوحدات الأصيلة لاحقًا، فيمكنك أيضًا إنشاء نسخة منها وإضافتها إلى القائمة المُعادة.<blockquote>'''ملاحظة''': تجدر الإشارة إلى أن هذه الطريقة في تسجيل الوحدات الأصيلة تؤدي إلى تهيئة جميع الوحدات الأصيلة عند بدء تشغيل التطبيق، مما يزيد من وقت بدء تشغيل التطبيق. يمكنك استخدام الحزمة [https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/TurboReactPackage.java TurboReactPackage] كبديل، إذ تطبّق هذه الحزمة التابع <code>getModule(String name, ReactApplicationContext rac)</code> الذي ينشئ كائن الوحدة الأصيلة عند الحاجة، بدلًا من الدالة <code>createNativeModules</code> التي تعيد قائمة بكائنات الوحدة الأصيلة، ولكن الحزمة TurboReactPackage أكثر تعقيدًا في التطبيق حاليًا. يجب تنفيذ التابع <code>getReactModuleInfoProvider()‎</code> بالإضافة إلى تنفيذ التابع <code>getModule()‎</code>، حيث يعيد التابع <code>getReactModuleInfoProvider()‎</code> قائمة بجميع الوحدات الأصيلة التي يمكن للحزمة إنشاء نسخة منها مع دالة تنشئها (اطّلع على [https://github.com/facebook/react-native/blob/8ac467c51b94c82d81930b4802b2978c85539925/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java#L86-L165 هذا المثال]). سيسمح استخدام TurboReactPackage لتطبيقك بالحصول على وقت بدء تشغيل أسرع، ولكن تُعَد كتابته حاليًا أمرًا مرهقًا وأكثر تعقيدًا، لذلك تابع بحذر إذا اخترت استخدام حزم TurboReactPackages.</blockquote>يجب إضافة <code>MyAppPackage</code> إلى قائمة الحزم التي يعيدها تابع ReactNativeHost الذي هو <code>getPackages()‎</code>، لتسجيل الحزمة <code>CalendarModule</code>. افتح الملف <code>MainApplication.java</code> الذي يمكن العثور عليه في المسار التالي: <code>android/app/src/main/java/com/your-app-name/MainApplication.java</code>. حدد موقع تابع ReactNativeHost الذي هو <code>getPackages()‎</code>، وأضف حزمتك إلى قائمة الحزم التي يعيدها التابع <code>getPackages()‎</code>:<syntaxhighlight lang="java">
@Override
  protected List<ReactPackage> getPackages() {
    @SuppressWarnings("UnnecessaryLocalVariable")
    List<ReactPackage> packages = new PackageList(this).getPackages();
    // ‫تُضاف الحزمة MyAppPackage إلى قائمة الحزم المُعادة أدناه
    packages.add(new MyAppPackage());
     return packages;
   }
   }
</syntaxhighlight>لقد نجحت الآن في تسجيل وحدتك الأصيلة لنظام Android.


}
=== اختبر ما بنيته ===
</syntaxhighlight>
أجريت حتى الآن الإعداد الأساسي لوحدتك الأصيلة في Android. اختبر هذا الإعداد من خلال الوصول إلى الوحدة الأصيلة واستدعاء تابعها المُصدَّر في شيفرة JavaScript.
يجب توفير الحزمة في تابع <code>getPackages</code> الخاص بملفّ <code>MainApplication.java</code>. يوجد هذا الملف ضمن مجلد android في مجلّد تطبيق react-native الخاص بك. مسار هذا الملف هو: ‎‎<code>android/app/src/main/java/com/your-app-name/MainApplication.java</code>‎‎.
 
<syntaxhighlight lang="java">
ابحث عن مكانٍ ما في تطبيقك حيث تريد إضافة استدعاء تابع الوحدة الأصيلة <code>createCalendarEvent()‎</code>. يحتوي المثال التالي المكوّن <code>NewModuleButton</code> الذي يمكنك إضافته إلى تطبيقك، إذ يمكنك استدعاء الوحدة الأصيلة ضمن الدالة <code>onPress()‎</code> الخاصة بالمكوّن <code>NewModuleButton</code> كما يلي:<syntaxhighlight lang="javascript">
// MainApplication.java
import React from 'react';
import { NativeModules, Button } from 'react-native';
 
const NewModuleButton = () => {
  const onPress = () => {
    console.log('We will invoke the native module here!');
  };


...
  return (
import com.your-app-name.CustomToastPackage; // <-- Add this line with your package name.
    <Button
...
      title="Click to invoke your native module!"
      color="#841584"
      onPress={onPress}
    />
  );
};


protected List<ReactPackage> getPackages() {
export default NewModuleButton;
    return Arrays.<ReactPackage>asList(
</syntaxhighlight>يجب استيراد <code>NativeModules</code> من React Native من أجل الوصول إلى الوحدة الأصيلة الخاصة بك من شيفرة JavaScript:<syntaxhighlight lang="javascript">
            new MainReactPackage(),
import { NativeModules } from 'react-native';
            new CustomToastPackage()); // <-- أضف هذا السطر مع اسم حزمتك
</syntaxhighlight>ثم يمكنك الوصول إلى الوحدة الأصيلة <code>CalendarModule</code> من خارج <code>NativeModules</code>:<syntaxhighlight lang="javascript">
}
const { CalendarModule } = NativeModules;
</syntaxhighlight>يمكنك الآن استدعاء التابع الأصيل <code>createCalendarEvent()‎</code> بعد أن أصبحت الوحدة الأصيلة CalendarModule متاحة. أُضيف فيما يلي هذا التابع الأصيل إلى التابع <code>onPress()‎</code> في المكوّن <code>NewModuleButton</code>:<syntaxhighlight lang="javascript">
const onPress = () => {
  CalendarModule.createCalendarEvent('testName', 'testLocation');
};
</syntaxhighlight>الخطوة الأخيرة هي إعادة بناء تطبيق React Native بحيث يمكنك الحصول على أحدث شيفرة أصيلة (مع الوحدة الأصيلة الجديدة). شغّل الأمر التالي في سطر الأوامر ضمن المكان الذي يوجد فيه تطبيق react native:<syntaxhighlight lang="bash">
npx react-native run-android
</syntaxhighlight>
</syntaxhighlight>
لتبسيط الوصول إلى وظيفتك الجديدة من JavaScript، من الشائع تغليف الوحدة الأصيلة في وحدة JavaScript. هذا ليس ضروريًا ولكنه يُجنِّب لمستهلكي مكتبتك الحاجة لسحبها من <code>NativeModules</code> في كل مرة. يصبح ملف JavaScript هذا أيضًا موقعًا جيدًا لك لإضافة أي وظيفةٍ خاصّة بجهة JavaScript.


أنشئ ملف JavaScript جديد باسم <code>ToastExample.js</code> بالمحتوى أدناه:
=== إعادة البناء عند التكرار ===
<syntaxhighlight lang="javascript">
ستحتاج أثناء تكرار وحدتك الأصيلة إلى إعادة بناء أصيلة لتطبيقك بهدف الوصول إلى أحدث التغييرات من شيفرة JavaScript، لأن الشيفرة التي تكتبها موجودة ضمن الجزء الأصيل من تطبيقك. يمكن لمجمّع metro الخاص بإطار عمل React Native مراقبة التغييرات في شيفرة JavaScript وإعادة إنشاء حزمة JS سريعًا نيابةً عنك، إلّا أنه لن يفعل ذلك مع الشيفرة الأصيلة. لذلك إذا أردت اختبار أحدث التغييرات الأصيلة، فيجب إعادة البناء باستخدام الأمر <code>npx react-native run-android</code>.
/**
* هذا يوفِّر الوصول إلى وحدة
* ToastExample
* الأصيلة كوحدةِ جافاسكريبت. وتحتوي على دالّةٍ باسم
* 'show'
* والتي تأخذ ما يلي من معاملات
*
* 1. String message: سلسلة نصية تمثّل الرسالة
* 2. int duration:مدّة الرّسالة، إمّا
*    ToastExample.SHORT أو ToastExample.LONG
*/
import {NativeModules} from 'react-native';
module.exports = NativeModules.ToastExample;
</syntaxhighlight>
الآن، من ملف JavaScript الآخر الخاص بك، يمكنك استدعاء التابع هكذا:
<syntaxhighlight lang="javascript">
import ToastExample from './ToastExample';


ToastExample.show('Awesome', ToastExample.SHORT);
=== الخلاصة ===
</syntaxhighlight>
يجب أن تكون الآن قادرًا على استدعاء التابع <code>createCalendarEvent()‎</code> للوحدة الأصيلة في تطبيقك، ويحدث ذلك في مثالنا من خلال الضغط على <code>NewModuleButton</code>. يمكنك تأكيد ذلك من خلال عرض السجل الذي ضبطته في التابع <code>createCalendarEvent()‎</code> الخاصة بك (اتبع [https://developer.android.com/studio/debug/am-logcat.html هذه الخطوات] لعرض سجلات ADB في تطبيقك). يجب أن تكون قادرًا بعد ذلك على البحث عن رسالة <code>Log.d</code> (في مثالنا الرسالة هي “Create event called with name: testName and location: testLocation”) ومشاهدة رسالتك مُسجَّلة في كل مرة تستدعي فيها تابع الوحدة الأصيلة.
[[ملف:native-modules-android-logs.png|بديل=native modules android logs|مركز|تصغير|680x680px|سجلات ADB في Android Studio]]
أنشأت حتى الآن وحدة Android أصيلة واستدعيت تابعها الأصيل من شيفرة JavaScript في تطبيق React Native الخاص بك. تابع القراءة لمعرفة المزيد عن أنواع الوسطاء التي يأخذها تابع الوحدة الأصيلة الخاصة بك وكيفية إعداد دوال رد النداء callbacks والوعود promises.


يرجى التأكد من أن ملف  JavaScript هذا يوجد في نفس التسلسل الهرمي (hierarchy) الخاص بالملفّ <code>ToastExample.js</code>.
== ما بعد وحدة التقويم الأصيلة ==


==ما بعد وحدة Toast==
=== تصدير الوحدة الأصيلة الأفضل ===
===دوال رد النداء (Callbacks)===
يُعَد استيراد وحدتك الأصيلة عن طريق سحبها من <code>NativeModules</code> كما ذكرنا سابقًا أمرًا صعبًا بعض الشيء. يمكنك إنشاء مغلِّف JavaScript للوحدة، للحفاظ على مستهلكي وحدتك الأصيلة الخاصة من الحاجة إلى تنفيذ هذا الأمر الصعب في كل مرة يريدون فيها الوصول إلى وحدتك الأصيلة. أنشئ ملف JavaScript جديد باسم <code>CalendarModule.js</code> يحتوي على ما يلي:<syntaxhighlight lang="javascript">
تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات، وهو دوال رد النداء. ;والتي تُستخدم في معظم الحالات لتوفير نتيجة استدعاء دالةٍ إلى JavaScript.
/**
<syntaxhighlight lang="java">
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
import com.facebook.react.bridge.Callback;
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:


public class UIManagerModule extends ReactContextBaseJavaModule {
* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
*/
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules;
export default CalendarModule;
</syntaxhighlight>يصبح ملف JavaScript هذا أيضًا موقعًا جيدًا لإضافة أي عمليات من جانب شيفرة [[JavaScript]]. إذا استخدمت مثلًا نظام أنواع مثل [[TypeScript]]، فيمكنك إضافة تعليقات الأنواع للوحدة الأصيلة في هذا الملف. لا يدعم React Native حتى الآن أمان النوع Native to JS، ولكنّ جميع شيفرات JS الخاصة بك ستكون من النوع الآمن. ستسهّل هذه التعليقات عليك التبديل إلى الوحدات الأصيلة ذات النوع الآمن باستمرار. يوضّح المثال التالي إضافة النوع الآمن إلى وحدة التقويم <code>CalendarModule</code>:<syntaxhighlight lang="javascript">
/**
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:
*
* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
*/
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules
interface CalendarInterface {
  createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;
</syntaxhighlight>يمكنك في ملفات JavaScript الأخرى الوصول إلى الوحدة الأصيلة واستدعاء تابعها كما يلي:<syntaxhighlight lang="javascript">
import CalendarModule from './CalendarModule';
CalendarModule.createCalendarEvent('foo', 'bar');
</syntaxhighlight><blockquote>'''لاحظ''' أن هذا المثال افترض أن المكان الذي تستورد منه <code>CalendarModule</code> موجود ضمن تسلسل الملف <code>CalendarModule.js</code> الهرمي نفسه، لذلك يجب أن تحدّث مسار الاستيراد النسبي عند الحاجة.</blockquote>


...
=== أنواع الوسطاء ===
يحوّل React Native الوسطاء من كائنات JS إلى مثيلاتها في لغة Java عند استدعاء تابع الوحدة الأصلية في شيفرة JavaScript . إن قَبِل تابع وحدة Java الأصيلة عددًا مضاعفًا double على سبيل المثال، فستحتاج في شيفرة JS إلى استدعاء التابع باستخدام وسيط من النوع عدد number، حيث سيتوّلى React Native عملية التحويل نيابةً عنك. فيما يلي قائمة بأنواع الوسطاء المدعومة لتوابع الوحدات الأصيلة ومقابلاتها في JavaScript التي ستُربَط معها:
{| class="wikitable"
!JAVA
!JAVASCRIPT
|-
|Boolean
|‎?boolean قيمة منطقية
|-
|boolean
|boolean قيمة منطقية
|-
|Double
|‎?number عدد
|-
|double
|number عدد
|-
|String
|string سلسلة نصية
|-
|Callback
|Function تابع
|-
|ReadableMap
|Object كائن
|-
|ReadableArray
|Array مصفوفة
|}
<blockquote>الأنواع التالية مدعومة حاليًا ولكن لن يدعمها نظام TurboModules، لذلك يُرجَى تجنب استخدامها:


  @ReactMethod
* Integer -> ?number
  public void measureLayout(
* int -> number
      int tag,
* Float -> ?number
      int ancestorTag,
* float -> number
      Callback errorCallback,
</blockquote>ستحتاج إلى التعامل مع التحويل بنفسك بالنسبة لأنواع الوسائط غير المذكورة أعلاه، إذ لا يدعم Android تحويل النوع <code>Date</code> مثلًا. يمكنك التعامل مع التحويل إلى النوع <code>Date</code> ضمن التابع الأصيل بنفسك كما يلي:<syntaxhighlight lang="javascript">
      Callback successCallback) {
    String dateFormat = "yyyy-MM-dd";
    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
    Calendar eStartDate = Calendar.getInstance();
     try {
     try {
      measureLayout(tag, ancestorTag, mMeasureBuffer);
        eStartDate.setTime(sdf.parse(startDate));
      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());
     }
     }
  }
</syntaxhighlight>


...
=== تصدير الثوابت ===
يمكن للوحدة الأصيلة تصدير الثوابت عن طريق تطبيق التابع الأصيل <code>getConstants()‎</code> المتوفّر في JS. ستطبّق فيما يلي التابع <code>getConstants()‎</code> وتعيد Map تحتوي على الثابت <code>DEFAULT_EVENT_NAME</code> الذي يمكنك الوصول إليه في شيفرة JavaScript:<syntaxhighlight lang="java">
@Override
public Map<String, Object> getConstants() {
  final Map<String, Object> constants = new HashMap<>();
  constants.put("DEFAULT_EVENT_NAME", "New Event");
  return constants;
}
</syntaxhighlight>
</syntaxhighlight>
يُمكن الوصول إلى هذا التابع في JavaScript باستخدام:
يمكن بعد ذلك الوصول إلى الثابت عن طريق استدعاء التابع <code>getConstants()‎</code> ضمن الوحدة الأصيلة في شيفرة JS كما يلي:<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
const { DEFAULT_EVENT_NAME } = CalendarModule.getConstants();
UIManager.measureLayout(
console.log(DEFAULT_EVENT_NAME);
  100,
  100,
  (msg) => {
    console.log(msg);
  },
  (x, y, width, height) => {
    console.log(x + ':' + y + ':' + width + ':' + height);
  },
);
</syntaxhighlight>
</syntaxhighlight>
يمكن الوصول إلى الثوابت المُصدَّرة في التابع <code>getConstants()‎</code> مباشرةً من كائن الوحدة الأصيلة، ولكن لن يدعم نظام TurboModules ذلك، لذلك نشجّع المجتمع على التبديل إلى الطريقة السابقة لتجنّب التهجير الضروري باستمرار.<blockquote>'''ملاحظة''': تُصدَّر الثوابت الحالية في وقت التهيئة initialization time فقط، لذلك إذا غيّرت قيم التابع <code>getConstants()‎‎</code> في وقت التشغيل، فلن يؤثر ذلك على بيئة JavaScript. لكن ذلك سيتغيّر مع Turbomodules، إذ سيصبح التابع <code>getConstants()‎</code> تابع وحدة أصيلة نمطية، وسيصل كلّ استدعاء إلى الجانب الأصيل.</blockquote>


من المفترض أن تستدعي الوحدةُ الأصيلة دالةَ رد النداء الخاصة بها مرة واحدة فقط. لكن يمكنها تخزين دالة رد النداء واستدعائها لاحقًا.
=== دوال رد النداء Callbacks ===
تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات الذي يتمثّل في دوال رد النداء Callbacks. تُستخدَم دوال رد النداء لتمرير البيانات من شيفرة Java إلى شيفرة JavaScript للتوابع غير المتزامنة، ويمكن استخدامها أيضًا لتنفيذ شيفرة JS تنفيذًا غير متزامن من الجانب الأصيل.


من المهم إدراك أن دالة رد النداء لن تُستدعى فور اكتمال الدالة الأصيلة - تذكر أن التواصل عبر الجسر غير متزامن، وهذا مرتبط أيضًا بحلقة التشغيل (run loop).
استورد أولًا واجهة رد النداء <code>Callback</code> ثم أضف معاملًا جديدًا إلى تابع الوحدة الأصيلة الخاص بك من النوع <code>Callback</code>، لإنشاء تابع وحدة أصيلة مع دالة رد نداء. هناك بعض الفروق مع وسطاء دالة رد النداء التي ستُزال قريبًا باستخدام نظام TurboModules، إذ لا يمكن أن يكون لديك سوى دالتَي رد نداء في وسطاء دوالك هما رد نداء النجاح successCallback ورد نداء الفشل failureCallback، ويجري التعامل مع الوسيط الأخير لاستدعاء تابع الوحدة الأصيلة -إذا كان دالة- على أنه SuccessCallback، ويجري التعامل مع الوسطاء من الوسيط الثاني إلى الوسيط الأخير لاستدعاء تابع الوحدة الأصيلة -إذا كانت دوالًا- على أنها failureCallback.<syntaxhighlight lang="java">
import com.facebook.react.bridge.Callback;


===الوعود (Promises)===
@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
}
</syntaxhighlight>
يمكنك استدعاء دالة رد النداء في تابع Java الخاص بك، مع توفير أي بيانات تريد تمريرها إلى شيفرة JavaScript. لاحظ أنه يمكنك فقط تمرير البيانات القابلة للسَّلسَلة من الشيفرة الأصيلة إلى شيفرة JavaScript. إذا احتجت إعادة كائن أصيل، فيمكنك استخدام <code>WriteableMaps</code>، بينما إذا احتجت استخدام مجموعة منها، فاستخدم <code>WritableArrays</code>. يجب التركيز على أن دالة رد النداء لا تُستدعَى فور اكتمال الدالة الأصيلة. يُمرَّر فيما يلي معرّف حدث أُنشِئ في استدعاء سابق إلى دالة رد النداء:<syntaxhighlight lang="java">
  @ReactMethod
  public void createCalendarEvent(String name, String location, Callback callBack) {
      Integer eventId = ...
      callback.invoke(eventId);
  }
</syntaxhighlight>
يمكن بعد ذلك الوصول إلى هذا التابع في شيفرة JavaScript كما يلي:<syntaxhighlight lang="javascript">
const onPress = () => {
  CalendarModule.createCalendarEvent(
    'Party',
    'My House',
    (eventId) => {
      console.log(`Created a new event with id ${eventId}`);
    }
  );
};
</syntaxhighlight>
هناك تفصيل آخر مهم يجب ملاحظته وهو أن تابع الوحدة الأصيلة يمكنه استدعاء دالة رد نداء واحدة ومرة واحدةً فقط. هذا يعني أنه يمكنك إما استدعاء دالة رد نداء نجاح أو دالة رد نداء فشل، ولا يمكن استدعاء كل دالة رد نداء إلا مرة واحدة على الأكثر، ولكن يمكن للوحدة الأصيلة أن تخزّن دالة رد النداء وتستدعيها لاحقًا.
 
هناك طريقتان لمعالجة أخطاء دوال رد النداء، فالطريقة الأولى هي اتباع عُرف Node ومعاملة الوسيط الأول الذي يُمرَّر إلى دالة رد النداء ككائن خطأ.<syntaxhighlight lang="javascript">
@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
    Integer eventId = ...
    callBack.invoke(null, eventId);
}
</syntaxhighlight>
ثم يمكنك في شيفرة JavaScript التحقق من أن الوسيط الأول قد مرّر خطأً أم لا كما يلي:<syntaxhighlight lang="javascript">
const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    (error, eventId) => {
      if (error) {
        console.error(`Error found! ${error}`);
      }
      console.log(`event id ${eventId} returned`);
    }
  );
};
</syntaxhighlight>
أما الطريقة الثانية هي استخدام دالتي رد نداء منفصلتين هما: <code>onFailure</code> و <code>onSuccess</code>.<syntaxhighlight lang="javascript">
@ReactMethod
public void createCalendarEvent(String name, String location, Callback myFailureCallback, Callback mySuccessCallback) {
}
</syntaxhighlight>
ثم يمكنك في شيفرة JavaScript إضافة دالة رد نداء منفصلة لاستجابات الخطأ والنجاح كما يلي:<syntaxhighlight lang="javascript">
const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    (error) => {
      console.error(`Error found! ${error}`);
    },
    (eventId) => {
      console.log(`event id ${eventId} returned`);
    }
  );
};
</syntaxhighlight>


يمكن للوحدات الأصيلة أيضًا الوفاء بالوعود، ما يمكن أن يبسط شيفرتك، خاصة عند استخدام بنية ‎‎<code>async/await</code>‎‎ الجديدة في نسخة ES2016. عندما يكون <code>Promise</code> آخر معاملٍ من معاملات التابع الأصيل الموصَل عن طريق الجسر، فسيُعيد تابع JavaScript الخاص به والمقابل له كائنَ <code>Promise</code> في JavaScript.
=== الوعود Promises ===
يمكن للوحدات الأصيلة أيضًا أن تفي [[JavaScript/Promise/Using promises|بالوعود Promise]]، وهذا يبسّط شيفرة JavaScript الخاصة بك، خاصةً عند استخدام صيغة <code>[[JavaScript/await|async/await]]</code> في النسخة ES2016. إذا كان المعامل الأخير لتابع Java للوحدة الأصيلة هو وعد Promise، فإن تابع JS المقابل لها سيعيد كائن JS هو كائن الوعد Promise.


ستبدو [https://wiki.hsoub.com/Refactoring إعادة تصميم الشيفرة] أعلاه لاستخدام وعدٍ بدلاً من دوال رد النداء كما يلي:
تبدو إعادة تصميم الشيفرة السابقة بهدف استخدام وعد بدلًا من دوال رد النداء كما يلي:<syntaxhighlight lang="javascript">
<syntaxhighlight lang="java">
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.Promise;


public class UIManagerModule extends ReactContextBaseJavaModule {
@ReactMethod
 
public void createCalendarEvent(String name, String location, Promise promise) {
...
  private static final String E_LAYOUT_ERROR = "E_LAYOUT_ERROR";
  @ReactMethod
  public void measureLayout(
      int tag,
      int ancestorTag,
      Promise promise) {
     try {
     try {
      measureLayout(tag, ancestorTag, mMeasureBuffer);
        Integer eventId = ...
 
        promise.resolve(eventId);
      WritableMap map = Arguments.createMap();
     } catch(Exception e) {
 
        promise.reject("Create Event Error", e);
      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);
     }
     }
  }
}
 
</syntaxhighlight><blockquote>'''ملاحظة''': يمكن لتابع الوحدة الأصيلة إما أن يرفض أو يؤكّد resolve الوعد (ولكن ليس كليهما) ويمكنه تنفيذ ذلك مرة واحدة على الأكثر مثل دوال رد النداء. هذا يعني أنه يمكنك إما استدعاء دالة رد نداء نجاح أو رد نداء فشل، ولكن ليس كليهما، ولا يمكن استدعاء كل رد نداء إلا مرة واحدة على الأكثر، ولكن يمكن للوحدة الأصلية أن تخزّن دالة رد النداء وتستدعيها لاحقًا.</blockquote>
...
يعيد نظير JavaScript الخاص بهذا التابع وعدًا (أي كائن ‎‎Promise‎‎). هذا يعني أنه يمكنك استخدام الكلمة المفتاحية <code>await</code> ضمن دالة غير متزامنة لاستدعائها وانتظار نتيجتها كما يلي:<syntaxhighlight lang="javascript">
</syntaxhighlight>
const onSubmit = async () => {
يقوم نظير JavaScript الخاص بهذا التابع بإرجاع وعد (أي كائن ‎‎<code>Promise</code>‎‎). هذا يعني أنه يمكنك استخدام الكلمة المفتاحيّة <code>await</code> داخل دالة غير متزامنة (async function) لاستدعائها وانتظار نتيجتها:
<syntaxhighlight lang="javascript">
async function measureLayout() {
   try {
   try {
     var {relativeX, relativeY, width, height} = await UIManager.measureLayout(
     const eventId = await CalendarModule.createCalendarEvent(
       100,
       'Party',
       100,
       'My House'
     );
     );
 
     console.log(`Created a new event with id ${eventId}`);
     console.log(relativeX + ':' + relativeY + ':' + width + ':' + height);
   } catch (e) {
   } catch (e) {
     console.error(e);
     console.error(e);
   }
   }
}
};
</syntaxhighlight>
يأخذ تابع الرفض مجموعات مختلفة من الوسطاء التالية:<syntaxhighlight lang="javascript">
String code, String message, WritableMap userInfo, Throwable throwable
</syntaxhighlight>
اطّلع على المزيد من التفاصيل عن الواجهة <code>[https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/Promise.java#L1 Promise.java]</code>. إن لم تتوفّر معلومات المستخدم <code>userInfo</code>، فسيضبطها ReactNative على القيمة الخالية null، وسيستخدم قيمة افتراضية للمعاملات الأخرى. يوفّر الوسيط <code>message</code> رسالة الخطأ الموضّحة في أعلى مكدس استدعاءات الخطأ. يوضّح المثال التالي رسالة الخطأ الموضّحة في شيفرة JavaScript من استدعاء الرفض التالي في شيفرة Java.


measureLayout();
استدعاء الرفض في شيفرة Java:<syntaxhighlight lang="java">
promise.reject("Create Event error", "Error parsing date", e);
</syntaxhighlight>
</syntaxhighlight>
===التسلسل Threading===
رسالة الخطأ في تطبيق React Native عند رفض الوعد:
لا ينبغي أن تَفترِض الوحدة الأصيلة ماهية السلسلة التي يتم استدعاؤها عليها، لأن ذلك قد يتغيَّر في المستقبل. إذا كانت هناك حاجة إلى استدعاءٍ موقِف (blocking call)، فيجب إرسال العمل طويل الأمد إلى سلسلةِ عاملٍ مُدارة داخليًا (internally managed worker thread) ويجب توزيع أي دوال رد نداء من هناك.
[[ملف:native-modules-android-errorscreen.png|بديل=native modules android errorscreen|مركز|تصغير|رسالة الخطأ]]
===إرسال الأحداث إلى JavaScript===
يمكن أن تشير الوحدات الأصيلة إلى الأحداث (signal events) للغة JavaScript دون استدعائها مباشرة. أسهل طريقة للقيام بذلك هي استخدام <code>RCTDeviceEventEmitter</code> والذي يمكن الحصول عليه من <code>ReactContext</code> كما في الشيفرة أدناه:


<syntaxhighlight lang="java">
=== إرسال الأحداث إلى JavaScript ===
يمكن للوحدات الأصيلة أن تشير إلى أحداث JavaScript دون استدعائها مباشرة، فقد ترغب مثلًا في إرسال إشارة إلى شيفرة JavaScript لتذكيرها بأن حدث تقويم من تطبيق تقويم Android الأصيل سيحدث قريبًا. الطريقة الأسهل لتنفيذ ذلك هي استخدام <code>RCTDeviceEventEmitter</code> الذي يمكن الحصول عليه من <code>ReactContext</code> كما في جزء الشيفرة التالي:<syntaxhighlight lang="java">
...
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
...
...
private void sendEvent(ReactContext reactContext,
private void sendEvent(ReactContext reactContext,
                      String eventName,
                      String eventName,
                      @Nullable WritableMap params) {
                      @Nullable WritableMap params) {
  reactContext
reactContext
      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
      .emit(eventName, params);
    .emit(eventName, params);
}
}
...
...
WritableMap params = Arguments.createMap();
WritableMap params = Arguments.createMap();
params.putString("eventProperty", "someValue");
...
...
sendEvent(reactContext, "keyboardWillShow", params);
sendEvent(reactContext, "EventReminder", params);
</syntaxhighlight>
</syntaxhighlight>ثم يمكن لوحدات JavaScript التسجيل لتلقّي الأحداث عن طريق <code>addListener</code> للصنف [https://github.com/facebook/react-native/blob/master/Libraries/EventEmitter/NativeEventEmitter.js <code>NativeEventEmitter</code>].<syntaxhighlight lang="javascript">
 
import { NativeEventEmitter, NativeModules } from 'react-native';
يمكن لوحدات JavaScript التسجيل لتلقي الأحداث عن طريق <code>addListenerOn</code> باستخدام مخلوط <code>Subscribable</code>.
<syntaxhighlight lang="javascript">
import { DeviceEventEmitter } from 'react-native';
...
...


var ScrollResponderMixin = {
componentDidMount() {
  mixins: [Subscribable.Mixin],
  ...
  const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
  this.eventListener = eventEmitter.addListener('EventReminder', (event) => {
      console.log(event.eventProperty) // "someValue"
  });
  ...
}


 
componentWillUnmount() {
  componentDidMount() {
  this.eventListener.remove(); //‫حذف المستمع listener
    ...
}
    this.addListenerOn(DeviceEventEmitter,
                      'keyboardWillShow',
                      this.scrollResponderKeyboardWillShow);
    ...
  },
  scrollResponderKeyboardWillShow:function(e: Event) {
    this.keyboardWillOpenTo = e;
    this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
  },
</syntaxhighlight>
</syntaxhighlight>
يمكنك أيضًا استخدام وحدة <code>DeviceEventEmitter</code> مباشرةً للاستماع للأحداث.
<syntaxhighlight lang="javascript">
...
componentDidMount() {
  this.subscription = DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    // handle event
  });
}


componentWillUnmount() {
===الحصول على نتيجة النشاط activity result من <code>startActivityForResult</code>===
  // إذا أردت التوقف عن الاستماع لأحداث جديدة، فاستدعِ التابع
ستحتاج إلى الاستماع إلى <code>onActivityResult</code> إذا رغبت في الحصول على نتائجٍ من نشاطٍ بدأت به باستخدام <code>startActivityForResult</code>، من خلال وراثة الصنف <code>BaseActivityEventListener</code> أو تطبيق <code>ActivityEventListener</code>. الخيار الأول هو المفضل لأنه أكثر مقاومةً لتغييرات واجهة API. تحتاج بعد ذلك إلى تسجيل المستمع في الدالة البانية constructor للوحدة كما يلي:<syntaxhighlight lang="java">
  // .remove()
 
  this.subscription.remove();
}
...
</syntaxhighlight>
===الحصول على نتيجة النشاط (activity result) من <code>startActivityForResult</code>===
ستحتاج إلى الاستماع إلى <code>onActivityResult</code> إذا رغبت في الحصول على نتائجٍ من نشاطٍ بدأت به باستخدام <code>startActivityForResult</code>. للقيام بذلك، يجب عليك توسيع (extend) الصنف <code>BaseActivityEventListener</code> أو إجراء (implement) <code>ActivityEventListener</code>. الخيار الأول هو المفضل لأنه أكثر مقاومةً لتغييرات الواجهة البرمجية (API). ثم تحتاج إلى تسجيل المستمع في الدالة البانية (constructor) للوحدة.
<syntaxhighlight lang="javascript">
reactContext.addActivityEventListener(mActivityResultListener);
reactContext.addActivityEventListener(mActivityResultListener);
</syntaxhighlight>
</syntaxhighlight>يمكنك الآن الاستماع إلى <code>onActivityResult</code> من خلال تطبيق التابع التالي:<syntaxhighlight lang="javascript">
 
يمكنك الآن الاستماع إلى <code>onActivityResult</code> من خلال إجراء التابع التالي:
<syntaxhighlight lang="java">
@Override
@Override
public void onActivityResult(
public void onActivityResult(
  final Activity activity,
final Activity activity,
  final int requestCode,
final int requestCode,
  final int resultCode,
final int resultCode,
  final Intent intent) {
final Intent intent) {
  // ضع شيفرتك هنا
// ضع شيفرتك هنا
}
}
</syntaxhighlight>
</syntaxhighlight>سنطبّق مُنتقِي صور image picker بسيط لشرح هذا الأمر. سيوفر منتقي الصور  الوصول لتابع <code>pickImage</code> من شيفرة JavaScript، والذي سيعيد مسار الصورة عند استدعائه.<syntaxhighlight lang="java">
سنُجري (implement) مُنتَقِيَ صُوَرٍ (image picker) بسيط لشرح هذا الأمر. سيوفر منتقي الصور  الوصول لتابع <code>pickImage</code> للغة JavaScript، والذي سيعيد مسار الصورة عند استدعائه.
<syntaxhighlight lang="java">
public class ImagePickerModule extends ReactContextBaseJavaModule {
public class ImagePickerModule extends ReactContextBaseJavaModule {


   private static final int IMAGE_PICKER_REQUEST = 467081;
   private static final int IMAGE_PICKER_REQUEST = 1;
   private static final String E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST";
   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_PICKER_CANCELLED = "E_PICKER_CANCELLED";
سطر 374: سطر 440:
   };
   };


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


     // أضف المُستمِع للتابع
     // ‫أضِف المستمع إلى `onActivityResult`
    // `onActivityResult`
     reactContext.addActivityEventListener(mActivityEventListener);
     reactContext.addActivityEventListener(mActivityEventListener);
   }
   }
سطر 396: سطر 461:
     }
     }


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


سطر 414: سطر 479:
}
}
</syntaxhighlight>
</syntaxhighlight>
===الاستماع إلى أحداث LifeCycle===
===الاستماع إلى أحداث LifeCycle===
يشبه الاستماع إلى أحداث LifeCycle الخاصة بالنشاط (مثل <code>onResume</code> و <code>onPause</code> إلخ)، إجراء <code>ActivityEventListener</code>. يجب أن تُجريَ الوحدة  <code>LifecycleEventListener</code>. ثم تحتاج إلى تسجيل مستمعٍ في دالّة الوحدة البانيّة:
يشبه الاستماع إلى أحداث LifeCycle الخاصة بالنشاط (مثل <code>onResume</code> و <code>onPause</code> وغير ذلك) كيفية تطبيق <code>ActivityEventListener</code>. يجب أن تطبّق الوحدة  <code>LifecycleEventListener</code>، ثم يجب تسجيل مستمعٍ في دالّة الوحدة البانيّة كما يلي:<syntaxhighlight lang="javascript">
<syntaxhighlight lang="javascript">
reactContext.addLifecycleEventListener(this);
reactContext.addLifecycleEventListener(this);
</syntaxhighlight>
</syntaxhighlight>يمكنك الآن الاستماع إلى أحداث LifeCycle الخاصة بالنشاط من خلال تطبيق التوابع التالية:<syntaxhighlight lang="java">
يمكنك الآن الاستماع إلى أحداث LifeCycle الخاصة بالنشاط من خلال تطبيق التوابع التالية:
<syntaxhighlight lang="java">
@Override
@Override
public void onHostResume() {
public void onHostResume() {
    // Activity `onResume`
  // ‫النشاط `onResume`
}
}
@Override
@Override
public void onHostPause() {
public void onHostPause() {
    // Activity `onPause`
  // ‫النشاط `onPause`
}
}
@Override
@Override
public void onHostDestroy() {
public void onHostDestroy() {
    // Activity `onDestroy`
  // ‫النشاط `onDestroy`
}
}
</syntaxhighlight>
</syntaxhighlight>


=== استخدام الخيوط Threading ===
تُنفَّذ جميع توابع الوحدة الأصيلة غير المتزامنة على خيط واحد على Android حتى الآن. لا ينبغي أن تَفترِض الوحدة الأصيلة الخيط الذي تُستدعَى عليه، لأنه قد يتغيَّر في المستقبل. إذا كانت هناك حاجة إلى إيقاف استدعاءٍ، فيجب إرسال العمل الطويل إلى خيط عامل مُدار داخليًا وتوزيع أي دوال رد نداء من هناك.
== مصادر ==
== مصادر ==
* [https://facebook.github.io/react-native/docs/native-modules-android صفحة Native Modules في توثيق React Native الرسمي.]
* [https://reactnative.dev/docs/native-modules-android صفحة Android Native Modules في توثيق React Native الرسمي.]
[[تصنيف:ReactNative]]
[[تصنيف:ReactNative]]
[[تصنيف:React Native Docs]]

المراجعة الحالية بتاريخ 13:49، 9 أكتوبر 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 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) المفيد للوحدات الأصيلة التي تحتاج إلى استخدام خطّاف hook لتوابع دورة حياة النشاط. سيسهّل استخدام ReactContextBaseJavaModule جعل الوحدة الأصيلة آمنة النوع مستقبلًا. سيُطرَح أمان نوع الوحدة الأصيلة في الإصدارات المستقبلية، حيث يبحث React Native في مواصفات JavaScript لكل وحدة أصيلة ويُنشئ صنفًا أساسيًا مجردًا يرث ReactContextBaseJavaModule.

اسم الوحدة

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

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

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

const { CalendarModule } = ReactNative.NativeModules;

تصدير تابع أصيل إلى شيفرة JavaScript

ستحتاج بعد ذلك إلى إضافة تابع إلى الوحدة الأصيلة الخاصة بك الذي سينشئ أحداث التقويم ويمكن استدعاؤه في شيفرة JavaScript. يجب الإشارة إلى جميع توابع الوحدات الأصيلة التي يُراد استدعاؤها من JavaScript باستخدام ‎@ReactMethod.

اضبط التابع createCalendarEvent()‎ للصنف CalendarModule الذي يمكن استدعاؤه في شيفرة JS بالشكل CalendarModule.createCalendarEvent()‎. سيأخذ هذا التابع حاليًا وسيطَي الاسم name والموقع location كسلاسل نصية (سنتكلم عن خيارات نوع الوسيط لاحقًا).

@ReactMethod
public void createCalendarEvent(String name, String location) {
}

أضِف سجل وحدة التحكم console log في هذا التابع، لتتمكّن من تأكيد استدعاء التابع عندما استدعيته من تطبيقك. يوضَح المثال التالي كيفية استيراد واستخدام الصنف Log من حزمة استخدام Android:

import android.util.Log;

@ReactMethod
public void createCalendarEvent(String name, String location) {
   Log.d("CalendarModule", "Create event called with name: " + name
   + " and location: " + location);
}

يمكنك اتباع هذه الخطوات لعرض السجلات logs من تطبيقك، بعد الانتهاء من تنفيذ الوحدة الأصيلة واستخدام خطّاف لربطها مع شيفرة JavaScript .

التوابع المتزامنة

يمكنك تمرير isBlockingSynchronousMethod = true إلى تابع أصيل لتمييزه على أنه تابع متزامن.

@ReactMethod(isBlockingSynchronousMethod = true)

لا نوصي حاليًا باستخدام توابع متزامنة، لأن استدعاء التوابع المتزامنة يمكن أن يكون لها عواقب على الأداء ويمكن أن يدخل أخطاءً مرتبطة بالخيوط إلى وحداتك الأصيلة. لاحظ أيضًا أنه إذا اخترت تفعيل isBlockingSynchronousMethod، فلن يتمكن تطبيقك من استخدام منقّح أخطاء Google Chrome مرة أخرى، لأن التوابع المتزامنة تتطلب آلة JS الافتراضية لمشاركة الذاكرة مع التطبيق. بينما بالنسبة لمنقّح أخطاء Google Chrome، فإن React Native يعمل داخل آلة JS الافتراضية في Google Chrome، ويتواصل تواصلًا غير متزامن مع الأجهزة المتنقلة عبر WebSockets.

تسجيل الوحدة (خاص بنظام Android)

يجب تسجيل الوحدة الأصيلة بعد كتابتها في React Native، لذلك تحتاج إلى إضافة الوحدة الخاصة بك إلى الحزمة ReactPackage وتسجيل هذه الحزمة باستخدام React Native. سيعمل React Native أثناء التهيئة على تكرار كل الحزم ويسجّل كل وحدة أصيلة بداخل حزمتها.

يستدعي React Native التابع createNativeModules()‎ في الحزمة ReactPackage للحصول على قائمة الوحدات الأصيلة لتسجيلها. إن لم تُنشَأ وحدة في نظام Android ولكن أعادها التابع createNativeModules، فلن تكون متاحة من شيفرة JavaScript.

يمكنك إضافة الوحدة الخاصة بك إلى الحزمة ReactPackage من خلال إنشاء صنف Java جديد باسم MyAppPackage.java يطبّق الحزمة ReactPackage داخل المجلد android/app/src/main/java/com/your-app-name/‎، ثم أضِف ما يلي:

package com.your-app-name; // ‫استبدل 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 MyAppPackage 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 CalendarModule(reactContext));

       return modules;
   }

}

يستورد هذا الملف الوحدة الأصيلة التي أنشأتها وهي CalendarModule، ثم ينشئ نسخة منها ضمن دالة createNativeModules()‎ ويعيدها كقائمة وحدات NativeModules لتسجيلها. إذا أضفت المزيد من الوحدات الأصيلة لاحقًا، فيمكنك أيضًا إنشاء نسخة منها وإضافتها إلى القائمة المُعادة.

ملاحظة: تجدر الإشارة إلى أن هذه الطريقة في تسجيل الوحدات الأصيلة تؤدي إلى تهيئة جميع الوحدات الأصيلة عند بدء تشغيل التطبيق، مما يزيد من وقت بدء تشغيل التطبيق. يمكنك استخدام الحزمة TurboReactPackage كبديل، إذ تطبّق هذه الحزمة التابع getModule(String name, ReactApplicationContext rac) الذي ينشئ كائن الوحدة الأصيلة عند الحاجة، بدلًا من الدالة createNativeModules التي تعيد قائمة بكائنات الوحدة الأصيلة، ولكن الحزمة TurboReactPackage أكثر تعقيدًا في التطبيق حاليًا. يجب تنفيذ التابع getReactModuleInfoProvider()‎ بالإضافة إلى تنفيذ التابع getModule()‎، حيث يعيد التابع getReactModuleInfoProvider()‎ قائمة بجميع الوحدات الأصيلة التي يمكن للحزمة إنشاء نسخة منها مع دالة تنشئها (اطّلع على هذا المثال). سيسمح استخدام TurboReactPackage لتطبيقك بالحصول على وقت بدء تشغيل أسرع، ولكن تُعَد كتابته حاليًا أمرًا مرهقًا وأكثر تعقيدًا، لذلك تابع بحذر إذا اخترت استخدام حزم TurboReactPackages.

يجب إضافة MyAppPackage إلى قائمة الحزم التي يعيدها تابع ReactNativeHost الذي هو getPackages()‎، لتسجيل الحزمة CalendarModule. افتح الملف MainApplication.java الذي يمكن العثور عليه في المسار التالي: android/app/src/main/java/com/your-app-name/MainApplication.java. حدد موقع تابع ReactNativeHost الذي هو getPackages()‎، وأضف حزمتك إلى قائمة الحزم التي يعيدها التابع getPackages()‎:

@Override
  protected List<ReactPackage> getPackages() {
    @SuppressWarnings("UnnecessaryLocalVariable")
    List<ReactPackage> packages = new PackageList(this).getPackages();
    // ‫تُضاف الحزمة MyAppPackage إلى قائمة الحزم المُعادة أدناه
    packages.add(new MyAppPackage());
    return packages;
  }

لقد نجحت الآن في تسجيل وحدتك الأصيلة لنظام Android.

اختبر ما بنيته

أجريت حتى الآن الإعداد الأساسي لوحدتك الأصيلة في Android. اختبر هذا الإعداد من خلال الوصول إلى الوحدة الأصيلة واستدعاء تابعها المُصدَّر في شيفرة JavaScript.

ابحث عن مكانٍ ما في تطبيقك حيث تريد إضافة استدعاء تابع الوحدة الأصيلة createCalendarEvent()‎. يحتوي المثال التالي المكوّن NewModuleButton الذي يمكنك إضافته إلى تطبيقك، إذ يمكنك استدعاء الوحدة الأصيلة ضمن الدالة onPress()‎ الخاصة بالمكوّن NewModuleButton كما يلي:

import React from 'react';
import { NativeModules, Button } from 'react-native';

const NewModuleButton = () => {
  const onPress = () => {
    console.log('We will invoke the native module here!');
  };

  return (
    <Button
      title="Click to invoke your native module!"
      color="#841584"
      onPress={onPress}
    />
  );
};

export default NewModuleButton;

يجب استيراد NativeModules من React Native من أجل الوصول إلى الوحدة الأصيلة الخاصة بك من شيفرة JavaScript:

import { NativeModules } from 'react-native';

ثم يمكنك الوصول إلى الوحدة الأصيلة CalendarModule من خارج NativeModules:

const { CalendarModule } = NativeModules;

يمكنك الآن استدعاء التابع الأصيل createCalendarEvent()‎ بعد أن أصبحت الوحدة الأصيلة CalendarModule متاحة. أُضيف فيما يلي هذا التابع الأصيل إلى التابع onPress()‎ في المكوّن NewModuleButton:

const onPress = () => {
  CalendarModule.createCalendarEvent('testName', 'testLocation');
};

الخطوة الأخيرة هي إعادة بناء تطبيق React Native بحيث يمكنك الحصول على أحدث شيفرة أصيلة (مع الوحدة الأصيلة الجديدة). شغّل الأمر التالي في سطر الأوامر ضمن المكان الذي يوجد فيه تطبيق react native:

npx react-native run-android

إعادة البناء عند التكرار

ستحتاج أثناء تكرار وحدتك الأصيلة إلى إعادة بناء أصيلة لتطبيقك بهدف الوصول إلى أحدث التغييرات من شيفرة JavaScript، لأن الشيفرة التي تكتبها موجودة ضمن الجزء الأصيل من تطبيقك. يمكن لمجمّع metro الخاص بإطار عمل React Native مراقبة التغييرات في شيفرة JavaScript وإعادة إنشاء حزمة JS سريعًا نيابةً عنك، إلّا أنه لن يفعل ذلك مع الشيفرة الأصيلة. لذلك إذا أردت اختبار أحدث التغييرات الأصيلة، فيجب إعادة البناء باستخدام الأمر npx react-native run-android.

الخلاصة

يجب أن تكون الآن قادرًا على استدعاء التابع createCalendarEvent()‎ للوحدة الأصيلة في تطبيقك، ويحدث ذلك في مثالنا من خلال الضغط على NewModuleButton. يمكنك تأكيد ذلك من خلال عرض السجل الذي ضبطته في التابع createCalendarEvent()‎ الخاصة بك (اتبع هذه الخطوات لعرض سجلات ADB في تطبيقك). يجب أن تكون قادرًا بعد ذلك على البحث عن رسالة Log.d (في مثالنا الرسالة هي “Create event called with name: testName and location: testLocation”) ومشاهدة رسالتك مُسجَّلة في كل مرة تستدعي فيها تابع الوحدة الأصيلة.

native modules android logs
سجلات ADB في Android Studio

أنشأت حتى الآن وحدة Android أصيلة واستدعيت تابعها الأصيل من شيفرة JavaScript في تطبيق React Native الخاص بك. تابع القراءة لمعرفة المزيد عن أنواع الوسطاء التي يأخذها تابع الوحدة الأصيلة الخاصة بك وكيفية إعداد دوال رد النداء callbacks والوعود promises.

ما بعد وحدة التقويم الأصيلة

تصدير الوحدة الأصيلة الأفضل

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

/**
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:

* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
*/
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules;
export default CalendarModule;

يصبح ملف JavaScript هذا أيضًا موقعًا جيدًا لإضافة أي عمليات من جانب شيفرة JavaScript. إذا استخدمت مثلًا نظام أنواع مثل TypeScript، فيمكنك إضافة تعليقات الأنواع للوحدة الأصيلة في هذا الملف. لا يدعم React Native حتى الآن أمان النوع Native to JS، ولكنّ جميع شيفرات JS الخاصة بك ستكون من النوع الآمن. ستسهّل هذه التعليقات عليك التبديل إلى الوحدات الأصيلة ذات النوع الآمن باستمرار. يوضّح المثال التالي إضافة النوع الآمن إلى وحدة التقويم CalendarModule:

/**
* ‫يُظهِر هذا الملف الوحدة الأصيلة CalendarModule كوحدة JS ويحتوي على
* ‫الدالة 'createCalendarEvent' التي تأخذ المعاملات التالية:
*
* ‫1. السلسلة النصية name التي تمثّل اسم الحدث
* ‫2. السلسلة النصية location التي تمثّل موقع الحدث
*/
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules
interface CalendarInterface {
   createCalendarEvent(name: string, location: string): void;
}
export default CalendarModule as CalendarInterface;

يمكنك في ملفات JavaScript الأخرى الوصول إلى الوحدة الأصيلة واستدعاء تابعها كما يلي:

import CalendarModule from './CalendarModule';
CalendarModule.createCalendarEvent('foo', 'bar');

لاحظ أن هذا المثال افترض أن المكان الذي تستورد منه CalendarModule موجود ضمن تسلسل الملف CalendarModule.js الهرمي نفسه، لذلك يجب أن تحدّث مسار الاستيراد النسبي عند الحاجة.

أنواع الوسطاء

يحوّل React Native الوسطاء من كائنات JS إلى مثيلاتها في لغة Java عند استدعاء تابع الوحدة الأصلية في شيفرة JavaScript . إن قَبِل تابع وحدة Java الأصيلة عددًا مضاعفًا double على سبيل المثال، فستحتاج في شيفرة JS إلى استدعاء التابع باستخدام وسيط من النوع عدد number، حيث سيتوّلى React Native عملية التحويل نيابةً عنك. فيما يلي قائمة بأنواع الوسطاء المدعومة لتوابع الوحدات الأصيلة ومقابلاتها في JavaScript التي ستُربَط معها:

JAVA JAVASCRIPT
Boolean ‎?boolean قيمة منطقية
boolean boolean قيمة منطقية
Double ‎?number عدد
double number عدد
String string سلسلة نصية
Callback Function تابع
ReadableMap Object كائن
ReadableArray Array مصفوفة

الأنواع التالية مدعومة حاليًا ولكن لن يدعمها نظام TurboModules، لذلك يُرجَى تجنب استخدامها:

  • Integer -> ?number
  • int -> number
  • Float -> ?number
  • float -> number

ستحتاج إلى التعامل مع التحويل بنفسك بالنسبة لأنواع الوسائط غير المذكورة أعلاه، إذ لا يدعم Android تحويل النوع Date مثلًا. يمكنك التعامل مع التحويل إلى النوع Date ضمن التابع الأصيل بنفسك كما يلي:

    String dateFormat = "yyyy-MM-dd";
    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
    Calendar eStartDate = Calendar.getInstance();
    try {
        eStartDate.setTime(sdf.parse(startDate));
    }

تصدير الثوابت

يمكن للوحدة الأصيلة تصدير الثوابت عن طريق تطبيق التابع الأصيل getConstants()‎ المتوفّر في JS. ستطبّق فيما يلي التابع getConstants()‎ وتعيد Map تحتوي على الثابت DEFAULT_EVENT_NAME الذي يمكنك الوصول إليه في شيفرة JavaScript:

@Override
public Map<String, Object> getConstants() {
   final Map<String, Object> constants = new HashMap<>();
   constants.put("DEFAULT_EVENT_NAME", "New Event");
   return constants;
}

يمكن بعد ذلك الوصول إلى الثابت عن طريق استدعاء التابع getConstants()‎ ضمن الوحدة الأصيلة في شيفرة JS كما يلي:

const { DEFAULT_EVENT_NAME } = CalendarModule.getConstants();
console.log(DEFAULT_EVENT_NAME);

يمكن الوصول إلى الثوابت المُصدَّرة في التابع getConstants()‎ مباشرةً من كائن الوحدة الأصيلة، ولكن لن يدعم نظام TurboModules ذلك، لذلك نشجّع المجتمع على التبديل إلى الطريقة السابقة لتجنّب التهجير الضروري باستمرار.

ملاحظة: تُصدَّر الثوابت الحالية في وقت التهيئة initialization time فقط، لذلك إذا غيّرت قيم التابع getConstants()‎‎ في وقت التشغيل، فلن يؤثر ذلك على بيئة JavaScript. لكن ذلك سيتغيّر مع Turbomodules، إذ سيصبح التابع getConstants()‎ تابع وحدة أصيلة نمطية، وسيصل كلّ استدعاء إلى الجانب الأصيل.

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

تدعم الوحدات الأصيلة أيضًا نوعًا خاصًا من المعاملات الذي يتمثّل في دوال رد النداء Callbacks. تُستخدَم دوال رد النداء لتمرير البيانات من شيفرة Java إلى شيفرة JavaScript للتوابع غير المتزامنة، ويمكن استخدامها أيضًا لتنفيذ شيفرة JS تنفيذًا غير متزامن من الجانب الأصيل.

استورد أولًا واجهة رد النداء Callback ثم أضف معاملًا جديدًا إلى تابع الوحدة الأصيلة الخاص بك من النوع Callback، لإنشاء تابع وحدة أصيلة مع دالة رد نداء. هناك بعض الفروق مع وسطاء دالة رد النداء التي ستُزال قريبًا باستخدام نظام TurboModules، إذ لا يمكن أن يكون لديك سوى دالتَي رد نداء في وسطاء دوالك هما رد نداء النجاح successCallback ورد نداء الفشل failureCallback، ويجري التعامل مع الوسيط الأخير لاستدعاء تابع الوحدة الأصيلة -إذا كان دالة- على أنه SuccessCallback، ويجري التعامل مع الوسطاء من الوسيط الثاني إلى الوسيط الأخير لاستدعاء تابع الوحدة الأصيلة -إذا كانت دوالًا- على أنها failureCallback.

import com.facebook.react.bridge.Callback;

@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
}

يمكنك استدعاء دالة رد النداء في تابع Java الخاص بك، مع توفير أي بيانات تريد تمريرها إلى شيفرة JavaScript. لاحظ أنه يمكنك فقط تمرير البيانات القابلة للسَّلسَلة من الشيفرة الأصيلة إلى شيفرة JavaScript. إذا احتجت إعادة كائن أصيل، فيمكنك استخدام WriteableMaps، بينما إذا احتجت استخدام مجموعة منها، فاستخدم WritableArrays. يجب التركيز على أن دالة رد النداء لا تُستدعَى فور اكتمال الدالة الأصيلة. يُمرَّر فيما يلي معرّف حدث أُنشِئ في استدعاء سابق إلى دالة رد النداء:

  @ReactMethod
   public void createCalendarEvent(String name, String location, Callback callBack) {
       Integer eventId = ...
       callback.invoke(eventId);
   }

يمكن بعد ذلك الوصول إلى هذا التابع في شيفرة JavaScript كما يلي:

const onPress = () => {
  CalendarModule.createCalendarEvent(
    'Party',
    'My House',
    (eventId) => {
      console.log(`Created a new event with id ${eventId}`);
    }
  );
};

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

هناك طريقتان لمعالجة أخطاء دوال رد النداء، فالطريقة الأولى هي اتباع عُرف Node ومعاملة الوسيط الأول الذي يُمرَّر إلى دالة رد النداء ككائن خطأ.

@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
    Integer eventId = ...
    callBack.invoke(null, eventId);
}

ثم يمكنك في شيفرة JavaScript التحقق من أن الوسيط الأول قد مرّر خطأً أم لا كما يلي:

const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    (error, eventId) => {
      if (error) {
        console.error(`Error found! ${error}`);
      }
      console.log(`event id ${eventId} returned`);
    }
  );
};

أما الطريقة الثانية هي استخدام دالتي رد نداء منفصلتين هما: onFailure و onSuccess.

@ReactMethod
public void createCalendarEvent(String name, String location, Callback myFailureCallback, Callback mySuccessCallback) {
}

ثم يمكنك في شيفرة JavaScript إضافة دالة رد نداء منفصلة لاستجابات الخطأ والنجاح كما يلي:

const onPress = () => {
  CalendarModule.createCalendarEventCallback(
    'testName',
    'testLocation',
    (error) => {
      console.error(`Error found! ${error}`);
    },
    (eventId) => {
      console.log(`event id ${eventId} returned`);
    }
  );
};

الوعود Promises

يمكن للوحدات الأصيلة أيضًا أن تفي بالوعود Promise، وهذا يبسّط شيفرة JavaScript الخاصة بك، خاصةً عند استخدام صيغة async/await في النسخة ES2016. إذا كان المعامل الأخير لتابع Java للوحدة الأصيلة هو وعد Promise، فإن تابع JS المقابل لها سيعيد كائن JS هو كائن الوعد Promise.

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

import com.facebook.react.bridge.Promise;

@ReactMethod
public void createCalendarEvent(String name, String location, Promise promise) {
    try {
        Integer eventId = ...
        promise.resolve(eventId);
    } catch(Exception e) {
        promise.reject("Create Event Error", e);
    }
}

ملاحظة: يمكن لتابع الوحدة الأصيلة إما أن يرفض أو يؤكّد resolve الوعد (ولكن ليس كليهما) ويمكنه تنفيذ ذلك مرة واحدة على الأكثر مثل دوال رد النداء. هذا يعني أنه يمكنك إما استدعاء دالة رد نداء نجاح أو رد نداء فشل، ولكن ليس كليهما، ولا يمكن استدعاء كل رد نداء إلا مرة واحدة على الأكثر، ولكن يمكن للوحدة الأصلية أن تخزّن دالة رد النداء وتستدعيها لاحقًا.

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

const onSubmit = async () => {
  try {
    const eventId = await CalendarModule.createCalendarEvent(
      'Party',
      'My House'
    );
    console.log(`Created a new event with id ${eventId}`);
  } catch (e) {
    console.error(e);
  }
};

يأخذ تابع الرفض مجموعات مختلفة من الوسطاء التالية:

String code, String message, WritableMap userInfo, Throwable throwable

اطّلع على المزيد من التفاصيل عن الواجهة Promise.java. إن لم تتوفّر معلومات المستخدم userInfo، فسيضبطها ReactNative على القيمة الخالية null، وسيستخدم قيمة افتراضية للمعاملات الأخرى. يوفّر الوسيط message رسالة الخطأ الموضّحة في أعلى مكدس استدعاءات الخطأ. يوضّح المثال التالي رسالة الخطأ الموضّحة في شيفرة JavaScript من استدعاء الرفض التالي في شيفرة Java.

استدعاء الرفض في شيفرة Java:

promise.reject("Create Event error", "Error parsing date", e);

رسالة الخطأ في تطبيق React Native عند رفض الوعد:

native modules android errorscreen
رسالة الخطأ

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

يمكن للوحدات الأصيلة أن تشير إلى أحداث JavaScript دون استدعائها مباشرة، فقد ترغب مثلًا في إرسال إشارة إلى شيفرة JavaScript لتذكيرها بأن حدث تقويم من تطبيق تقويم Android الأصيل سيحدث قريبًا. الطريقة الأسهل لتنفيذ ذلك هي استخدام RCTDeviceEventEmitter الذي يمكن الحصول عليه من ReactContext كما في جزء الشيفرة التالي:

...
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.Arguments;
...
private void sendEvent(ReactContext reactContext,
                      String eventName,
                      @Nullable WritableMap params) {
 reactContext
     .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
     .emit(eventName, params);
}
...
WritableMap params = Arguments.createMap();
params.putString("eventProperty", "someValue");
...
sendEvent(reactContext, "EventReminder", params);

ثم يمكن لوحدات JavaScript التسجيل لتلقّي الأحداث عن طريق addListener للصنف NativeEventEmitter.

import { NativeEventEmitter, NativeModules } from 'react-native';
...

 componentDidMount() {
   ...
   const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
   this.eventListener = eventEmitter.addListener('EventReminder', (event) => {
      console.log(event.eventProperty) // "someValue"
   });
   ...
 }

 componentWillUnmount() {
   this.eventListener.remove(); //‫حذف المستمع listener
 }

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

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

reactContext.addActivityEventListener(mActivityResultListener);

يمكنك الآن الاستماع إلى onActivityResult من خلال تطبيق التابع التالي:

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

سنطبّق مُنتقِي صور image picker بسيط لشرح هذا الأمر. سيوفر منتقي الصور الوصول لتابع pickImage من شيفرة JavaScript، والذي سيعيد مسار الصورة عند استدعائه.

public class ImagePickerModule extends ReactContextBaseJavaModule {

  private static final int IMAGE_PICKER_REQUEST = 1;
  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;
        }
      }
    }
  };

  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() {
   // ‫النشاط `onResume`
}
@Override
public void onHostPause() {
   // ‫النشاط `onPause`
}
@Override
public void onHostDestroy() {
   // ‫النشاط `onDestroy`
}

استخدام الخيوط Threading

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

مصادر