Marcos
Marcos

Reputation: 13

Kotlin with navigation - how to keep data in previous fragment

I'm using a navigation graph and my fragment A has a RecylerView. When I go to fragment B I don't want to lose the data from fragment A when I back there. How I can do this?

Upvotes: 1

Views: 1651

Answers (1)

iknow
iknow

Reputation: 9852

Step by step solution. Create a navigation graph and save ListView content in a fragment after changing fragment using ViewModel:

  1. To build.gradle (Module: app) add this:
dependencies { 
    ...

    def nav_version = "2.3.0"
    implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
    implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

    def lifecycle_version = "2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
}

And

android {
    ...

    kotlinOptions {
        jvmTarget = "1.8"
    }
}
  1. Create two fragments (FragemntSecond can be empty):

FragemntStart:

<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start Fragment"
        />

    <Button
        android:id="@+id/butGoTo2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Go To Second"
        />

    <Button
        android:id="@+id/butAddRandomValue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Add random value"
        />

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

</androidx.appcompat.widget.LinearLayoutCompat>
  1. Create navigation graph, add both fragments and connect them on graph. To MainActivity add FragmentContainerView:
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/navigation"
    />
  1. Create a ViewModel class:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class CommonViewModel : ViewModel()
{
    // This list will keep all values for Your ListView
    private val _list: MutableLiveData<ArrayList<String>> = MutableLiveData()
    val list: LiveData<ArrayList<String>>
        get() = _list

    init
    {
        _list.value = ArrayList()
    }

    // function which will add new values to Your list
    fun addNewValue()
    {
        _list.value!!.add((0..100).random().toString())
        _list.value = _list.value //this will notify observers
    }
}
  1. Add ViewModel to StartFragment:
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation
import kotlinx.android.synthetic.main.fragment_start.*

class StartFragment : Fragment()
{
    private lateinit var viewModel: CommonViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View?
    {
        viewModel = ViewModelProvider(requireActivity()).get(CommonViewModel::class.java) // init view model
        return inflater.inflate(R.layout.fragment_start, container, false)
    }

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

        butGoTo2.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_startFragment_to_secondFragment)) // set clickListener to navigate to second fragment
        butAddRandomValue.setOnClickListener { viewModel.addNewValue() } // listener for second button to add new value to ListView

        // set up observer. Every time You add sth to list, ListView will be updated
        viewModel.list.observe(viewLifecycleOwner, {
            listView.adapter = ArrayAdapter(
                requireContext(),
                android.R.layout.simple_list_item_1,
                it
            )
        })
    }
}

Now You can go to the second fragment and when You return to Start You won't lose any data. This is a simple example of how You can use ViewModel to save data in ListView. When You are using RecyclerView You can create an adapter that extends ListAdapter and in the observer, You just have to write adapter.submitList(it).


If You want to access data in the second fragment (e.g. this list) from ViewModel You can do it like this:

class SecondFragment : Fragment()
{
    private val viewModel: CommonViewModel by activityViewModels() // retrieve the same view model
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View?
    {
        Log.d("MyTag", "${viewModel.list.value}") // here You can access list which is used in StartFragment 
        return inflater.inflate(R.layout.fragment_second, container, false)
    }
}

Upvotes: 1

Related Questions