在服务和片段之间使用 Flow to "talk"
Using Flow to "talk" between service and fragment
2年多了,我正在用android/kotlin变化来“更新”自己,天哪,变化很大吗。
场景
- 我有一个主 activity 和
MyFragment
以及一个 MyFragmentViewModel
- 我有前台服务
MyService
- 我有一个包含
Flow<MyState>
的存储库,它应该由 MyFragmentViewModel 和 MySerice 收集
基本上在过去,当我想在一个未导出的服务和主要的 activity 之间进行通信时,我使用了 LocalBroadCastReceiver
,它工作得非常好并且消除了两者之间的紧密耦合。现在它已被弃用,所以我想为什么不在存储库中有一个在它发生变化时收集的流,这样任何客户端都可以对变化做出反应。
为了简单起见,这里是一些相关的基本代码
enum class MyState{
STATE_LOADING,
STATE_NORMAL,
....
}
class MyRepository(){
//for simplicity there is no private immutable _state for now
val state:MutableStateFlow<MyState> = MutableStateFlow(MyState.STATE_NORMAL)
fun updateState(newState: MyState){
state.value = newState
}
}
class MyFragmentViewModel @Inject constructor(
private val myRepository: MyRepository
): ViewModel(){
fun updateCurrentState(){
myRepository.updateState(MyState.STATE_LOADING)
}
}
@AndroidEntryPoint
class MyService:Service(){
@Inject lateinitvar myRepository: MyRepository
private val myJob = SupervisorJob()
private val myServiceScope = CoroutineScope(Dispachers.IO+myJob)
fun listenForState(){
myServiceScope.launch{
myRepository.state.collect{
when(it)
....
}
}
}
}
发生的情况是,在启动时,MyService 中的 collect
确实获得了初始值 STATE_NORMAL,但是当我从 MyFragmentViewModel 更新 MyRepository 状态时,该服务未收到该值。
我的问题:
- 我做错了什么?是否与服务有关 scope/coroutines 以及收集的工作原理?
- 从架构上讲,这是一种好方法还是有更好的方法?
您的服务不应该与存储库通信,因为它应该属于 UI 模块,因此它必须与进一步与存储库通信的 ViewModel 通信。
你可以在这里阅读我关于 MVVM 模式的回答:
。我在这里解释了 MVVM 模式。
另外,对于您的特定用例,我建议您查看此 github - 项目:
在自述文件部分有一个 link 到 Youtube 的视频,它将深入解释如何使用 MVVM 服务。
同样在您的代码中,您使用了枚举 classes,这没有错,但由于您正在使用,您可以使用 Sealed Classes,它建立在顶部枚举并提供维护严格层次结构的方法。您的枚举 class 以密封 Class 的形式将按以下方式显示:
sealed class MyState{
object State_Loading : MyState()
object State_Normal : MyState()
}
还有关于无法更新数据的问题,建议您尝试
fun updateState(newState: MyState){
state.emit( newState)
}
如果这不起作用,您需要在数据通过的每一步使用 Log 进行调试,并知道错误发生在哪里
2年多了,我正在用android/kotlin变化来“更新”自己,天哪,变化很大吗。
场景
- 我有一个主 activity 和
MyFragment
以及一个MyFragmentViewModel
- 我有前台服务
MyService
- 我有一个包含
Flow<MyState>
的存储库,它应该由 MyFragmentViewModel 和 MySerice 收集 基本上在过去,当我想在一个未导出的服务和主要的 activity 之间进行通信时,我使用了LocalBroadCastReceiver
,它工作得非常好并且消除了两者之间的紧密耦合。现在它已被弃用,所以我想为什么不在存储库中有一个在它发生变化时收集的流,这样任何客户端都可以对变化做出反应。
为了简单起见,这里是一些相关的基本代码
enum class MyState{
STATE_LOADING,
STATE_NORMAL,
....
}
class MyRepository(){
//for simplicity there is no private immutable _state for now
val state:MutableStateFlow<MyState> = MutableStateFlow(MyState.STATE_NORMAL)
fun updateState(newState: MyState){
state.value = newState
}
}
class MyFragmentViewModel @Inject constructor(
private val myRepository: MyRepository
): ViewModel(){
fun updateCurrentState(){
myRepository.updateState(MyState.STATE_LOADING)
}
}
@AndroidEntryPoint
class MyService:Service(){
@Inject lateinitvar myRepository: MyRepository
private val myJob = SupervisorJob()
private val myServiceScope = CoroutineScope(Dispachers.IO+myJob)
fun listenForState(){
myServiceScope.launch{
myRepository.state.collect{
when(it)
....
}
}
}
}
发生的情况是,在启动时,MyService 中的 collect
确实获得了初始值 STATE_NORMAL,但是当我从 MyFragmentViewModel 更新 MyRepository 状态时,该服务未收到该值。
我的问题:
- 我做错了什么?是否与服务有关 scope/coroutines 以及收集的工作原理?
- 从架构上讲,这是一种好方法还是有更好的方法?
您的服务不应该与存储库通信,因为它应该属于 UI 模块,因此它必须与进一步与存储库通信的 ViewModel 通信。
你可以在这里阅读我关于 MVVM 模式的回答:
。我在这里解释了 MVVM 模式。
另外,对于您的特定用例,我建议您查看此 github - 项目:
在自述文件部分有一个 link 到 Youtube 的视频,它将深入解释如何使用 MVVM 服务。
同样在您的代码中,您使用了枚举 classes,这没有错,但由于您正在使用,您可以使用 Sealed Classes,它建立在顶部枚举并提供维护严格层次结构的方法。您的枚举 class 以密封 Class 的形式将按以下方式显示:
sealed class MyState{
object State_Loading : MyState()
object State_Normal : MyState()
}
还有关于无法更新数据的问题,建议您尝试
fun updateState(newState: MyState){
state.emit( newState)
}
如果这不起作用,您需要在数据通过的每一步使用 Log 进行调试,并知道错误发生在哪里