Plama
Natywne komponenty Android w React Native

Istnieje wiele gotowych natywnych komponentów interfejsu użytkownika, których można użyć w aplikacjach React Native. Niektóre z nich są częściami samej platformy natywnej, a inne są bibliotekami stron trzecich.

W wielu przypadkach użycie wbudowanych w React NAtive komponentów nie będzie wystarczające podczas tworzenia aplikacji mobilnej. Na szczęście możemy w dość prosty sposób zintegrować istniejące natywne komponenty z naszą aplikacją.

W artykule tym, przedstawiam skrótową koncepcję integracji gotowego komponentu natywnego dla platformy Android z aplikacją React Native oraz komunikację między stronami z użyciem tzw. mostu. Do pełnego zrozumienia treści wymagana jest podstawowa wiedza związana z tworzeniem aplikacji mobilnych w React Native oraz aplikacji natywnych Android.

Pierwsze kroki

Na samym początku w ramach przygotowania, stworzymy nową aplikację React Native z użyciem polecenia:

react-native init bridge_android_test_app

Naszym celem będzie integracja natywnego komponentu material-wheel-view z aplikacją React Native. Komponent służy do wyboru elementu w sposób identyczny jak ten znany z platformy iOS.

Repozytorium biblioteki: https://github.com/BlackBoxVision/material-wheel-view

Wybraną przez nas bibliotekę należy wpierw zainstalować – zrobimy to z użyciem Gradle. Aby to zrobić bardzo szybko, dodajmy w pliku android/app/build.gradle w sekcji dependencies, linię:

implementation 'com.github.BlackBoxVision:material-wheel-view:v0.0.1'


Użycie natywnego komponentu

Integracja między technologiami przebiega dwuetapowo. Najpierw musimy zająć się częścią natywną, a później częścią po stronie Javascript.

Stwórzmy odpowiednie klasy w Javie, które wykorzystają naszą bibliotekę. Dla ułatwienia, polecam w tym celu użycie Android Studio, a nie innego edytora, którego używamy do pracy z aplikacjami React Native.

Dodajemy więc nowy plik o nazwie RCTWheelPickerManager.java w katalogu android/app/src/main/java/com/bridge_android_test_app.

W celu wyrenderowania komponentu użyjemy rozszerzenia klasy ViewManager, a dokładnie SimpleViewManager dodającej proste i niezbędne właściwości komponentów, jak wielkości czy kolor tła.

public class RCTWheelPickerManager extends SimpleViewManager<WheelView> {

}

W klasie musimy dodać nazwę, z użyciem której będziemy się komunikować z React.

public static final String REACT_CLASS = "RCTWheelPicker";

Oraz publiczną metodę, która będzie ją zwracać:

@Override
public String getName() {
	return REACT_CLASS;
}

Kolejnym krokiem będzie dodanie metody createViewInstance, w której deklarujemy początkowy stan komponentu i zwracamy jego instancję, którą chcemy zrenderować.

W metodzie tej musimy utworzyć nową instancję komponentu, który chcemy wyświetlić. Aby to w późniejszym czasie przetestować, dodajmy również zbiór danych i przypiszmy do pickera. Ostatnim krokiem będzie zwrócenie obiektu.

@Override
protected WheelView createViewInstance(ThemedReactContext reactContext) {
    final WheelView wheelView = new WheelView(reactContext);
    List<String> testList = new LinkedList<>();

    testList.add("Akanza 1");
    testList.add("Akanza 2");
    testList.add("Akanza 3");

    wheelView.setItems(testList);
    return wheelView;
}


Tworzenie paczki

Wyżej przygotowany komponent musimy zarejestrować jako natywny moduł. W tym celu utworzymy nowy plik RCTWheelPickerPackage.java w katalogu android/app/src/main/java/com/bridge_android_test_app o zawartości:

package com.bridge_android_test_app;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class RCTWheelPickerPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager> asList(
            new RCTWheelPickerManager()
        );
    }
}

Następnie tak przygotowany moduł musimy dodać w istniejącym pliku MainApplication.java w metodzie getPackages(), dopisując linijkę:

packages.add(new RCTWheelPickerPackage());

Po tych krokach, możemy zacząć używać komponentu po stronie React Native.

Użycie modułu natywnego w React Native

W celu integracji po stronie Javascriptu, musimy zarejestrować znaleziony natywny komponent. Proces ten jest bardzo prosty i nie wymaga skomplikowanych operacji.

Stwórzmy plik WheelPickerAndroid.js w katalogu głównym aplikacji. W pliku tym tworzymy odniesienie do zewnętrznego, natywnego modułu.

import {View, requireNativeComponent} from 'react-native';

const settings = {
     name: 'RCTWheelPicker',
     propTypes: {
          ...View.propTypes,
     },
};

export default requireNativeComponent('RCTWheelPicker', settings);

