Expo SDK 32.0. Zadania w tle
W lutym 2019 roku wyszła nowa wersja Expo SDK – 32.0.0, do której wprowadzono niewiele nowych funkcjonalności. Głównymi zmianami są interaktywne notyfikacje i bardzo oczekiwana przez deweloperów opcja zbierania lokalizacji, gdy aplikacja działa w tle. Możliwe jest to dzięki nowemu modułowi – TaskManager. Pozwala on na zarejestrowanie zadań uruchamianych cyklicznie w tle. Zasada działania jest prosta – jeśli nasza aplikacja nie działa podczas, gdy zadanie zostanie uruchomione, TaskManager uruchomi aplikację w tle i wykona wcześniej zdefiniowane polecenie. Brzmi nieskomplikowanie i tak jest, ale tylko dopóty, dopóki zależy nam na działaniu w tle na iOS. Jeśli chcemy przetestować identyczne rozwiązanie na systemie Android w wersji 8.0+, to… czar pryska. Dlaczego? Jednym z głównych powodów jest oszczędzanie baterii – aplikacja uruchamiająca usługę powoduje dodatkowe obciążenie systemu. Od Androida 9.0 obowiązkowym jest pytanie przez aplikacje o pozwolenie dotyczące działania w tle. Dodatkowo każda taka aplikacja musi wyświetlać powiadomienie (np. na pasku stanu) o tym, że jest uruchomiona w tle. A więc jak rozwiązać ten problem w aplikacji korzystającej z Expo? Na ten moment nie da się. Na szczęście istnieje alternatywa – ExpoKit.
Czym jest ExpoKit?
ExpoKit to biblioteka Objective-C i Java, która umożliwia korzystanie z platformy Expo i istniejącego projektu Expo w ramach większego standardowego projektu natywnego – takiego, który normalnie tworzyłbyś za pomocą XCode, Android Studio lub react-native init . Ponieważ Expo zapewnia już możliwość renderowania rodzimych plików binarnych dla App Store, większość programistów Expo nie musi korzystać z ExpoKit. W niektórych przypadkach projekty muszą korzystać z kodu źródłowego Objective-C lub Java pochodzącego od innych firm, który nie jest zawarty w podstawowym Expo SDK. ExpoKit daje taką możliwość.
Jak przejść z Expo do ExpoKit?
Szczegółowy opis przejścia na ExpoKit opisuje dokumentacja Expo: https://docs.expo.io/versions/v32.0.0/expokit/eject/
Co, gdy już mamy aplikację opartą o ExpoKit? W końcu możemy korzystać z natywnych bibliotek, dzięki którym w łatwy sposób uruchomimy foreground service.
Czym jest foreground service? Po co jest on nam w ogóle potrzebny?
W Androidzie wyróżniamy dwa główne typy serwisów (istnieje jeszcze trzeci, “bound”, jednak nie będzie on opisany w tym artykule):
- Foreground – usługa pierwszego planu wykonuje pewne operacje, które są zauważalne dla użytkownika, muszą wyświetlać powiadomienie. Nadal działają, nawet jeśli użytkownik nie wchodzi w interakcję z aplikacją.
- Background – usługa w tle wykonuje operacje, które nie są bezpośrednio zauważalne przez użytkownika.
Gdy już znamy teorię, przejdźmy do tworzenia prostej aplikacji na Androida wykorzystującej działanie w tle. Aplikacja sama w sobie będzie ograniczona do minimum, nie chcę marnować czasu na tłumaczenie kodu dotyczącego warstwy prezentacyjnej aplikacji, chętnych zapraszam do przejrzenia repozytorium.
Aplikacja będzie pobierała lokalizację użytkownika w tle. Dodatkowo, aby sprawdzić czy rzeczywiście działa, wyświetlimy informację w notyfikacji.
Zacznę od momentu posiadania gotowej aplikacji z ExpoKit. Nasz plik package.json powinien wyglądać następująco
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"eject": "expo eject"
},
"dependencies": {
"@voximplant/react-native-foreground-service": "^1.1.0",
"expo": "^32.0.0",
"expokit": "32.1.1",
"react": "16.5.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz"
},
"devDependencies": {
"babel-preset-expo": "^5.0.0"
},
"private": true
}
W tym momencie musimy sięgnąć do bilbioteki umożliwiającej korzystanie z foreground serwisu. Daje ona możliwość manualnej i automatycznej instalacji (osobiscie preferuję zrobić to manualnie i mieć pewność, że wszystko jest na swoim miejscu). Konfiguracja jest bardzo prosta i zamyka się w 6 krokach.
- npm install/yarn add @voximplant/react-native-foreground-service –save
- W pliku MainApplication.java dodajemy import com.voximplant.foregroundservice.VIForegroundServicePackage; na górze pliku oraz w metodzie w liście zwracanej przez metodę getPackages() dodaję
new VIForegroundServicePackage()
- W pliku android/settings.gradle dodaję 2 linię kodu
include ‘:@voximplant_react-native-foreground-service’project(‘:@voximplant_react-native-foreground-service’).projectDir = new File(rootProject.projectDir, ‘../node_modules/@voximplant/react-native-foreground-service/android’)
- W /android/app/build.gradle w dependencies dodaję
implementation project(‘:@voximplant_react-native-foreground-service’)
- Następnie w AndroidManifest.xml upewniam się, że istnieje <uses-permission android:name=”android.permission.FOREGROUND_SERVICE”/> Jeśli nie, to trzeba dodać
- Również w AndroidManifest.xml trzeba dodać
<service android:name="com.voximplant.foregroundservice.VIForegroundService"> </service>
Tak skonfigurowany projekt da nam możliwość korzystania z foreground serwisów, a tym samym spowoduje, że zadziała background task.
Przejdźmy do tworzenia notyfikacji. Nie jest ona wymagana do działania zadania w tle, pozwoli natomiast sprawdzić, czy zadanie działa.
Od Androida 8.0+ każda notyfikacja musi być przypisana do kanału, dlatego w pierwszej kolejności tworzymy kanał. Następnie możemy tworzyć własne notyfikacje, które dostarczą krótkich, aktualnych informacji o wydarzeniach w aplikacji.
const channelConfig = { id: 1, name: 'Channel name', description: 'Channel description', enableVibration: false }; VIForegroundService.createNotificationChannel(channelConfig);
Aby przypisać notyfikację do kanału, należy podać jako “channelId” “id” z konfiguracji kanału.
const notificationConfig = { channelId: 1, id: 3456, title: 'Title', text: 'Some text', icon: 'ic_icon' };
Teraz wystaczy wystartować foreground service, któremu jako parametr przekażę konfigurację kanału.
VIForegroundService.startService(notificationConfig)
W tym momencie aplikacja powinna wyświetlać notyfikację informującą o tym, że foreground service jest uruchomiony.
Do uruchomienia zadania w tle w aplikacji konieczne jest użycie TaskManagera z bilbioteki Expo.
Zadanie TaskManagera powinno być zdefiniowane w globalnym scopie, ponieważ podczas gdy aplikacja jest uruchamiana w tle, żadne widoki nie są wczytywane.
const LOCATION_BACKGROUND_TASK_NAME = 'background-location-task'; TaskManager.defineTask(LOCATION_BACKGROUND_TASK_NAME, async({data, error}) =>{ if (error) { console.log(error); } if (data) { console.log(data.locations[0]); } })
Location.startLocationUpdatesAsync(LOCATION_BACKGROUND_TASK_NAME, { timeInterval: 1000, distanceInterval: 0, accuracy: Location.Accuracy.High, showsBackgroundLocationIndicatior: true, });
Powyższy kod zarejestruje zadanie w celu otrzymywania aktualizacji lokalizacji, które mogą również pojawić się, gdy aplikacja jest w tle. W tej sytuacji aplikacja będzie każdorazowo wyłapywała zmiany lokalizacji (minimalnie w ciągu 1 milisekundy, jak wynika z timeInterval), a w konsoli pojawią się dane lokalizacyjne. Po przejściu aplikacji do tła w konsoli nadal będziemy mieli dostęp do logów z danymi.
Tym prostym sposobem udało nam się zaimplementować background taski w React Native z alternatywą Expo. Warto zauważyć, że teoretycznie nie jest to jedyna możliwość – istnieją też inne bilbioteki oferujące zbieranie danych lokalizayjnych. Żadna z nich nie spełniła jednak naszych projetktowych wymogów, w których priorytetowe znaczenie miały dla nas krótki czas aktualizacji i zbieranie informacji w tle. Prawdopodobnie w nowych SDK Expo będzię to w standardzie i nie będzie ograniczało się tylko do GPS.