Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fatal Exception: java.lang.IndexOutOfBoundsException #26

Open
TikTak123 opened this issue May 4, 2020 · 1 comment
Open

Fatal Exception: java.lang.IndexOutOfBoundsException #26

TikTak123 opened this issue May 4, 2020 · 1 comment

Comments

@TikTak123
Copy link

TikTak123 commented May 4, 2020

The callback method onPageSelected receives a position greater than the list of elements.

CardSlider version:
implementation 'com.github.IslamKhSh:CardSlider:1.0.1'

API level:
Android 6, 9

Error log:

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)

Sample code:

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)
    }
}
@TikTak123
Copy link
Author

I was able to reproduce the error.
Here is an example video where it is reproduced
https://drive.google.com/file/d/1RK2yI_KOsl2Mdu3KpWu6vSKX417pNvov/view?usp=sharing
I just added this peace of code to your sample project

viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)
                if (position > movies.size - 1)
                    throw IndexOutOfBoundsException()
            }
        })

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant