Reputation: 14791
I am developing an Android application using Kotlin. I am building a TabLayout with each tab handling a fragment. This is the layout
Each tab will be sharing the same Fragment class that has a recycler view in it. Data for the recycler view will be loaded based on the parameter passed through to the fragment via the bundle.
This is my pager adapter class to be set up with the view pager in the activity
class EventListPagerAdapter (fragmentManager: FragmentManager): FragmentPagerAdapter(fragmentManager)
{
var fragments: ArrayList<Fragment> = ArrayList<Fragment>()
init {
var currentFragment: Fragment = EventListFragment()
var currentBundle: Bundle = Bundle()
currentBundle.putInt(EventListFragment.KEY_TYPE, ApplicationController.EVENT_TYPE_CURRENT)
currentFragment.arguments = currentBundle
this.fragments.add(currentFragment)
var futureFragment: Fragment = EventListFragment()
var futureBundle: Bundle = Bundle()
futureBundle.putInt(EventListFragment.KEY_TYPE, ApplicationController.EVENT_TYPE_FUTURE)
futureFragment.arguments = futureBundle
this.fragments.add(futureFragment)
var pastFragment: Fragment = EventListFragment()
var pastBundle: Bundle = Bundle()
pastBundle.putInt(EventListFragment.KEY_TYPE, ApplicationController.EVENT_TYPE_PAST)
pastFragment.arguments = pastBundle
this.fragments.add(pastFragment)
}
override fun getItem(position: Int): Fragment {
return this.fragments.get(position)
}
override fun getCount(): Int {
return this.fragments.size
}
override fun getPageTitle(position: Int): CharSequence? {
when (position) {
0 -> return ApplicationController.instance.getString(R.string.event_list_tab_current)
1 -> return ApplicationController.instance.getString(R.string.event_list_tab_future)
}
return ApplicationController.instance.getString(R.string.event_list_tab_past)
}
}
As you can see I use the same fragment for each tab but pass the different variables through the bundle.
This is the fragment class with the recycler view implementation
class EventListFragment: Fragment()
{
@Inject
lateinit var eventService: IEventService
lateinit var eventList: ArrayList<EventModel>
lateinit var eventListAdapter: EventListAdapter
var eventType: Int? = 0
val TAG = "EVENT_LIST_FRAGMENT"
companion object{
val KEY_TYPE = "key_type"
lateinit var eventListObservable: Observable<List<EventModel>>
lateinit var eventListObserver: Observer<List<EventModel>>
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_event_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
this.initialise()
super.onViewCreated(view, savedInstanceState)
}
private fun initialise() {
ApplicationController.instance.appComponent.inject(this)
eventType = arguments?.getInt(KEY_TYPE, 0)
Log.i(TAG, "event type is $eventType")
//initialising the RxKotlin observable and observer
eventListObservable = Observable.empty()
eventListObserver = setUpEventListObserver()
eventListObservable.subscribe(eventListObserver)
//initialising the event list recycler view
eventList = ArrayList<EventModel>()
event_list_rc_list.layoutManager = GridLayoutManager(activity, 1)
eventListAdapter = EventListAdapter(eventList)
event_list_rc_list.adapter = eventListAdapter
eventService.getEvents(eventType as Int)
}
private fun setUpEventListObserver(): Observer<List<EventModel>> {
return object : Observer<List<EventModel>> {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: List<EventModel>) {
eventList.addAll(t)
eventListAdapter.notifyDataSetChanged()
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
}
}
}
As you can see my fragment class is using the Rx observable and the Rx observer. The eventService property is responsible for loading the data for the recycler view. For now I am using the fake version of the class to load the data and sending the data back to the fragment through the Rx observer.
class FakeEventService: IEventService
{
override fun getEvents(eventType: Int) {
Handler().postDelayed({
EventListFragment.eventListObserver.onNext(listOf(
EventModel(11, "Event 1"),
EventModel(12, "Event 2"),
EventModel(13, "Event 3"),
EventModel(14, "Event 4"),
EventModel(15, "Event 5"),
EventModel(16, "Event 6"),
EventModel(17, "Event 7")
))
}, 1000)
}
}
According to my code, each recycler view under each tab should be loaded with the same data. But it is not working as expected. This is what I get instead.
Current tab
There is no data loaded into the recycler view under this tab
Future tab
This the recycler view under this tab is loading the data twice:
"Event 1"
"Event 2"
"Event 3"
"Event 4"
"Event 5"
"Event 6"
"Event 7"
"Event 1"
"Event 2"
"Event 3"
"Event 4"
"Event 5"
"Event 6"
"Event 7"
Past tab
The recycler view under this tab is loaded with the right data. (This one is working as expected)
The followings are the values for event types
val EVENT_TYPE_CURRENT: Int = 1
val EVENT_TYPE_FUTURE: Int = 2
val EVENT_TYPE_PAST: Int = 3
What is wrong with my code? Why the first tab is not loading any data and why the second tab is loading the data twice?
Upvotes: 0
Views: 824
Reputation: 344
IIRC in a fragment adapter, the fragment at the current position and the next fragment are instantiated, one after the other.
In your fragment implementation:
lateinit var eventListObservable: Observable<List<EventModel>>
lateinit var eventListObserver: Observer<List<EventModel>>
are contained in the companion object. They are linked to the class and not the instance.
When the future fragment is created, it will setup the observables and this will overwrite the values for the current fragment instance.
What I would suggest:
class FakeEventService: IEventService{
private val observableCurrent = Observable.just(listOf(
EventModel(11, "Event Current 1"),
EventModel(12, "Event Current 2"),
EventModel(13, "Event ... 3"),
EventModel(14, "Event 4"),
EventModel(15, "Event 5"),
EventModel(16, "Event 6"),
EventModel(17, "Event 7")
).delay(1, TimeUnit.SECOND)
)
private val observableFuture = Observable.just(listOf(
EventModel(11, "Event Future 1"),
EventModel(12, "Event Future 2"),
EventModel(13, "Event Future 3"),
EventModel(14, "Event ... 4"),
EventModel(15, "Event 5"),
EventModel(16, "Event 6"),
EventModel(17, "Event 7")
).delay(1, TimeUnit.SECOND)
)
override fun getEvents(eventType: Int): Observable<List<EventModel>> {
return when(eventType){
EventType.CURRENT -> observableCurrent
EventType.FUTURE -> observableFuture
}
}
Then in your fragment you can remove your observable code and do
var disposableSubscription: Disposable? = null
override fun onStart(){
super.onStart()
//Please add an onError listener if the call could fail.
disposableSubscription = eventService.getEvents(eventType).subscribe{ events ->
eventList.addAll(t)
eventListAdapter.notifyDataSetChanged()
}
}
override onStop(){
super.onStop()
disposableSubscription?.dispose()
disposableSubscription = null
}
Upvotes: 1