Kotlin Flow 成功后仍然活跃在 fragment 中
Kotlin Flow still active in fragment after success
我有一个片段根据结果发出网络请求,我正在导航到下一个片段。
我无法返回到之前的片段,这是问题所在:https://streamable.com/4m2vzg
这是上一个片段中的代码
class EmailInputFragment :
BaseFragment<FragmentEmailInputBinding>(FragmentEmailInputBinding::inflate) {
private val viewModel by viewModels<EmailInputViewModel>()
private lateinit var progressButton: ProgressButton
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.emailToolbar.setNavigationOnClickListener {
val activity = activity as AuthActivity
activity.onSupportNavigateUp()
}
binding.emailNextButton.pbTextview.text = getString(R.string.next)
binding.emailNextButton.root.setOnClickListener {
checkValidEmail()
}
binding.enterEmail.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
checkValidEmail()
}
false
}
binding.enterEmail.doAfterTextChanged {
binding.enterEmailLayout.isErrorEnabled = false
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.emailCheck.collect {
when (it) {
State.Empty -> {
}
is State.Failed -> {
Timber.e(it.message)
progressButton.buttonFinished("Next")
}
State.Loading -> {
progressButton.buttonActivate("Loading")
}
is State.Success<*> -> {
it.data as EmailCheckModel
when (it.data.registered) {
// New User
0 -> {
findNavController().navigate(
EmailInputFragmentDirections.actionEmailFragmentToSignupFragment(
binding.enterEmail.text.toString().trim()
)
)
}
// Existing User
1 -> {
findNavController().navigate(
EmailInputFragmentDirections.actionEmailFragmentToPasswordInputFragment(
binding.enterEmail.text.toString().trim()
)
)
}
// Unverified user
2 -> {
findNavController().navigate(
EmailInputFragmentDirections.actionEmailFragmentToVerifyUserFragment(
"OK"
)
)
}
}
}
}
}
}
}
private fun checkValidEmail() {
if (!binding.enterEmail.text.toString().trim().isValidEmail()) {
binding.enterEmailLayout.error = "Please enter valid Email ID"
return
}
progressButton = ProgressButton(requireContext(), binding.emailNextButton.root)
viewModel.checkUser(binding.enterEmail.text.toString().trim())
}
}
当我从下一个片段按回时,由于状态仍然是成功,正在收集流并转到下一个片段,我已经尝试 this.cancel 在创建时取消协程但仍然没有'不工作。
我该怎么做?
将流收集移动到按钮的 onClick 会引发无法找到目的地的导航操作的错误
我使用 解决方法 成功使用
将流程状态重置回 State.EMPTY
viewModel.resetState()
在 onSuccess 中,我认为这不是最好的方法,有什么建议吗?
视图模型代码:
private val _emailCheckResponse = MutableStateFlow<State>(State.Empty)
val emailCheck: StateFlow<State> get() = _emailCheckResponse
如果你的viewModel.emailCheck
流是热流,那么你需要自己管理它的生命周期。如果不是热点Flow,那么就需要用LiveData来控制接口,而不是简单的采集Flow。需要将流转为LiveData,并在相应位置添加Observer到LiveData。
Cold Flow中没有API接口生命周期相关,但LiveData中已经管理了生命周期。
viewModel.emailCheckLiveData.observe(viewLifecycleOwner, {
when (it) {
State.Empty -> {
}
is State.Failed -> {
Timber.e(it.message)
progressButton.buttonFinished("Next")
}
State.Loading -> {
progressButton.buttonActivate("Loading")
}
is State.Success<*> -> {
it.data as EmailCheckModel
if (it.data.registered) {
val action =
EmailInputFragmentDirections.actionEmailFragmentToPasswordInputFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
} else {
val action =
EmailInputFragmentDirections.actionEmailFragmentToSignupFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
}
}
})
您需要定义emailCheckLiveData。在 Flow.asLiveData()
private val _emailCheckResponse = MutableStateFlow<State>(State.Empty)
val emailCheck: StateFlow<State> get() = _emailCheckResponse
private var mJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenResumed {
if (mJob?.isActive == true) return
mJob = _emailCheckResponse.collectLatest {
when (it) {
State.Empty -> {
}
is State.Failed -> {
Timber.e(it.message)
progressButton.buttonFinished("Next")
}
State.Loading -> {
progressButton.buttonActivate("Loading")
}
is State.Success<*> -> {
it.data as EmailCheckModel
if (it.data.registered) {
val action =
EmailInputFragmentDirections.actionEmailFragmentToPasswordInputFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
} else {
val action =
EmailInputFragmentDirections.actionEmailFragmentToSignupFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
}
}
}
}
}
}
override fun onDestroy() {
mJob?.apply {
if (isActive) cancel()
}
super.onDestroy()
}
过了一段时间,偶然发现了这篇文章。
https://proandroiddev.com/flow-livedata-what-are-they-best-use-case-lets-build-a-login-system-39315510666d
向下滚动到底部找到了我的问题的解决方案。
我有一个片段根据结果发出网络请求,我正在导航到下一个片段。
我无法返回到之前的片段,这是问题所在:https://streamable.com/4m2vzg
这是上一个片段中的代码
class EmailInputFragment :
BaseFragment<FragmentEmailInputBinding>(FragmentEmailInputBinding::inflate) {
private val viewModel by viewModels<EmailInputViewModel>()
private lateinit var progressButton: ProgressButton
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.emailToolbar.setNavigationOnClickListener {
val activity = activity as AuthActivity
activity.onSupportNavigateUp()
}
binding.emailNextButton.pbTextview.text = getString(R.string.next)
binding.emailNextButton.root.setOnClickListener {
checkValidEmail()
}
binding.enterEmail.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
checkValidEmail()
}
false
}
binding.enterEmail.doAfterTextChanged {
binding.enterEmailLayout.isErrorEnabled = false
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.emailCheck.collect {
when (it) {
State.Empty -> {
}
is State.Failed -> {
Timber.e(it.message)
progressButton.buttonFinished("Next")
}
State.Loading -> {
progressButton.buttonActivate("Loading")
}
is State.Success<*> -> {
it.data as EmailCheckModel
when (it.data.registered) {
// New User
0 -> {
findNavController().navigate(
EmailInputFragmentDirections.actionEmailFragmentToSignupFragment(
binding.enterEmail.text.toString().trim()
)
)
}
// Existing User
1 -> {
findNavController().navigate(
EmailInputFragmentDirections.actionEmailFragmentToPasswordInputFragment(
binding.enterEmail.text.toString().trim()
)
)
}
// Unverified user
2 -> {
findNavController().navigate(
EmailInputFragmentDirections.actionEmailFragmentToVerifyUserFragment(
"OK"
)
)
}
}
}
}
}
}
}
private fun checkValidEmail() {
if (!binding.enterEmail.text.toString().trim().isValidEmail()) {
binding.enterEmailLayout.error = "Please enter valid Email ID"
return
}
progressButton = ProgressButton(requireContext(), binding.emailNextButton.root)
viewModel.checkUser(binding.enterEmail.text.toString().trim())
}
}
当我从下一个片段按回时,由于状态仍然是成功,正在收集流并转到下一个片段,我已经尝试 this.cancel 在创建时取消协程但仍然没有'不工作。 我该怎么做?
将流收集移动到按钮的 onClick 会引发无法找到目的地的导航操作的错误
我使用 解决方法 成功使用
将流程状态重置回 State.EMPTYviewModel.resetState()
在 onSuccess 中,我认为这不是最好的方法,有什么建议吗?
视图模型代码:
private val _emailCheckResponse = MutableStateFlow<State>(State.Empty)
val emailCheck: StateFlow<State> get() = _emailCheckResponse
如果你的viewModel.emailCheck
流是热流,那么你需要自己管理它的生命周期。如果不是热点Flow,那么就需要用LiveData来控制接口,而不是简单的采集Flow。需要将流转为LiveData,并在相应位置添加Observer到LiveData。
Cold Flow中没有API接口生命周期相关,但LiveData中已经管理了生命周期。
viewModel.emailCheckLiveData.observe(viewLifecycleOwner, {
when (it) {
State.Empty -> {
}
is State.Failed -> {
Timber.e(it.message)
progressButton.buttonFinished("Next")
}
State.Loading -> {
progressButton.buttonActivate("Loading")
}
is State.Success<*> -> {
it.data as EmailCheckModel
if (it.data.registered) {
val action =
EmailInputFragmentDirections.actionEmailFragmentToPasswordInputFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
} else {
val action =
EmailInputFragmentDirections.actionEmailFragmentToSignupFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
}
}
})
您需要定义emailCheckLiveData。在 Flow.asLiveData()
private val _emailCheckResponse = MutableStateFlow<State>(State.Empty)
val emailCheck: StateFlow<State> get() = _emailCheckResponse
private var mJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launchWhenResumed {
if (mJob?.isActive == true) return
mJob = _emailCheckResponse.collectLatest {
when (it) {
State.Empty -> {
}
is State.Failed -> {
Timber.e(it.message)
progressButton.buttonFinished("Next")
}
State.Loading -> {
progressButton.buttonActivate("Loading")
}
is State.Success<*> -> {
it.data as EmailCheckModel
if (it.data.registered) {
val action =
EmailInputFragmentDirections.actionEmailFragmentToPasswordInputFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
} else {
val action =
EmailInputFragmentDirections.actionEmailFragmentToSignupFragment(
binding.enterEmail.text.toString().trim()
)
findNavController().navigate(action)
}
}
}
}
}
}
override fun onDestroy() {
mJob?.apply {
if (isActive) cancel()
}
super.onDestroy()
}
过了一段时间,偶然发现了这篇文章。 https://proandroiddev.com/flow-livedata-what-are-they-best-use-case-lets-build-a-login-system-39315510666d
向下滚动到底部找到了我的问题的解决方案。