Question

Fix Kotlin findViewById Type Inference Errors in Android

kotlinandroidandroid-8.0-oreo

Question

I am building an Android ListView in Kotlin and receive this compiler error:

Type inference failed: Not enough information to infer parameter T in
fun <T : View!> findViewById(id: Int): T!
Please specify it explicitly.

The error occurs when calling findViewById, such as when locating the ListView or the TextView in a row holder. How can I tell Kotlin which view type findViewById should return and fix the adapter code?

Short Answer

By the end of this page, you will understand why Android's findViewById uses a generic type in Kotlin, why Kotlin sometimes cannot infer that type, and how to specify it safely with <ViewType>, typed properties, or View Binding.

Concept

findViewById searches a view hierarchy using an integer resource ID, such as R.id.label. At runtime, Android knows which view is stored at that ID. At compile time, however, the ID itself does not tell Kotlin whether the result is a TextView, ListView, Button, or another View subclass.

In Kotlin, the Android method is represented conceptually as:

fun <T : View> findViewById(id: Int): T

T is a generic type parameter. Kotlin must determine a concrete type for T before it can compile the call.

For example, this explicitly tells Kotlin that the view is a TextView:

val label = row.findViewById<TextView>(R.id.label)

This matters because Kotlin can then check your code. If R.id.label actually points to a Button, the mistake is exposed at runtime when the layout is used, rather than allowing vague View values to flow through the program.

Older Android/Kotlin code commonly used an unchecked cast:

val label = row.findViewById(R.id.label) as TextView

Explicit generic types are clearer and avoid relying on a cast for type inference.

Mental Model

Think of findViewById as asking a building receptionist for the person assigned to room 12.

  • The room number (R.id.label) identifies where to look.
  • It does not describe the person's job.
  • Kotlin asks you to say what kind of person you expect: TextView, ListView, or Button.

Writing findViewById<TextView>(R.id.label) is like saying: “Please get the TextView from room label.” The ID finds the object; the generic type tells Kotlin how your code will use it.

Take Quiz

Syntax and Examples

The general syntax is:

val view = root.findViewById<ViewType>(R.id.some_view_id)

For a ListView in an activity:

val listView = findViewById<ListView>(R.id.list)
listView.adapter = ListExampleAdapter(this)

For a TextView inside an inflated list-row layout:

class ListRowHolder(row: View) {
    val label: TextView = row.findViewById<TextView>(R.id.label)
}

The type inside angle brackets is the expected view class. It must match the XML layout:

<TextView
    android:id="@+id/label" />

Kotlin can also infer the type when a property has an explicit type:

val label: TextView = row.findViewById(R.id.label)

Using <TextView> is often easier to read in Android code because the expected type appears directly at the call site.

Step by Step Execution

Consider this row-holder code:

class ListRowHolder(row: View) {
    val label = row.findViewById<TextView>(R.id.label)
}

Step by step:

  1. The adapter inflates R.layout.list_row, producing a root View for one row.
  2. That root view is passed to ListRowHolder as row.
  3. R.id.label identifies a child view in that row layout.
  4. <TextView> supplies the generic type T required by findViewById.
  5. Android locates the child view, and Kotlin treats the result as a TextView.
  6. The adapter can safely assign text:
holder.label.text = "Eins"

If the XML view for @id/label is not actually a TextView (or a subclass), the app will fail when that layout is loaded. Keep the Kotlin type and XML element consistent.

Real World Use Cases

Explicit findViewById types appear in many Android UI tasks:

  • Activity setup: Find a RecyclerView, ListView, Toolbar, or button after setContentView.
  • Custom views: Find child controls inside a reusable custom layout.
  • Dialog layouts: Inflate a dialog and find its EditText or action buttons.
  • List and RecyclerView rows: Find row labels, images, and checkboxes when binding data.
  • Legacy Android projects: Maintain screens written before View Binding was enabled.

Example for a dialog:

val dialogView = layoutInflater.inflate(R.layout.dialog_login, null)
val emailInput = dialogView.findViewById<EditText>(R.id.email_input)
val submitButton = dialogView.findViewById<Button>(R.id.submit_button)
Take Quiz

Real Codebase Usage

In current Android projects, developers generally avoid repeated findViewById calls when possible by using View Binding. It creates type-safe properties based on layout XML.

However, when maintaining a legacy screen or a small custom view, findViewById is still common. Typical practices include:

  • Make required root views non-null: use row: View, not row: View?.
  • Look up a row's child views once, then store them in a holder.
  • Reuse the holder through View.tag in a ListView adapter.
  • Use explicit generic types at lookup sites.
  • Validate layout assumptions: an ID should refer to the same view type in every layout variant.

