Plama
WatermelonDB w React Native (1/2)

Częstym problemem przy tworzeniu aplikacji mobilnych działających w trybie online jest wybór odpowiedniej bazy danych działającej lokalnie na urządzeniu. W tym wpisie postaram się przybliżyć działanie na takiej bazie od strony praktycznej, tworząc prostą aplikację To Do we frameworku React Native z wykorzystaniem bazy danych WatermelonDB.

WatermelonDB jest biblioteką zbudowaną na podstawie SQLite przystosowaną specjalnie dla React / React Native, wnoszącą znaczną poprawę wydajności i oferującą ciekawe narzędzia. Jednak najciekawszą funkcją z perspektywy widoku aplikacji jest możliwość obserwowania zbiorów danych, a w przypadku ich zmiany automatyczne ponowne zrenderowanie widoku.

Nowy projekt

Przed rozpoczęciem pracy, musimy przygotować środowisko. Szczegółowa instrukcja jest dostępna tutaj: https://reactnative.dev/docs/environment-setup.

Następnie w nowym oknie terminala, po wybraniu odpowiedniego katalogu stwórzmy nowy projekt React Native, wykorzystując polecenie:

react-native init ToDoApp

Po kliku chwilach, możemy otworzyć nasz projekt w edytorze kodu i zająć się konfiguracją potrzebnych bibliotek.

Instalacja WatermelonDB

Kolejnym elementem początkowej konfiguracji jest instalacja sytemu bazy danych. Na początku zainstalujmy wymagane biblioteki używając poleceń:

yarn add @nozbe/watermelondb
yarn add @nozbe/with-observables
yarn add --dev @babel/plugin-proposal-decorators

Następnie utwórzmy nowy plik konfiguracyjny .babelrc umożliwiający stosowanie dekoratorów w kodzie Javascript.

{ 
  "presets": ["module:metro-react-native-babel-preset"],
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ] 
}

Konfiguracja iOS

Aby poprawnie zalinkować bibliotekę w aplikacji iOS, wejdźmy w xCode i w katalogu głównym aplikacji stwórzmy nowy plik z rozszerzeniem swift (nazwa nie odgrywa tutaj znaczącej roli). Po stworzeniu Xcode zapyta nas czy chcemy utworzyć również tzw. Bridging Header – wybieramy Create Bridging Header.

Dodawanie możliwości wykonywania plików Swift.


Na dzień dzisiejszy niestety brak jest wsparcia dla CocoaPods, więc będziemy musieli zaimportować bibliotekę manualnie.

W Xcode w bocznym panelu wybieramy główny projekt i katalog Libraries. Klikamy na nim PPM oraz wybieramy opcję Add Files. Odnajdujemy plik node_modules/@nozbe/watermelondb/native/ios/WatermelonDB.xcodeproj i dodajemy referencję do projektu.

Następnie wybieramy po kliknięciu na projekt w bocznym panelu, przechodzimy do sekcji Build Phases. W zakładce Link Binary With Libraries, dodajemy nowy element o nazwie libWatermelonDB.a.

Poprawnie dodana biblioteka WatermelonDB w Xcode

Konfiguracja Android

Niestety WatermelonDB ma spory problem z automatycznym zalinkowaniem biblioteki na Androidzie, więc trzeba jej odrobinę pomóc. Dlatego potrzebne jest wykonanie szeregu akcji modyfikacji plików projektu. Poniżej lista modyfikacji:

android/settings.gradle:

include ':watermelondb'
project(':watermelondb').projectDir =
    new File(rootProject.projectDir, '../node_modules/@nozbe/watermelondb/native/android')

android/app/build.gradle

apply plugin: "com.android.application"
apply plugin: 'kotlin-android'

... 

dependencies {  
  ... 
  implementation project(':watermelondb')
}

android/build.gradle

buildscript {
  ext.kotlin_version = '1.3.21'

  ...

  dependencies { 
    ...
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 
  }
}

android/app/src/main/java/com/todoapp/MainApplication.java

...

import com.nozbe.watermelondb.WatermelonDBPackage;

...

@Override protected List<ReactPackage> getPackages() {
  return Arrays.<ReactPackage>asList( new MainReactPackage(), new WatermelonDBPackage());
}

Po tak przeprowadzonej konfiguracji początkowej, możemy utworzyć bazę danych.

Konfiguracja bazy danych

Po przygotowaniu biblioteki od strony natywnej, przechodzimy do tworzenia struktury i modelu bazy danych. Naszym celem będzie przygotowanie odpowiedniego schematu zawierającego informacje istotne z punktu widzenia działania naszej aplikacji.

Aby odseparować pliki związane z powyższym tematem utwórzmy w katalogu głównym projektu nowy katalog o nazwie database, w którym będziemy przechowywać pliki bazy danych i modeli.

