Spec-Zone.ru › React Native

Модули Android Native

Добро пожаловать в Native Modules для Android. Начните с чтения Введения в Native Modules, чтобы узнать, что такое native модули.

Создание модуля Native Calendar

В этом руководстве вы создадите native модуль, CalendarModule, который позволит вам получить доступ к API календаря Android из JavaScript. В конце вы сможете вызывать CalendarModule.createCalendarEvent('Dinner Party', 'My House'); из JavaScript, вызывая метод Java/Kotlin, который создаёт событие в календаре.

Команда React Native в настоящее время работает над перестройкой системы Native Modules. Эта новая система называется TurboModules, и она поможет обеспечить более эффективную и безопасную типизацию коммуникации между JavaScript и нативным кодом, не полагаясь на React Native мост. Она также позволит новые расширения, которые были невозможны с устаревшей системой Native Modules. Подробнее вы можете прочитать здесь. В этих документах мы добавили заметки о частях Native Modules, которые изменятся в выпуске TurboModules, и о том, как лучше подготовиться к плавному обновлению до TurboModules.

Настройка

Для начала откройте проект Android в вашем приложении React Native в Android Studio. Вы можете найти свой Android проект здесь в приложении React Native:

Image of opening up an Android project within a React Native app inside of Android Studio.
Изображение того, где вы можете найти свой Android проект

Мы рекомендуем использовать Android Studio для написания нативного кода. Android Studio — это IDE, созданная для разработки приложений для Android, и её использование поможет вам быстро решать мелкие проблемы, такие как синтаксические ошибки в коде.

Мы также рекомендуем включить Gradle Daemon, чтобы ускорить сборку во время итераций над кодом Java/Kotlin.

Создание файла пользовательского Native модуля

Первый шаг — создание файла Java/Kotlin (CalendarModule.java или CalendarModule.kt) внутри папки android/app/src/main/java/com/your-app-name/ (папка одинаковая для Kotlin и Java). Этот файл Java/Kotlin будет содержать ваш нативный модуль Java/Kotlin класс.

Image of adding a class called CalendarModule.java within the Android Studio.
Изображение добавления класса CalendarModuleClass

Затем добавьте следующий контент:

  • Java
  • Kotlin
package com.your-app-name; // replace com.your-app-name with your app’s 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);
   }
}
package com.your-app-name // replace com.your-app-name with your app’s 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

class CalendarModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {...}

Как вы видите, ваш класс CalendarModule расширяет класс ReactContextBaseJavaModule. Для Android, native модули Java/Kotlin пишутся как классы, которые расширяют ReactContextBaseJavaModule и реализуют функциональность, требуемую JavaScript.

Стоит отметить, что технически классы Java/Kotlin должны только расширять класс BaseJavaModule или реализовывать интерфейс NativeModule для того, чтобы считаться Native Module в React Native.

Однако мы рекомендуем использовать ReactContextBaseJavaModule, как показано выше. ReactContextBaseJavaModule предоставляет доступ к ReactApplicationContext (RAC), что полезно для Native Modules, которые должны подключаться к методам жизненного цикла активности. Использование ReactContextBaseJavaModule также упростит создание безопасных по типу native модулей в будущем. Для безопасности по типам native модулей, которая появится в будущих выпусках, React Native анализирует спецификацию каждого native модуля JavaScript и генерирует абстрактный базовый класс, который расширяет ReactContextBaseJavaModule.

Имя модуля

Все нативные модули Java/Kotlin в Android должны реализовывать метод getName(). Этот метод возвращает строку, представляющую имя нативного модуля. К нативному модулю затем можно получить доступ в JavaScript, используя его имя. Например, в приведенном ниже фрагменте кода getName() возвращает "CalendarModule".

  • Java
  • Kotlin
// add to CalendarModule.java
@Override
public String getName() {
   return "CalendarModule";
}
// add to CalendarModule.kt
override fun getName() = "CalendarModule"

К нативному модулю затем можно получить доступ в JS так:

