Traditional Android UI: XML Layouts, RecyclerView, ViewBinding

Android Development with Kotlin: 21 Real Projects · Architecture, Data and Networking · 4 min read · Updated

RecyclerView reuses rows

  1. You give the adapter a new list.
  2. DiffUtil compares it with the old list to find what changed.
  3. The adapter binds data into recycled ViewHolders for the visible rows.
  4. RecyclerView animates the change and keeps scrolling smooth.

Most Android code in production today still uses XML layouts, and many jobs ask for it. You do not need to love it, but you should be able to read and fix it. This lesson shows the key pieces and how they map to Compose ideas you already know.

Layouts

  • LinearLayout: children in one row or column (like Row and Column).
  • FrameLayout: children stacked (like Box).
  • ConstraintLayout: each view is positioned by constraints to its parent or to other views. It is the standard for complex screens and keeps the layout flat.
  • ScrollView: scrolls a single child.
  • RelativeLayout: an old layout that ConstraintLayout replaced.
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="@string/app_name"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/save"
        android:layout_width="0dp" android:layout_height="wrap_content"
        android:text="Save"
        app:layout_constraintTop_toBottomOf="@id/title"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Common views: TextView, EditText, ImageView, Button, CheckBox, RadioButton, Switch, ProgressBar, Spinner (a dropdown) and CardView/MaterialCardView. Use dp for sizes and sp for text.

ViewBinding

Do not call findViewById. Turn on ViewBinding in Gradle (buildFeatures { viewBinding = true }) and Android generates a class for each layout with a typed property for every view that has an id. DataBinding goes further and lets the XML bind to data, but is used less in new work.

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.save.setOnClickListener { binding.title.text = "Saved" }
    }
}

RecyclerView, adapters and ViewHolders

RecyclerView shows long lists by reusing a small number of row views. An adapter creates a ViewHolder for each row type and fills it with data. Use ListAdapter with a DiffUtil.ItemCallback: you submit a new list and it works out the changes and animates them.

class NoteAdapter : ListAdapter<Note, NoteAdapter.Holder>(Diff) {
    class Holder(val b: ItemNoteBinding) : RecyclerView.ViewHolder(b.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
        Holder(ItemNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false))

    override fun onBindViewHolder(holder: Holder, position: Int) {
        holder.b.title.text = getItem(position).title
    }

    object Diff : DiffUtil.ItemCallback<Note>() {
        override fun areItemsTheSame(a: Note, b: Note) = a.id == b.id
        override fun areContentsTheSame(a: Note, b: Note) = a == b
    }
}

// in the Activity or Fragment
binding.list.layoutManager = LinearLayoutManager(this)
binding.list.adapter = adapter
adapter.submitList(notes)

Themes, styles, fonts and custom views

A theme (in themes.xml) sets colours and shapes for the whole app. A style groups attributes for one kind of view. Put fonts in res/font, and vector icons in res/drawable. A custom view extends View (or a layout) and overrides onDraw or composes other views. Compose can sit inside XML with ComposeView, and XML inside Compose with AndroidView, so you can migrate one screen at a time.

The migration path matters in real jobs. Teams rarely rewrite everything. They add Compose to new screens, keep stable XML screens, and share the same theme colours and fonts through design tokens so both look alike.

Compose or XML?

  • New apps: Compose.
  • Existing apps: keep working XML screens, write new ones in Compose, and migrate when a screen needs big changes.
  • Learn XML well enough to read, debug and extend it. That is what most maintenance jobs need.

Your task

  1. Recreate the notes list from project 5 using RecyclerView, ListAdapter and ViewBinding.
  2. Build a login screen in ConstraintLayout that works at both rotations.
  3. Add a theme colour and use it from a style.
  4. Show a Compose button inside an XML screen with ComposeView.

How to check it worked

  • You never call findViewById.
  • The list updates smoothly when you submit a new list.
  • You can explain what a ViewHolder is.

Frequently asked questions

Why use ViewBinding instead of findViewById?

It is type-safe and null-safe. A wrong id is a compile error, not a crash.

Why is it called RecyclerView?

When a row scrolls off the screen its view is recycled for a new row instead of creating a new one, which saves memory and time.

Can I mix Compose and XML?

Yes. ComposeView embeds Compose in XML, and AndroidView embeds a view in Compose.