Search code examples
androidandroid-recyclerviewandroid-viewandroid-constraintlayout

Recycler items are not shown when creating programatically the UI


For some reason, I don't want to use XML's and I just want to create my UI programmatically. I now have the issue that my views are not shown on the screen, no matter how many elements I supply. This is my screen:

enter image description here

I don't understand why it isn't shown. I added an extremely easy view to the parent view's context using ConstraintLayout and I even supplied a fixed height.

Note that I am using the AndroidView because I want to use this RecyclerView with Jetpack Compose. I don't think that this causes the issue though.

I added the code below, I also upload the project here (running the app will instantly show the problem): https://github.com/Jasperav/RecyclerView/tree/main

fun createChat(context: Context): ConstraintLayout {
    val recyclerView = createRecyclerView(context)

    return recyclerView
}

fun createRecyclerView(context: Context): ConstraintLayout {
    val constraintLayout = createConstraintLayout(context)
    val parent = applyDefaults(constraintLayout.constraintLayout, View(context), MATCH_PARENT, MATCH_PARENT)

    parent.setBackgroundColor(context.getColor(R.color.yellow))

    constraintLayout.fill(parent, constraintLayout.constraintLayout)

    val recyclerView = applyDefaults(constraintLayout.constraintLayout, RecyclerView(context), MATCH_PARENT, MATCH_PARENT)

    recyclerView.setBackgroundColor(context.getColor(R.color.purple_500))

    constraintLayout.fill(recyclerView, parent)

    val shapeDrawable = MaterialShapeDrawable()

    shapeDrawable.fillColor = ColorStateList.valueOf(context.getColor(R.color.teal_200))
    shapeDrawable.setStroke(5f, context.getColor(R.color.red))

    recyclerView.background = shapeDrawable
    recyclerView.adapter = ChatAdapter()
    recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)

    return constraintLayout.constraintLayout
}

class ChatCell : View {
    private val textView: TextView

    constructor(parent: ViewGroup) : super(parent.context) {
        println("Created")

        val constraintLayout = createConstraintLayout(context)

        textView = applyDefaults(constraintLayout.constraintLayout, TextView(context), MATCH_PARENT, MATCH_PARENT)

        constraintLayout.constraintSet.constrainHeight(textView.id, 100)
        constraintLayout.constraintSet.applyTo(constraintLayout.constraintLayout)

        constraintLayout.fill(textView, constraintLayout.constraintLayout)

        textView.text = "test"
        textView.setBackgroundColor(context.getColor(R.color.red))
        setBackgroundColor(context.getColor(R.color.red))
    }

    fun x() {
    }
}

class ViewHolderTemp : RecyclerView.ViewHolder {
    val chatCell: ChatCell

    constructor(chatCell: ChatCell) : super(chatCell) {
        this.chatCell = chatCell
    }
}

class ChatAdapter : RecyclerView.Adapter<ViewHolderTemp>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderTemp {
        val chatCell = ChatCell(parent)

        return ViewHolderTemp(chatCell)
    }

    override fun getItemCount() = 2

    override fun onBindViewHolder(holder: ViewHolderTemp, position: Int) {
        holder.chatCell.x()
    }
}

class Layout(val constraintLayout: ConstraintLayout, val constraintSet: ConstraintSet)

fun createConstraintLayout(context: Context): Layout {
    val c = ConstraintLayout(context).apply {
        id = View.generateViewId()
        layoutParams = ViewGroup.LayoutParams(
            MATCH_PARENT,
            MATCH_PARENT,
        )
    }

    val set = ConstraintSet()

    set.clone(c)

    return Layout(c, set)
}


fun <T: View> applyDefaults(constraintLayout: ConstraintLayout, view: T, width: Int, height: Int): T {
    view.id = View.generateViewId()
    view.layoutParams = ConstraintLayout.LayoutParams(
        width,
        height,
    )

    constraintLayout.addView(view)

    return view
}

fun Layout.fill(view: View, inView: View) {
    val constraintSet = ConstraintSet()

    constraintSet.clone(constraintLayout)

    constraintSet.connect(view.id, ConstraintSet.LEFT, inView.id, ConstraintSet.LEFT);
    constraintSet.connect(view.id, ConstraintSet.TOP, inView.id, ConstraintSet.TOP);
    constraintSet.connect(view.id, ConstraintSet.RIGHT, inView.id, ConstraintSet.RIGHT);
    constraintSet.connect(view.id, ConstraintSet.BOTTOM, inView.id, ConstraintSet.BOTTOM);

    constraintSet.applyTo(constraintLayout)
}

@Composable
fun GreetingX() {
    AndroidView(factory = { context ->
        createChat(context)
    })
}

@Preview(showBackground = true)
@Composable
fun DefaultPreviewX() {
    RecyclverTestTheme {
        GreetingX()
    }
}

Solution

  • Your class ChatCell is not doing what you think it is. Below is a version which, I think, does what you want. I have included comments to explain what is happening.

    class ChatCell(parent: ViewGroup) : ConstraintLayout(parent.context) {
        private val textView: TextView
    
        init {
            // LayoutParams for ConstraintLayout within the RecyclerView
            layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
    
            // Create the TextView within the ConstraintLayout and add it to the layout.
            textView = TextView(parent.context).apply {
                id = View.generateViewId()
                // Always use ConstraintSet.MATCH_CONSTRAINT (size = 0) with appropriate constraints
                // for MATCH_PARENT within a ConstraintLayout.
                layoutParams = LayoutParams(ConstraintSet.MATCH_CONSTRAINT, 100)
                setBackgroundColor(context.getColor(R.color.white))
            }
            addView(textView)
    
            // Now that the TextView is added to the ConstraintLayout, we can constrain it.
            // Note that the TextView MUST be added to the ConstraintLayout before we create
            // the ConstraintSet.
            val cs = ConstraintSet()
            cs.clone(this)
            cs.connect(
                textView.id,
                ConstraintSet.START,
                ConstraintSet.PARENT_ID,
                ConstraintSet.START
            )
            cs.connect(textView.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END)
            cs.applyTo(this)
    
            // Our item view is now created and will be drawn appropriately within the RecyclerView.
        }
    
        // Here is where we bind the text to the item view's TextView.
        fun x(text: String) {
            textView.text = text
        }
    }
    

    The onBindViewHolder() function would look something like the following to work with the new code:

    override fun onBindViewHolder(holder: ViewHolderTemp, position: Int) {
        holder.chatCell.x("Text at position $position")
    }
    

    Here is the app on an emulator:

    enter image description here

    A better function declaration for the ChatCell custom view would be

    class ChatCell @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null
    ) : ConstraintLayout(context, attrs) 
    

    This would allow the class to be used in XML as well as silencing Android Studio complaining about the way the class is declared.