const { CalendarModule } = ReactNative.NativeModules;

Экспорт нативного метода в JavaScript

Далее вам нужно добавить метод в ваш нативный модуль, который будет создавать события календаря и может быть вызван из JavaScript. Все методы нативного модуля, предназначенные для вызова из JavaScript, должны быть аннотированы @ReactMethod.

Создайте метод createCalendarEvent() для CalendarModule, который может быть вызван в JS через CalendarModule.createCalendarEvent(). Пока что метод будет принимать имя и местоположение в качестве строк. Опции типов аргументов будут рассмотрены в ближайшее время.

  • Java
  • Kotlin
@ReactMethod
public void createCalendarEvent(String name, String location) {
}
@ReactMethod fun createCalendarEvent(name: String, location: String) {}

Добавьте логирование отладки в метод, чтобы убедиться, что он был вызван, когда вы вызываете его из своего приложения. Ниже приведен пример того, как вы можете импортировать и использовать класс Log из пакета Android util:

  • Java
  • Kotlin
import android.util.Log;

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

@ReactMethod
fun createCalendarEvent(name: String, location: String) {
    Log.d("CalendarModule", "Create event called with name: $name and location: $location")
}

После завершения реализации нативного модуля и его подключения в JavaScript вы можете следовать этим шагам, чтобы просмотреть логи из вашего приложения.

Синхронные методы

Вы можете передать isBlockingSynchronousMethod = true в нативный метод, чтобы отметить его как синхронный метод.

  • Java
  • Kotlin
@ReactMethod(isBlockingSynchronousMethod = true)
@ReactMethod(isBlockingSynchronousMethod = true)

В данный момент мы не рекомендуем это, так как вызов методов синхронно может иметь сильные последствия для производительности и вносить проблемы, связанные с потоками, в ваши нативные модули. Кроме того, обратите внимание, что если вы решите включить isBlockingSynchronousMethod, ваше приложение больше не сможет использовать отладчик Google Chrome. Это происходит потому, что синхронные методы требуют, чтобы JS VM обменивался памятью с приложением. Для отладчика Google Chrome React Native работает внутри JS VM в Google Chrome и асинхронно взаимодействует с мобильными устройствами через WebSockets.

Регистрация модуля (специфика Android)

После написания нативного модуля его нужно зарегистрировать в React Native. Для этого вам нужно добавить свой нативный модуль в ReactPackage и зарегистрировать ReactPackage в React Native. Во время инициализации React Native перебирает все пакеты и для каждого ReactPackage, регистрирует каждый нативный модуль внутри.

React Native вызывает метод createNativeModules() на ReactPackage для получения списка нативных модулей для регистрации. Для Android, если модуль не инициализирован и не возвращается в createNativeModules, он не будет доступен из JavaScript.

Чтобы добавить ваш Native Module в ReactPackage, сначала создайте новый класс Java/Kotlin с именем (MyAppPackage.java или MyAppPackage.kt) , который реализует ReactPackage внутри папки android/app/src/main/java/com/your-app-name/:

Затем добавьте следующий контент:

  • Java
  • Kotlin
package com.your-app-name; // replace your-app-name with your app’s 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;
   }

}
package com.your-app-name // replace your-app-name with your app’s name

import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager

class MyAppPackage : ReactPackage {

    override fun createViewManagers(
        reactContext: ReactApplicationContext
    ): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()

    override fun createNativeModules(
        reactContext: ReactApplicationContext
    ): MutableList<NativeModule> = listOf(CalendarModule(reactContext)).toMutableList()
}

Этот файл импортирует нативный модуль, который вы создали, CalendarModule. Затем он создаёт экземпляр CalendarModule внутри функции createNativeModules() и возвращает его как список NativeModules для регистрации. Если вы добавите больше нативных модулей в дальнейшем, вы также можете создать их экземпляры и добавить в возвращаемый список.

