آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

آموزش React Native: ماژول های native در android

دوره های مرتبط با این مقاله

آموزش React Native: ماژول های native در android

گاهی اوقات یک برنامه نیاز به دسترسی به یک API از پلتفرم دارد که React Native هنوز ماژول مربوط برای آن را ندارد. ممکن است بخواهید کد Javaی موجود را بدون پیاده سازی مجدد آن در JavaScript دوباره استفاده کنید یا کدی با عملکرد خوب و multi thread مثلا برای پردازش تصویر، دیتابیس، یا هر افزونه ی پیشرفته دیگری بنویسید.

React Native طوری طراحی شده است که بتوانید کد native بنویسید و روی از همه ی پتانسیل های هر پلتفرم استفاده کنید. این یک ویژگی پیشرفته تر است و انتظار نداریم که بخشی از روند معمولی توسعه باشد، اما ضروری است که باشد. اگر React Native از یک قابلیت native که نیاز دارید پشتیبانی نمی کند، باید بتوانید خودتان آن را بسازید.

راه اندازی ماژول native

ماژول های native معمولا به شکل package های npm توزیع می شوند، و علاوه بر فایل ها و منابع JavaScriptی حاوی یک پروژه android نیز هستند. این پروژه از دید npm ، مثل بقیه ی media asset ها است، و از این نظر تفاوتی ندارد. برای راه اندازی زیرساخت اولیه برای این کار، بخش آموزش راه اندازی ماژول های native را ببینید.

فعال کردن Gradle

اگر قصد تغییر کد Java را دارید، توصیه می کنیم برای سرعت بخشیدن به کار Gradle Daemon را فعال کنید.

ماژول Toast

این جا از مثال Toast استفاده می کنیم. فرضا می خواهیم یک پیام به شکل Toast در JavaScript بسازیم.

اول با ساخت یک ماژول native شروع می کنیم. یک ماژول native یک کلاس Javaست که معمولا از کلاس ReactContextBaseJavaModule، extends می شود، و عملکرد مورد نیاز برای JavaScript را پیاده سازی می کند. هدف این است که بتوانیم در JavaScript کد ToastExample.show('Awesome', ToastExample.SHORT); را بنویسیم و یک toast روی صفحه نمایش دهیم.

یک کلاس Java به نام ToastModule.java درون پوشه android/app/src/main/java/com/your-app-name/ با محتوای زیر بسازید:


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 کلاس را ملزم می کند متدی به نام getName را پیاده سازی کند. کار این متد بازگرداندن یک نام NativeModule در قالب یک رشته است که معرف این کلاس در JavaScript خواهد بود. در این مثال نامش را ToastExample می گذاریم و در JavaScript از طریق React.NativeModules.ToastExample قابل دسترسی خواهد بود.


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

یکی از متدها که پیاده سازی آن اختیاری است getConstants است که مقادیر ثابتی را برمی گرداند که در JavaScript قابل دسترسی است. با وجود اینکه پیاده سازی اش اختیاری است، برای ارسال مقادیر از پیش تعریف شده از Java به JavaScript، به کار می آید.


@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;
  }
                        

برای اینکه متدی در Java از JavaScript قابل دسترسی باشد، باید با ReactMethod@ نشانه گذاری شود. نوع بازگشتی توابع این چنینی (bridge methods) همواره void است. ارتباط React Native بین Java و JavaScript (bridge) به صورت async است، درنتیجه تنها راه ارسال یک نتیجه به JavaScript با استفاده از callbackها یا event است.


@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 بیشتر مطالعه کنید.

register کردن ماژول

آخرین مرحله در Java رجیستر کردن ماژول است؛ این کار در متد createNativeModules از application package ها انجام می شود. اگر ماژولی register نشود در 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;
  }
}

package باید در متد getPackages از فایل MainApplication.java قابل دسترس باشد. این فایل در پوشه android در دایرکتوری application 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()); //  < -- Add this line with your package name.
}

برای ساده کردن دسترسی به این ماژول در JavaScript، بهتر است آن را در یک ماژول JavaScriptی قرار دهیم. این کار ضروری نیست اما در صورت انجام، برای دسترسی به آن دیگر لازم نیست هربار آن را از NativeModules بیرون بکشیم. علاوه بر این، این فایل Javaااسکریپت مکان مناسبی برای افزودن قابلیت های JavaScriptی دیگر به ماژول است.

یک فایل Javaسکریپت جدید با نام ToastExample.js و محتوای زیر ایجاد کنید:


/**
 * This exposes the native ToastExample module as a JS module. This has a
 * function 'show' which takes the following parameters:
 *
 * 1. String message: A string with the text to toast
 * 2. int duration: The duration of the toast. May be ToastExample.SHORT or
 *    ToastExample.LONG
 */
import {NativeModules} from 'react-native';
module.exports = NativeModules.ToastExample;