Taki komponent JSX możemy już używać. Zastąpmy domyślny po utworzeniu plik App.js taką zawartością:

import React from 'react';
import {View, StyleSheet} from 'react-native';

const App = () => {
     return (
          <View style={styles.container}>
          </View>
     );
};

const styles = StyleSheet.create({
     flex: 1,
     width: '100%',
});

export default App;

Możemy zaimportować nasz wcześniej utworzony komponent:

import WheelPickerAndroid from './WheelPickerAndroid';

Oraz go wyrenderować:

<WheelPickerAndroid />

Po włączeniu aplikacji, nic jednak nie zostanie wyświetlone. Dlaczego? Otóż w przypadku komponentów natywnych React Native posiada pewne sztywne reguły, które wymagają podania od nas wielkości elementu. Dlatego nadajmy odpowiedni styl naszemu komponentowi, opisując jego wielkość. Po tej zmianie plik App.js wygląda tak:

import React from 'react';
import {View, StyleSheet} from 'react-native';
import WheelPickerAndroid from './WheelPickerAndroid';

const App = () => {
     return (
          <View style={styles.container}>
               <WheelPickerAndroid style={styles.wheelPicker} />
          </View>
     );
};

const styles = StyleSheet.create({
     container: {
          flex: 1,
          width: '100%',
     },
     wheelPicker: {
          width: '100%',
          height: 200,
     },
});

export default App;

Sprawdźmy teraz efekty naszej pracy.

Wyświetlenie natywnego komponentu na widoku React Native.


Komunikacja React Native > Komponent natywny

Kolejnym krokiem, będzie dodanie możliwości zmiany danych prezentowanych w naszym komponencie. Aby tego dokonać, będziemy przekazywać odpowiednie informacje w props komponentu JSX:

<WheelPickerAndroid
     data={['test1', 'test2', 'test3', 'test4', 'test5']}
     style={styles.wheelPicker}
/>

Teraz w pliku RCTWheelPickerManager.java musimy dodać metodę odpowiedzialną za zmianę właściwości komponentów.

@ReactProp(name = "data")
public void setData(WheelView wheelView, ReadableArray data) {
     List<String> dataList = new LinkedList<>();
     for (int i = 0; i < data.size() ; i++) {
          dataList.add(data.getString(i));
     }
     wheelView.setItems(dataList);
}

Oraz usunąć niepotrzebne już operacje z metody createWebInstance

@Override
protected WheelView createViewInstance(ThemedReactContext reactContext) {
     final WheelView wheelView = new WheelView(reactContext);
     return wheelView;
}

Proste? Teraz możemy sprawdzić już działanie komponentu.

Testowanie komunikacji React Native -> Komponent natywny.


Komunikacja Komponent natywny > React native

Zwykle, oprócz przekazywania danych do komponentu natywnego, zachodzi również potrzeba komunikacji w drugą stronę. Dodajmy teraz obsługę takiego procesu.

W pliku RCTWheelPickerManager.java w metodzie createViewInstance dodajmy:

wheelView.setLoopListener(new LoopScrollListener() {
     @Override
     public void onItemSelect(int item) {
          WritableMap event = Arguments.createMap();
          event.putInt("data", item);
          ((ReactContext) wheelView.getContext()).getJSModule(RCTEventEmitter.class).receiveEvent(
          wheelView.getId(),
          "topChange",
          event);
     }
});

Oraz nową metodę, która doda nam event do wykorzystania po stronie Javascriptu.

@Override
public Map getExportedCustomBubblingEventTypeConstants() {
     return MapBuilder.builder()
     .put(
     "topChange",
     MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onChange"))
     )
     .build();
}

Teraz dodajemy props z funkcją która będzie wykonywana podczas ruchu pickera, oraz samą funkcję:

const onItemSelected = event => {
     console.log(event.nativeEvent);
};

[...]

<WheelPickerAndroid
     onChange={onItemSelected}
     data={['test1', 'test2', 'test3', 'test4', 'test5']}
     style={styles.wheelPicker}
/>

Na sam koniec przetestujmy naszą pracę:

Testowanie komunikacji Komponent natywny -> React Native.


Podsumowanie

W wielu przypadkach, podczas pracy nad aplikacjami React Native nie ma potrzeby wchodzenia w kod natywny. Jednak czasami zdarzają się sytuacje, w których jest to pożądane lub niezbędne. Warto sobie jednak zdać sprawę, że proces ten nie jest trudny, a efekty które można w ten sposób uzyskać mogą stanowić pozytywną zmianę w działaniu aplikacji.

Źródła:

https://reactnative.dev/docs/native-modules-android
https://reactnative.dev/docs/communication-android
https://reactnative.dev/docs/native-components-android.html
https://github.com/BlackBoxVision/material-wheel-view

Kategorie
Inne posty