Стоит отметить, что этот способ регистрации нативных модулей жадно инициализирует все нативные модули при запуске приложения, что увеличивает время запуска приложения. Вы можете использовать TurboReactPackage в качестве альтернативы. Вместо createNativeModules, которые возвращают список созданных объектов нативного модуля, TurboReactPackage реализует метод getModule(String name, ReactApplicationContext rac), который создаёт объект нативного модуля при необходимости. TurboReactPackage немного сложнее реализовать в данный момент. Помимо реализации метода getModule(), вам нужно реализовать метод getReactModuleInfoProvider(), который возвращает список всех нативных модулей, которые может создать пакет, вместе с функцией, которая их создаёт, пример здесь. Опять же, использование TurboReactPackage позволит вашему приложению иметь более быстрое время запуска, но в настоящее время его реализация немного затруднительна. Поэтому будьте осторожны, если решите использовать TurboReactPackages.

Чтобы зарегистрировать пакет CalendarModule, необходимо добавить MyAppPackage в список пакетов, возвращаемых методом getPackages() в ReactNativeHost. Откройте файл MainApplication.java или MainApplication.kt, который находится по следующему пути: android/app/src/main/java/com/your-app-name/.

Найдите метод getPackages() в ReactNativeHost и добавьте свой пакет в список пакетов, который возвращает getPackages():

  • Java
  • Kotlin
@Override
  protected List<ReactPackage> getPackages() {
    @SuppressWarnings("UnnecessaryLocalVariable")
    List<ReactPackage> packages = new PackageList(this).getPackages();
    // below MyAppPackage is added to the list of packages returned
    packages.add(new MyAppPackage());
    return packages;
  }
override fun getPackages(): List<ReactPackage> =
    PackageList(this).packages.apply {
        // Packages that cannot be autolinked yet can be added manually here, for example:
        // packages.add(new MyReactNativePackage());
        add(MyAppPackage())
    }

Вы успешно зарегистрировали свой нативный модуль для Android!

Тестирование созданного

На данном этапе вы настроили базовые элементы для своего нативного модуля в Android. Протестируйте это, обратившись к нативному модулю и вызвав его экспортированный метод в JavaScript.

Найдите место в приложении, где вы хотели бы добавить вызов метода createCalendarEvent() нативного модуля. Ниже приведён пример компонента NewModuleButton, который можно добавить в ваше приложение. Вы можете вызвать нативный модуль внутри функции NewModuleButton компонента onPress().

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;

Для доступа к вашему нативному модулю из JavaScript, вам необходимо сначала импортировать NativeModules из React Native:

import { NativeModules } from 'react-native';

Затем вы можете получить доступ к нативному модулю CalendarModule через NativeModules.

const { CalendarModule } = NativeModules;

Теперь, когда нативный модуль CalendarModule доступен, вы можете вызвать ваш нативный метод createCalendarEvent(). Ниже он добавлен в метод onPress() в NewModuleButton:

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

Последний шаг — перестроить приложение React Native, чтобы сделать доступным самый последний нативный код (с вашим новым нативным модулем!). В командной строке, где расположено приложение React Native, выполните следующее:

npx react-native run-android

Перестройка при итерации

Работая над этими руководствами и итерациями нативного модуля, вам потребуется перестройка нативного кода приложения, чтобы получить доступ к вашим последним изменениям из JavaScript. Это связано с тем, что ваш код находится в нативной части приложения. Хотя инструмент сборки React Native, metro, может отслеживать изменения в JavaScript и перестраивать их на лету, он не делает этого для нативного кода. Поэтому, если вы хотите протестировать свои последние изменения нативного кода, вам необходимо выполнить перестройку с помощью команды npx react-native run-android.

Подведение итогов✨

Теперь вы должны иметь возможность вызывать метод createCalendarEvent() своего нативного модуля в приложении. В нашем примере это происходит при нажатии на NewModuleButton. Вы можете подтвердить это, просмотрев журнал, который вы настроили в методе createCalendarEvent() вашего нативного модуля. Вы можете следовать этим шагам, чтобы просмотреть журналы ADB в вашем приложении. Затем вы должны сможете найти сообщение Log.d (в нашем примере «Create event called with name: testName and location: testLocation») и увидеть, как оно записывается в журнал каждый раз, когда вы вызываете метод нативного модуля.

