SNM
SNM

Reputation: 6795

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

Upvotes: 4

Views: 1208

Answers (3)

Florian Walther
Florian Walther

Reputation: 6971

Don't call fetchShops in onViewCreated. Instead, call it only in the init block of your ViewModel, cache the data in a field in the ViewModel and observe that value in onViewCreated. As long as your ViewModel is alive (which should be the case when Fragment A is on the backstack), it won't run the init block again.

Upvotes: 2

SNM
SNM

Reputation: 6795

Thanks to Henrique I have solved the problem in a more easy way

private var shouldFetchAgain = true

    fun fetchShopList(shopId: String, location: String) = liveData(Dispatchers.IO) {

        if (shouldFetchAgain) {
            emit(Resource.Loading())
            try {
                emit(repo.getShopList(shopId, location))
                shouldFetchAgain = false
            } catch (e: Exception) {
                emit(Resource.Failure(e))
            }
        }
    }

I know that this is not safe because I cant add up multiple observers to this , but for my use case, I just have 1 observer for this data

Upvotes: 0

Henrique Vasconcellos
Henrique Vasconcellos

Reputation: 1183

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?

Upvotes: 2

Related Questions