0

I have an Activity, where I have a fragment, in which I have referenced a Navigation Graph component:

activity XML:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.fragment.app.FragmentContainerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_fragment"
        app:defaultNavHost="true"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:id="@+id/fragment_main" />
</LinearLayout>

Navigation graph:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_fragment"
    app:startDestination="@id/countryPickerFragment">

    <fragment
        android:id="@+id/countryPickerFragment"
        android:name="me.sparker0i.ottcontent.view.composer.countrypicker.CountryPickerFragment"
        android:label="Pick a Country"
        tools:layout="@layout/fragment_country_picker">
        <action android:id="@+id/countryPickerFragmentToPlatformPickerFragment"
            app:destination="@+id/platformPickerFragment">
            <argument
                android:name="countryValue"
                app:argType="string" />
        </action>
    </fragment>
    <fragment
        android:id="@+id/platformPickerFragment"
        android:name="me.sparker0i.ottcontent.view.composer.platformpicker.PlatformPickerFragment"
        android:label="Pick a Platform"
        tools:layout="@layout/fragment_platform_picker">
        <argument
            android:name="countryValue"
            app:argType="string" />
    </fragment>
</navigation>

Activity Code:

class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setSupportActionBar(toolbar)

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

        NavigationUI.setupActionBarWithNavController(this, navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        return NavigationUI.navigateUp(navController, null)
    }
}

In the first fragment that opens up (CountryPickerFragment), I have code which observes a value from a MutableLiveData inside a ViewModel. When the value is observed, it opens up the 2nd fragment, navigated using the Navigation Library. The relevant code for the CountryPickerFragment is below:

class CountryPickerFragment : Fragment(), KodeinAware, CoroutineScope {
    override val kodein by closestKodein()

    private lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

    private var mutableCountries = MutableLiveData<List<Country>>()

    private val viewModelFactory: ContentViewModelFactory by instance()
    private lateinit var viewModel: ContentViewModel
    private lateinit var selectionTracker: SelectionTracker<Long?>

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_country_picker, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(this, viewModelFactory).get(ContentViewModel::class.java)

        viewModel.countryValue.observeForever{value ->
            val action = CountryPickerFragmentDirections.countryPickerFragmentToPlatformPickerFragment(value)
            requireView().findNavController().navigate(action)
        }
    }
}

Here's the second fragment as well:

class PlatformPickerFragment : Fragment(), KodeinAware, CoroutineScope {
    override val kodein by closestKodein()

    private lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        job = Job()
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

    val args: PlatformPickerFragmentArgs by navArgs()

    private val viewModelFactory: ContentViewModelFactory by instance()
    private lateinit var viewModel: ContentViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_platform_picker, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProvider(this, viewModelFactory).get(ContentViewModel::class.java)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val countryValue = args.countryValue
        Log.i("Country Value", countryValue)
    }
}

While this piece of code does open up the Second fragment within the same activity and also shows up the Country Value inside Logcat, it also brings a Back button on the Action Bar. When I try to press that back button, it does not go back to the previous fragment, instead it puts up yet another Country Value inside the Logcat. Pressing the back button on the phone's navigation bar also does the same.

How do I go back to the previous fragment (CountryPickerFragment) from the newly spawned fragment (PlatformPickerFragment)?

PS. If anyone wants to have a look at the code, you may go here

Sparker0i
  • 1,787
  • 4
  • 35
  • 60
  • Went through your code. I think the navigation works correctly, the issue is with how you observe your liveData. Try debugging your app with a breakpoint on line 62 in your `CountryPickerFragment`, you will see that every time you hit "navigate up" from `PlatformPickerFragment`, the code on line 62 is executed, meaning the navigation does try to bring you back to `CountryPickerFragment`, but as soon as it does that, the fragment's liveData fire up, including the logic to bring you again to the `PlatformPickerFragment`. It happens fast, that's why it seems like the back button does nothing. – Dat Pham Tat Aug 13 '20 at 16:52
  • As a side note, you should never be using `observeForever` in a Fragment - use `observe(viewLifecycleOwner)` when observing from `onActivityCreated()`. – ianhanniballake Aug 13 '20 at 17:17
  • @DatPhamTat thanks for that. Yeah I do notice that it does try to come back to the `CountryPickerFragment`. As a side note, I did try to use `observe` instead of `observeForever` inside my Fragment as suggested by @ianhanniballake, sadly that didn't work either. It is showing same behaviour – Sparker0i Aug 13 '20 at 18:04
  • Cant add answer as someone closed the issue. I did come up with a temporary fix: === viewModel.countryValue.removeObservers(this@CountryPickerFragment.viewLifecycleOwner) viewModel.countryValue.observe(this@CountryPickerFragment.viewLifecycleOwner, Observer {value -> if (value != null) { // navigate to PlatformPickerFragment viewModel.countryValue.postValue(null) } }) === It worked for me, try to understand the logic yourself. In general though, you should try to remove all the `observeForever`s. – Dat Pham Tat Aug 13 '20 at 18:47
  • PS. Change from `observeForever` to `observer` did nothing. I had to modify my viewModel value to become nullable. That fixed my problem. – Sparker0i Aug 16 '20 at 18:07

0 Answers0