Android 上 Kotlin Flows 存储库级别的内存缓存

Cal*_*ity 6 android repository mvvm android-jetpack-compose kotlin-flow

假设您有一个从 Android 应用程序中的远程数据源下载的用户列表,并且由于某种原因您没有本地数据库。然后,该用户列表会在整个应用程序中多次用于ViewModel发出其他网络请求,因此您肯定希望在应用程序存在期间就将其缓存,并仅根据需要重新获取它。这必然意味着您希望将其缓存在Data Layer中(Repository在我的例子中是 a ),然后从您的ViewModels 中获取它。
\n在像 a 这样的状态持有者中很容易做到ViewModel- 只需制作 aStateFlow或其他什么。但是,如果我们希望存储库内有一个可用Flow的 of List<User>(在每个 API 请求后缓存在 RAM 中),然后从 UI 层收集该存储库,该怎么办?实现这一目标最可测试、最稳定最正确的方法是什么?
\n我最初的想法是这样的:

\n
class UsersRepository @Inject constructor(\n    private val usersApi: UsersApi,\n    private val handler: ResponseHandler\n) {\n\n    private val _usersFlow = MutableStateFlow<Resource<List<UserResponse>>>(Resource.Empty)\n    val usersFlow = _usersFlow.asStateFlow()\n\n    suspend fun fetchUserList() = withContext(Dispatchers.IO) {\n        _usersFlow.emit(Resource.Loading)\n        _usersFlow.emit(\n            handler {\n                usersApi.getUsers()\n            }\n        )\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

哪里ResponseHandler

\n
class ResponseHandler {\n    suspend operator fun <T> invoke(block: suspend () -> T) = try {\n        Resource.Success(block())\n    } catch (e: Exception) {\n        Log.e(javaClass.name, e.toString())\n        val errorCode = when (e) {\n            is HttpException -> e.code()\n            is SocketTimeoutException -> ErrorCodes.SocketTimeOut.code\n            is UnknownHostException -> ErrorCodes.UnknownHost.code\n            else -> Int.MAX_VALUE\n        }\n        Resource.Error(getErrorMessage(errorCode))\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但在研究时,我发现互联网上随机有人告诉我这是错误的:

\n
\n

目前 StateFlow 本质上很热门,因此不建议在存储库中使用它\xe2\x80\x99s。对于冷流和反应流,您可以在存储库中使用 flow、channelFlow 或callbackFlow。

\n
\n

他说得对吗?如果是的话,冷流在这种情况下究竟有何帮助,我们如何正确管理它们?

\n

如果有帮助的话,我的 UI 层仅使用 Jetpack Compose 编写

\n

Vin*_*nce 6

In the official "Guide to app architecture" from Google for Android:

\n

About the source of true: \xe2\x9c\x85 The repository can contain an in-memory-cache.

\n
\n

The source of truth can be a data source\xe2\x80\x94for example, the database\xe2\x80\x94or even an in-memory cache that the repository might contain. Repositories combine different data sources and solve any potential conflicts between the data sources to update the single source of truth regularly or due to a user input event.

\n
\n

About the lifecycle: \xe2\x9c\x85 You can scope an instance of your repository to the Application class (but take care).

\n
\n

If a class contains in-memory data\xe2\x80\x94for example, a cache\xe2\x80\x94you might want\nto reuse the same instance of that class for a specific period of\ntime. This is also referred to as the lifecycle of the class instance.

\n

If the class\'s responsibility is crucial for the whole application,\nyou can scope an instance of that class to the Application class. This\nmakes it so the instance follows the application\'s lifecycle.

\n
\n

About the implementation: I recommend you to check the link directly.

\n
class NewsRepository(\n  private val newsRemoteDataSource: NewsRemoteDataSource\n) {\n    // Mutex to make writes to cached values thread-safe.\n    private val latestNewsMutex = Mutex()\n\n    // Cache of the latest news got from the network.\n    private var latestNews: List<ArticleHeadline> = emptyList()\n\n    suspend fun getLatestNews(refresh: Boolean = false): List<ArticleHeadline> {\n        if (refresh || latestNews.isEmpty()) {\n            val networkResult = newsRemoteDataSource.fetchLatestNews()\n            // Thread-safe write to latestNews\n            latestNewsMutex.withLock {\n                this.latestNews = networkResult\n            }\n        }\n\n        return latestNewsMutex.withLock { this.latestNews }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

You should read the following page, I think it will answer a lot of your questions : https://developer.android.com/topic/architecture/data-layer

\n