Question

Fix Unresolved Reference kotlinx in Kotlin Android Projects

kotlinandroid-studiokotlin-android-extensions

Question

After converting an Android MainActivity to Kotlin and configuring the project for Kotlin, I added a TextView with the ID hello in content_main.xml:

<TextView
    android:id="@+id/hello"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!" />

Android Studio suggested this import:

import kotlinx.android.synthetic.main.content_main.*

However, compiling the app produces these errors:

Unresolved reference: kotlinx
Unresolved reference: hello

Why is the kotlinx.android.synthetic import unresolved, and what is the correct way to access the hello view from Kotlin?

Short Answer

You will learn the difference between an IDE Kotlin plugin and a Gradle build plugin, why the old Kotlin Android synthetic view feature needed explicit build configuration, and why modern Android apps should use View Binding instead.

Concept

kotlinx.android.synthetic belonged to Kotlin Android Extensions, an old Kotlin compiler plugin for Android. It generated properties for Android XML view IDs, allowing code such as:

hello.text = "Hey"

For that import to compile in older projects, the Android Extensions Gradle plugin had to be applied to the app module. Installing or updating an Android Studio plugin was not enough: Gradle still needed to load the compiler plugin during the build.

apply plugin: 'kotlin-android-extensions'

The kotlinx error means the compiler does not have the library/compiler feature that defines the imported package. The hello error follows because no synthetic property was generated.

However, Kotlin Android Extensions and synthetic view properties are now deprecated and removed from modern Kotlin Android development. New code should use View Binding, which generates a type-safe binding class from each layout. View Binding makes view ownership explicit and avoids several problems synthetic properties had, especially around fragments and incorrect layouts.

Mental Model

Think of the Android Studio plugin as a toolbox installed in your workshop. It helps the editor understand Kotlin and offer suggestions.

Gradle configuration is the instruction sheet used when the app is actually built. Even if the toolbox suggests a tool, the build can only use it when the instruction sheet includes it.

With the old synthetic feature, the import was like asking for an automatically generated shortcut named hello. Without the Android Extensions Gradle plugin, that shortcut was never generated.

View Binding is more like receiving a labeled map of a particular layout. For activity_main.xml, Android generates an ActivityMainBinding map containing the views declared in that layout.

Take Quiz

Syntax and Examples

Historical solution: Kotlin Android synthetics

In older Gradle-based Android projects, the app module needed the Android Extensions plugin in addition to Kotlin Android support:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

Then this could compile if content_main.xml contained @+id/hello:

import kotlinx.android.synthetic.main.content_main.*

hello.text = "Hey"

This approach is no longer recommended. Do not add kotlin-android-extensions to a new project.

Modern solution: View Binding

Enable View Binding in the module-level build.gradle file:

android {
    buildFeatures {
        viewBinding true
    }
}

For a layout named activity_main.xml, Android generates ActivityMainBinding:

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

        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.hello.text = "Hey"
    }
}

The property name comes from the XML ID. For example, android:id="@+id/hello" becomes binding.hello.

Step by Step Execution

Consider this layout file, activity_main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello" />
</LinearLayout>

And this activity:

val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.hello.text = "Hey"

Step by step:

  1. During the build, View Binding reads activity_main.xml and generates ActivityMainBinding.
  2. inflate(layoutInflater) creates the layout's view hierarchy and returns a binding object.
  3. binding.root is the top-level LinearLayout and becomes the activity content view.
  4. binding.hello refers to the TextView whose ID is hello.
  5. Setting .text updates the text displayed on screen.

A key rule is that the binding class matches the layout file name: activity_main.xml becomes ActivityMainBinding.

Real World Use Cases

View Binding is useful whenever Kotlin code needs to interact with XML views:

  • Activity screens: Set a title, configure a toolbar, and attach click listeners.
  • Fragments: Read form fields and update loading or error messages.
  • RecyclerView rows: Bind a product name, price, and image in each item layout.
  • Settings pages: Display saved preferences and react to toggle changes.
  • API results: Show returned user, weather, or order data in views safely.

For example, a login screen can read two input fields and show an error:

binding.loginButton.setOnClickListener {
    val email = binding.emailInput.text.toString()

    if (email.isBlank()) {
        binding.errorText.text = "Email is required"
        return@setOnClickListener
    }

    binding.errorText.text = ""
}
Take Quiz

Real Codebase Usage

In production Android code, developers usually create a binding once for an activity and use it throughout onCreate:

class ProfileActivity : AppCompatActivity() {
    private lateinit var binding: ActivityProfileBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityProfileBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.saveButton.setOnClickListener { saveProfile() }
    }

    private fun saveProfile() {
        val name = binding.nameInput.text.toString().trim()

        if (name.isEmpty()) {
            binding.nameInput.error = "Enter a name"
            return
        }

        binding.statusText.text = "Saved"
    }
}

