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
This should be happening due to your
ViewModel
.Typically, there's one
ViewModel
perActivity
orFragment
, due to the way aViewModel
is designed to work. One of the major benefits in using aViewModel
is that it's lifecycle is completely separate from the lifecycle of yourFragment
, therefore, yourFragment
can be destroyed and recreated multiple times and you'll still be able to restore current data that's stored in yourViewModel
.Therefore, this means that with the typical code to fetch the
ViewModel
from theViewModelProviders
, you'll be fetching the exact sameViewModel
.Typically, this won't cause a problem, but in your
ViewPager
, you're reusing the sameTimesListFragment
which is most likely calling up the sameViewModel
, therefore causing each fragment to show the same data.The solution for this is to use:
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 uniqueViewModel
for eachTimesListFragment
.