Eli*_*ido 9 android kotlin kotlin-stateflow kotlin-sharedflow
最近,我一直在使用 StateFlow、SharedFlow 和 Channels API,但在尝试将代码从 LiveData 迁移到表示层中的 StateFlow 时,我遇到了一个常见用例。
我面临的问题是,当我发出数据并将其收集到 viewModel 中时,我可以将值设置为 mutableStateFlow,当它最终到达片段时,它会使用 Toast 显示一些信息性消息,让用户知道是否发生错误发生了或者一切都很顺利。接下来,有一个按钮可以导航到另一个片段,但是如果我返回到已经有失败意图结果的上一个屏幕,它会再次显示 Toast。这正是我想要弄清楚的。如果我已经收集了结果并向用户显示了消息,我不想继续这样做。如果我导航到另一个屏幕并返回(当应用程序从后台返回时也会发生这种情况,它会再次收集最后一个值)。LiveData 没有发生这个问题,我只是做了完全相同的事情,公开来自存储库的流并通过 ViewModel 中的 LiveData 收集。
代码:
class SignInViewModel @Inject constructor(
private val doSignIn: SigninUseCase
) : ViewModel(){
private val _userResult = MutableStateFlow<Result<String>?>(null)
val userResult: StateFlow<Result<String>?> = _userResult.stateIn(viewModelScope, SharingStarted.Lazily, null) //Lazily since it's just one shot operation
fun authenticate(email: String, password: String) {
viewModelScope.launch {
doSignIn(LoginParams(email, password)).collect { result ->
Timber.e("I just received this $result in viewmodel")
_userResult.value = result
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后在我的片段中:
override fun onViewCreated(...){
super.onViewCreated(...)
launchAndRepeatWithViewLifecycle {
viewModel.userResult.collect { result ->
when(result) {
is Result.Success -> {
Timber.e("user with code:${result.data} logged in")
shouldShowLoading(false)
findNavController().navigate(SignInFragmentDirections.toHome())
}
is Result.Loading -> {
shouldShowLoading(true)
}
is Result.Error -> {
Timber.e("error: ${result.exception}")
if(result.exception is Failure.ApiFailure.BadRequestError){
Timber.e(result.exception.message)
shortToast("credentials don't match")
} else {
shortToast(result.exception.toString())
}
shouldShowLoading(false)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
launchAndRepeatWithViewLifecycle扩展函数:
inline fun Fragment.launchAndRepeatWithViewLifecycle(
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
crossinline block: suspend CoroutineScope.() -> Unit
) {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(minActiveState) {
block()
}
}
}
Run Code Online (Sandbox Code Playgroud)
关于为什么会发生这种情况以及如何使用 StateFlow 解决它有什么想法吗?我还尝试了使用 replay = 0 的 SharedFlow 和使用 receiveAsFlow() 的 Channels,但随后出现了其他问题。
您可以创建一个扩展函数,如下所示:
suspend fun <T> MutableStateFlow<T?>.set(value: T, idle: T? = null, delay: Long = 100) {
this.value = value
delay(delay)
this.value = idle
}
Run Code Online (Sandbox Code Playgroud)
它在发出新值后设置所需的默认值。就我而言,我使用null
作为所需的值。
在 ViewModel 内部,您可以使用扩展函数,而不是直接设置值set()
。
fun signIn() = authRepository.signIn(phoneNumber.value).onEach {
_signInState.set(it)
}.launchIn(viewModelScope)
Run Code Online (Sandbox Code Playgroud)
看来您正在寻找具有 Kotlin 流程的 SingleLiveEvent。
class MainViewModel : ViewModel() {
sealed class Event {
data class ShowSnackBar(val text: String): Event()
data class ShowToast(val text: String): Event()
}
private val eventChannel = Channel<Event>(Channel.BUFFERED)
val eventsFlow = eventChannel.receiveAsFlow()
init {
viewModelScope.launch {
eventChannel.send(Event.ShowSnackBar("Sample"))
eventChannel.send(Event.ShowToast("Toast"))
}
}
}
Run Code Online (Sandbox Code Playgroud)
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private val viewModel by viewModels<MainViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Note that I've chosen to observe in the tighter view lifecycle here.
// This will potentially recreate an observer and cancel it as the
// fragment goes from onViewCreated through to onDestroyView and possibly
// back to onViewCreated. You may wish to use the "main" lifecycle owner
// instead. If that is the case you'll need to observe in onCreate with the
// correct lifecycle.
viewModel.eventsFlow
.onEach {
when (it) {
is MainViewModel.Event.ShowSnackBar -> {}
is MainViewModel.Event.ShowToast -> {}
}
}
.flowWithLifecycle(lifecycle = viewLifecycleOwner.lifecycle, minActiveState = Lifecycle.State.STARTED)
.onEach {
// Do things
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
}
Run Code Online (Sandbox Code Playgroud)
图片来源:Michael Ferguson 撰写了一篇关于更新的库增强功能的精彩文章。建议你去经历一下。我已经复制了它的摘录。
https://proandroiddev.com/android-singleliveevent-redux-with-kotlin-flow-b755c70bb055
归档时间: |
|
查看次数: |
6668 次 |
最近记录: |