使用导航组件导航时如何保存片段状态

时间:2020-01-07 13:38:18

标签: android android-fragments kotlin

我正在尝试使用android体系结构组件创建一个活动应用。我有一个带有一些文本字段的片段A,当用户按下一个按钮时,我导航到片段B,在该应用使用以下代码导航回到A之后,他上传并编辑了一些图像:

findNavController().navigate(R.id.action_from_B_to_A, dataBundle)

向后导航时,B使用dataBundle将一些数据传递给A。问题在于所有文本字段都已重置,因为片段A基本上是从头开始重新创建的。我在某处读到某个Google开发人员建议您可以将视图保存到var中,而不是每次都放大视图的地方。因此尝试这样做:

private var savedViewInstance: View? = null

override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return if (savedViewInstance != null) {
        savedViewInstance
    } else {
        savedViewInstance =
                inflater.inflate(R.layout.fragment_professional_details, container, false)
        savedViewInstance
    }
}

但是这不起作用,导航回A时所有文本字段都会重置。我在做什么错?处理此类案件的正确方法是什么?

9 个答案:

答案 0 :(得分:10)

我会一一回答您的问题。

但是这不起作用,所有文本字段在向后导航时都会重置 到A。我在做什么错了?

在FragmentB中,当用户完成工作后,应用程序将调用以下方法以返回FragmentA。

findNavController().navigate(R.id.action_from_B_to_A, dataBundle)

您期望该应用程序将用户带回FragmentA,但实际结果是创建了一个新的FragmentA,并将其放在后堆栈的顶部。现在,后堆栈将是这样。

FragmentA (new instance)
FragmentB
FragmentA (old instance)

这就是为什么您看到所有文本字段都重置的原因,因为它是FragmentA的全新实例。

处理此类案件的正确方法是什么?

您要开始创建一个片段,然后从该片段接收结果,这似乎是startActivityForResult的Activity方法。

Android Dev Summit 2019 - Architecture Components 2:43 中,Android开发人员有一个问题。

导航是否可以使用类似startFragmentForResult的名称 控制器?

答案是他们正在努力,将来会提供此功能。

回到您的问题,这是我的解决方法。

步骤1:创建一个名为SharedViewModel

的类
class SharedViewModel : ViewModel() {

    // This is the data bundle from fragment B to A
    val bundleFromFragmentBToFragmentA = MutableLiveData<Bundle>()
}

步骤2:将这些代码行添加到FragmentA

private lateinit var viewModel: SharedViewModel

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class.java)
    viewModel.bundleFromFragmentBToFragmentA.observe(viewLifecycleOwner, Observer {
        // This will execute when fragment B set data for `bundleFromFragmentBToFragmentA`
        // TODO: Write your logic here to handle data sent from FragmentB
        val message = it.getString("ARGUMENT_MESSAGE", "")
        Toast.makeText(requireActivity(), message, Toast.LENGTH_SHORT).show()
    })
}

步骤3:将这些代码行添加到FragmentB

// 1. Declare this as class's variable
private lateinit var viewModel: SharedViewModel

// 2. Use the following code when you want to return FragmentA           
// findNavController().navigate(R.id.action_from_B_to_A) // Do not use this one

// Set data for `bundleFromFragmentBToFragmentA`
val data = Bundle().apply { putString("ARGUMENT_MESSAGE", "Hello from FragmentB") }
viewModel.bundleFromFragmentBToFragmentA.value = data

// Pop itself from back stack to return FragmentA
requireActivity().onBackPressed()

答案 1 :(得分:2)

您只需要做的就是将标签<fragment>更改为<androidx.fragment.app.FragmentContainerView> 建议您使用它,它会为您保存状态,您无需执行其他任何操作 并创建导航控制器,您需要在主要活动中使用此代码

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

答案 2 :(得分:1)

基本上,您需要使用ViewModel来管理生命周期。然后检查此Android Architecture Components Advanced Navigation Sample

答案 3 :(得分:1)

如果您想在弹出后保持滚动视图的位置,请确保将 ID 放在视图中,例如 NestedScrollView,即使在代码级别不需要它:

android:id="@+id/some_id"

这样当 Fragment 从 backstack 返回时,滚动位置保持不变。

答案 4 :(得分:0)

您可以使用基本片段,但这只是解决方法。实际上,导航组件仍然存在问题。这是GitHub上的issue。 示例:

define i64 @add(i64 %x, i64 %y) {
entry:
    %0 = add i64 %x, %y
    ret i64 %0
}

代码摘自:This project

答案 5 :(得分:0)

一件小事,如果您的视图没有id状态,将不会保留! 不幸的是,这种情况在Google文档中没有提到。 因此,请为您的片段视图设置ID

答案 6 :(得分:0)

您需要创建范围为导航图的ViewModel。

查看此指南,您可以保留导航组件。它非常容易实现并且对我有用! https://medium.com/sprinthub/a-step-by-step-guide-on-how-to-use-nav-graph-scoped-viewmodels-cf82de4545ed

答案 7 :(得分:0)

检查 Google 文档的 link

如果您的操作是 fragmentA -> fragmentB -> fragmentC。然后从fragmentC回到fragmentA,你想删除fragmentC,fragmentB并保持fragmentA的状态。

所以你应该:

  1. 将一个动作从 fragmentC 添加到 fragmentA
  2. 将“popUpTo”设置为fragmentA,将popUpToInclusive设置为true。

enter image description here

答案 8 :(得分:0)

在frag A中,我创建了两个全局变量

private var mRootView: ViewGroup? = null
private var mIsFirstLoad = false

在frag A的onCreateView()中,我写

_binding = FragmentDashBoardBinding.inflate(inflater, container, false)

    if (mRootView == null) {
        mRootView = _binding?.root
        mIsFirstLoad = true
    } else {
        mIsFirstLoad = false
    }
    return mRootView

在片段 A 的 onViewCreated() 中,我检查了“mIsFirstLoad”的值

if(mIsFirstLoad) {
    initAdapter()
    getMovies()
} else {
    //Continue with previously initialised variables. e.g. adapter   
}