امکان ساخت و اجرای یک کتابخانه اندروید React Native
چگونه یک کتابخانه اندروید React Native بسازیم
به‌عنوان یک توسعه‌دهنده جاوا اسکریپت، درگیر شدن با بخش بومی‌ یا Native از React Native می‌تواند دلهره‌آور باشد. اما چنین برداشتی چندان هم درست نیست. می‌توانید به‌راحتی یک کد Native را به همراه یک کیت توسعه نرم‌افزاری جاوا که به شکل یک کتابخانه در کد اصلی واردشده ایجاد و استفاده کنید. برای شروع، استفاده از اسناد و راهنماهای موجود در مورد نحوه شروع به کار اجرای کد در React Native به همراه سایر مقاله‌ها و آموزش‌های موجود در این‌باره می‌تواند بسیار مفید باشد. اما کنار هم قرار دادن تمام این موارد به شکل یک کتابخانه کاربردی و همه‌منظوره می‌تواند چالش‌برانگیز و زمان‌بر باشد. در این نوشتار یک پروژه از پیش آماده‌شده (https://github.com/mdcuk34/react-native-library-boilerplate) را به شما معرفی می‌کنیم که امکان ساخت و اجرای یک کتابخانه اندروید React Native را در اختیارتان می‌گذارد.

گام اول: جاوا اسکریپت و ساختار کتابخانه 

قبل از آغاز هر اقدامی‌ ابتدا به این موضوع فکر کنید آیا اصلا برای پروژه خود به یک کتابخانه احتیاج دارید. اگر شما قصد نوشتن مقدار کمی‌ از کد بومی‌ (Native Code) را داشته باشید که کاملا با اپلیکیشن شما ترکیب می‌شود، الصاق کردن یک ماژول بومی‌ (Native Module) به داخل اپلیکیشن سریع‌ترین و بهترین راهکار است.

 react-native-create-bridge
(https://github.com/peggyrayzis/react-native-create-bridge)

یک مکان عالی برای شروع این کار است که در این نوشتار از کدهای آن استفاده می‌کنیم. 
در ابتدا نگاهی خواهیم داشت به نحوه ساخت یک کتابخانه مستقل که می‌توانید از آن در انواع مختلفی از اپلیکیشن‌ها استفاده کنید.
اجازه دهید ابتدا کار را با ابزار توصیه‌شده در اسناد React Native آغاز کنیم:

$ npm install -g react-native-create-library
$ react-native-create-library TestLib

مقدمه‌ای خوب برای این کتابخانه در اختیار ما قرار می‌دهد، اما احتمالا شما هم در مورد نحوه برقراری ارتباط همه این‌ها با یکدیگر کمی‌ دچار سردرگمی‌ شده‌اید. برای روشن شدن موضوع در ادامه به معرفی ساختار کد این کتابخانه و ساخت یک اپلیکیشن آزمایشی برای امتحان آن خواهیم پرداخت. 

اپلیکیشن آزمایشی 

در ابتدا کمی‌ تنظیمات مدیریتی نیاز داریم. پس پوشه‌ای به نام library ایجاد کرده و تمام محتوای پوشه TestLib را به آن منتقل می‌کنیم. سپس react-native init ExampleApp را اجرا می‌کنیم تا اپلیکیشن آزمایشی ما ایجاد شود.
سپس باید تعیین کنیم، چگونه می‌خواهیم از کتابخانه خود استفاده کنیم. در زیر تغییرات اعمال شده به App.js را مشاهده می‌کنید:

• import RNTest from ‘react-native-test-lib’ – کتابخانه ما
• state برای نگهداری پاسخ کد native ما
• getNativeResult() که یک تابع Async است، ابتدا state.nativeResult را برای loading… و بعد از آن دریافت پاسخ یا خطا RNTest.getValaue() را تنظیم می‌کنیم. 
• در JSX ما this.state.nativeResult را نمایش می‌دهیم و یک دکمه اضافه می‌کنیم تا getNativeResult() را فراخوانی کند.

همچنین ما باید package.json را ویرایش کنیم تا کتابخانه خود را که قبلا به App.js وارد کرده بودیم، در بر بگیرد. در زیر updateLib Script ماژول‌های گره را حذف کرده، کش را خالی می‌کند و بسته‌های ما را دوباره نصب می‌کند و بسته React Native شما را شروع می‌کند.

DEPENDENCY: “react-native-test-lib”: “../library”
SCRIPT: “updateLib”: “rm -rf node_modules/ && yarn --reset-cache && yarn start”

داربست کتابخانه

هدف ما ساخت یک مجموعه کامل و واضح با استفاده از Native به Javascript است. این ساختار بر این باور است که کدنویسی با جاوا اسکریپت به جاوا برتری دارد.
با پیروی از این قانون ما یک پوشه به نام testBridge ایجاد می‌کنیم که شامل تمام کدهای جاوا اسکریپت مورد نیاز ما است.

> testBridge
 > __tests__    -> Will contain our tests
 > bridgeOperations -> Operations available in native
   getValue.js  -> Calls the getValue native code
   index.js    -> Defines the bridge and exports operations
 > library     -> Contains the features of our library
   index.js    -> Crafts the library API
 index.js      -> exports our library to the world

از پایین شروع می‌کنیم. testBridge/bridgeOperations/index پلی را که ما استفاده می‌کنیم تعیین می‌کند، در این مورد NativeModules.RNTestLib و این پل را به تمام bridgeOperations (برای مثال getValue) بازمی‌گرداند، این‌ها متدهای جاوای در دسترس هستند و هر کدام در فایل مختص به خود تعریف می‌شوند. دو دلیل برای این کار وجود دارد: اول این‌که ما قصد داریم برای آزمایش پروژه خود از این پل استفاده کنیم؛ دوم این‌که این کد native پیچیده است و عملیات انجام‌شده به شفاف‌سازی نیاز دارد. برای مثال، فرض کنید ما یک متد جاوا را اجرا می‌کنیم که یک متد فراخوانی جداگانه دارد و ما برای استخراج مقدار این متد فراخوانی از DeviceEventEmitter استفاده می‌کنیم. 
یک مرحله بالاتر، ما یک API را که در testBridge/library/index ساخته‌شده است، دریافت می‌کنیم. این پوشه می‌تواند شامل فایل‌های دیگری باشد که امکانات کتابخانه شما را از قبیل الصاق چند bridgeOperations در یک API جدید می‌سازد. یک مثال این است که شما می‌خواهید کتابخانه شما یک تابع connect را بازگرداند. CheckPermission و Connect به شما اجازه انجام چنین کاری را می‌دهد. 

مطلب پیشنهادی

یادگیری جاوااسکریپت برای توسعه‌دهندگان وب از نان شب واجب‌تر است!

گام دوم: یکپارچه‌سازی Native

حالا زمان آن رسیده تا به سراغ فایل getValue.js برویم تا رشته‌ای را که ما در کد native وارد کرده‌ایم، پردازش کند. توصیه می‌شود برای ادامه تغییرات Native از Android Studio استفاده شود. برای این منظور باید library/android را باز کنید.

تغییرات کتابخانه 

اپلیکیشن آزمایشی این پروژه روی آخرین نسخه از React Native نوشته‌شده است، به همین دلیل ابتدا باید build.gradle کتابخانه را به‌روزرسانی کنیم. مرحله بعد مربوط به کد native موجود در
java/[…]/RNTestLibModule.java است، این همان جایی است که شما بیشترین زمان خود را برای یکپارچه‌سازی امکانات native و SDK در آن صرف می‌کنید. فایل از پیش آماده‌شده react-native-create-library (https://github.com/frostney/react-native-create-library) کمی‌ گمراه‌‌‌کننده به نظر می‌رسد. چیزی که ما نیاز داریم یک نقطه شروع خوب، اما درعین‌حال ساده‌تر از مثالی است که در اسناد React Native آمده است. برای این منظور ما از module.java به‌عنوان نقطه شروع استفاده می‌کنیم. برای سازگاری با این کتابخانه تنها لازم است تا اسامی‌ کلاس‌ها را به‌روزرسانی کنیم.
سرانجام ما getValue Promise را برای ماژول خود ایجاد می‌کنیم تا جایگزین نمونه اصلی شود:

import com.facebook.react.bridge.Promise;
@ReactMethod // Notates a method that should be exposed to React
public void getValue(final Promise promise) {
 promise.resolve(“A real native value”);
}

به یاد داشته باشید که جاوا اسکریپت ما در حال حاضر Promise را برمی‌گرداند، بنابراین باید آن را به شکلی تغییر دهيم تا کد Native را برگرداند:

const getValue = bridge => bridge.getValue();

همچنین باید نام ماژول‌های Native را که در پل موجود در bridgeOperations/index تعریف کرده بودیم، به NativeModules.RNTestLibModule تغییر دهيم تا با نام کلاس یکسان باشد.

گام سوم: عکس‌العمل به رویدادها 

در نهایت، ما باید کدهای مربوط به چگونگی اداره عکس‌العمل به رویدادها را آماده کرده و این کتابخانه را آزمایش کنیم.
بعد از کار با جاوا و یکپارچه‌سازی آن با SDKهای ثالث، متوجه خواهید شد که جاوا اسکریپت به اجرای یک متد native نیاز دارد تا مقادیرتان را فراخوانی کند. خوشبختانه انجام چنین کاری با DeviceEventEmitter به‌راحتی قابل انجام است. در ادامه متد RequestDeviceID را مشاهده می‌کنید که یک فراخوانی از یک SDK را اداره می‌کند.

import com.facebook.react.bridge.Arguments;
@ReactMethod
public void requestDeviceId() {
 // A method to request the device ID, below you could be calling an SDK implementation
 // Remember to consider error handing here to void app crashes
 deviceID(“10001”); deviceID(“10001”);
}

private void deviceID(String id){
 // This might be a method within a class that implements the SDK you’re using
 // We use Arguments.createMap to build an object to return
 WritableMap idData = Arguments.createMap();
 idData.putString(“id”, id);
 emitDeviceEvent(“device-id”, idData);
}

کار با Native در اینجا به پایان می‌رسد و حالا زمان جاوا اسکریپت است. در bridgeOperations یک فایل به نام requestDeviceId.js ایجاد کنید. این فایل به ما اجازه می‌دهد تا به‌وسیله رویداد device-id متد RequestDeviceId را فراخوانی کنیم.

const requestDeviceId = (bridge, eventEmitter) => {
 bridge.requestDeviceId();
 return new Promise((resolve, reject) => {
  const listener = eventEmitter.addListener(‘device-id’, (response) => {
   resolve(response);
   listener.remove();
  });
  // Could add a listener for errors here too
 });
};

export default requestDeviceId;

حالا ما باید فایل ایندکس BridgeOperations را به‌روزرسانی کنیم و از همان ساختاری که برای ایجاد پل استفاده کرده بودیم برای EventEmitter استفاده ‌کنیم.

import { NativeModules, NativeEventEmitter } from ‘react-native’;
import getValue from ‘./getValue’;
import requestDeviceId from ‘./requestDeviceId’;

const bridge = NativeModules.RNTestLibModule;
const eventEmitter = new NativeEventEmitter(bridge);

export default {
 getValue: () => getValue(bridge),
 requestDeviceId: () => requestDeviceId(bridge, eventEmitter),
};

اجازه دهید این کد را در اپلیکیشن آزمایشی خود امتحان کنیم. ما تابع زیر را به‌منظور درخواست device id اضافه کرده‌ایم:

getDeviceId = async () => {
  this.setState({ deviceId: “loading...”})
  try {
   const { id } = await RNTest.requestDeviceId()
   this.setState({ deviceId: id})
  } catch(e) {
   this.setState({ deviceId: e})
  }
}

علاوه بر عبارت deviceId، یک دکمه برای فراخوانی این تابع و یک تگ <Text> برای نمایش وضعیت وجود دارد. با فشردن این دکمه deviceId در کد Native ما نمایش داده می‌شود.
حالا زمان آن رسیده تا ایده پشت فولدر library را با الصاق دو متد Native که در کتابخانه API ایجاد کردیم، بررسی کنیم.در کتابخانه ما یک فولدر جدید به نام CoolFeature.js ایجاد کردیم. اینجا همان‌جایی است که ما دو متد را متصل می‌کنیم. این کد چیزی شبیه این است:

const coolFeature = async (bridgeOperations) => {
 try {
  const value = await bridgeOperations.getValue();
  // Our emitter sends an object by creating a writable map
  // Below we just destructure that object
  const { id } = await bridgeOperations.requestDeviceId();
  return `Device: ${id}, says you are seeing ${value} `;
 } catch (e) {
  throw (new Error(e));
 }
};

export default coolFeature;

همچنین ما باید از CoolFeature به‌عنوان بخشی از کتابخانه خود در library/index.js خروجی بگیریم. تنها کافی است coolFeature: () => coolFeature(bridgeOperations) را به آبجت خروجی اضافه کنید. 

مطلب پیشنهادی

10 ابزار رایگان تحلیلی و نظارتی بر شبکه‌های کامپیوتری

آزمايش‌ها

حالا زمان آزمايش و اجرای پروژه کوچک ما فرا رسیده است، یک فایل .babelrc در سطح بالای /library ایجاد کرده و محتوای زیر را به آن اضافه کنید. 

{
 “presets”: [“env”]
}

سپس یک فولدر به نام __test__ بسازید و محتوای /library/coolFeature.js را به آن اضافه کنید. ما قرار است یک فایل برای آزمایش پاسخ به کد native خود ایجاد کنیم تا مطمئن شویم coolFeature یک رشته با ساختار درست را به کتابخانه کاربر بازمی‌گرداند. کد زیر را اضافه کرده و فایل آزمايش را اجرا کنید:

const responses = {
 value: ‘A real native value’,
 requestId: { id: ‘10001’ },
};

const bridgeOperations = {
 getValue: jest.fn().mockReturnValueOnce(responses.value),
 requestDeviceId: jest.fn().mockReturnValueOnce(responses.requestId),
};

describe(‘Cool feature’, () => {
 test(‘is returning a correctly structured string’, async () => {
  try {
   const response = await coolFeature(bridgeOperations);
   expect(response).toBe(`Device: ${responses.requestId.id}, says you are seeing ${responses.value}`);
  } catch (e) {
   throw (e);
  }
 });
});

این تنها مثالی بود از چگونگی آزمايش امکانات این کتابخانه که به‌وسیله bridgeOperations انجام می‌شود. اگر شما می‌خواهید این کتابخانه را به اپلیکیشن دیگری اضافه کنید که روی یک مخزن Github خصوصی میزبانی می‌شود، آن را به شکل زیر اضافه کنید:

“react-native-test-lib”: “git+ssh://git@github.com/COMPANY/REPO.git#BRANCH”,

به این ترتیب می‌توانید تمام ابزاری را که برای ساخت یک کتابخانه Native که می‌تواند از SDKهای ثالث بهره‌مند شود، در اختیار داشته باشید. 
کدهای این بخش را می‌توانید در آدرس:

https://github.com/mdcuk34/react-native-library-boilerplate/tree/STEP-TH...پیدا کنید.

برچسب: 

مطالب پربازدید روز