如何在使用协程和流的情况下使用房间数据库?

huangapple go评论65阅读模式
英文:

How should I use room database among with coroutines, and flow?

问题

我正在尝试学习如何重新编写我的Room数据库(dao、repository、viewmodel),以使其更高效。我之前没有使用过这些,而且在寻找基础资源方面遇到了困难,因为其中许多资源要么不使用repository(因此我开始认为我不必要地实现了它),要么使用了Hilt,而我对要学习的新东西感到有些不知所措。

我应该如何实现repository和viewmodel,以使其包含流和协程?

英文:

I'm trying to learn how I should re-write my room database (dao, repository, viewmodel) so it can be more efficient. I haven't using any of these, and I have a hard time with trying to find resources to base on, because many of them either don't use repository (so I've started to think that I have implemented it unnecesseairly) or they are using hilt, and I'm kinda overwhelmed by new stuff to learn.

How I should implement repository, and viewmodel to have flow and coroutines inside?

答案1

得分: 11

以下是已翻译的部分:

场景:

假设有一个简单的应用程序,向用户显示图书信息,用户可以将任何图书添加到收藏夹中,从收藏夹中删除它们,并拥有一个屏幕来显示收藏的图书。

我们有一个名为Book的简单实体类:

@Entity
data class Book(
   @PrimaryKey
   val ispn: String
   val title: String,
   val description: String,
   val pages: Int
)

现在,让我们创建一个具有Flow和挂起函数的DAO接口:

@Dao
interface FavoriteBooksDao {
    @Query("SELECT * FROM book")
    fun selectAll(): Flow<List<Book>> // 无需添加suspend关键字,因为它返回Flow,Flow已经使用协程。

    @Insert
    suspend fun insert(book: Book) // 只需在函数中添加suspend关键字,以使其与协程一起工作。

    @Delete
    suspend fun delete(book: Book) // 只需在函数中添加suspend关键字,以使其与协程一起工作。
}

解释:

我们有3个函数:
selectAll(): 用于检索收藏的图书列表。
insert(): 用于插入新书。
delete(): 用于删除一本书。

为了使插入和删除与协程一起工作,为这两个函数添加suspend关键字。对于selectAll()函数,它返回一个Flow,您可以将其视为LiveData的替代品,这使我们能够观察book表的更改,当插入或删除新书时,selectAll()将在插入/删除后发出新的列表,从而允许您更新UI。请注意,selectAll()不是挂起函数,因为它返回一个Flow,Flow已经使用协程,因此不需要suspend关键字。

现在让我们创建仓库:

class FavoriteBooksRepository @Inject constructor(
    private val dao: FavoriteBooksDao
) {

    fun getAll() = dao.selectAll() // 非挂起函数

    suspend fun add(book: Book) = dao.insert(book) // 挂起函数

    suspend fun remove(book: Book) = dao.delete(book) // 挂起函数
}

解释:

现在,您需要在仓库中使用DAO实例,使用Hilt进行注入。

您的仓库中有3个函数,它们将调用DAO函数,您将add()remove()声明为挂起函数,因为您在DAO中声明了insert()delete()为挂起函数,而getAll()不是挂起函数,因为在DAO中的selectAll()中它返回一个Flow,如前所述。

最后,让我们实现ViewModel:

