Informacja jest prawdopodobnie najważniejszym zasobem, któremu użytkownicy ufają w aplikacjach. Dla twórców aplikacji, informacje te mówią nam, kim jest użytkownik, co daje nam możliwość zapewnienia dobrego doświadczenia użytkownika (UX). Ponadto, poprzez zastosowanie reguł biznesowych do tych informacji, definiujemy zachowanie, jakie powinna mieć aplikacja. Informacje te mogą być sensowne i narażać prywatne dane użytkowników, więc bardzo ważne jest, abyśmy prawidłowo obchodzili się z nimi, aby zapewnić ich integralność, prywatność i właściwe przechowywanie. W tym tutorialu Android Room, pokażemy Ci jak możesz pracować z tymi danymi łatwiej, jednocześnie zapewniając ich integralność i bezpieczeństwo, a wszystko to za pomocą Room. Room jest częścią Android Architecture Components.
Do You Need Room?
Gdy mówimy o przechowywaniu informacji w sposób trwały w systemie Android, najważniejszą i „łatwą” opcją jest użycie zwykłego SQLite. Jednakże, aby mieć dobrą implementację bazy danych używającą SQLite, trzeba wygenerować dużo kodu, który nie dostarcza prawdziwej wartości. Architektura, która z tego wynika, często nie jest tak czysta i przejrzysta, jak mogłaby być.
Można również użyć mapowania obiektowo-relacyjnego, czyli ORM, ale nadal trzeba będzie ręcznie definiować i tworzyć bazę danych, podklasując SQLiteOpenHelper i tworząc klasy kontraktowe.
Więc, pytanie brzmi: Czy jest jakiś sposób na uproszczenie całego tego procesu? Odpowiedź brzmi: Tak, pokój jest twoim rozwiązaniem.
Bliższe spojrzenie na Room i jak to działa
Room jest jednym z najważniejszych narzędzi w Android Architectural Components. Wydany w Google I/O 2016, jest potężnym narzędziem do przechowywania i manipulowania informacjami w aplikacjach na Androida. Zapewnia bardzo łatwy sposób pracy z danymi i zawsze zapewnia ich bezpieczeństwo i integralność.
Room nie jest ORM; zamiast tego jest to cała biblioteka, która pozwala nam na łatwiejsze tworzenie i manipulowanie bazami danych SQLite. Używając adnotacji, możemy zdefiniować nasze bazy danych, tabele i operacje. Room automatycznie przetłumaczy te adnotacje na instrukcje/kwerendy SQLite, aby wykonać odpowiednie operacje w silniku bazy danych.
Trzy główne komponenty Room to:
– Entity: Reprezentuje tabelę w ramach bazy danych Room Database. Powinien być opatrzony adnotacją @Entity.
– DAO: Interfejs, który zawiera metody dostępu do bazy danych. Należy opatrzyć go adnotacją @Dao.
– Database: Reprezentuje bazę danych. Jest to obiekt, który utrzymuje połączenie do bazy danych SQLite, a wszystkie operacje są wykonywane za jego pośrednictwem. Jest on opatrzony adnotacją @Database.
Architektura pomieszczenia wygląda następująco:
Too Much Talk-Let’s Look at an Example
Zanurzmy się w tym samouczku Android Room. Wyobraźmy sobie, że musimy stworzyć aplikację do przechowywania rutynowych ćwiczeń na siłowni. Będziemy mieli cztery encje w naszej bazie danych, jak pokażemy poniżej. Cały przykładowy kod jest napisany przy użyciu Kotlina (jeśli nie znasz Kotlina lub chcesz dowiedzieć się więcej, zapraszam do przeczytania mojego artykułu na ten temat).
Pierwszą rzeczą, którą musimy zrobić, to zaktualizować nasz plik gradle. Powinien on wyglądać tak:
apply plugin: 'com.android.application'apply plugin: 'kotlin-android'apply plugin: 'kotlin-android-extensions'apply plugin: 'kotlin-kapt'android { //.. Omitted since it is not relevant for the example}dependencies { //... some dependencies were omitted due to they are not relevant for the example def room_version = "2.2.0-rc01" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" compile 'com.google.code.gson:gson:2.2.4' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.17'}
Zobaczmy i przeanalizujmy każdy z trzech głównych komponentów Room: Entities, DAOs, oraz Database.
Entities
Dla naszego przykładu użyjemy czterech encji: Gender, Exercise, Routine i Trainee.
Gender.kt
Oprezentuje płeć uczestnika szkolenia
@Entitydata class Gender( @PrimaryKey(autoGenerate = true) val id: Int? = null, val name: String)
Rzeczy do zauważenia:
– Wszystkie klasy, które reprezentują encję bazy danych, muszą mieć adnotację @Entity
– Z adnotacją @PrimaryKey(autoGenerate = true) wskazujemy, że id jest kluczem głównym encji i powinien być automatycznie generowany przez silnik bazy danych.
Exercise.kt
Oprezentuje ćwiczenie, które jest częścią rutyny.
@Entitydata class Exercise( @PrimaryKey(autoGenerate = true) val exerciseId: Int, val name: String, val repetitions:Int, @ColumnInfo(name = "machine_name") val machineName: String, val liftedWeight: Int)
Rzeczy do zauważenia:
– Domyślnie, Room używa nazw pól jako nazw kolumn w bazie danych. Jeśli chcesz, aby kolumna miała inną nazwę, dodaj do pola adnotację @ColumnInfo.
Routine.kt
Podstawowo kontener ćwiczeń, które razem tworzą rutynę ćwiczeń.
@Entity(tableName = "traineeRoutine")data class Routine( @PrimaryKey(autoGenerate = true) val routineId: Int, @ColumnInfo(name = "due_day") val dueDay: Date, @TypeConverters(ListConverter::class) val exercises: List)
Rzeczy do zauważenia:
– Gdy klasa jest opatrzona adnotacją @Entity, nazwa tabeli będzie nazwą klasy. Jeśli chcemy użyć innej nazwy, musimy dodać właściwość tableName wraz z adnotacją @Entity.
– Adnotacja @TypeConverters musi być użyta, gdy deklarujemy właściwość, dla której typ jest klasą niestandardową, listą, typem daty lub jakimkolwiek innym typem, którego Room i SQL nie wiedzą jak serializować. W tym przypadku używamy adnotacji na poziomie pola klasy, dzięki czemu tylko to pole będzie mogło z niej korzystać. W zależności od miejsca umieszczenia adnotacji, będzie ona zachowywała się inaczej, jak wyjaśniono tutaj.
Trainee.kt
Oprezentuje właściciela rutyny.
@Entity(indices = , foreignKeys = , childColumns = )])data class Trainee( @PrimaryKey(autoGenerate = true) val id: Int, val name: String, val age: Int, val gender: Int?, @Embedded val routine: Routine)
DAOs
Obiekty dostępu do danych (DAO) są używane do dostępu do naszych danych, gdy implementujemy Room. Każde DAO musi zawierać zestaw metod do manipulowania danymi (wstawiania, aktualizowania, usuwania lub uzyskiwania).
A DAO może być zaimplementowane jako interfejs lub jako klasa abstrakcyjna. W naszym przypadku używamy interfejsu. Ponieważ wszystkie DAO są w zasadzie identyczne, pokażemy tylko jedno.
GenderDao.kt@Daointerface GenderDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertGender(gender: Gender) @Update fun updateGender(gender: Gender) @Delete fun deleteGender(gender: Gender) @Query("SELECT * FROM Gender WHERE name == :name") fun getGenderByName(name: String): List @Query("SELECT * FROM Gender") fun getGenders(): List}
Kilka rzeczy, na które należy zwrócić uwagę:
– Wszystkie DAO muszą być opatrzone adnotacją @Dao.
– Funkcja opatrzona adnotacją @Insert, @Update lub @Delete musi otrzymać jako parametr instancję żądanej klasy, która reprezentuje obiekt, który chcemy odpowiednio wstawić, zaktualizować lub usunąć.
– W przypadku operacji wstawiania i aktualizacji możemy użyć właściwości onConflict, aby wskazać, co zrobić, gdy wystąpi konflikt podczas wykonywania operacji. Dostępne do użycia strategie to: REPLACE, ABORT, FAIL, IGNORE oraz ROLLBACK.
– Jeśli chcemy uzyskać określone informacje z jednej lub wielu encji, możemy opatrzyć funkcję adnotacją @Query i przekazać skrypt SQL jako parametr.
Database
Reprezentuje bazę danych. Przechowuje połączenie do rzeczywistej bazy danych SQLite.
AppDatabase.kt@Database(entities = , version = 1)@TypeConverters(DateTypeConverter::class)abstract class AppDatabase : RoomDatabase() { abstract fun exerciseDao(): ExerciseDao abstract fun genderDao(): GenderDao abstract fun routineDao(): RoutineDao abstract fun traineeDao(): TraineeDao companion object { var INSTANCE: AppDatabase? = null fun getAppDataBase(context: Context): AppDatabase? { if (INSTANCE == null){ synchronized(AppDatabase::class){ INSTANCE = Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "myDB").build() } } return INSTANCE } fun destroyDataBase(){ INSTANCE = null } }}
Rzeczy do zauważenia tutaj:
– Jest to klasa abstrakcyjna, która musi rozszerzyć RoomDatabase.
– Musi być opatrzona adnotacją @Database i otrzymuje listę encji ze wszystkimi klasami, które składają się na bazę danych (wszystkie te klasy muszą być opatrzone adnotacją @Entity). Musimy również podać wersję bazy danych.
– Musimy zadeklarować funkcję abstrakcyjną dla każdej z encji zawartych w adnotacji @Database. Funkcja ta musi zwrócić odpowiednie DAO (klasę opatrzoną adnotacją @Dao).
– Na koniec deklarujemy obiekt towarzyszący, aby uzyskać statyczny dostęp do metody getAppDataBase, która daje nam singletonową instancję bazy danych.
Konwertery typów
Konwertery typów są używane, gdy deklarujemy właściwość, której Room i SQL nie wiedzą, jak serializować. Zobaczmy przykład, jak serializować typ danych 'date’.
DateTypeConverter.ktclass DateTypeConverter { @TypeConverter fun fromTimestamp(value: Long?): Date? { return if (value == null) null else Date(value) } @TypeConverter fun dateToTimestamp(date: Date?): Long? { return date?.time }}
Używanie bazy danych Room
Przyjrzyjrzyjmy się teraz bardzo prostemu przykładowi, jak używać bazy danych Room, którą właśnie stworzyliśmy:
MainActivity.ktclass MainActivity : AppCompatActivity() { private var db: AppDatabase? = null private var genderDao: GenderDao? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Observable.fromCallable({ db = AppDatabase.getAppDataBase(context = this) genderDao = db?.genderDao() var gender1 = Gender(name = "Male") var gender2 = Gender(name = "Female") with(genderDao){ this?.insertGender(gender1) this?.insertGender(gender2) } db?.genderDao()?.getGenders() }).doOnNext({ list -> var finalString = "" list?.map { finalString+= it.name+" - " } tv_message.text = finalString }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe() }}
Co robimy?
– Pobieramy instancję bazy danych i GenderDao.
– Tworzymy dwie instancje Gender: Male i Female.
– Wstawienie dwóch utworzonych instancji do bazy danych.
– Zapytanie do bazy danych w celu uzyskania wszystkich przechowywanych w niej płci.
– Scalenie nazw wszystkich płci, które otrzymaliśmy z bazy danych, i ustawienie tekstu TextView z tą wartością.
Room to Do More with Less
Room jest jednym z ważnych elementów komponentów architektury Androida. Daje nam bardzo solidne ramy do pracy z i trwałymi informacjami, zawsze zapewniając bezpieczeństwo i integralność danych. Zapewnia również łatwość użycia dla programistów, dzięki czemu mogą oni pisać czytelny i zrozumiały dla siebie kod. Jeśli chcesz zrobić więcej z mniejszą ilością kodu, a także zapewnić bezpieczeństwo danych użytkownika, powinieneś używać Sali jako warstwy persystencji w swojej aplikacji.
I to by było na tyle! To prawie wszystko, co musisz wiedzieć, aby utworzyć i używać bazy danych na Androida z Room. Pobierz cały kod dla tego projektu tutaj. Dzięki za przeczytanie tego tutoriala Android Room!