A conventional ListView row-reuse pattern is:

val holder = if (convertView == null) {
    val row = inflater.inflate(R.layout.list_row, parent, false)
    ListRowHolder(row).also { row.tag = it }
} else {
    convertView.tag as ListRowHolder
}

The view lookup occurs only when a row is created, not every time a row is reused.

Common Mistakes

Calling a generic method without enough type context

This may fail in older Kotlin/Android API combinations:

val label = row.findViewById(R.id.label)

Fix it by supplying the type:

val label = row.findViewById<TextView>(R.id.label)

Using a nullable row when it should always exist

A holder represents an inflated row, so it normally should not accept View?:

// Avoid unnecessary nullability
class ListRowHolder(row: View?)

A nullable value forces safe calls and can hide a programming error. Prefer:

class ListRowHolder(row: View)

Casting after every lookup

This works but is less clear:

val label = row.findViewById(R.id.label) as TextView

Prefer:

val label = row.findViewById<TextView>(R.id.label)

Using the wrong type for the XML view

This compiles but fails when run if @id/label is an ImageView:

val label = row.findViewById<TextView>(R.id.label)

Check the corresponding XML layout and ensure the ID is attached to a TextView or subclass.

Forgetting setContentView

An activity must load its layout before searching it:

setContentView(R.layout.activity_main)
val listView = findViewById<ListView>(R.id.list)

Without the layout, the requested ID cannot be found in the activity's view hierarchy.

Comparisons

ApproachExampleBest useNotes
Explicit generic typefindViewById<TextView>(R.id.label)Legacy code and custom viewsClear and type-aware.
Type from propertyval label: TextView = findViewById(R.id.label)Short local codeKotlin uses the declared property type as context.
Unsafe castfindViewById(R.id.label) as TextViewOlder code you are maintainingWorks, but is less expressive and can obscure inference issues.
View Bindingbinding.labelNew Android layoutsUsually the preferred modern approach; generated properties are typed.

findViewById and View Binding both depend on the XML layout being correct. View Binding reduces manual IDs and casts, while findViewById is useful when binding is unavailable or when writing reusable code around arbitrary view roots.

Take Quiz

Cheat Sheet

// Find a view from an Activity, Fragment root, dialog, or row view
val text = root.findViewById<TextView>(R.id.label)
val button = root.findViewById<Button>(R.id.save_button)
val list = findViewById<ListView>(R.id.list)
  • findViewById is generic: its return type is T, where T is a View subtype.
  • Use <TextView>, <Button>, <ListView>, and so on when Kotlin cannot infer T.
  • The requested Kotlin type must match the view associated with that ID in XML.
  • Call setContentView(...) before looking up activity views.
  • Use View, not View?, for a holder's required row view.
  • In new Android code, consider View Binding for generated, type-safe view references.

FAQ

Why does Kotlin say it cannot infer parameter T for findViewById?

findViewById is a generic method. Kotlin needs to know which View subtype it should return, but an ID integer alone may not provide enough compile-time information.

How do I explicitly specify the type for findViewById in Kotlin?

Put the expected view type in angle brackets:

val label = findViewById<TextView>(R.id.label)

Should I use as TextView or findViewById<TextView>?

Prefer findViewById<TextView>. It communicates the expected return type directly and avoids depending on a separate cast.

Can Kotlin infer the findViewById type from a property declaration?

Yes:

val label: TextView = findViewById(R.id.label)

Still, an explicit generic type is often clearer in Android UI code.

Why should ListRowHolder receive View instead of View??

A holder cannot work without a row view. Declaring it non-null makes that requirement explicit and prevents unnecessary safe calls such as row?.findViewById(...).

Does findViewById verify the XML type at compile time?

No. Kotlin knows the type you request, but Android layouts are loaded at runtime. If an ID points to the wrong view type, the error appears when that layout is used.

Is ListView still appropriate for new Android screens?

ListView remains available and is common in older apps. For many new, dynamic list screens, Android developers often choose RecyclerView, which has a more flexible adapter design.

Take Quiz

Mini Project

Description

Build a small ListView screen that displays three translated number labels. The project demonstrates typed findViewById calls and the ViewHolder pattern used to reuse list-row views efficiently.

Goal

Create a working Kotlin BaseAdapter that finds a ListView and row TextView without type-inference errors.

Requirements

Create an activity layout containing a ListView with the ID list. Create a row layout containing a TextView with the ID label. Use findViewById<ListView> when obtaining the activity's list. Use findViewById<TextView> in a row holder. Display the values Eins, Zwei, and Drei. Reuse an existing row through convertView when available.

Take Quiz

Keep learning

Related questions

View more