Common patterns include:

  • Validation and guard clauses: Return early when input is invalid.
  • Event listeners: Attach listeners through binding.button.setOnClickListener.
  • Rendering state: Put UI updates in a render function that assigns text, visibility, and enabled state.
  • Fragment cleanup: Clear the fragment binding in onDestroyView() because a fragment's view can be destroyed while the fragment object remains alive.

A typical fragment pattern is:

private var _binding: FragmentProfileBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    _binding = FragmentProfileBinding.inflate(inflater, container, false)
    return binding.root
}

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

Common Mistakes

Expecting an IDE suggestion to configure Gradle

Android Studio may suggest an import that is unavailable to the configured build. Code completion is not proof that the compiler plugin or dependency is present.

Using synthetic imports in a modern project

This old code is not valid for current Kotlin Android projects:

import kotlinx.android.synthetic.main.activity_main.*

hello.text = "Hey"

Use View Binding instead:

binding.hello.text = "Hey"

Inflating one layout but importing or binding another

The original example called:

setContentView(R.layout.activity_main)

but imported synthetics for content_main. This can be valid only if activity_main includes content_main, but it is easy to confuse the two layouts. With View Binding, bind the layout you inflate directly, or use the binding for the included layout when appropriate.

Forgetting to enable View Binding

Without this Gradle configuration, ActivityMainBinding will be unresolved:

android {
    buildFeatures {
        viewBinding true
    }
}

After changing Gradle files, sync the project.

Assuming every XML ID creates a binding property

View Binding only exposes views that have an ID. Add one when code needs to refer to the view:

android:id="@+id/hello"

Keeping a fragment binding after its view is destroyed

A fragment can outlive its view. Holding the old binding can leak the view hierarchy. Set the nullable backing field to null in onDestroyView().

Comparisons

ApproachHow a view is accessedStatusMain consideration
findViewByIdfindViewById<TextView>(R.id.hello)SupportedExplicit, but repetitive and less type-safe than binding
Kotlin syntheticshello.text after a kotlinx.android.synthetic importDeprecated and removedRequired the old Android Extensions plugin and could hide layout/lifecycle mistakes
View Bindingbinding.hello.textRecommended for XML layoutsType-safe reference generated from the layout
Jetpack ComposeState-driven composable functionsRecommended for new Compose UIDoes not use XML view IDs or View Binding

findViewById remains useful when working with a view dynamically, but View Binding is usually clearer for regular XML screens. View Binding is not the same as Data Binding: it creates view references but does not evaluate XML expressions or automatically observe data.

Take Quiz

Cheat Sheet

  • Unresolved reference: kotlinx for kotlinx.android.synthetic... means the old synthetic-view feature is not available to the build.

  • Historically, the app module required:

    apply plugin: 'kotlin-android-extensions'
    
  • Kotlin Android Extensions/synthetics are obsolete. Use View Binding in new code.

  • Enable View Binding:

    android {
        buildFeatures {
            viewBinding true
        }
    }
    
  • Layout-to-binding naming:

    activity_main.xml  -> ActivityMainBinding
    fragment_profile.xml -> FragmentProfileBinding
    item_product.xml -> ItemProductBinding
    
  • Inflate and install an activity binding:

    val binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
    
  • Access @+id/hello:

    binding.hello.text = "Hey"
    
  • In fragments, set the backing binding field to null in onDestroyView().

FAQ

Why does Android Studio suggest kotlinx.android.synthetic but Gradle cannot resolve it?

The editor can recognize historical APIs or offer generic import suggestions, while the Gradle build only compiles features configured for the module. The old Android Extensions compiler plugin was required for synthetic imports.

Is kotlinx.android.synthetic still supported?

No. Kotlin Android Extensions synthetic view accessors were deprecated and removed. Use View Binding for XML-based Android interfaces.

Do I need an ID for every view when using View Binding?

No. Only give an ID to a view that code needs to access. Views without IDs are still displayed normally.

Why is ActivityMainBinding unresolved?

Check that View Binding is enabled in the module Gradle file, that activity_main.xml exists in the appropriate res/layout directory, and that the Gradle project has been synced.

Does View Binding replace findViewById completely?

For most ordinary XML screen layouts, yes. findViewById remains available and can be useful for dynamic or unusual cases.

Can a binding refer to a view in a different layout file?

A binding directly contains views in its own layout. For layouts brought in with <include>, View Binding can expose the included layout through its generated binding, depending on the include structure.

What should I use if my app uses Jetpack Compose?

Compose does not use XML view bindings. Build the interface with composable functions and update it from state instead.

Take Quiz

Mini Project

Description

Build a small XML-based greeting screen using View Binding. The user types a name, presses a button, and the app displays a personalized greeting. This demonstrates safe, direct access to XML views without synthetic imports or repeated findViewById calls.

Goal

Create an activity that reads text from an input field and updates a TextView through View Binding.

Requirements

Enable View Binding in the app module. Create a layout with an input field, a button, and a text view, each with an ID. Inflate the generated activity binding before calling setContentView. Show a validation message when the input is blank. Display a greeting when the user enters a name.

Take Quiz

Keep learning

Related questions

View more