Save fragment state while navigating with navigation component

Problem: Textfields Reset When Navigating Back to Fragment A

I’m trying to create a single activity app using android architecture components. I’m facing an issue with Fragment A, which has some textfields. When user pushes a button, the app navigates to Fragment B, where the user uploads and edits some images. After that, the app navigates back to A using the following code:

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

When navigating back, Fragment B passes some data to A using dataBundle. The problem is that all the textfields in Fragment A reset because the fragment is recreated from scratch. I read somewhere that a developer from google suggests that you can save the view in a variable instead of inflating it everytime. So I tried doing that:

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
    }
}

But this does not work, and the textfields still reset when navigating back to Fragment A. What am I doing wrong? What is the proper way of handling cases like this?

The issue is that Fragment A is being recreated every time it is navigated back to, causing the textfields to reset. Saving the view in a variable using onCreateView will not solve this problem.

To properly handle cases like this, you can save the data entered in the textfields in Fragment A in a ViewModel. Then, when navigating back to Fragment A, you can retrieve the data from the ViewModel and set it back in the textfields.

Here’s an example of how to do this:

  1. Create a ViewModel for Fragment A:
class FragmentAViewModel : ViewModel() {
    var textField1Value: String = ""
    var textField2Value: String = ""
    // add more textfield values as needed
}
  1. In Fragment A, create an instance of the ViewModel and save the textfield values in it when they are changed:
class FragmentA : Fragment() {

    private lateinit var viewModel: FragmentAViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewModel = ViewModelProviders.of(this).get(FragmentAViewModel::class.java)
        // inflate view and set textfield values from viewModel
        return view
    }

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

        // set up textfields to save values to viewModel on change
        textField1.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                viewModel.textField1Value = s.toString()
            }
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
        })

        textField2.addTextChangedListener(object : TextWatcher {
            override fun afterTextChanged(s: Editable?) {
                viewModel.textField2Value = s.toString()
            }
            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
        })

        // add more textfields as needed
    }
}
  1. In Fragment B, pass the data back to Fragment A using the ViewModel:
class FragmentB : Fragment() {

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

        // when user is done editing images, navigate back to Fragment A and pass data
        val dataBundle = Bundle()
        dataBundle.putString("textField1Value", viewModel.textField1Value)
        dataBundle.putString("textField2Value", viewModel.textField2Value)
        // add more textfield values as needed

        findNavController().navigate(R.id.action_from_B_to_A, dataBundle)
    }
}
  1. In Fragment A, retrieve the data from the ViewModel and set it back in the textfields:
class FragmentA : Fragment() {

    private lateinit var viewModel: FragmentAViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewModel = ViewModelProviders.of(this).get(FragmentAViewModel::class.java)
        // inflate view and set textfield values from viewModel
        textField1.setText(viewModel.textField1Value)
        textField2.setText(viewModel.textField2Value)
        // add more textfields as needed

        return view
    }
}

With this approach, the textfield values in Fragment A will be preserved even when navigating back to it from Fragment B.