@HiltViewModel
class FavoriteBooksViewModel @Inject constructor(
    private val repository: FavoriteBooksRepository
): ViewModel() {

    // 这是一个可变的StateFlow,在ViewModel中将被内部使用,空列表作为初始值。
    private val _favoriteBooks = MutableStateFlow(emptyList<Book>())

    // 向UI公开的不可变StateFlow
    val favoriteBooks = _favoriteBooks.asStateFlow()

     init {
          getFavoriteBooks()
     }

    /**
     * 此函数用于从数据库中获取所有图书,并更新favoriteBooks的值。
     * 1. viewModelScope.launch用于在viewModel生命周期内启动协程。
     * 2. repository.getAll()用于从数据库中获取所有图书。
     * 3. flowOn(Dispatchers.IO)用于将flow的分发器更改为IO,这对于IO操作是最佳的,并且不会阻塞主线程。
     * 4. collect是一个用于收集图书列表流的挂起函数,并将值分配给favoriteBooks。
     * 5. 每当流发出新值时,collect函数将被调用,带有图书列表。
     */
    fun getFavoriteBooks() {
        viewModelScope.launch { //this: CoroutineScope
            repository.getAll().flowOn(Dispatchers.IO).collect { books: List<Book> ->
                _favoriteBooks.update { books }
            }
        }
    }

    /**
     * 此函数用于将图书添加到数据库。
     * 1. viewModelScope.launch用于在viewModel生命周期内启动协程。
     * 2. Dispatchers.IO用于将协程的分发器更改为IO,这对于IO操作是最佳的,并且不会阻塞主线程。
     * 3. repository.add(book)用于将图书添加到数据库。
     */
    fun addBook(book: Book) {
        viewModelScope.launch(Dispatchers.IO) { //this: CoroutineScope
            repository.add(book)
        }
    }

    /**
     * 此函数用于从数据库中删除图书。
     * 1. viewModelScope.launch用于在viewModel生命周期内启动协程。
     * 2. Dispatchers.IO用于将协程的分发器更改为IO,这对于IO操作是最佳的,并且不会阻塞主线程。
     * 3. repository.remove(book)用于从数据库中删除图书。
     */
    fun removeBook(book: Book) {
        viewModelScope.launch(Dispatchers.IO) { //this: CoroutineScope
            repository.remove(book)
        }
    }
}

解释:

现在,您需要在ViewModel中使用仓库实例,使用Hilt进行注入。

我已经添加了文件说明,分别描述了所有工作和函数,以使其尽可能清晰。还请注意,这些函数不是挂起函数,因为我们在ViewModel中启动了viewModelScope协程,这已足够,不需要将这些函数标记为挂起函数。

这就是如何将协程和Flow与您的应用程序中的Room数据库、仓库和ViewModel集成。随着您的学习,您可以在流和协程上执行更高级的操作,以使您的应用程序更强大和高效。根据应用程序的需求,您可以添加更多操作和代码,我尽量以最简单的方式表示它。

最后,感谢您花时间

英文:

Well, your question is very generic, but I will do my best to answer, I suppose you have at least a basic knowledge of coroutines, flows, and Hilt, if you don't, no problem, at least try to learn something new, I tried to make it simple as much as possible.

Scenario:

Suppose there is a simple application that displays books info to the user, the user can add any book to the favorite, delete them from favorite, and have a screen to display the favorite books.

We have a simple entity class called Book:

@Entity
data class Book(
   @PrimaryKey
   val ispn: String
   val title: String,
   val description: String,
   val pages: Int
)

Now, let's create a DAO interface with Flow and suspend functions:

@Dao
interface FavoriteBooksDao {
    @Query(&quot;SELECT * FROM book&quot;)
    fun selectAll(): Flow&lt;List&lt;Book&gt;&gt; // No need to add suspend keyword, because it returns Flow, flows already uses coroutines.

    @Insert
    suspend fun insert(book: Book) // Simply, add suspend keyword to the function, to make it work with coroutines.

    @Delete
    suspend fun delete(book: Book) // Simply, add suspend keyword to the function, to make it work with coroutines.
}

Explanation:

We have 3 functions:
selectAll(): To retrieve the list of favorite books.
insert(): To insert a new book.
delete(): To delete a book.

To make inserting and deleting work with coroutines, add the suspend keyword for both functions. For the selectAll() function, it returns a flow, you can think of it as a replacement for LiveData, this allows us to observe the changes on the book table when a new book is inserted or deleted, selectAll() will emit a new list after the insertion/deletion, allowing you to update your UI. Note that selectAll() is not a suspend function, because it returns a flow, flows already use coroutines, so we don't need suspend keyword.

