Przejdź do treści głównej

Moduły natywne dla Androida

Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

Nieoficjalne Tłumaczenie Beta

Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →

informacja

Native Module i Native Components to nasze stabilne technologie używane w starszej architekturze. Zostaną one wycofane w przyszłości, gdy Nowa Architektura stanie się stabilna. Nowa Architektura wykorzystuje Turbo Native Module i Fabric Native Components, aby osiągnąć podobne rezultaty.

Witamy w module natywnych modułów dla Androida. Zacznij od przeczytania Wprowadzenia do modułów natywnych, aby dowiedzieć się, czym są moduły natywne.

Tworzenie natywnego modułu kalendarza

W tym przewodniku utworzysz natywny moduł CalendarModule, który umożliwi dostęp do interfejsów API kalendarza Androida z poziomu JavaScript. Pod koniec będziesz mógł wywołać z JavaScriptu metodę CalendarModule.createCalendarEvent('Dinner Party', 'My House');, co spowoduje wywołanie metody Java/Kotlin tworzącej wydarzenie w kalendarzu.

Konfiguracja

Aby rozpocząć, otwórz projekt Android w swojej aplikacji React Native w Android Studio. Projekt Android w aplikacji React Native znajduje się w następującej lokalizacji:

Image of opening up an Android project within a React Native app inside of Android Studio.
Image of where you can find your Android project

Zalecamy używanie Android Studio do pisania kodu natywnego. Android Studio to IDE stworzone do tworzenia aplikacji na Androida, a jego używanie pomaga szybko rozwiązywać drobne problemy, takie jak błędy składni kodu.

Zalecamy również włączenie demon Gradle, aby przyspieszyć budowanie podczas iteracji kodu Java/Kotlin.

Tworzenie własnego pliku modułu natywnego

Pierwszym krokiem jest utworzenie pliku Java/Kotlin (CalendarModule.java lub CalendarModule.kt) w folderze android/app/src/main/java/com/your-app-name/ (folder jest taki sam dla Kotlina i Javy). Ten plik Java/Kotlin będzie zawierał klasę Java/Kotlin twojego modułu natywnego.

Image of adding a class called CalendarModule.java within the Android Studio.
Image of how to add the CalendarModuleClass

Następnie dodaj następującą zawartość:

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

Jak widać, twoja klasa CalendarModule rozszerza klasę ReactContextBaseJavaModule. W przypadku Androida natywne moduły Java/Kotlin są pisane jako klasy rozszerzające ReactContextBaseJavaModule i implementujące funkcjonalność wymaganą przez JavaScript.

uwaga

Warto zauważyć, że technicznie klasy Java/Kotlin muszą jedynie rozszerzać klasę BaseJavaModule lub implementować interfejs NativeModule, aby zostać uznane za moduł natywny przez React Native.

Jednak zalecamy używanie ReactContextBaseJavaModule, jak pokazano powyżej. ReactContextBaseJavaModule daje dostęp do ReactApplicationContext (RAC), co jest przydatne dla modułów natywnych, które muszą podłączać się do metod cyklu życia aktywności. Używanie ReactContextBaseJavaModule ułatwi również w przyszłości zapewnienie bezpieczeństwa typów w twoim module natywnym. W celu zapewnienia bezpieczeństwa typów modułów natywnych, co pojawi się w przyszłych wersjach, React Native analizuje specyfikację JavaScript każdego modułu natywnego i generuje abstrakcyjną klasę bazową rozszerzającą ReactContextBaseJavaModule.

Nazwa modułu

Wszystkie natywne moduły Java/Kotlin w Androidzie muszą implementować metodę getName(). Metoda ta zwraca ciąg znaków reprezentujący nazwę modułu natywnego. Moduł natywny może być następnie dostępny w JavaScripcie za pomocą swojej nazwy. Na przykład, w poniższym fragmencie kodu, getName() zwraca "CalendarModule".

java
// add to CalendarModule.java
@Override
public String getName() {
return "CalendarModule";
}

Moduł natywny może być następnie dostępny w JS w następujący sposób:

tsx
const {CalendarModule} = ReactNative.NativeModules;

Eksportowanie natywnej metody do JavaScriptu

Następnie musisz dodać do modułu natywnego metodę, która będzie tworzyć wydarzenia w kalendarzu i może być wywoływana z JavaScriptu. Wszystkie metody modułów natywnych przeznaczone do wywołania z JavaScriptu muszą być opatrzone adnotacją @ReactMethod.

