The callback method onPageSelected receives a position greater than the list of elements.
Fatal Exception: java.lang.IndexOutOfBoundsException: Index: 4, Size: 4
at java.util.ArrayList.get(ArrayList.java:437)
at com.example.ui.quiz.info.InfoViewModel$init$1.onPageSelected(InfoViewModel.java:47)
at com.github.islamkhsh.viewpager2.CompositeOnPageChangeCallback.onPageSelected(CompositeOnPageChangeCallback.java:72)
at com.github.islamkhsh.viewpager2.CompositeOnPageChangeCallback.onPageSelected(CompositeOnPageChangeCallback.java:72)
at com.github.islamkhsh.viewpager2.ScrollEventAdapter.dispatchSelected(ScrollEventAdapter.java:386)
at com.github.islamkhsh.viewpager2.ScrollEventAdapter.onScrolled(ScrollEventAdapter.java:176)
at androidx.recyclerview.widget.RecyclerView.dispatchOnScrolled(RecyclerView.java:5173)
at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:5338)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:988)
at android.view.Choreographer.doCallbacks(Choreographer.java:765)
at android.view.Choreographer.doFrame(Choreographer.java:697)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:967)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7156)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)
class PrizeAdapter1 @Inject constructor() : CardSliderAdapter<PrizeAdapter1.PrizeViewHolder>() {
val items = ArrayList<Prize>()
private val backgrounds = arrayListOf(
R.drawable.frame_1,
R.drawable.frame_2,
R.drawable.frame_3,
R.drawable.frame_3
)
override fun bindVH(holder: PrizeViewHolder, position: Int) {
holder.bind(items[position], backgrounds[position % backgrounds.size])
}
override fun getItemCount(): Int = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PrizeViewHolder {
return PrizeViewHolder(
ViewholderQuizPrize1Binding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
class PrizeViewHolder(val binding: ViewholderQuizPrize1Binding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Prize, background: Int) {
binding.item = item
binding.ivBackground.setImageResource(background)
}
}
}
@PerController
class InfoViewModel @Inject constructor() : BaseViewModel<InfoCallback>() {
@Inject
lateinit var mPrizesAdapter: PrizeAdapter1
@Inject
lateinit var mCategoriesAdapter: CategoryAdapter
@Inject
lateinit var mTopMembersAdapter: TopMemberAdapter
@Inject
lateinit var mHistoryAdapter: HistoryAdapter
lateinit var onPrizeChangeListener: ViewPager2.OnPageChangeCallback
val shouldShowHistory = ObservableBoolean(true)
val havePointsText = ObservableField<String>()
val fromPointsPerDayText = ObservableField<String>()
val shouldShowContent = ObservableBoolean(false)
var isDirty = false
var mLoadGameStateDisposable: Disposable? = null
override fun init(args: Bundle) {
onPrizeChangeListener = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
if (isDirty) {
val prize = mPrizesAdapter.items[position]
loadGameState(prize.index)
if (prize.index == 0 || prize.index == 1)
fromPointsPerDayText.set(
mContext.getString(
R.string.from_n_points_per_day,
mDecimalFormat.format(prize.maxScore)
)
)
else
fromPointsPerDayText.set(
mContext.getString(
R.string.out_of_n_available,
mDecimalFormat.format(prize.maxScore)
)
)
}
isDirty = true
}
}
mCategoriesAdapter.listener = object : CategoryAdapter.Listener {
override fun onScrollToPosition(position: Int) {
mCallback.setCategoriesCurrentPosition(position)
}
override fun onPlayClick() {
mCallback.openQuestions()
}
}
mCallback.bindPrizes(mPrizesAdapter)
mCallback.bindCategories(mCategoriesAdapter)
mCallback.bindTopMembers(mTopMembersAdapter)
mCallback.bindHistory(mHistoryAdapter)
}
override fun onAttach() {
super.onAttach()
loadQuizInfo(false)
}
@SuppressLint("CheckResult")
fun loadQuizInfo(getNewData: Boolean) {
mLoadGameStateDisposable?.dispose()
mRepository.getQuizInfoAggregated(
mPreferencesHelper.myPhoneNumber,
mPreferencesHelper.subAccount,
mPreferencesHelper.locale,
getNewData
)
.compose(RxUtil.applyDefaults(this))
.subscribe({
handleQuizInfoResponse(it)
}, {
handleError(it)
})
}
@SuppressLint("CheckResult")
fun loadGameState(topType: Int) {
mLoadGameStateDisposable?.dispose()
mLoadGameStateDisposable = mNetworkHelper.getQuizGameState(
mPreferencesHelper.myPhoneNumber,
mPreferencesHelper.subAccount,
topType
)
.compose(RxUtil.applySchedulers())
.subscribe({ response ->
mTopMembersAdapter.items.clear()
mTopMembersAdapter.items.addAll(response.top)
mTopMembersAdapter.notifyDataSetChanged()
}, {
handleError(it)
})
}
private fun handleQuizInfoResponse(response: InfoAggregated) {
shouldShowContent.set(true)
havePointsText.set(
mContext.getString(
R.string.i_have_n_points,
mDecimalFormat.format(response.gameStateResult.score)
)
)
for (prize in response.prizeResult.prizes)
if (prize.index == response.stateResult.gameLevel) {
fromPointsPerDayText.set(
mContext.getString(
R.string.from_n_points_per_day,
mDecimalFormat.format(prize.maxScore)
)
)
break
}
mPrizesAdapter.items.clear()
mPrizesAdapter.items.addAll(response.prizeResult.prizes)
mPrizesAdapter.notifyDataSetChanged()
mCallback.removeOnPrizeChangeListener(onPrizeChangeListener)
mCallback.setPrizesCurrentPosition(0)
mCallback.setOnPrizeChangeListener(onPrizeChangeListener)
mCategoriesAdapter.items.clear()
mCategoriesAdapter.items.addAll(response.levelResult.levels)
mCategoriesAdapter.notifyDataSetChanged()
mCallback.setCategoriesCurrentPosition(Integer.MAX_VALUE / 2)
mTopMembersAdapter.items.clear()
mTopMembersAdapter.items.addAll(response.gameStateResult.top)
mTopMembersAdapter.notifyDataSetChanged()
mHistoryAdapter.items.clear()
mHistoryAdapter.items.addAll(response.gameStateResult.history)
mHistoryAdapter.notifyDataSetChanged()
}
fun onMoreDetailsButtonClick() {
mCallback.openParticipateRules()
}
fun onShowHistoryClick(toggle: Boolean) {
shouldShowHistory.set(toggle)
if (toggle)
mCallback.scrollToBottom()
}
fun onCloseClick() {
mCallback.closeController()
}
}
interface InfoCallback : BaseViewModel.BaseCallback {
fun openParticipateRules()
fun bindPrizes(adapter: PrizeAdapter1)
fun bindCategories(adapter: CategoryAdapter)
fun bindTopMembers(adapter: TopMemberAdapter)
fun bindHistory(adapter: HistoryAdapter)
fun setOnPrizeChangeListener(listener: ViewPager2.OnPageChangeCallback)
fun removeOnPrizeChangeListener(listener: ViewPager2.OnPageChangeCallback)
fun setCategoriesCurrentPosition(position: Int)
fun setPrizesCurrentPosition(position: Int)
fun openQuestions()
fun closeController()
fun scrollToBottom()
}
class InfoController : BaseController(), InfoCallback {
@Inject
lateinit var mViewModel: InfoViewModel
lateinit var binding: ControllerQuizInfoBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
BaseApplication.getComponent().controllerComponent(ControllerModule(activity)).inject(this)
binding = ControllerQuizInfoBinding.inflate(inflater, container, false)
binding.viewmodel = mViewModel
mViewModel.setCallback(this, args)
binding.swipeLayout.setColorSchemeResources(R.color.colorPrimary, R.color.black)
return binding.root
}
override fun openParticipateRules() {
router.pushController(
RouterTransaction.with(ParticipateRulesController())
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
override fun openQuestions() {
router.pushController(
RouterTransaction.with(QuestionController())
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler())
)
}
override fun bindPrizes(adapter: PrizeAdapter1) {
binding.vpPrizes.adapter = adapter
}
override fun bindCategories(adapter: CategoryAdapter) {
binding.vpCategories.adapter = adapter
}
override fun bindTopMembers(adapter: TopMemberAdapter) {
binding.rvTopMembers.layoutManager = LinearLayoutManager(activity)
binding.rvTopMembers.adapter = adapter
}
override fun bindHistory(adapter: HistoryAdapter) {
binding.rvHistory.layoutManager = LinearLayoutManager(activity)
binding.rvHistory.adapter = adapter
}
override fun setOnPrizeChangeListener(listener: ViewPager2.OnPageChangeCallback) {
binding.vpPrizes.registerOnPageChangeCallback(listener)
}
override fun removeOnPrizeChangeListener(listener: ViewPager2.OnPageChangeCallback) {
binding.vpPrizes.unregisterOnPageChangeCallback(listener)
}
override fun setCategoriesCurrentPosition(position: Int) {
binding.vpCategories.currentItem = position
}
override fun setPrizesCurrentPosition(position: Int) {
binding.vpPrizes.currentItem = position
}
override fun closeController() {
router.handleBack()
}
override fun scrollToBottom() {
binding.root.postDelayed({
binding.nestedScrollView.fullScroll(View.FOCUS_DOWN)
}, 30)
}
override fun onChangeStarted(
changeHandler: ControllerChangeHandler,
changeType: ControllerChangeType
) {
super.onChangeStarted(changeHandler, changeType)
if (!changeType.isEnter) {
mViewModel.onDetach()
} else {
binding.root.postDelayed({
mTabStateManager.setTab(TabStateManager.Tabs.QUIZ_INFO_SCREEN)
mViewModel.onAttach()
}, 30)
}
}
override fun showMessage(message: String, vararg duration: Int) {
showToast(binding.root, message)
}
override fun showMessage(message: Int, vararg duration: Int) {
showToast(binding.root, message)
}
}