Background
Room is Android SQL Lite wrapper to use easily.
This example is from Android Room with a View – Kotlin
In this example, use Room / ViewModel / RecyclerView, Coroutine
Different from original code is
- Use only one activity to understand easily
- Update library version
This sample explains following technology in Android
- LiveData
- ViewModel
- Entity
- Room database
- SQLite
- DAO (Data Access Object)
Steps
Codes are little bit complicated, so will explain steps
This is Room parts
- Import dependencies
- Create Model(Entity)
- Create DAO (SQL)
- Observe database update (Flow)
- Add Roomdatabase(Extend class, Singleton with context, work with kapt)
- Create Repository with DAO
Next is App
- Create ViewModel with Repository
- Create Database instance in App layer(Application class) with lazy load
- Repository with lazy load
- Data access from ViewModel
- Activity has viewmodel with repository
Database – Sample Code
build.gradle
Apply kotlin kapt, add dependencies, room, viewmodel, livedata, lifecycle, activity, lifecycle with kapt
plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' id 'kotlin-kapt' } android { compileSdk 32 defaultConfig { applicationId "com.daiji110.rommapp" minSdk 26 targetSdk 32 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' freeCompilerArgs += [ '-Xjvm-default=enable' ] } } dependencies { def room_version = "2.4.2" def lifecycle_version = "2.5.0-alpha04" def coroutines_version = "1.6.0" def activity_version = "1.4.0" implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' // room implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" // lifecycle // ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" // ViewModel utilities for Compose implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" // LiveData implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" // Lifecycles only (without ViewModel or LiveData) implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" // Activity implementation "androidx.activity:activity-ktx:$activity_version" // Saved state module for ViewModel implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" // Annotation processor kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // coroutines api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' // room test testImplementation "androidx.room:room-testing:$room_version" }
Item.kt
Model class.
Add @Entity with tableName
This is data class with value
@Entity(tableName = "item_table") data class Item(@PrimaryKey val id: Int, val name: String)
ItemDAO.kt
This is Data Access Object (SQL) codes.
@Dao interface ItemDAO { @Query("SELECT * FROM item_table ORDER BY id ASC") fun geteAllItems(): Flow<List<Item>> @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insert(item: Item) @Query("DELETE FROM item_table") suspend fun deleteAll() }
ItemRoomDatabase
This is abstract class (handle by kapt) extends RoomDatabase class
This manages version, database access , initalization etc…
@Database(entities = [Item::class], version = 1) abstract class ItemRoomDatabase : RoomDatabase() { abstract fun itemDao() : ItemDAO companion object { @Volatile private var INSTANCE: ItemRoomDatabase? = null fun getDatabase( context: Context, scope: CoroutineScope ): ItemRoomDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, ItemRoomDatabase::class.java, "item_database" ) .fallbackToDestructiveMigration() .addCallback(ItemDatabaseCallback(scope)) .build() INSTANCE = instance instance } } private class ItemDatabaseCallback( private val scope: CoroutineScope ) : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) INSTANCE?.let { database -> scope.launch(Dispatchers.IO) { populateDatabase(database.itemDao()) } } } } suspend fun populateDatabase(itemDao: ItemDAO) { itemDao.deleteAll() // Populate data when create database var item = Item(1, "firstitem") itemDao.insert(item) } } }
ItemRepository.kt
This is repository codes.
Have DAO instance and manipulate DB with DAO. This is interface layer to use DB or other access.
class ItemRepository(private val itemDao: ItemDAO) { val allitems: Flow<List<Item>> = itemDao.geteAllItems() @Suppress("RedundantSuspendModifier") @WorkerThread suspend fun insert(item: Item) { itemDao.insert(item) } }
This is an preparation to access DB.
Next is app
Application – Sample Codes
ItemViewModel.kt
ViewModel, in this app, get all data and show them as list, add random data when the user press button. Prepare 2 methods to access. And it provides ViewModel instance
class ItemViewModel(private val repository: ItemRepository) : ViewModel() { val allItems: LiveData<List<Item>> = repository.allitems.asLiveData() fun insert(item: Item) = viewModelScope.launch { repository.insert(item) } } class ItemViewModelFactory(private val repository: ItemRepository) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(ItemViewModel::class.java)) { //@SuppressWarnings("UNCHECKED_CAST") return ItemViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
RoomApp.kt
This is application class.
Prepare Coroutine scope, Database access ,and repository to access from anywhere in app
class RoomApp : Application() { val applicationScope = CoroutineScope(SupervisorJob()) val database by lazy { ItemRoomDatabase.getDatabase(this, applicationScope) } val repository by lazy { ItemRepository(database.itemDao()) } }
ItemListAdapter.kt
RecyclerView Adapter.
class ItemListAdapter : ListAdapter<Item, ItemListAdapter.ItemViewHolder>(WORDS_COMPARATOR) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder { return ItemViewHolder.create(parent) } override fun onBindViewHolder(holder: ItemViewHolder, position: Int) { val current = getItem(position) holder.bind(current.name) } class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val itemItemView: TextView = itemView.findViewById(R.id.textView) fun bind(text: String?) { itemItemView.text = text } companion object { fun create(parent: ViewGroup): ItemViewHolder { val view: View = LayoutInflater.from(parent.context) .inflate(R.layout.recyclerview_item, parent, false) return ItemViewHolder(view) } } } companion object { private val WORDS_COMPARATOR = object : DiffUtil.ItemCallback<Item>() { override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean { return oldItem === newItem } override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean { return oldItem.name == newItem.name } } } }
recyclerview_item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/holo_orange_light" /> </LinearLayout>
MainActivity.kt
This is main layout codes
class MainActivity : AppCompatActivity() { private val charPool : List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9') private val itemViewModel: ItemViewModel by viewModels { ItemViewModelFactory((application as RoomApp).repository) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val recyclerView = findViewById<RecyclerView>(R.id.recyclerview) val adapter = ItemListAdapter() recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(this) val fab = findViewById<FloatingActionButton>(R.id.fab) fab.setOnClickListener { var id = 0 itemViewModel.allItems.let { item -> item.value?.let { list -> id = list[list.size-1].id + 1 } } val item = Item(id, createItem(6)) itemViewModel.insert(item) } itemViewModel.allItems.observe(this) { items -> items.let { adapter.submitList(it) } } } private fun createItem(num: Int) : String { if (num < 1) return "" val size = charPool.size-1 var sb = StringBuilder() for (i in 1..num) { sb.append(charPool[(0..size).random()]) } return sb.toString() } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:src="@drawable/ic_add_black_24dp"/> </androidx.constraintlayout.widget.ConstraintLayout>
Completed. Start and can see list items we inserted. We can see + button at right bottom, and press to create new item (random name) and update list realtime
コメント