Skonfiguruj metodę createCalendarEvent() dla CalendarModule, którą można wywołać w JS za pomocą CalendarModule.createCalendarEvent(). Na razie metoda będzie przyjmować nazwę i lokalizację jako ciągi znaków. Opcje typów argumentów zostaną omówione wkrótce.

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

Dodaj log debugowania w metodzie, aby potwierdzić, że została wywołana, gdy wywołasz ją z aplikacji. Poniżej znajduje się przykład, jak można zaimportować i użyć klasy Log z pakietu util Androida:

java
import android.util.Log;

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

Po zakończeniu implementacji modułu natywnego i podłączeniu go w JavaScripcie, możesz wykonać te kroki, aby wyświetlić logi z aplikacji.

Metody synchroniczne

Możesz przekazać parametr isBlockingSynchronousMethod = true do metody natywnej, aby oznaczyć ją jako metodę synchroniczną.

java
@ReactMethod(isBlockingSynchronousMethod = true)

Obecnie nie zalecamy tego podejścia, ponieważ wywoływanie metod synchronicznie może powodować znaczne spadki wydajności i wprowadzać błędy związane z wątkami do modułów natywnych. Dodatkowo, jeśli włączysz isBlockingSynchronousMethod, Twoja aplikacja nie będzie mogła korzystać z debugera Google Chrome. Wynika to z faktu, że metody synchroniczne wymagają współdzielenia pamięci między maszyną wirtualną JavaScript a aplikacją. W przypadku debugera Chrome, React Native działa wewnątrz maszyny wirtualnej JS w przeglądarce i komunikuje się asynchronicznie z urządzeniami mobilnymi poprzez WebSockety.

Rejestracja modułu (specyficzne dla Androida)

Po napisaniu modułu natywnego należy go zarejestrować w React Native. W tym celu musisz dodać moduł do ReactPackage i zarejestrować ReactPackage w React Native. Podczas inicjalizacji, React Native iteruje po wszystkich pakietach i dla każdego ReactPackage rejestruje zawarte w nim moduły natywne.

React Native wywołuje metodę createNativeModules() w ReactPackage, aby uzyskać listę modułów natywnych do zarejestrowania. W przypadku Androida, jeśli moduł nie zostanie utworzony i zwrócony w createNativeModules, nie będzie dostępny z poziomu JavaScript.

Aby dodać moduł natywny do ReactPackage, najpierw utwórz nową klasę Java/Kotlin o nazwie (MyAppPackage.java lub MyAppPackage.kt) implementującą ReactPackage w folderze android/app/src/main/java/com/your-app-name/:

Następnie dodaj następującą zawartość:

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

}

Ten plik importuje utworzony moduł natywny CalendarModule. Następnie tworzy instancję CalendarModule w funkcji createNativeModules() i zwraca ją jako listę NativeModules do zarejestrowania. Jeśli dodasz później więcej modułów natywnych, możesz utworzyć ich instancje i dodać do zwracanej listy.

uwaga

Warto zauważyć, że ta metoda rejestracji modułów inicjalizuje wszystkie moduły natywne podczas uruchamiania aplikacji, co wydłuża czas startu. Alternatywnie możesz użyć TurboReactPackage. Zamiast createNativeModules (które zwraca listę utworzonych obiektów modułów), TurboReactPackage implementuje metodę getModule(String name, ReactApplicationContext rac) tworzącą obiekt modułu na żądanie. TurboReactPackage jest obecnie bardziej skomplikowany w implementacji - oprócz getModule() musisz zaimplementować getReactModuleInfoProvider(), który zwraca listę wszystkich modułów wraz z funkcjami tworzącymi ich instancje (przykład tutaj). Użycie TurboReactPackage przyspieszy start aplikacji, ale obecnie jego implementacja jest bardziej złożona - zalecamy ostrożność przy korzystaniu z TurboReactPackages.

Aby zarejestrować pakiet CalendarModule, dodaj MyAppPackage do listy pakietów zwracanych przez metodę getPackages() w ReactNativeHost. Otwórz plik MainApplication.java lub MainApplication.kt znajdujący się w ścieżce: android/app/src/main/java/com/your-app-name/.

Znajdź metodę getPackages() w ReactNativeHost i dodaj swój pakiet do listy pakietów zwracanej przez getPackages():

java
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new MyAppPackage());
return packages;
}

Pomyślnie zarejestrowałeś moduł natywny dla Androida!

Przetestuj swoją implementację

W tym momencie masz już podstawową strukturę modułu natywnego dla Androida. Przetestuj ją, uzyskując dostęp do modułu natywnego i wywołując jego eksportowaną metodę w JavaScript.

