How to save fragment state while navigating with navigation component

后端 未结 6 995
既然无缘
既然无缘 2021-01-02 14:24

I\'m trying to create a single activity app using android architecture components. I have a fragment A which has some textfields, when user pushes a button I navigate to fra

相关标签:
6条回答
  • 2021-01-02 14:43

    I will answer your questions one by one.

    But this does not work, all the textfields reset when navigating back to A. What am I doing wrong?

    From FragmentB, when users finish their work and the app call the below method to return FragmentA.

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

    You expected that the app will bring users back to FragmentA, but the actual result is a new FragmentA is created and put on the top of the back stack. Now the back stack will be like this.

    FragmentA (new instance)
    FragmentB
    FragmentA (old instance)
    

    That why you see all textfields reset, because it is a totally new instance of FragmentA.

    What is proper way of handling cases like this?

    You want to start a fragment, then receive result form that fragment, it seems like startActivityForResult method of Activity.

    In Android Dev Summit 2019 - Architecture Components, at 2:43, there is a question for Android developers.

    Can we have something like startFragmentForResult for the Navigation Controller?

    The answer is they are working on it, and this feature will be available in future.

    Back to your problem, here is my solution.

    Step 1: Create a class called SharedViewModel

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

    Step 2: Add these lines of code to 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()
        })
    }
    

    Step 3: Add these lines of code to 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()
    
    0 讨论(0)
  • 2021-01-02 14:47

    Just a little thing , if your views doesn't have id state will not kept! This case is not mentioned in Google docs unfortunately. so , SET ID TO YOUR VIEWS IN FRAGMENT

    0 讨论(0)
  • 2021-01-02 14:49

    Basically you need to use ViewModel for manage lifecycle. Then after check this Android Architecture Components Advanced Navigation Sample

    0 讨论(0)
  • 2021-01-02 14:51

    You can use base fragment, but it's just workaround. Actually, navigation component is still buggy. Here is an issue on GitHub. Example:

    class SearchFragment : BaseBottomTabFragment() {
    
        private var _binding: FragmentSearchBinding? = null
        private val binding get() = _binding!!
    
        override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
        ): View? {
            // Inflate the layout for this fragment
            _binding = FragmentSearchBinding.inflate(inflater, container, false)
            return binding.root
        }
    
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            binding.buttonDynamicTitleNavigate.setOnClickListener {
                navigateWithAction(
                    SearchFragmentDirections.actionSearchFragmentToDynamicTitleFragment(
                        binding.editTextTitle.text.toString()
                    )
                )
            }
        }
    
        override fun onDestroyView() {
            super.onDestroyView()
            _binding = null
        }
    }
    

    Code snipped from: This project

    0 讨论(0)
  • 2021-01-02 14:53

    Only thing you need to do is to change from tag <fragment> to <androidx.fragment.app.FragmentContainerView> it is recommended and will save the state for you and you won't need to do anything else and to create navigation controller you need this code in main activity

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val navController = navHostFragment.navController
    
    0 讨论(0)
  • 2021-01-02 14:53

    You need create ViewModels scoped to a Navigation Graph.

    Check this nice guide and you can keep your navigation component. Its very easy to implement and works for me!! https://medium.com/sprinthub/a-step-by-step-guide-on-how-to-use-nav-graph-scoped-viewmodels-cf82de4545ed

    0 讨论(0)
提交回复
热议问题