Now let's create the repository:

class FavoriteBooksRepository @Inject constructor(
    private val dao: FavoriteBooksDao
) {

    fun getAll() = dao.selectAll() //Non-suspending function

    suspend fun add(book: Book) = dao.insert(book) //suspending function


    suspend fun remove(book: Book) = dao.delete(book) //suspending function

}

Explanation:

Now, you need the DAO instance in your repository, inject it using Hilt.

You have 3 functions in the repository which will call the DAO functions, you will have add() and remove() as suspend functions as you declare insert() and delete() as suspending functions in your DAO, and getAll() is not suspending as selectAll() in your DAO because it returns a flow as said earlier.

Finally, let's implement the ViewModel:

@HiltViewModel
class FavoriteBooksViewModel @Inject constructor(
    private val repository: FavoriteBooksRepository
): ViewModel() {

    // This is a mutable state flow that will be used internally in the viewmodel, empty list is given as initial value.
    private val _favoriteBooks = MutableStateFlow(emptyList&lt;Book&gt;())

    //Immutable state flow that you expose to your UI
    val favoriteBooks = _favoriteBooks.asStateFlow()

     init {
          getFavoriteBooks()
     }


    /**
     * This function is used to get all the books from the database, and update the value of favoriteBooks.
     * 1. viewModelScope.launch is used to launch a coroutine within the viewModel lifecycle.
     * 2. repository.getAll() is used to get all the books from the database.
     * 3. flowOn(Dispatchers.IO) is used to change the dispatcher of the flow to IO, which is optimal for IO operations, and does not block the main thread.
     * 4. collect is a suspending function used to collect the flow of books list, and assign the value to favoriteBooks.
     * 5. each time the flow emits a new value, the collect function will be called with the list of books.
     */
    fun getFavoriteBooks() {
        viewModelScope.launch { //this: CoroutineScope
            repository.getAll().flowOn(Dispatchers.IO).collect { books: List&lt;Book&gt; -&gt;
                _favoriteBooks.update { books }
            }
        }
    }

    /**
     * This function is used to add a book to the database.
     * 1. viewModelScope.launch is used to launch a coroutine within the viewModel lifecycle.
     * 2. Dispatchers.IO is used to change the dispatcher of the coroutine to IO, which is optimal for IO operations, and does not block the main thread.
     * 3. repository.add(book) is used to add the book to the database.
     */
    fun addBook(book: Book) {
        viewModelScope.launch(Dispatchers.IO) { //this: CoroutineScope
            repository.add(book)
        }
    }

    /**
     * This function is used to remove a book from the database.
     * 1. viewModelScope.launch is used to launch a coroutine within the viewModel lifecycle.
     * 2. Dispatchers.IO is used to change the dispatcher of the coroutine to IO, which is optimal for IO operations, and does not block the main thread.
     * 3. repository.remove(book) is used to remove the book from the database.
     */
    fun removeBook(book: Book) {
        viewModelScope.launch(Dispatchers.IO) { //this: CoroutineScope
            repository.remove(book)
        }
    }
}

Explanation:

Now, you need the Repository instance in your viewModel, inject it using Hilt.

I have added documentation that describes all the work and functions separately to make it clear as much as possible, also notice that the functions are not suspending, because we are launching a viewModelScope coroutine, that will be enough, no need to mark these functions as suspending.

That's how you integrate coroutines and flows with your application in your Room database, repository, and viewModels. You can do more advanced operations on flows and coroutines to make your application more robust and efficient, as you learn more. You can add more operations and codes depending on your application requirements, and I tried to represent it in the simplest format.

Finally, thank you for your time reading this, hope this helps you.

huangapple
  • 本文由 发表于 2023年2月24日 03:30:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/75549490.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定