Search code examples
androidandroid-fragmentskotlinmvvmandroid-architecture-navigation

How to do one time fetch operations when Fragments are recreated with ViewModel


I go to FragmentA() , after I get into FragmentA() I fetch data to my server, when this data comes I populate a List.

Now, if I go from FragmentA() to FragmentB() and from FragmentB() I press the back button or navigate back to FragmentA() , it refetch the list to the server and repopulates the list again.

I dont want this, instead , I want my viewmodel method to not fire again, I'm seeking for this help sinde Navigation Components does not let me do a .add operation to save the state of my FragmentA()

Is there anyway to do this as one time fetch operation instead of refetching each time I go from FragmentB() to FragmentA() when doing a backpress ?

FragmentA()

 private val viewModel by viewModels<LandingViewModel> {
        VMLandingFactory(
            LandingRepoImpl(
                LandingDataSource()
            )
        )
    }

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val sharedPref = requireContext().getSharedPreferences("LOCATION", Context.MODE_PRIVATE)
        val nombre = sharedPref.getString("name", null)
        location = name!!
    }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setupRecyclerView()
        fetchShops(location)
    }

 private fun fetchShops(localidad: String) {

        viewModel.setLocation(location.toLowerCase(Locale.ROOT).trim())
        viewModel.fetchShopList
            .observe(viewLifecycleOwner, Observer {

                when (it) {

                    is Resource.Loading -> {
                        showProgress()
                    }
                    is Resource.Success -> {
                        hideProgress()
                        myAdapter.setItems(it.data)
                    }
                    is Resource.Failure -> {
                        hideProgress()
                        Toast.makeText(
                            requireContext(),
                            "There was an error loading the shops.",
                            Toast.LENGTH_SHORT
                        ).show()
                    }
                }
            })

    }

Viewmodel

 private val locationQuery = MutableLiveData<String>()

    fun setLocation(location: String) {
        locationQuery.value = location
    }

    fun fetchShopList(shopId:String) = locationQuery.distinctUntilChanged().switchMap { location ->
        liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
            emit(Resource.Loading())
            try{
                emit(repo.getShopList(shopId,location))
            }catch (e:Exception){
                emit(Resource.Failure(e))
            }
        }
        }

How to fetch just once at FragmentA() keep those values inside the viewmodel and then when trying to refetch just not do it again ?

I'm desperate to know how I can make this work !

Thanks


Solution

  • The almighty google, in its latest updated best practice architecture components github repository, does like this:

        fun setId(owner: String, name: String) {
            val update = RepoId(owner, name)
            if (_repoId.value == update) {
                return
            }
            _repoId.value = update
        }
    

    This way, even when your fragment is recreated, as long as the viewmodel is alive, as it should, you will not request new data from the server, but the livedata will send its latest data again so the fragment can update its view.

    Also, are you that locationQuery.distinctUntilChanged() is not working? Are you monitoring if a request is being sent to your server?