Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewPager with viewmodel and live data , all 6 tabs data is replaced by last tab data

I am working on a ViewPager with 6 tabs where it has only one fragment TimesListFragment

Depending on the arguments passed to TimesListFragment it calls api eg; science , technology, travel etc

I have followed Google's GithubBrowserSample for my app

I have TimesListFragment -> TimesViewModel -> TimesRepository

There are 6 tabs , when I hit the api all the tabs show the same result which if of the last argument

StoriesPagerAdapter.kt

class StoriesPagerAdapter(fragmentManager: FragmentManager?)
    :FragmentStatePagerAdapter(fragmentManager){
    private val sections= arrayListOf("science","technology","business","world","movies","travel")
    override fun getItem(position: Int): Fragment {
        return TimesListFragment.newInstance(sections[position])
    }
    override fun getCount(): Int {
        return sections.size
    }
    override fun getPageTitle(position: Int): CharSequence? {
        return sections[position]
    }
}

issue : all tabs shows data of travel arguments

TimesViewModel

class TimesViewModel @Inject constructor(private val timesRepository: TimesRepository) :
        ViewModel() {
    lateinit var data: LiveData<Resource<TimesStoriesResponse>>
    fun fetchStories(section:String): LiveData<Resource<TimesStoriesResponse>> {
        data = timesRepository.loadStories(section)
        return data
    }
}

TimesRepository.kt

class TimesRepository @Inject constructor(private val apiService: ApiService,
                                          private val timesDao: TimesDao,
                                          private val appExecutors: AppExecutors) {

    private val repoListRateLimit = RateLimiter<String>(10, TimeUnit.MINUTES)

    fun loadStories(section:String): LiveData<Resource<TimesStoriesResponse>> {
        return object : NetworkBoundResource<TimesStoriesResponse, TimesStoriesResponse>(appExecutors) {
            override fun saveCallResult(item: TimesStoriesResponse) {
                timesDao.insert(item)
            }

            override fun shouldFetch(data: TimesStoriesResponse?): Boolean {
                return data == null  || repoListRateLimit.shouldFetch(section)
            }

            override fun loadFromDb() = timesDao.load()

            override fun createCall() = apiService.getTopStories(section,ConfigConstant.TIMES_KEY)

            override fun onFetchFailed() {
                repoListRateLimit.reset(section)
            }
        }.asLiveData()
    }

ApiService.kt

interface ApiService {
    @GET("svc/topstories/v2/{section}.json?")
    fun getTopStories(@Path ("section") section:String,@Query("api-key") apiKey:String)
            :LiveData<ApiResponse<TimesStoriesResponse>>
}

TimesListFragment.kt

private fun initViewModel() {

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(section,TimesViewModel::class.java)

    }

    private fun initData() {
        viewModel?.fetchStories(section)?.observe(this, Observer {

            when (it?.status) {

                Status.LOADING -> {
                    showLoading(true)
                    showError(false,null)
                }


                Status.SUCCESS -> {
                    showLoading(false)
                    showError(false,null)
                    showSuccessData(it.data)
                }

                Status.ERROR -> {
                    showLoading(false)
                    showError(true,it.message)
                }

            }
        })
    }

note : both methods are called in onViewCreated() of TimesListFragmnet

like image 526
karthik kolanji Avatar asked Feb 25 '19 08:02

karthik kolanji


People also ask

How do I update my Android ViewPager adapter?

Firstly set viewpager adapter to null then recreate the adapter and set i to it to viewpager. Show activity on this post. Define which message which is needed to update. Write below code in the Fragment which is needed update.

Can I use ViewPager with views not with fragments?

yes...you can use View instead of Fragment in viewpager.

What is ViewPager used for?

Layout manager that allows the user to flip left and right through pages of data. You supply an implementation of a PagerAdapter to generate the pages that the view shows. ViewPager is most often used in conjunction with android.

How do I use TabLayout with ViewPager?

Tab layout are visible below toolbar with View pager, used to create swipeable views . Tabs are designed to work with fragments. Use them to swipe fragments in view pager.


2 Answers

This should be happening due to your ViewModel.

Typically, there's one ViewModel per Activity or Fragment, due to the way a ViewModel is designed to work. One of the major benefits in using a ViewModel is that it's lifecycle is completely separate from the lifecycle of your Fragment, therefore, your Fragment can be destroyed and recreated multiple times and you'll still be able to restore current data that's stored in your ViewModel.

Therefore, this means that with the typical code to fetch the ViewModel from the ViewModelProviders, you'll be fetching the exact same ViewModel.

Typically, this won't cause a problem, but in your ViewPager, you're reusing the same TimesListFragment which is most likely calling up the same ViewModel, therefore causing each fragment to show the same data.

The solution for this is to use:

ViewModelProviders.of(this).get(KEY, TimesViewModel::class.java)

Note the KEY which is used to differentiate between the ViewModels that needs to be fetched. So by using the positions in the ViewPager as a unique key, you should be able to have a unique ViewModel for each TimesListFragment.

like image 179
Jackey Avatar answered Sep 24 '22 17:09

Jackey


You need to tell the ViewPager not to load pages that are currently not showing. By default it loads at least one page to the left and one to the right, I think.

like image 20
VladimirVip Avatar answered Sep 22 '22 17:09

VladimirVip