-2

The app has a bottomnavigation of two tabs one for tasks and one for notes.

I get no errror on the start as it starts by loding the tasks. but when I press the notes tab it crashes.

logcat on crash when clicking the notes app

have subtracted 1 from the position of the masterlist as I have added a button to the top (1st position in the array) thinking this would correct it.

BaseRecyclerAdapter class

abstract class BaseRecyclerAdapter<T>(
    protected val masterList: MutableList<T> = mutableListOf()
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {


    override fun getItemCount(): Int = masterList.size + 1

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is TaskAdapter.AddButtonViewHolder) {
            holder.onBind(Unit)
        } else {
            (holder as BaseViewHolder<T>).onBind(masterList[position - 1])
        }
    }


    override fun getItemViewType(position: Int): Int =
        if (position == 0) {
            TYPE_ADD_BUTTON
        } else {
            TYPE_INFO
        }

    abstract class BaseViewHolder<E>(val view: View) : RecyclerView.ViewHolder(view) {
        abstract fun onBind(data: E)
    }

    abstract class AddButtonViewHolder(view: View) : BaseViewHolder<Unit>(view)

    companion object {
        const val TYPE_ADD_BUTTON = 0
        const val TYPE_INFO = 1
    }
}

NotesAdapter

class NoteAdapter(notesList: MutableList<Note> = mutableListOf()) : BaseRecyclerAdapter<Note>(notesList) {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =

        if(viewType == TYPE_ADD_BUTTON){
            AddButtonViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_add_button, parent, false))
        } else {
            NoteViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_note, parent, false))

        }

    override fun getItemViewType(position: Int): Int =
        if(position == 0){
            TYPE_ADD_BUTTON
        }else {
            TYPE_INFO
        }


    override fun getItemCount(): Int = masterList.size + 1


    class NoteViewHolder(view: View) :BaseViewHolder<Note>(view){

        override fun onBind(data: Note) {
            (view as NoteView).initView(data)
        }
    }

    class AddButtonViewHolder(view: View): BaseRecyclerAdapter.AddButtonViewHolder(view){
        override fun onBind(data: Unit) {
            view.buttonText.text = view.context.getText(R.string.add_button_note)
        }

    }
}

NoteListFragment

class NotesListFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_notes_list, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        recyclerViewNote.layoutManager = LinearLayoutManager(context)
        val adapter = NoteAdapter(mutableListOf(
            Note("Ben Mohammad", null),
            Note("Joesph and and Maria", null)
        ))
        recyclerViewNote.adapter = adapter
    }

    companion object {

        fun newInstance() = NotesListFragment()
    }

}

NoteView

class NoteView @JvmOverloads constructor (context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 1) :
    ConstraintLayout(context, attrs, defStyleAttr){

    fun initView(note: Note){
        description.text = note.description
  }
}

I've used Custom views nested That is what I'm trying to do....obviously trying to keep it very OOP style getting stuck with it...

class TaskAdapter(
    tasksList: MutableList<Task> = mutableListOf()
) : BaseRecyclerAdapter<Task>(tasksList) {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =

        if (viewType == TYPE_INFO) {
            TaskViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_task, parent, false))
        } else {
            AddButtonViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_add_button, parent, false))
        }

    class TaskViewHolder(view: View) : BaseViewHolder<Task>(view) {

        override fun onBind(data: Task) {
            (view as TaskView).initView(data)

        }
    }


    class AddButtonViewHolder(view: View) : BaseRecyclerAdapter.AddButtonViewHolder(view) {
        override fun onBind(data: Unit) {
            view.buttonText.text = view.context.getText(R.string.add_button_task)
        }

    }


}

TasksListfragment

class TasksListFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_tasks_list, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        recyclerView.layoutManager = LinearLayoutManager(context)
        val adapter = TaskAdapter(
            mutableListOf(
                Task(
                    "Testing one!!",
                    mutableListOf(
                        Todo("Test 1!!"),
                        Todo("Test 2!!", true)


                    )
                ),
                Task("Testing two!!")
            )
        )
        recyclerView.adapter = adapter
    }

    companion object {


        fun newInstance() = TasksListFragment()
    }

}

TaskView

class TaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 1) :
    ConstraintLayout(context, attrs, defStyleAttr) {


    lateinit var task: Task

    fun initView(task: Task) {
        this.task = task

        titleView.text = task.title
        task.todos.forEach {

                todo ->
            val todoView =
                (LayoutInflater.from(context).inflate(R.layout.view_todo, todoContainer, false) as TodoView).apply {
                    initView(todo) {
                        if (isTaskcomplete()) {
                            createstrikeThrough()
                        } else {
                            removeStrikeThrough()
                        }

                    }
                }


            todoContainer.addView(todoView)

        }
    }

    fun isTaskcomplete(): Boolean = task.todos.filter { !it.isComplete }.isEmpty()

    private fun createstrikeThrough() {
        titleView.apply {
            paintFlags = paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
        }


    }

    private fun removeStrikeThrough() {
        titleView.apply {
            paintFlags = paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()
        }

    }
}

For further classes, all code is pushed to GitHub.

halfer
  • 19,824
  • 17
  • 99
  • 186
beni
  • 141
  • 1
  • 10
  • Possible duplicate of [What causes a java.lang.ArrayIndexOutOfBoundsException and how do I prevent it?](https://stackoverflow.com/questions/5554734/what-causes-a-java-lang-arrayindexoutofboundsexception-and-how-do-i-prevent-it) – Zoe May 27 '19 at 13:41
  • Please read [mcve] with emphasis on the **minimal** part. People aren't very willing to trawl through hundreds of lines of someone else's code distributed over various files to debug it. Make us volunteers happy and more willing to help you by trying to write the smallest code necessary to reproduce this issue. – Adriaan May 27 '19 at 13:44

1 Answers1

-1

The problem is here:

if (holder is TaskAdapter.AddButtonViewHolder) { // <--- problematic line
    holder.onBind(Unit)
} else {
    (holder as BaseViewHolder<T>).onBind(masterList[position - 1])
}

You have two different subclasses of BaseRecyclerAdapter: your TaskAdapter and your NotesAdapter. When this is called for position 0 for your NotesAdapter, the if (holder is ...) check will fail, because the type is NotesAdapter.AddButtonViewHolder.

This will cause execution to fall into the else case, and your masterList[position - 1] logic will wind up querying for index -1.

To work around this, use the view type instead of checking the subclass with is. The base RecyclerView.ViewHolder class has a getItemViewType() method that will return to you whatever you returned from your adapter. Since the view type logic is defined in your base adapter class, this will be the same regardless of which concrete implementation type is being used.

if (holder.itemViewType == TYPE_ADD_BUTTON) {
    (holder as AddButtonViewHolder).onBind(Unit)
} else {
    (holder as BaseViewHolder<T>).onBind(masterList[position - 1])
}
Ben P.
  • 52,661
  • 6
  • 95
  • 123