【问题标题】:How to implement back button handling on Android with Redux如何使用 Redux 在 Android 上实现后退按钮处理
【发布时间】:2019-12-19 01:29:41
【问题描述】:

我正在尝试使用 CoRedux 为我的 Redux 商店在 Android 上实现后退按钮处理。我确实找到了一种方法来做到这一点,但我希望有一个更优雅的解决方案,因为我的似乎是一个 hack。

问题

问题的核心是返回到 Android Fragment 与第一次渲染该 Fragment 不同。

当用户第一次访问 Fragment 时,我将它与 FragmentManager 作为事务渲染,为“主”屏幕添加一个返回堆栈条目

fragmentManager?.beginTransaction()
   ?.add(R.id.myFragmentContainer, MyFragment1())
   ?.addToBackStack("main")?.commit()

当用户从另一个fragment返回到那个fragment时,返回的方式是pop back stack:

fragmentManager?.popBackStack()

这似乎与 Redux 原则相冲突,其中状态应该足以呈现 UI,但在这种情况下,状态路径也很重要。

破解解决方案

我希望有人可以改进这个解决方案,但我通过引入一些驻留在 Redux 之外的状态(一个名为 skipRendering 的布尔值)设法解决了这个问题。也许你可以称这种“短暂”状态。初始化为 false,skipRendering 在用户点击返回按钮时设置为 true:

fun popBackStack() {
    fragmentManager?.popBackStack()
    mapViewModel.dispatchAction(MapViewModel.ReduxAction.BackButton)
    skipRendering = true
}

将后退按钮分派到 redux 存储区会将 redux 状态回退到之前的状态,如下所示:

return when (action) {
// ...
    ReduxAction.BackButton -> {
        state.pastState
            ?: throw IllegalStateException("More back taps processed than past state frames")
    }
}

不管怎样,只要用户请求访问用户随后可以从其中返回的片段,reducer 就会填充pastState

return when (action) {
// ...
    ReduxAction.ShowMyFragment1 -> {
        state.copy(pastState = state, screenDisplayed = C)
    }
}

最后,如果skipRendering,渲染会跳过处理,因为调用fragmentManager?.popBackStack() 的必要工作是在调​​度BackButton 操作之前处理的。

我怀疑有更好的解决方案使用 Redux 构造,例如副作用。但我一直在想办法更优雅地解决这个问题。

【问题讨论】:

  • 对 redux 不太确定,但你可以通过调用 override fun onBackPressed(){ //code what you want to do here } 覆盖 Android Kotlin 上的后退按钮活跃
  • 感谢@RowanBerry,这实际上是我已经在做的。 Activity 覆盖onBackPressed() 并调用上面的Fragment 的popBackStack() 函数。抱歉,我没有澄清这一点。

标签: android redux android-architecture-components


【解决方案1】:

为了解决这个问题,我决定接受无法直接解决冲突。冲突发生在 Redux 和 Android 的原生后退按钮处理之间,因为 Redux 需要掌握状态,但 Android 持有返回堆栈信息。认识到这两者不能很好地混合,我决定放弃 Android 的后台堆栈处理并完全在我的 Redux 商店中实现它。

data class LLReduxState(
    // ... 
    val screenBackStack: List<ScreenDisplayed> = listOf(ScreenDisplayed.MainScreen)
)

sealed class ScreenDisplayed {
    object MainScreen : ScreenDisplayed()
    object AScreen : ScreenDisplayed()
    object BScreen : ScreenDisplayed()
    object CScreen : ScreenDisplayed()
}

减速器的外观如下:

private fun reducer(state: LLReduxState, action: ReduxAction): LLReduxState {
    return when (action) {
        // ...

        ReduxAction.BackButton -> {
            state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
                it.addAll(state.screenBackStack)
                it.removeAt(0)
            })
        }

        ReduxAction.AButton -> {
            state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
                it.add(ScreenDisplayed.AScreen)
                it.addAll(state.screenBackStack)
            })
        }

        ReduxAction.BButton -> {
            state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
                it.add(ScreenDisplayed.BScreen)
                it.addAll(state.screenBackStack)
            })
        }

        ReduxAction.CButton -> {
            state.copy(screenBackStack = mutableListOf<ScreenDisplayed>().also {
                it.add(ScreenDisplayed.CScreen)
                it.addAll(state.screenBackStack)
            })
        }
    }
}

在我的片段中,当 Activity 的 onBackPressed() 被操作系统调用时,Activity 可以调用我公开的这个 API:

fun popBackStack() {
    mapViewModel.dispatchAction(MapViewModel.ReduxAction.BackButton)
}

最后,Fragment 呈现如下:

private fun render(state: LLReduxState) {
    // ...


    if (ScreenDisplayed.AScreen == state.screenBackStack[0]) {
        fragmentManager?.beginTransaction()
            ?.replace(R.id.llNavigationFragmentContainer, AFragment())
            ?.commit()
    }

    if (ScreenDisplayed.BScreen == state.screenBackStack[0]) {
        fragmentManager?.beginTransaction()
            ?.replace(R.id.llNavigationFragmentContainer, BFragment())
            ?.commit()
    }

    if (ScreenDisplayed.CScreen == state.screenBackStack[0]) {
        fragmentManager?.beginTransaction()
            ?.replace(R.id.llNavigationFragmentContainer, CFragment())
            ?.commit()
    }
}

此解决方案非常适合处理后退按钮,因为它以应有的方式应用 Redux。作为证据,我能够编写自动化测试,通过将初始状态设置为具有最深回栈的状态来模拟回栈,如下所示:

        LLReduxState(
            screenBackStack = listOf(
                ScreenDisplayed.CScreen,
                ScreenDisplayed.BScreen,
                ScreenDisplayed.AScreen,
                ScreenDisplayed.MainScreen
            )
        )

我遗漏了一些特定于 CoRedux 的细节。

【讨论】:

  • 我现在急切地等待当您在 FragmentB 或 FragmentC 之一上测试 stackoverflow.com/questions/49046773/… 时您的应用会发生什么
  • 希望 ViewModel 的使用能够保护我们免受此类配置更改的影响。虽然我不确定从后台返回是否被视为配置更改。
  • 这不是配置更改,这是完全不同的机制。
猜你喜欢
  • 2011-10-26
  • 2013-04-22
  • 1970-01-01
  • 2012-04-23
  • 1970-01-01
  • 2012-06-06
  • 2013-05-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多