3

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

SNM
  • 5,625
  • 9
  • 28
  • 77
  • 1
    Fragments can use the Activity scope and share a ViewModel like described [here](https://developer.android.com/topic/libraries/architecture/viewmodel#sharing) – Bö macht Blau May 12 '20 at 18:00
  • `fetchShopList` variable runs once if i understood correctly, so it will fetch only once by definition at the instantiation of ViewModel. – Animesh Sahu May 12 '20 at 18:01
  • but, how do I clear that only viewmodel when poping just FragmentA() ? because if I cant clear it, the viewmodel will be alive untill I kill the activity containing that fragment @BömachtBlau – SNM May 12 '20 at 18:02
  • There a post on how to [manually clear a ViewModel](https://stackoverflow.com/questions/53653157/manually-clearing-an-android-viewmodel) – Bö macht Blau May 12 '20 at 18:07
  • yes but that clears all the viewmodels not just one – SNM May 12 '20 at 18:15

3 Answers3

2

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?

Community
  • 1
  • 1
Henrique Vasconcellos
  • 1,144
  • 1
  • 8
  • 13
  • But, the viewmodel instance will die when the fragment is destroyed right ? because if that happends when going from fragmentA to fragmentB the viewmodel will be destroyed too – SNM May 12 '20 at 18:52
  • it is not going to be destroyed if you are simple navigating from A to B, fragment A will be held in the backstack. You can easily try it with a couple of Logs in your code to see if it is requesting again. – Henrique Vasconcellos May 12 '20 at 18:54
  • so, when I backpress into FragmentA() now it will be poped from the backstack and also the viewmodel will be cleared right ? – SNM May 12 '20 at 18:55
  • I saw in that link something about AbsentLiveData, that data class is not a normal data class right ? – SNM May 12 '20 at 18:55
  • when you pressback, the viewmodel will be the same as before, retaining the state/data that it previously held. So your fragment will recreate its view, request the data again, the livedata will provide without requesting to the server. – Henrique Vasconcellos May 12 '20 at 18:56
  • "now it will be poped from the backstack and also the viewmodel will be cleared right ?" yes. – Henrique Vasconcellos May 12 '20 at 18:57
  • if you navigate from B to A, instead of a back, it will create another instance of fragment A. For that, I believe you can accomplish a never request again using a shared view model, using an activity instead of the fragment A as a reference. – Henrique Vasconcellos May 12 '20 at 19:00
  • i guess this won't work if the fragment is used in a BottomNavigationView? because it recreates the fragments. – chitgoks Jan 22 '21 at 05:53
1

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.

Florian Walther
  • 6,237
  • 5
  • 46
  • 104
0

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

SNM
  • 5,625
  • 9
  • 28
  • 77