Question
Fix Unresolved Reference kotlinx in Kotlin Android Projects
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.
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:
- During the build, View Binding reads
activity_main.xmland generatesActivityMainBinding. inflate(layoutInflater)creates the layout's view hierarchy and returns a binding object.binding.rootis the top-levelLinearLayoutand becomes the activity content view.binding.hellorefers to theTextViewwhose ID ishello.- Setting
.textupdates 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 = ""
}
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
renderfunction 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
| Approach | How a view is accessed | Status | Main consideration |
|---|---|---|---|
findViewById | findViewById<TextView>(R.id.hello) | Supported | Explicit, but repetitive and less type-safe than binding |
| Kotlin synthetics | hello.text after a kotlinx.android.synthetic import | Deprecated and removed | Required the old Android Extensions plugin and could hide layout/lifecycle mistakes |
| View Binding | binding.hello.text | Recommended for XML layouts | Type-safe reference generated from the layout |
| Jetpack Compose | State-driven composable functions | Recommended for new Compose UI | Does 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.
Cheat Sheet
-
Unresolved reference: kotlinxforkotlinx.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
nullinonDestroyView().
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.
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.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.