Schemat bazy

Najpierw musimy utworzyć plik odpowiedzialny za schemat tabel naszej bazy. Utwórzmy więc plik schema.js o zawartości:

import {appSchema, tableSchema} from '@nozbe/watermelondb';

export const mySchema = appSchema({
  version: 1,
  tables: [],
});

W tablicy będącej wartością pola tables, będziemy definiować schematy tabel bazy danych. W naszej aplikacji (jak sama nazwa To Do wskazuje), będziemy pracować na zadaniach. Dlatego musimy stworzyć odpowiednią tabelę, która będzie przechowywała o nich informacje. W tablicy tej dodajmy więc:

tableSchema({
  name: 'tasks',
  columns: [
    {name: 'name', type: 'string'},
    {name: 'completed', type: 'boolean'},
  ],
}),

Nasza tabela będzie zawierała dwie kolumny: nazwę i status wykonania. Przy ich tworzeniu należy również podać typ danych.

Typy kolumn

Każda kolumna może mieć przypisany jeden z trzech typów: string, number oraz boolean. W przypadku gdy dopuszczamy by pole było opcjonalne ustawiając wartość isOptional: true to w polu może znaleść się również wartość null.

Przygotowanie providera

Aby z poziomu aplikacji uzyskać dostęp do bazy danych, należy skonfigurować tzw. Provider’a. W tym celu zmodyfikujemy plik App.js znajdującym się w katalogu głównym projektu.

Na początku zaimportujmy potrzebne metody biblioteki:

import {Database} from '@nozbe/watermelondb';
import DatabaseProvider from '@nozbe/watermelondb/DatabaseProvider';
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';

Jak wcześniej wspomniałem, WatermelonDB jest biblioteką działającą nad SQLite. Dlatego dodajmy odpowiedni adapter:

import {mySchema} from './database/schema';

...

const adapter = new SQLiteAdapter({
  schema: mySchema,
});

Schematem mySchema jest tutaj nasz wyżej utworzony schemat tabel. Następnie przechodzimy do zainicjowania samej bazy danych:

const database = new Database({
  adapter,
  modelClasses: [],
  actionsEnabled: true,
});

W polu modelClasses, będziemy za chwilę umieszczać model naszego zadania. Zanim to jednak zrobimy, przyjrzyjmy się głównej klasie App.js:

export default class App extends Component {
  render = () => {
    return (
      <DatabaseProvider database={database}>
        <SafeAreaView style={styles.container}>
          {/* ... */}
        </SafeAreaView>
      </DatabaseProvider>
    );
  };
}

Dzięki zastosowaniu komponentu DataProvider, mamy możliwość odwoływania się do bazy danych z poziomu wszystkich dzieci tego komponentu.

Model zadania

Definicja schematu nie jest definicją modelu. Głównym obiektem na którym będziemy działać w naszej aplikacji jest Zadanie, więc musimy stworzyć jego model (definicję klasy). W katalogu database dodajmy nowy plik o nazwie Task.js.

import {Model} from '@nozbe/watermelondb';

export default class Task extends Model {
  static table = 'tasks';
}

Następnie dodajmy odpowiednie atrybuty. Nazwy w field odpowiadają nazwie kolumny wcześniej utworzonej tabeli. 

import {field} from '@nozbe/watermelondb/decorators';

...

@field('name') name;
@field('completed') completed;

Całość pliku wygląda tak:

import {Model} from '@nozbe/watermelondb';
import {field} from '@nozbe/watermelondb/decorators';

export default class Task extends Model {
  static table = 'tasks';

  @field('name') name;
  @field('completed') completed;
}

Powróćmy do pliku App.js. Przy definicji bazy danych dodaliśmy pole modelClasses, które zawierało pustą tablicę. Dodajmy jako jej element wyżej utworzony model Task:

import Task from './database/Task';

...

const database = new Database({
  adapter,
  modelClasses: [Task],
  actionsEnabled: true,
});

Aby sprawdzić czy wszystko działa jak należy, możemy uruchomić aplikację używając polecenia:

react-native run-ios

Jeśli biblioteka została poprawnie zainstalowana i zalinkowana oraz konfiguracja została przeprowadzona w prawidłowy sposób, na ekranie powinno się pojawić białe tło oraz brak żadnego błędu.

Podsumowanie

W pierwszej części wpisu zapoznaliśmy się z procesem konfiguracji biblioteki WatermelonDB oraz tworzeniem definicji tabeli i modelu. Aby jednak przejść do sedna aplikacji w następnej części zajmiemy się stworzeniem widoku aplikacji oraz operacją na danych w bazie.

Wkrótce cześć 2.

Kategorie
Inne posty