66
votes

I have an activity with a product list fragment and many other fragments and I am trying to use architecture component navigation controller.

The problem is: it replaces the (start destination) product list fragment and I don't want the list to be reloaded when user click back button.

How to make the fragment transaction as add not replace?

7
You have to provide a bit more information about what transaction are replacing the Start Destination. Does it get replaced when you Navigate to other Activities OR is the list in the Start Destination reloaded when you navigate back to the Start Destination?Bam
Unfortunately, it seems impossible (at least with the current 2.0.0 version). If you check the androidx.navigation.fragment.FragmentNavigator#navigate method you will see that it internally uses ft.replace(mContainerId, frag);. I think the only option here is to start a new activity as a destination.Kyrylo Zapylaiev
This is terror! Today I faced an issue that WebView is always reloaded when coming back from different Fragment! And I don't see any way to prevent it.xinaiz
Anyone find any solution about this or the work around so the list position can be retained when coming back and user does not have to scroll again !Umair
@UtkuKUTLU page not foundMaster Zzzing

7 Answers

10
votes

Android navigation component just replace but you want to add fragment instead of replace like dialog you can use this but need to min. "Version 2.1.0" for navigation component.

Solution

and you can see "Dialog destinations"

5
votes

I faced the same problem, while waiting on add and other options for fragment transactions I implemented this work around to preserve the state when hitting back.

I just added a check if the binding is present then I just restore the previous state, the same with the networking call, I added a check if the data is present in view model then don't do the network refetching. After testing it works as expected.

EDIT: For the recycler view I believe it will automatically return to the same sate the list was before you navigated from the fragment but storing the position in the onSavedInstanceSate is also possible

  private lateinit var binding: FragmentSearchResultsBinding

  override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        viewModel =
            ViewModelProviders.of(this, mViewModelFactory).get(SearchResultsViewModel::class.java)
        return if (::binding.isInitialized) {
            binding.root
        } else {
            binding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_results, container, false)

            with(binding) {
               //some stuff
                root
            }
        }
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //reload only if search results are empty
        if (viewModel.searchResults.isEmpty()) {
           args.searchKey.let {
                binding.toolbarHome.title = it
                viewModel.onSearchResultRequest(it)
            }
        }
    }
5
votes

You have to override NavHostFragment's createFragmentNavigator method and return YourFragmentNavigator.

YourFragmentNavigator must override FragmentNavigator's navigate method.

Copy and paste FragmentNavigator's navigate method to your YourFragmentNavigator.

In navigate method, change the line ft.replace(mContainerId, frag); with

if (fragmentManager.fragments.size <= 0) {
    ft.replace(containerId, frag)
} else {
    ft.hide(fragmentManager.fragments[fragmentManager.fragments.size - 1])
    ft.add(containerId, frag)
}

The solution will look like this:

class YourNavHostFragment : NavHostFragment() {
override fun createFragmentNavigator(): Navigator<...> {
    return YourFragmentNavigator(...)
}}

....

class YourFragmentNavigator(...) : FragmentNavigator(...) {

override fun navigate(...){
    ....
    if (fragmentManager.fragments.size <= 0) {
        ft.replace(containerId, frag)
    } else {
        ft.hide(fragmentManager.fragments[fragmentManager.fragments.size - 1])
        ft.add(containerId, frag)
     }
     ....
}}

in your xml use YourNavHostFragment.

1
votes

I was facing the same issue but in my case I updated my code to use livedata and viewmodel. when you press back the viewmodel is not created again and thus your data is retained.

make sure you do the api call in init method of viewmodel, so that it happens only once when viewmodel is created

1
votes

just copy the FragmentNavigator's code (300 lines) and replace replace() with add(). this is the best solution for me at the moment.

@Navigator.Name("fragment")
public class CustomFragmentNavigator extends 
Navigator<...> {
    ...

    public NavDestination navigate(...) {
        ...
        ft.add(mContainerId, frag);
        ...
    }

    ...
}
0
votes

@Rainmaker is right in my opinion, I did the same thing. We can also save the recycler view position/state in onSaveInstanceState in order to return to the same recycler view position when navigating back to the list fragment.

-1
votes

After searching a bit, it's not possible, but the problem itself can be solved with viewmodel and livedata or rxjava. So fragment state is kept after transactions and my product list will not reload each time