Information är förmodligen den viktigaste resursen som användarna litar på när det gäller appar. För apputvecklare berättar den informationen vem användaren är, vilket ger oss möjlighet att erbjuda en bra användarupplevelse (UX). Genom att tillämpa affärsregler på denna information definierar vi också det beteende som appen ska ha. Den här informationen kan vara känslig och avslöja användarnas privata uppgifter, så det är mycket viktigt att vi hanterar den på rätt sätt för att säkerställa dess integritet, sekretess och korrekt lagring. Inom den här Android Room-handledningen kommer vi att visa dig hur du kan arbeta med denna data enklare, samtidigt som du säkerställer dess integritet och säkerhet, allt genom att använda Room. Room är en del av Android Architecture Components.
Behövs Room?
När vi talar om att lagra information på ett beständigt sätt på Android är det viktigaste och ”enklaste” alternativet att använda vanlig SQLite. Att ha en bra databasimplementation med SQLite innebär dock att man genererar en massa kod som inte ger något verkligt värde. Arkitekturen som följer är ofta inte så ren eller tydlig som den skulle kunna vara.
Du skulle också kunna använda objektrelationell mappning, eller ORM, men du kommer fortfarande att behöva definiera och skapa databasen manuellt, genom att underklassa SQLiteOpenHelper och skapa kontraktsklasserna.
Frågan är alltså: Finns det något sätt att förenkla hela denna process? Svaret är: Ja, Room är din lösning.
En närmare titt på Room och hur det fungerar
Room är ett av de viktigaste verktygen i Android Architectural Components. Det släpptes under Google I/O 2016 och är ett kraftfullt verktyg för att lagra och manipulera information i Android-appar. Det ger ett mycket enkelt sätt att arbeta med data och garanterar alltid dess säkerhet och integritet.
Room är inte en ORM, utan ett helt bibliotek som gör det möjligt för oss att skapa och manipulera SQLite-databaser på ett enklare sätt. Genom att använda annotationer kan vi definiera våra databaser, tabeller och operationer. Room översätter automatiskt dessa annotationer till SQLite-instruktioner/frågor för att utföra motsvarande operationer i databasmotorn.
De tre huvudkomponenterna i Room är:
– Entitet: Representerar en tabell i Room-databasen. Den bör kommenteras med @Entity.
– DAO: Ett gränssnitt som innehåller metoder för att få tillgång till databasen. Det ska kommenteras med @Dao.
– Databas: Representerar databasen. Det är ett objekt som har en anslutning till SQLite-databasen, och alla operationer utförs via den. Det är kommenterat med @Database.
Rumsarkitekturen ser ut så här:
Too Much Talk-Let’s Look at an Example
Låt oss dyka ner i denna Android Room-tutorial. Tänk dig att vi behöver skapa en app för att lagra din träningsrutin. Vi kommer att ha fyra enheter i vår databas, vilket vi visar nedan. All exempelkod är skriven med hjälp av Kotlin (om du inte kan Kotlin eller vill lära dig mer, uppmanar jag dig att läsa min artikel om det).
Det första vi behöver göra är att uppdatera vår gradle-fil. Den ska se ut så här:
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'}
Låt oss se och analysera var och en av de tre stora Room-komponenterna: Enheter, DAOs och databas.
Enheter
För vårt exempel kommer vi att använda fyra enheter: Kön, motion, rutin och praktikant.
Kön.kt
Genomför praktikantens kön
@Entitydata class Gender( @PrimaryKey(autoGenerate = true) val id: Int? = null, val name: String)
Saker att notera:
– Alla klasser som representerar en enhet i databasen måste annoteras med @Entity
– Med annotationen @PrimaryKey(autoGenerate = true) anger vi att id:et är enhetens primära nyckel och att den ska autogenereras av databasmotorn.
Exercise.kt
Det representerar en övning som är en del av en rutin.
@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)
Saker att lägga märke till:
– Som standard använder Room fältnamnen som kolumnnamn i databasen. Om du vill att en kolumn ska ha ett annat namn lägger du till annotationen @ColumnInfo till ett fält.
Routine.kt
I princip en behållare med övningar som tillsammans skapar en övningsrutin.
@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)
Saker att notera:
– När en klass annoteras med @Entity kommer namnet på tabellen att vara namnet på klassen. Om vi vill använda ett annat namn måste vi lägga till egenskapen tableName tillsammans med @Entity-annotationen.
– Annotationen @TypeConverters måste användas när vi deklarerar en egenskap vars typ är en anpassad klass, en lista, en datumtyp eller någon annan typ som Room och SQL inte vet hur man serialiserar. I det här fallet använder vi annotationen på klassfältnivå varvid endast det fältet kommer att kunna använda den. Beroende på var annotationen är placerad kommer den att bete sig olika, vilket förklaras här.
Trainee.kt
Den representerar ägaren av rutinen.
@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
Data Access Objects (DAOs) används för att komma åt våra data när vi implementerar Room. Varje DAO måste innehålla en uppsättning metoder för att manipulera data (infoga, uppdatera, ta bort eller hämta).
En DAO kan implementeras som ett gränssnitt eller som en abstrakt klass. I vårt fall använder vi ett gränssnitt. Eftersom alla DAO:er i princip är identiska kommer vi bara att visa en.
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}
Några saker att lägga märke till:
– Alla DAO:er måste annoteras med @Dao.
– En funktion som annoteras med @Insert, @Update eller @Delete måste ta emot en instans av den önskade klassen som parameter, vilket representerar objektet som vi vill infoga, uppdatera eller ta bort respektive.
– När det gäller insättnings- eller uppdateringsoperationer kan vi använda egenskapen onConflict för att ange vad vi ska göra när en konflikt som utför operationen inträffar. De strategier som finns tillgängliga att använda är: REPLACE, ABORT, FAIL, IGNORE och ROLLBACK.
– Om vi vill få specifik information från en eller flera enheter kan vi annotera en funktion med @Query och tillhandahålla ett SQL-skript som parameter.
Database
Den representerar databasen. Den har en anslutning till den faktiska SQLite-databasen.
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 } }}
Saker att notera här:
– Detta är en abstrakt klass som måste förlänga RoomDatabase.
– Den måste annoteras med @Database, och den tar emot en lista över entiteter med alla de klasser som ingår i databasen (alla dessa klasser måste annoteras med @Entity). Vi måste också tillhandahålla en databasversion.
– Vi måste deklarera en abstrakt funktion för var och en av de enheter som ingår i @Database-annotationen. Denna funktion måste returnera motsvarande DAO (en klass som är annoterad med @Dao).
– Slutligen deklarerar vi ett companion-objekt för att få statisk tillgång till metoden getAppDataBase, som ger oss en singletoninstans av databasen.
Type Converters
Type Converters används när vi deklarerar en egenskap som Room och SQL inte vet hur de ska serialisera. Låt oss se ett exempel på hur vi serialiserar en datatyp ”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 }}
Användning av Room-databasen
Nu ska vi titta på ett mycket enkelt exempel på hur vi använder Room-databasen som vi just skapat:
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() }}
Vad gör vi?
– Hämtar instansen av databasen och GenderDao.
– Skapa två Gender-instanser:
– Infoga de två skapade instanserna i databasen.
– Fråga databasen för att få fram alla kön som finns lagrade i den.
– Slå ihop namnen på alla kön som vi fick från databasen och ange det värdet i textvyn.
Room för att göra mer med mindre
Room är en av de viktiga delarna av de arkitektoniska komponenterna i Android. Den ger oss en mycket robust ram för att arbeta med och persistent information och garanterar alltid datasäkerhet och integritet. Det ger också användarvänlighet för utvecklare, så att de kan skriva läsbar och självförklarande kod. Om du vill göra mer med mindre kod och dessutom säkerställa säkerheten för användardata bör du använda Room som ditt persistenta lager i din app.
Och det var allt! Det här är nästan allt du behöver veta för att skapa och använda en databas på Android med Room. Få hela koden för det här projektet här. Tack för att du läste den här Android Room-handledningen!