Expo SDK 32.0. Zadania w tle

 In Mobile, React Native, Technicznie

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.

  1. npm install/yarn add @voximplant/react-native-foreground-service –save
  2. 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()
  3.  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’)
  4. W /android/app/build.gradle w dependencies dodaję
    implementation project(‘:@voximplant_react-native-foreground-service’)
  5.  Następnie w AndroidManifest.xml upewniam się, że istnieje <uses-permission android:name=”android.permission.FOREGROUND_SERVICE”/> Jeśli nie, to trzeba dodać
  6.  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.

Recent Posts