Znajdź miejsce w swojej aplikacji, gdzie chcesz dodać wywołanie metody createCalendarEvent() modułu natywnego. Poniżej znajduje się przykład komponentu NewModuleButton, który możesz dodać w swojej aplikacji. Moduł natywny możesz wywołać wewnątrz funkcji onPress() komponentu NewModuleButton.

tsx
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;

Aby uzyskać dostęp do modułu natywnego z JavaScript, najpierw zaimportuj NativeModules z React Native:

tsx
import {NativeModules} from 'react-native';

Następnie możesz uzyskać dostęp do modułu natywnego CalendarModule poprzez NativeModules.

tsx
const {CalendarModule} = NativeModules;

Gdy moduł CalendarModule jest już dostępny, możesz wywołać metodę natywną createCalendarEvent(). Poniżej dodano ją do metody onPress() w NewModuleButton:

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

Ostatnim krokiem jest przebudowanie aplikacji React Native, aby mieć dostęp do najnowszego kodu natywnego (z twoim nowym modułem natywnym!). W wierszu poleceń, w lokalizacji aplikacji React Native, uruchom:

shell
npm run android

Budowanie w trakcie iteracji

Podczas pracy z tymi przewodnikami i iterowania nad modułem natywnym, będziesz musiał przebudować natywną część aplikacji, aby mieć dostęp do najnowszych zmian z poziomu JavaScript. Dzieje się tak dlatego, że kod piszesz w natywnej części aplikacji. Podczas gdy metro bundler React Native może śledzić zmiany w JavaScript i przebudowywać aplikację w locie, nie robi tego dla kodu natywnego. Jeśli więc chcesz przetestować swoje najnowsze zmiany w kodzie natywnym, musisz przebudować aplikację przy użyciu powyższego polecenia.

Podsumowanie ✨

Powinieneś już móc wywoływać metodę createCalendarEvent() swojego modułu natywnego w aplikacji. W naszym przykładzie dzieje się to przez naciśnięcie przycisku NewModuleButton. Możesz to potwierdzić, przeglądając logi skonfigurowane w metodzie createCalendarEvent(). Możesz postępować zgodnie z tymi instrukcjami, aby wyświetlić logi ADB w swojej aplikacji. Powinieneś wtedy móc wyszukać swoją wiadomość Log.d (w naszym przykładzie "Create event called with name: testName and location: testLocation") i zobaczyć logowaną wiadomość przy każdym wywołaniu metody modułu natywnego.

Image of logs.
Image of ADB logs in Android Studio

W tym momencie utworzyłeś natywny moduł Androida i wywołałeś jego natywną metodę z JavaScript w aplikacji React Native. Możesz kontynuować czytanie, aby dowiedzieć się więcej o dostępnych typach argumentów dla metod modułów natywnych oraz o konfiguracji callbacków i promise'ów.

Poza modułem natywnym kalendarza

Lepszy eksport modułu natywnego

Importowanie modułu natywnego poprzez pobieranie go z NativeModules, jak pokazano powyżej, jest nieco toporne.

Aby użytkownicy twojego modułu natywnego nie musieli tego robić za każdym razem, możesz utworzyć wrapper JavaScript dla modułu. Utwórz nowy plik JavaScript o nazwie CalendarModule.js z następującą zawartością:

