Introduction

Kotlin's lateinit modifier allows you to declare a non-nullable property that will be initialized later. However, if you access it before it has been initialized, Kotlin throws UninitializedPropertyAccessException (often grouped with NullPointerException in crash reports). This is especially common in Android where view binding, dependency injection, and lifecycle callbacks determine initialization timing.

Symptoms

  • kotlin.UninitializedPropertyAccessException: lateinit property adapter has not been initialized
  • Crash occurs only in certain activity lifecycle paths
  • Works in some configurations but not others
  • Dependency injection did not run before property access
  • ViewBinding accessed before setContentView or onViewCreated

Example error: `` kotlin.UninitializedPropertyAccessException: lateinit property binding has not been initialized at com.example.MainActivity.getBinding(MainActivity.kt:15) at com.example.MainActivity.updateUI(MainActivity.kt:42) at com.example.MainActivity.onCreate(MainActivity.kt:28)

Common Causes

  • Accessing lateinit in onCreate before setContentView inflates views
  • Dagger/Hilt injection did not complete before property access
  • Fragment accessed before onViewCreated
  • Test setup did not initialize the property
  • Conditional initialization path did not execute

Step-by-Step Fix

  1. 1.Check initialization before access:
  2. 2.```kotlin
  3. 3.class MainActivity : AppCompatActivity() {
  4. 4.private lateinit var binding: ActivityMainBinding

fun updateUI() { // Check before accessing if (::binding.isInitialized) { binding.textView.text = "Updated" } else { Log.w(TAG, "Binding not yet initialized, skipping update") } } } ```

  1. 1.Use nullable type instead of lateinit:
  2. 2.```kotlin
  3. 3.// Instead of lateinit
  4. 4.class MainActivity : AppCompatActivity() {
  5. 5.private var binding: ActivityMainBinding? = null

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

fun updateUI() { binding?.textView?.text = "Updated" // Safe, no crash }

override fun onDestroy() { binding = null // Prevent memory leak super.onDestroy() } } ```

  1. 1.Initialize in the correct lifecycle callback:
  2. 2.```kotlin
  3. 3.class MyFragment : Fragment(R.layout.fragment_my) {
  4. 4.private var _binding: FragmentMyBinding? = null
  5. 5.private val binding get() = _binding!!

override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) _binding = FragmentMyBinding.bind(view) // Now binding is safe to use

binding.recyclerView.adapter = MyAdapter() }

override fun onDestroyView() { super.onDestroyView() _binding = null // Clear reference to prevent leak } } ```

  1. 1.Handle Dagger/Hilt injection timing:
  2. 2.```kotlin
  3. 3.@AndroidEntryPoint
  4. 4.class MainActivity : AppCompatActivity() {
  5. 5.@Inject lateinit var repository: DataRepository

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Hilt injection happens BEFORE super.onCreate() returns // So repository is initialized here repository.fetchData() // Safe } } ```

Prevention

  • Prefer nullable types (var x: Type? = null) over lateinit when appropriate
  • Always check ::property.isInitialized before accessing lateinit in uncertain contexts
  • Initialize lateinit properties in the earliest safe lifecycle callback
  • Clear binding references in onDestroyView to prevent memory leaks
  • Use ViewBinding with nullable backing property pattern for Fragments
  • Add unit tests that verify initialization order