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
setContentVieworonViewCreated
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
lateinitinonCreatebeforesetContentViewinflates 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.Check initialization before access:
- 2.```kotlin
- 3.class MainActivity : AppCompatActivity() {
- 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.Use nullable type instead of lateinit:
- 2.```kotlin
- 3.// Instead of lateinit
- 4.class MainActivity : AppCompatActivity() {
- 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.Initialize in the correct lifecycle callback:
- 2.```kotlin
- 3.class MyFragment : Fragment(R.layout.fragment_my) {
- 4.private var _binding: FragmentMyBinding? = null
- 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.Handle Dagger/Hilt injection timing:
- 2.```kotlin
- 3.@AndroidEntryPoint
- 4.class MainActivity : AppCompatActivity() {
- 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) overlateinitwhen appropriate - Always check
::property.isInitializedbefore accessinglateinitin uncertain contexts - Initialize
lateinitproperties in the earliest safe lifecycle callback - Clear binding references in
onDestroyViewto prevent memory leaks - Use ViewBinding with nullable backing property pattern for Fragments
- Add unit tests that verify initialization order