Image of logs.
Изображение журналов ADB в Android Studio

На данном этапе вы создали нативный модуль Android и вызвали его нативный метод из JavaScript в своём приложении React Native. Вы можете продолжить, чтобы узнать больше о типах аргументов, доступных для метода нативного модуля, и о том, как настроить обратные вызовы и промисы.

Выходя за рамки нативного модуля календаря

Лучший экспорт нативного модуля

Импортировать ваш нативный модуль, извлекая его из NativeModules как выше, немного неудобно.

Чтобы избежать необходимости этого для пользователей вашего нативного модуля каждый раз, когда они хотят получить доступ к нему, вы можете создать JavaScript-обёртку для модуля. Создайте новый JavaScript-файл с именем CalendarModule.js со следующим содержимым:

/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:

* 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
import { NativeModules } from 'react-native';
const { CalendarModule } = NativeModules;
export default CalendarModule;

Этот JavaScript-файл также станет хорошим местом для добавления любых функциональных возможностей со стороны JavaScript. Например, если вы используете систему типов, такую как TypeScript, вы можете добавить здесь аннотации типов для вашего нативного модуля. Хотя React Native ещё не поддерживает проверку типов от нативного кода к JS, весь ваш JS-код будет проверен на типы. Это также облегчит вам переход на безопасные по типам нативные модули в будущем. Ниже приведён пример добавления проверки типов в CalendarModule:

/**
* This exposes the native CalendarModule module as a JS module. This has a
* function 'createCalendarEvent' which takes the following parameters:
*
* 1. String name: A string representing the name of the event
* 2. String location: A string representing the location of the event
*/
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. Пожалуйста, обновите относительный импорт по мере необходимости.

Типы аргументов

Когда метод нативного модуля вызывается в JavaScript, React Native преобразует аргументы из JS-объектов в их аналоги Java/Kotlin. Например, если ваш Java-метод нативного модуля принимает double, в JS вам нужно вызвать метод с числом. React Native позаботится о преобразовании.

Java Kotlin JavaScript
Boolean Boolean ?boolean
boolean boolean
Double Double ?number
double number
String String string
Callback Callback Function
ReadableMap ReadableMap Object
ReadableArray ReadableArray Array

Следующие типы в настоящее время поддерживаются, но не будут поддерживаться в TurboModules. Пожалуйста, избегайте их использования:

  • Integer Java/Kotlin -> ?number
  • Float Java/Kotlin -> ?number
  • int Java -> number
  • float Java -> number

Для типов аргументов, не перечисленных выше, вам необходимо выполнить преобразование самостоятельно. Например, в Android, преобразование Date не поддерживается непосредственно. Вы можете обработать преобразование в тип Date в методе нативного модуля следующим образом:

  • Java
  • Kotlin
    String dateFormat = "yyyy-MM-dd";
    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
    Calendar eStartDate = Calendar.getInstance();
    try {
        eStartDate.setTime(sdf.parse(startDate));
    }
    val dateFormat = "yyyy-MM-dd"
    val sdf = SimpleDateFormat(dateFormat, Locale.US)
    val eStartDate = Calendar.getInstance()
    try {
        sdf.parse(startDate)?.let {
            eStartDate.time = it
        }
    }

Экспорт констант

Нативный модуль может экспортировать константы, реализовав нативный метод getConstants(), который доступен в JS. Ниже вы реализуете getConstants() и вернёте Map, содержащую константу DEFAULT_EVENT_NAME, к которой вы можете получить доступ в JavaScript:

  • Java
  • Kotlin
@Override
public Map<String, Object> getConstants() {
   final Map<String, Object> constants = new HashMap<>();
   constants.put("DEFAULT_EVENT_NAME", "New Event");
   return constants;
}
override fun getConstants(): MutableMap<String, Any> =
    hashMapOf("DEFAULT_EVENT_NAME" to "New Event")