tsx
/**
* 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;

Ten plik JavaScript jest również dobrym miejscem do dodawania funkcjonalności po stronie JavaScript. Na przykład, jeśli używasz systemu typów takiego jak TypeScript, możesz tutaj dodać adnotacje typów dla swojego modułu natywnego. Mimo że React Native nie obsługuje jeszcze bezpieczeństwa typów od strony natywnej do JS, cały twój kod JS będzie bezpieczny typowo. Zrobienie tego ułatwi również późniejsze przejście na typowo bezpieczne moduły natywne. Poniżej znajduje się przykład dodania bezpieczeństwa typów dla CalendarModule:

tsx
/**
* 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;

W innych plikach JavaScript możesz uzyskać dostęp do modułu natywnego i wywołać jego metodę w następujący sposób:

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

Zakłada to, że miejsce, w którym importujesz CalendarModule znajduje się w tej samej hierarchii co CalendarModule.js. Zaktualizuj względną ścieżkę importu w razie potrzeby.

Typy argumentów

Typy argumentów

JavaKotlinJavaScript
BooleanBoolean?boolean
booleanboolean
DoubleDouble?number
doublenumber
StringStringstring
CallbackCallbackFunction
PromisePromisePromise
ReadableMapReadableMapObject
ReadableArrayReadableArrayArray
informacja

Następujące typy są obecnie wspierane, ale nie będą obsługiwane w TurboModules. Prosimy o ich unikanie:

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

Dla typów argumentów niewymienionych powyżej, konieczne będzie samodzielne obsłużenie konwersji. Na przykład w Androidzie konwersja Date nie jest domyślnie wspierana. Możesz samodzielnie obsłużyć konwersję na typ Date w metodzie natywnej w następujący sposób:

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

Eksportowanie stałych

Moduł natywny może eksportować stałe poprzez implementację metody getConstants(), dostępnej w JS. Poniżej zaimplementujesz getConstants() i zwrócisz Mapę zawierającą stałą DEFAULT_EVENT_NAME, dostępną w JavaScripcie:

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

Do stałej można następnie uzyskać dostęp poprzez wywołanie getConstants na module natywnym w JS:

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

Teoretycznie możliwy jest bezpośredni dostęp do stałych eksportowanych w getConstants() bezpośrednio z obiektu modułu natywnego. To podejście nie będzie wspierane w TurboModules, dlatego zachęcamy społeczność do przejścia na powyższą metodę, aby uniknąć migracji w przyszłości.

uwaga

Obecnie stałe są eksportowane tylko podczas inicjalizacji, więc zmiana wartości getConstants podczas działania aplikacji nie wpłynie na środowisko JavaScript. To ulegnie zmianie w Turbomodules. W Turbomodules getConstants() stanie się zwykłą metodą modułu natywnego, a każde wywołanie będzie trafiać do warstwy natywnej.

Callbacki

Moduły natywne obsługują również specjalny rodzaj argumentu: callback. Callbacki służą do przekazywania danych z Java/Kotlin do JavaScript dla metod asynchronicznych. Mogą też asynchronicznie wykonywać JavaScript ze strony natywnej.

Aby utworzyć metodę modułu natywnego z callbackiem, najpierw zaimportuj interfejs Callback, a następnie dodaj nowy parametr typu Callback do swojej metody. Istnieją pewne niuanse związane z callbackami, które wkrótce zostaną usunięte w TurboModules. Po pierwsze, możesz mieć tylko dwa callbacki w argumentach funkcji - successCallback i failureCallback. Dodatkowo, ostatni argument wywołania metody modułu natywnego, jeśli jest funkcją, traktowany jest jako successCallback, a przedostatni jako failureCallback.

java
import com.facebook.react.bridge.Callback;

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

Callback możesz wywołać w metodzie Java/Kotlin, przekazując dowolne dane do JavaScript. Pamiętaj, że możesz przekazywać tylko dane serializowalne z kodu natywnego do JavaScript. Jeśli potrzebujesz przekazać obiekt natywny, użyj WriteableMaps, dla kolekcji użyj WritableArrays. Ważne jest również, że callback nie jest wywoływany natychmiast po zakończeniu funkcji natywnej. Poniżej ID wydarzenia utworzonego wcześniej jest przekazywane do callbacka.

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

Tę metodę można następnie wywołać w JavaScripcie:

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

Inny ważny szczegół: metoda modułu natywnego może wywołać tylko jeden callback, jeden raz. Oznacza to, że możesz wywołać albo callback sukcesu, albo błędu, ale nie oba, i każdy callback może zostać wywołany maksymalnie raz. Moduł natywny może jednak przechować callback i wywołać go później.

Istnieją dwa podejścia do obsługi błędów z callbackami. Pierwsze to naśladowanie konwencji Node.js i traktowanie pierwszego argumentu przekazanego do callbacka jako obiektu błędu.

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

W JavaScripcie możesz sprawdzić pierwszy argument, aby wykryć błąd:

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

Inna opcja to użycie osobnych callbacków onSuccess i onFailure:

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

W JavaScripcie możesz dodać osobne callbacki dla odpowiedzi sukcesu i błędu:

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

Promisy

Moduły natywne mogą również zwracać Promise, co upraszcza kod JavaScript, szczególnie przy użyciu składni async/await z ES2016. Gdy ostatni parametr metody Java/Kotlin jest typu Promise, odpowiadająca jej metoda JS zwróci obiekt Promise.

Refaktoryzacja powyższego kodu do użycia promisa zamiast callbacków wygląda następująco:

java
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);
}
}
uwaga

Podobnie jak w przypadku callbacków, metoda modułu natywnego może albo odrzucić, albo rozwiązać promisa (ale nie oba jednocześnie) i może to zrobić tylko raz. Oznacza to, że możesz wywołać callback sukcesu lub callback błędu, ale nie oba, i każdy callback może być wywołany tylko raz. Moduł natywny może jednak przechować callback i wywołać go później.

Odpowiednik tej metody w JavaScript zwraca Promisa. Oznacza to, że możesz użyć słowa kluczowego await wewnątrz funkcji asynchronicznej, aby ją wywołać i poczekać na wynik:

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

Metoda reject przyjmuje różne kombinacje następujących argumentów:

java
String code, String message, WritableMap userInfo, Throwable throwable

Więcej szczegółów znajdziesz w interfejsie Promise.java tutaj. Jeśli userInfo nie zostanie podany, ReactNative ustawi go na null. Dla pozostałych parametrów React Native użyje wartości domyślnych. Argument message dostarcza message błędu wyświetlany na górze stosu wywołań. Poniżej przykład komunikatu błędu w JavaScript z następującego wywołania reject w Javie/Kotlinie.

Wywołanie reject w Javie/Kotlinie:

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

Komunikat błędu w aplikacji React Native gdy promis jest odrzucony:

Image of error message in React Native app.
Image of error message

Wysyłanie zdarzeń do JavaScript

Moduły natywne mogą sygnalizować zdarzenia do JavaScript bez bezpośredniego wywoływania. Na przykład możesz chcieć powiadomić JavaScript o przypomnieniu, że wkrótce nastąpi zdarzenie z kalendarza w natywnej aplikacji kalendarza Androida. Najprostszym sposobem jest użycie RCTDeviceEventEmitter, który można uzyskać z ReactContext jak w poniższym fragmencie kodu.

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,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}

private int listenerCount = 0;

@ReactMethod
public void addListener(String eventName) {
if (listenerCount == 0) {
// Set up any upstream listeners or background tasks as necessary
}

listenerCount += 1;
}

@ReactMethod
public void removeListeners(Integer count) {
listenerCount -= count;
if (listenerCount == 0) {
// Remove upstream listeners, stop unnecessary background tasks
}
}
...
WritableMap params = Arguments.createMap();
params.putString("eventProperty", "someValue");
...
sendEvent(reactContext, "EventReminder", params);

Moduły JavaScript mogą następnie rejestrować się do odbierania zdarzeń przez addListener w klasie NativeEventEmitter.

tsx
import {NativeEventEmitter, NativeModules} from 'react-native';
...
useEffect(() => {
const eventEmitter = new NativeEventEmitter(NativeModules.ToastExample);
let eventListener = eventEmitter.addListener('EventReminder', event => {
console.log(event.eventProperty) // "someValue"
});

// Removes the listener once unmounted
return () => {
eventListener.remove();
};
}, []);

Uzyskiwanie wyniku aktywności z startActivityForResult

Będziesz musiał nasłuchiwać onActivityResult, jeśli chcesz uzyskać wyniki z aktywności rozpoczętej przez startActivityForResult. Aby to zrobić, musisz rozszerzyć BaseActivityEventListener lub zaimplementować ActivityEventListener. Pierwsze rozwiązanie jest preferowane, ponieważ jest bardziej odporne na zmiany w API. Następnie musisz zarejestrować nasłuchiwacz w konstruktorze modułu w następujący sposób:

java
reactContext.addActivityEventListener(mActivityResultListener);

Teraz możesz nasłuchiwać onActivityResult implementując następującą metodę:

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

Zaimplementujmy podstawowy selektor obrazów, aby to zademonstrować. Selektor obrazów będzie udostępniał metodę pickImage w JavaScript, która po wywołaniu zwróci ścieżkę obrazu.

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

Nasłuchiwanie zdarzeń cyklu życia

Nasłuchiwanie zdarzeń cyklu życia aktywności, takich jak onResume, onPause itp., jest bardzo podobne do implementacji ActivityEventListener. Moduł musi implementować LifecycleEventListener. Następnie musisz zarejestrować nasłuchiwacz w konstruktorze modułu w następujący sposób:

java
reactContext.addLifecycleEventListener(this);

Teraz możesz nasłuchiwać zdarzeń cyklu życia aktywności, implementując następujące metody:

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

Zarządzanie wątkami

Do tej pory na Androidzie wszystkie asynchroniczne metody modułów natywnych wykonują się w jednym wątku. Moduły natywne nie powinny zakładać, w którym wątku są wywoływane, ponieważ obecne przypisanie może ulec zmianie w przyszłości. Jeśli wymagane jest blokujące wywołanie, ciężka praca powinna być przekazana do wewnętrznie zarządzanego wątku roboczego, a callbacki dystrybuowane stamtąd.