حال، از فایل JavaScript دیگرتان می توانید متد را به شکل زیر صدا بزنید:


import ToastExample from './ToastExample';
ToastExample.show('Awesome', ToastExample.SHORT);

این فایل باید در کنار فایل ToastExample.js باشد.

فراتر از Toast

Callbacks

ماژول های native یک نوع خاص آرگومان را نیز می پذیرند ، یعنی Callback ها را. در اغلب موارد از این قابلیت برای برگرداندن نتیجه به Javaااسکریپت استفاده می شود.


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);
  },
);

از یک ماژول native انتظار می رود تنها یک بار callback خودش را فراخوانی کند. اما می تواند Callback را ذخیره و بعدا فراخوانی کند.

مهم است بدانید callback بلافاصله پس از پایان متد native فراخوانی نمی شود – ارتباط bridge به صورت async است.

Promise ها

ماژول های native می توانند یک promise را fulfill کنند. این قابلیت می تواند کد JavaScript را ساده کند، بخصوص اگر از دستور async/await موجود در ES2016 استفاده کنید. وقتی پارامتر آخر یک متد native، promise باشد، متد JavaScript معادل آن یک شی Promise Javaااسکریپتی برمی گرداند.

کد بالا را طوری تغییر داده ایم که به جای callback از promise استفاده کند:


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 برای فراخوانی آن و دریافت نتیجه استفاده کنید:


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

ماژول های native نباید چیزی درمورد thread که روی آن فراخوانی می شوند، بدانند. چرا که این ممکن است در آینده تغییر کند. اگر قرار است یک متد سنگین فراخوانی شود، کار آن باید به یک worker thread داخلی واگذار شود، و callback ها از آن جا فراخوانی شوند.

ارسال event به JavaScript

ماژول های native می توانند event هایی را به 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 می توانند خودشان را برای دریافت این event ها register کنند. این کار از طریق addListenerOn با استفاده از Subscribable mixin انجام می شود:


import { DeviceEventEmitter } from 'react-native';
...
var ScrollResponderMixin = {
  mixins: [Subscribable.Mixin],
  componentWillMount: function() {
    ...
    this.addListenerOn(DeviceEventEmitter,
                       'keyboardWillShow',
                       this.scrollResponderKeyboardWillShow);
    ...
  },
  scrollResponderKeyboardWillShow:function(e: Event) {
    this.keyboardWillOpenTo = e;
    this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
  },

می توانید مستقیم از ماژول DeviceEventEmitter برای دریافت event ها استفاده کنید:


...
componentWillMount: function() {
  DeviceEventEmitter.addListener('keyboardWillShow', function(e: Event) {
    // handle event.
  });
}
...

گرفتن پاسخ activity از startActivityForResult

اگر می خواهید از یک Activity که با startActivityForResult شروع شده، نتیجه ای بگیرید باید رویداد onActivityResult را دریافت کنید. برای این کار، باید BaseActivityEventListener را extend کنید، و یا ActivityEventListener را پیاده سازی کنید. روش دوم بهتر است چرا که امکان تغییر آن در آینده کمتر است. سپس باید در سازنده ی ماژول listener را register کنید:


reactContext.addActivityEventListener(mActivityResultListener);

حال می توانید با پیاده سازی متد زیر رویداد onActivityResult را listen کنید:


@Override
public void onActivityResult(
  final Activity activity,
  final int requestCode,
  final int resultCode,
  final Intent intent) {
  // Your logic here
}

یک ماژول ساده 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);
    // Add the listener for `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;
    }
    // Store the promise to resolve/reject when picker returns data
    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;
    }
  }
}

دریافت event های مربوط به چرخه حیات

اطلاع از event های مربوط به چرخه حیات Activity مثل onResume، onPause و غیره مشابه نحوه پیاده سازی ActivityEventListener است. ماژول باید LifecycleEventListener را پیاده سازی کند. سپس، باید در سازنده ی ماژول یکlistener برای آن register کنید:


reactContext.addLifecycleEventListener(this);

حال می توانید با پیاده سازی متدهای زیر event های مربوط به چرخه حیات Activity را دریافت کنید:


@Override
public void onHostResume() {
    // Activity `onResume`
}
@Override
public void onHostPause() {
    // Activity `onPause`
}
@Override
public void onHostDestroy() {
    // Activity `onDestroy`
}

  • 501
  •    0
  • تاریخ ارسال :   1398/06/10

دانشجویان گرامی اگر این مطلب برای شما مفید بود لطفا ما را در GooglePlus محبوب کنید
رمز عبور: tahlildadeh.com یا www.tahlildadeh.com
ارسال دیدگاه نظرات کاربران
شماره موبایل دیدگاه
عنوان پست الکترونیک

ارسال

آموزشگاه برنامه نویسی تحلیل داده
آموزشگاه برنامه نویسی تحلیل داده

تمامی حقوق این سایت متعلق به آموزشگاه تحلیل داده می باشد .