Затем константу можно получить, вызвав getConstants на нативном модуле в JS:

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

Технически возможно получить доступ к экспортированным константам в getConstants() напрямую из объекта нативного модуля. Это больше не будет поддерживаться с TurboModules, поэтому мы рекомендуем сообществу перейти к вышеописанному подходу, чтобы избежать необходимости миграции в будущем.

В настоящее время константы экспортируются только во время инициализации, поэтому если вы измените значения в getConstants во время выполнения, это не повлияет на JavaScript-среду. Это изменится с Turbomodules. С Turbomodules getConstants() станет обычным методом нативного модуля, и каждый вызов будет попадать на нативную сторону.

Обратные вызовы

Нативные модули также поддерживают особый тип аргумента: колбэк. Колбэки используются для передачи данных из Java/Kotlin в JavaScript для асинхронных методов. Они также могут использоваться для асинхронного выполнения JavaScript со стороны нативного кода.

Для создания метода нативного модуля с колбэком необходимо сначала импортировать интерфейс Callback, а затем добавить новый параметр в метод нативного модуля типа Callback. Есть несколько нюансов с аргументами колбэка, которые скоро будут устранены с помощью TurboModules. Во-первых, в аргументах вашей функции может быть только два колбэка — successCallback и failureCallback. Кроме того, последний аргумент вызова метода нативного модуля, если это функция, обрабатывается как successCallback, а предпоследний аргумент, если это функция, — как failureCallback.

  • Java
  • Kotlin
import com.facebook.react.bridge.Callback;

@ReactMethod
public void createCalendarEvent(String name, String location, Callback callBack) {
}
import com.facebook.react.bridge.Callback

@ReactMethod fun createCalendarEvent(name: String, location: String, callback: Callback) {}

Вы можете вызвать колбэк в своём методе Java/Kotlin, предоставив любые данные, которые вы хотите передать в JavaScript. Обратите внимание, что из нативного кода в JavaScript можно передавать только сериализуемые данные. Если вам нужно передать нативный объект, вы можете использовать WriteableMaps, а если вам нужна коллекция, используйте WritableArrays.

Важно отметить, что колбэк не вызывается немедленно после завершения нативной функции. Ниже ID события, созданного в предыдущем вызове, передаётся в колбэк.

  • Java
  • Kotlin
  @ReactMethod
   public void createCalendarEvent(String name, String location, Callback callBack) {
       Integer eventId = ...
       callBack.invoke(eventId);
   }
  @ReactMethod
  fun createCalendarEvent(name: String, location: String, callback: Callback) {
      val eventId = ...
      callback.invoke(eventId)
  }

Этот метод можно вызвать в JavaScript, используя:

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

Ещё один важный момент: метод нативного модуля может вызвать только один колбэк один раз. Это означает, что вы можете вызвать либо success callback, либо failure callback, но не оба, и каждый колбэк может быть вызван не более одного раза. Однако нативный модуль может сохранить колбэк и вызвать его позже.

