android_room_with_a_view_-_kotlin
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| android_room_with_a_view_-_kotlin [2024/11/15 11:23] – [10.Create the ViewModel] kalaschnikow | android_room_with_a_view_-_kotlin [2026/04/01 08:21] (current) – [Android Room with a View - Kotlin] kalaschnikow | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | ====== Android Room with a View - Kotlin ====== | ||
| + | |||
| + | My conspectus of [[https:// | ||
| + | |||
| + | ATTENTION: this codelab is now deprecated (because of Room 3.x ?) | ||
| + | |||
| + | ===== 1.Before you begin ===== | ||
| + | |||
| + | * [[https:// | ||
| + | * Data layer: https:// | ||
| + | |||
| + | * Prerequisites | ||
| + | * RecyclerView and adapters | ||
| + | * SQLite database and the SQLite query language | ||
| + | * Basic coroutines (If you're not familiar with coroutines, you can start with [[https:// | ||
| + | |||
| + | * Gitlab repository: https:// | ||
| + | |||
| + | ===== 2.Using the Architecture Components ===== | ||
| + | |||
| + | See this url for diagrams: https:// | ||
| + | |||
| + | Layers: | ||
| + | * UI controller (activity/ | ||
| + | * ViewModel | ||
| + | * contains LiveData models. | ||
| + | * LiveData is observable (can notify observers about changes) | ||
| + | * survives activity/ | ||
| + | * Repository | ||
| + | * necessary to manage multiple data sources | ||
| + | * RoomDatabase | ||
| + | * usually SQLite | ||
| + | * DAO | ||
| + | * mapping of SQL queries to functions | ||
| + | * Entities | ||
| + | * => tables | ||
| + | * Citation: " | ||
| + | |||
| + | |||
| + | ===== 3.Create your app ===== | ||
| + | ... | ||
| + | ===== 4.Update Gradle files ===== | ||
| + | ... | ||
| + | ===== 5.Create an Entity ===== | ||
| + | <code kotlin> | ||
| + | @Entity(tableName = " | ||
| + | class Word(@PrimaryKey @ColumnInfo(name = " | ||
| + | </ | ||
| + | |||
| + | * Each '' | ||
| + | * Autogenerate keys : '' | ||
| + | |||
| + | ===== 6.Create the DAO ===== | ||
| + | |||
| + | * In the DAO (data access object), you specify SQL queries and associate them with method calls. | ||
| + | * The DAO must be an interface or abstract class. | ||
| + | * By default, all queries must be executed on a separate thread. | ||
| + | * Room has [[https:// | ||
| + | |||
| + | <code kotlin> | ||
| + | @Dao | ||
| + | interface WordDao { | ||
| + | |||
| + | @Query(" | ||
| + | fun getAlphabetizedWords(): | ||
| + | |||
| + | @Insert(onConflict = OnConflictStrategy.IGNORE) | ||
| + | suspend fun insert(word: | ||
| + | |||
| + | @Query(" | ||
| + | suspend fun deleteAll() | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | * Room DAO Docs: [[https:// | ||
| + | ===== 7.Observing database changes ===== | ||
| + | * To observe data changes you will use [[https:// | ||
| + | |||
| + | <code kotlin> | ||
| + | | ||
| + | fun getAlphabetizedWords(): | ||
| + | </ | ||
| + | ===== 8.Add a Room database ===== | ||
| + | * Room is a database layer on top of an SQLite database. | ||
| + | * " | ||
| + | * By default when Room queries return '' | ||
| + | * Room provides compile-time checks of SQLite statements. | ||
| + | |||
| + | |||
| + | * Your Room database class must be abstract and extend '' | ||
| + | * '' | ||
| + | |||
| + | |||
| + | * Notes | ||
| + | * When you modify the database schema, you'll need to update the version number and define a migration strategy. | ||
| + | ===== 9.Create the Repository ===== | ||
| + | * A repository class abstracts access to multiple data sources (e.g. local / network ) | ||
| + | |||
| + | <code kotlin> | ||
| + | // Declares the DAO as a private property in the constructor. Pass in the DAO | ||
| + | // instead of the whole database, because you only need access to the DAO | ||
| + | class WordRepository(private val wordDao: WordDao) { | ||
| + | |||
| + | // Room executes all queries on a separate thread. | ||
| + | // Observed Flow will notify the observer when the data has changed. | ||
| + | val allWords: Flow< | ||
| + | |||
| + | // By default Room runs suspend queries off the main thread, therefore, we don't need to | ||
| + | // implement anything else to ensure we're not doing long running database work | ||
| + | // off the main thread. | ||
| + | @Suppress(" | ||
| + | @WorkerThread | ||
| + | suspend fun insert(word: | ||
| + | wordDao.insert(word) | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | * The main takeaways: | ||
| + | * The DAO is passed into the repository constructor as opposed to the whole database. This is because it only needs access to the DAO, since the DAO contains all the read/write methods for the database. There' | ||
| + | * The list of words is a public property. It's initialized by getting the Flow list of words from Room; you can do this because of how you defined the '' | ||
| + | * The '' | ||
| + | * Room executes suspend queries off the main thread. | ||
| + | |||
| + | |||
| + | * Notes: | ||
| + | * In this simple example, you only have one data source, so the Repository doesn' | ||
| + | * network synchronization ? | ||
| + | ===== 10.Create the ViewModel ===== | ||
| + | * The '' | ||
| + | * The '' | ||
| + | * For an introductory guide to this topic, see [[https:// | ||
| + | |||
| + | * The '' | ||
| + | * Flow | ||
| + | * observable | ||
| + | * NOT lifecycle aware (destroyed every time on configuration change) | ||
| + | * LiveData | ||
| + | * observable | ||
| + | * lifecycle aware | ||
| + | * ViewModel | ||
| + | * lifecycle aware | ||
| + | * transforms '' | ||
| + | |||
| + | <code kotlin> | ||
| + | class WordViewModel(private val repository: WordRepository) : ViewModel() { | ||
| + | |||
| + | // Using LiveData and caching what allWords returns has several benefits: | ||
| + | // - We can put an observer on the data (instead of polling for changes) and only update the | ||
| + | // the UI when the data actually changes. | ||
| + | // - Repository is completely separated from the UI through the ViewModel. | ||
| + | val allWords: LiveData< | ||
| + | |||
| + | /** | ||
| + | * Launching a new coroutine to insert the data in a non-blocking way | ||
| + | */ | ||
| + | fun insert(word: | ||
| + | repository.insert(word) | ||
| + | } | ||
| + | } | ||
| + | |||
| + | class WordViewModelFactory(private val repository: WordRepository) : ViewModelProvider.Factory { | ||
| + | override fun <T : ViewModel> | ||
| + | if (modelClass.isAssignableFrom(WordViewModel:: | ||
| + | @Suppress(" | ||
| + | return WordViewModel(repository) as T | ||
| + | } | ||
| + | throw IllegalArgumentException(" | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== 11.Add XML layout ===== | ||
| + | |||
| + | * Make your application theme material by setting the '' | ||
| + | * Add dimension resources in '' | ||
| + | * Add a '' | ||
| + | * In '' | ||
| + | * Replace the FAB icon with a ‘+' symbol | ||
| + | |||
| + | ===== 12.Add a RecyclerView ===== | ||
| + | |||
| + | * You'll need to create: | ||
| + | * The '' | ||
| + | * A nested '' | ||
| + | * The '' | ||
| + | |||
| + | * Add the '' | ||
| + | |||
| + | ===== 13.Instantiate the repository and the database ===== | ||
| + | |||
| + | * Create a new class called '' | ||
| + | |||
| + | <code kotlin> | ||
| + | class WordsApplication : Application() { | ||
| + | // Using by lazy so the database and the repository are only created when they' | ||
| + | // rather than when the application starts | ||
| + | val database by lazy { WordRoomDatabase.getDatabase(this) } | ||
| + | val repository by lazy { WordRepository(database.wordDao()) } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | * Update the '' | ||
| + | <code xml> | ||
| + | < | ||
| + | android: | ||
| + | android: | ||
| + | android: | ||
| + | android: | ||
| + | android: | ||
| + | android: | ||
| + | android: | ||
| + | ... | ||
| + | </ | ||
| + | |||
| + | ===== 14.Populate the database ===== | ||
| + | |||
| + | You will add data in two ways: Add some data when the database is created, and add an '' | ||
| + | |||
| + | To delete all content and repopulate the database whenever the app is created, you'll create a '' | ||
| + | |||
| + | To launch a coroutine you need a '' | ||
| + | <code kotlin> | ||
| + | fun getDatabase( | ||
| + | | ||
| + | | ||
| + | ): WordRoomDatabase { | ||
| + | ... | ||
| + | } | ||
| + | |||
| + | </ | ||
| + | |||
| + | Populating the database isn't related to a UI lifecycle, therefore you shouldn' | ||
| + | |||
| + | <code kotlin> | ||
| + | class WordsApplication : Application() { | ||
| + | // No need to cancel this scope as it'll be torn down with the process | ||
| + | val applicationScope = CoroutineScope(SupervisorJob()) | ||
| + | |||
| + | // Using by lazy so the database and the repository are only created when they' | ||
| + | // rather than when the application starts | ||
| + | val database by lazy { WordRoomDatabase.getDatabase(this, | ||
| + | val repository by lazy { WordRepository(database.wordDao()) } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | In the '' | ||
| + | |||
| + | <code kotlin> | ||
| + | private class WordDatabaseCallback( | ||
| + | private val scope: CoroutineScope | ||
| + | ) : RoomDatabase.Callback() { | ||
| + | |||
| + | override fun onCreate(db: | ||
| + | super.onCreate(db) | ||
| + | INSTANCE? | ||
| + | scope.launch { | ||
| + | populateDatabase(database.wordDao()) | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | suspend fun populateDatabase(wordDao: | ||
| + | // Delete all content here. | ||
| + | wordDao.deleteAll() | ||
| + | |||
| + | // Add sample words. | ||
| + | var word = Word(" | ||
| + | wordDao.insert(word) | ||
| + | word = Word(" | ||
| + | wordDao.insert(word) | ||
| + | |||
| + | // TODO: Add your own words! | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | Finally, add the callback to the database build sequence right before calling '' | ||
| + | |||
| + | The full code of '' | ||
| + | <code kotlin> | ||
| + | @Database(entities = arrayOf(Word:: | ||
| + | abstract class WordRoomDatabase : RoomDatabase() { | ||
| + | |||
| + | | ||
| + | |||
| + | | ||
| + | | ||
| + | ) : RoomDatabase.Callback() { | ||
| + | |||
| + | | ||
| + | | ||
| + | | ||
| + | | ||
| + | var wordDao = database.wordDao() | ||
| + | |||
| + | // Delete all content here. | ||
| + | | ||
| + | |||
| + | // Add sample words. | ||
| + | var word = Word(" | ||
| + | | ||
| + | word = Word(" | ||
| + | | ||
| + | |||
| + | // TODO: Add your own words! | ||
| + | word = Word(" | ||
| + | | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | | ||
| + | | ||
| + | | ||
| + | |||
| + | fun getDatabase( | ||
| + | | ||
| + | | ||
| + | ): WordRoomDatabase { | ||
| + | // if the INSTANCE is not null, then return it, | ||
| + | // if it is, then create the database | ||
| + | return INSTANCE ?: synchronized(this) { | ||
| + | val instance = Room.databaseBuilder( | ||
| + | context.applicationContext, | ||
| + | WordRoomDatabase:: | ||
| + | " | ||
| + | ) | ||
| + | | ||
| + | | ||
| + | INSTANCE = instance | ||
| + | // return instance | ||
| + | instance | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ===== 15.Add NewWordActivity ===== | ||
| + | ... | ||
| + | ===== 16.Connect with the data ===== | ||
| + | ... | ||
| + | ===== 17.Summary ===== | ||
| + | ... | ||
| + | ===== 18.Congratulations! ===== | ||
| + | ... | ||
| + | |||