Существует два подхода к обработке ошибок с помощью колбэков. Первый — следовать соглашению Node и обрабатывать первый аргумент, переданный в колбэк, как объект ошибки.

  • Java
  • Kotlin
  @ReactMethod
   public void createCalendarEvent(String name, String location, Callback callBack) {
       Integer eventId = ...
       callBack.invoke(null, eventId);
   }
  @ReactMethod
  fun createCalendarEvent(name: String, location: String, callback: Callback) {
      val 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`);
    }
  );
};

Другой вариант — использовать onSuccess и onFailure колбэки:

  • Java
  • Kotlin
@ReactMethod
public void createCalendarEvent(String name, String location, Callback myFailureCallback, Callback mySuccessCallback) {
}
@ReactMethod
  fun createCalendarEvent(
      name: String,
      location: String,
      myFailureCallback: Callback,
      mySuccessCallback: Callback
  ) {}

Затем в JavaScript вы можете добавить отдельный колбэк для обработки ошибок и успешных ответов:

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

Обещания

Нативные модули также могут выполнять Promise, что может упростить ваш JavaScript, особенно при использовании синтаксиса async/await ES2016. Когда последний параметр метода нативного модуля Java/Kotlin — Promise, соответствующий JS-метод вернёт JS-объект Promise.

Переработка вышеприведенного кода для использования обещания вместо колбэков выглядит следующим образом:

  • Java
  • Kotlin
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);
    }
}
@ReactMethod
fun createCalendarEvent(name: String, location: String, promise: Promise) {
    try {
        val eventId = ...
        promise.resolve(eventId)
    } catch (e: Throwable) {
        promise.reject("Create Event Error", e)
    }
}

Подобно колбэкам, метод нативного модуля может либо отклонить, либо разрешить обещание (но не оба) и сделать это не более одного раза. Это означает, что вы можете вызвать либо success callback, либо failure callback, но не оба, и каждый колбэк может быть вызван не более одного раза. Однако нативный модуль может сохранить колбэк и вызвать его позже.

Соответствующий JS-метод для этого метода возвращает 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);
  }
};

Метод reject принимает различные комбинации следующих аргументов:

  • Java
  • Kotlin
String code, String message, WritableMap userInfo, Throwable throwable
code: String, message: String, userInfo: WritableMap, throwable: Throwable

Подробнее вы можете найти интерфейс Promise.java здесь. Если userInfo не предоставлен, ReactNative установит его в значение null. Для остальных параметров React Native будет использовать значения по умолчанию. Аргумент message предоставляет ошибку message, показанную в верхней части стека вызовов ошибок. Ниже приведён пример сообщения об ошибке, отображённого в JavaScript при следующем вызове reject в Java/Kotlin.

Вызов reject в Java/Kotlin:

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

Сообщение об ошибке в приложении React Native при отклонении обещания:

Image of error message in React Native app.
Изображение сообщения об ошибке

Отправка событий в JavaScript

Нативные модули могут сигнализировать события в JavaScript без прямого вызова. Например, вы можете сигнализировать JavaScript о напоминании о событии в календаре из нативного приложения Android-календаря, которое скоро произойдёт. Самый простой способ сделать это — использовать RCTDeviceEventEmitter, который можно получить из ReactContext, как в приведённом ниже фрагменте кода.

  • Java
  • Kotlin
...
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);
}
@ReactMethod
public void addListener(String eventName) {
  // Set up any upstream listeners or background tasks as necessary
}

@ReactMethod
public void removeListeners(Integer count) {
  // Remove upstream listeners, stop unnecessary background tasks
}
...
WritableMap params = Arguments.createMap();
params.putString("eventProperty", "someValue");
...
sendEvent(reactContext, "EventReminder", params);
...
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.Arguments
import com.facebook.react.modules.core.DeviceEventManagerModule
...

private fun sendEvent(reactContext: ReactContext, eventName: String, params: WritableMap?) {
    reactContext
      .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
      .emit(eventName, params)
}

@ReactMethod
fun addListener(eventName: String) {
    // Set up any upstream listeners or background tasks as necessary
}

@ReactMethod
fun removeListeners(count: Int) {
    // Remove upstream listeners, stop unnecessary background tasks
}
...
val params = Arguments.createMap().apply {
    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(); //Removes the listener
 }

Получение результата активности из startActivityForResult

Вам потребуется прослушать onActivityResult, если вы хотите получить результаты от активности, запущенной с помощью startActivityForResult. Для этого необходимо расширить BaseActivityEventListener или реализовать ActivityEventListener. Предпочтите первый вариант, так как он более устойчив к изменениям API. Затем необходимо зарегистрировать слушатель в конструкторе модуля следующим образом:

  • Java
  • Kotlin
reactContext.addActivityEventListener(mActivityResultListener);
reactContext.addActivityEventListener(mActivityResultListener);

Теперь вы можете прослушивать onActivityResult путем реализации следующего метода:

  • Java
  • Kotlin
@Override
public void onActivityResult(
 final Activity activity,
 final int requestCode,
 final int resultCode,
 final Intent intent) {
 // Your logic here
}
override fun onActivityResult(
    activity: Activity?,
    requestCode: Int,
    resultCode: Int,
    intent: Intent?
) {
    // Your logic here
}

Давайте реализуем простой выбор изображения, чтобы продемонстрировать это. Выбор изображения предоставит метод pickImage для JavaScript, который вернёт путь к изображению при вызове.

  • Java
  • Kotlin
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);

    // 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;
    }
  }
}
class ImagePickerModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    private var pickerPromise: Promise? = null

    private val activityEventListener =
        object : BaseActivityEventListener() {
            override fun onActivityResult(
                activity: Activity?,
                requestCode: Int,
                resultCode: Int,
                intent: Intent?
            ) {
                if (requestCode == IMAGE_PICKER_REQUEST) {
                    pickerPromise?.let { promise ->
                        when (resultCode) {
                            Activity.RESULT_CANCELED ->
                                promise.reject(E_PICKER_CANCELLED, "Image picker was cancelled")
                            Activity.RESULT_OK -> {
                                val uri = intent?.data

                                uri?.let { promise.resolve(uri.toString()) }
                                    ?: promise.reject(E_NO_IMAGE_DATA_FOUND, "No image data found")
                            }
                        }

                        pickerPromise = null
                    }
                }
            }
        }

    init {
        reactContext.addActivityEventListener(activityEventListener)
    }

    override fun getName() = "ImagePickerModule"

    @ReactMethod
    fun pickImage(promise: Promise) {
        val activity = currentActivity

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

        pickerPromise = promise

        try {
            val galleryIntent = Intent(Intent.ACTION_PICK).apply { type = "image\/*" }

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

            activity.startActivityForResult(chooserIntent, IMAGE_PICKER_REQUEST)
        } catch (t: Throwable) {
            pickerPromise?.reject(E_FAILED_TO_SHOW_PICKER, t)
            pickerPromise = null
        }
    }

    companion object {
        const val IMAGE_PICKER_REQUEST = 1
        const val E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"
        const val E_PICKER_CANCELLED = "E_PICKER_CANCELLED"
        const val E_FAILED_TO_SHOW_PICKER = "E_FAILED_TO_SHOW_PICKER"
        const val E_NO_IMAGE_DATA_FOUND = "E_NO_IMAGE_DATA_FOUND"
    }
}

Прослушивание событий жизненного цикла

Прослушивание событий жизненного цикла активности, таких как onResume, onPause и т. д., очень похоже на то, как был реализован ActivityEventListener. Модуль должен реализовать LifecycleEventListener. Затем необходимо зарегистрировать слушателя в конструкторе модуля следующим образом:

  • Java
  • Kotlin
reactContext.addLifecycleEventListener(this);
reactContext.addLifecycleEventListener(this)

Теперь вы можете прослушивать события жизненного цикла активности, реализовав следующие методы:

  • Java
  • Kotlin
@Override
public void onHostResume() {
   // Activity `onResume`
}
@Override
public void onHostPause() {
   // Activity `onPause`
}
@Override
public void onHostDestroy() {
   // Activity `onDestroy`
}
override fun onHostResume() {
    // Activity `onResume`
}

override fun onHostPause() {
    // Activity `onPause`
}

override fun onHostDestroy() {
    // Activity `onDestroy`
}

Потоки

На данный момент на Android все асинхронные методы нативных модулей выполняются в одном потоке. Нативные модули не должны делать предположений о том, в каком потоке они вызываются, поскольку текущее назначение может измениться в будущем. Если требуется блокирующий вызов, тяжелая работа должна быть передана во внутренний управляемый поток обработки, а любые обратные вызовы распределены оттуда.

© 2022 Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://reactnative.dev/docs/native-modules-android

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API