Question

Kotlin Class Property Initialization: lateinit, nullable, and lazy Fields

kotlinandroid

Question

How can I declare a SharedPreferences field in a Kotlin Android base activity and assign it later in onCreate()?

In Java, I can declare the field first and initialize it later:

protected SharedPreferences mSharedPreferences;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
}

I want the Kotlin equivalent so that subclasses of the base activity can use the property:

protected var sharedPreferences: SharedPreferences

Then, in onCreate():

sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)

However, Kotlin reports: Property must be initialized or be abstract. Why does this happen, and how should the property be declared?

Short Answer

Kotlin requires non-null properties to have a value when an object is created. For Android properties that are assigned later in lifecycle methods such as onCreate(), use lateinit var when the property is guaranteed to be initialized before use. Nullable properties and lazy initialization are alternatives with different trade-offs.

Concept

Java lets an uninitialized reference field contain null by default. Kotlin is designed to make possible null values explicit: a property declared as SharedPreferences is non-null, so Kotlin requires it to receive a value during construction.

This declaration is therefore incomplete:

protected var sharedPreferences: SharedPreferences

Kotlin cannot prove that onCreate() will run before another method accesses sharedPreferences. It requires one of these choices:

  • Give the property an initial value.
  • Make it nullable with SharedPreferences?.
  • Use lateinit var to promise that it will be initialized before access.
  • Use by lazy when its value can be calculated on first access.

For a SharedPreferences field initialized in an Activity's onCreate(), the usual Kotlin declaration is:

protected lateinit var sharedPreferences: SharedPreferences

lateinit is appropriate only for a mutable (var), non-null reference property. It postpones initialization while keeping the property's type non-null.

Mental Model

Think of a non-null Kotlin property as a labelled box that must contain an object before the Activity is fully ready. Java silently puts an invisible null marker into an empty box. Kotlin does not do that for non-null types.

lateinit is a note attached to the box saying: “I will place the object in this box before anyone opens it.” If code opens the box too early, Kotlin reports an initialization error instead of quietly using null.

Take Quiz

Syntax and Examples

Declare a property that will be initialized later with lateinit var:

import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager

open class BaseActivity : AppCompatActivity() {

    protected lateinit var sharedPreferences: SharedPreferences

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

        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
    }
}

A subclass can use it after the base class has initialized it:

class SettingsActivity : BaseActivity() {

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

        val darkMode = sharedPreferences.getBoolean("dark_mode", false)
    }
}

The order matters: super.onCreate(savedInstanceState) runs BaseActivity.onCreate(), which assigns sharedPreferences, before the subclass reads it.

You can check whether a lateinit property has been initialized from inside the declaring class:

if (::sharedPreferences.isInitialized) {
    // It is safe to read sharedPreferences.
}

Step by Step Execution

Consider this base and child Activity:

open class BaseActivity : AppCompatActivity() {
    protected lateinit var sharedPreferences: SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
    }
}

class HomeActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val welcomeShown = sharedPreferences.getBoolean("welcome_shown", false)
    }
}

Execution proceeds as follows:

  1. Android creates HomeActivity. At this point, sharedPreferences has no assigned value.
  2. Android calls HomeActivity.onCreate().
  3. HomeActivity calls super.onCreate(savedInstanceState).
  4. BaseActivity.onCreate() obtains the preferences object and assigns it to sharedPreferences.
  5. Control returns to HomeActivity.onCreate().
  6. sharedPreferences.getBoolean(...) is safe because the property is now initialized.

If step 3 were omitted, the base initialization would not run and accessing the property would throw UninitializedPropertyAccessException.

Real World Use Cases

Delayed property initialization is common in Android because many values are only available after an Activity, Fragment, View, or dependency container is ready.

Examples include:

  • SharedPreferences created from an Android Context.
  • View binding assigned after setContentView() or view inflation.
  • A database or repository supplied during application startup.
  • A RecyclerView adapter configured in onCreate().
  • Services initialized after configuration or authentication is available.

Use lateinit when the value is required and the lifecycle guarantees assignment before every valid use.

Take Quiz

Real Codebase Usage

In production Kotlin code, developers choose initialization style based on ownership and lifetime.

Base Activity property

A base Activity may expose a protected dependency to children:

open class BaseActivity : AppCompatActivity() {
    protected lateinit var sharedPreferences: SharedPreferences

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
    }
}

Prefer constructor injection outside Android lifecycle classes

For regular classes, make required dependencies available immediately:

class UserSettings(private val preferences: SharedPreferences) {
    fun isNotificationsEnabled(): Boolean {
        return preferences.getBoolean("notifications_enabled", true)
    }
}

This avoids an object that is temporarily incomplete.

Guard a lifecycle-dependent property when necessary

If an operation can occur before initialization, check it or redesign the lifecycle:

fun clearSettingsIfReady() {
    if (::sharedPreferences.isInitialized) {
        sharedPreferences.edit().clear().apply()
    }
}

Usually, a guard is a signal to reconsider where the method is called. Required dependencies should ideally be initialized at a predictable boundary.

Common Mistakes

Declaring a non-null property without initializing it

This does not compile:

protected var sharedPreferences: SharedPreferences

Fix it with lateinit, an initializer, lazy, or a nullable type.

Accessing a lateinit property too early

protected lateinit var sharedPreferences: SharedPreferences

fun readTheme() = sharedPreferences.getString("theme", "system")

This throws UninitializedPropertyAccessException if readTheme() runs before onCreate() assigns the property. Ensure lifecycle order, or use a safer design.

Forgetting super.onCreate() in a subclass

class HomeActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        // Missing super.onCreate(savedInstanceState)
        sharedPreferences.getBoolean("welcome_shown", false)
    }
}

The base Activity never initializes the property. Call super.onCreate(savedInstanceState) before using inherited lifecycle-initialized properties.

Using lateinit with unsupported types

lateinit cannot be used with primitive types such as Int, Boolean, or Double:

// Invalid
lateinit var retryCount: Int

Use an initial value, a nullable type, or another representation:

var retryCount: Int = 0

Making everything nullable

protected var sharedPreferences: SharedPreferences? = null

This compiles, but every access requires null handling:

val enabled = sharedPreferences?.getBoolean("enabled", false) ?: false

Use nullable properties only when null is a meaningful valid state, not merely to bypass initialization rules.

Comparisons

ApproachExampleBest whenMain trade-off
Immediate initializationval prefs = ...The value is available during constructionCannot depend on a later lifecycle phase
lateinit varlateinit var prefs: SharedPreferencesA required reference is assigned before useThrows if accessed before assignment
Nullable propertyvar prefs: SharedPreferences? = nullAbsence is a real, valid stateRequires null handling at each use
Lazy propertyval prefs by lazy { ... }Value should be created only on first useInitialization happens at access time, not explicitly in onCreate()
Constructor parameterclass Service(val prefs: SharedPreferences)Regular classes with required dependenciesCaller must provide the dependency immediately

val means the property reference cannot be reassigned after initialization. var means it can change. Since lateinit means “assign this later,” it works only with var.

A lazy alternative can be useful in an Activity when the context is already valid at first access:

protected val sharedPreferences: SharedPreferences by lazy {
    PreferenceManager.getDefaultSharedPreferences(this)
}

Unlike lateinit, this has no uninitialized period after the first access is requested; Kotlin runs the block automatically and stores the result.

Take Quiz

Cheat Sheet

// Required reference, assigned later
lateinit var property: SomeReferenceType

// Check from the declaring class
if (::property.isInitialized) {
    use(property)
}

// Optional value
var property: SomeReferenceType? = null

// Compute once on first access
val property: SomeReferenceType by lazy {
    createProperty()
}
  • A property of type T is non-null and needs a value.
  • A property of type T? may hold null.
  • lateinit works only with var and non-null reference types.
  • Accessing an uninitialized lateinit property throws UninitializedPropertyAccessException.
  • Initialize base class lifecycle properties before subclasses use them.
  • Prefer constructor parameters for required dependencies in ordinary Kotlin classes.

FAQ

Why does Kotlin require properties to be initialized?

A property declared as a non-null type, such as SharedPreferences, promises that it always has an object. Kotlin enforces that promise at compile time.

What is the Kotlin equivalent of an uninitialized Java reference field?

Use lateinit var property: Type if it will be assigned before use, or var property: Type? = null if null is a valid state.

Should I use lateinit or lazy for SharedPreferences?

Use lateinit when you deliberately initialize it in onCreate(). Use lazy when creating it on its first access is clearer and safe for your component lifecycle.

Can a lateinit property be null?

No. Its declared type is non-null. Before assignment it is uninitialized, which is different from containing null.

Can I declare lateinit val?

No. lateinit requires var because assignment happens after construction. A lazy property is commonly used when you want a read-only val initialized later.

What happens if I access lateinit before initialization?

Kotlin throws UninitializedPropertyAccessException at runtime. Ensure initialization happens before access.

Can subclasses access a protected lateinit property?

Yes. A subclass can access it, but it must call the base class initialization path first, typically through super.onCreate(savedInstanceState).

Take Quiz

Mini Project

Description

Create a base Activity that manages an application preference and lets a child Activity read and update it. This demonstrates how lateinit supports lifecycle-based initialization while retaining a non-null property type.

Goal

Build a base Activity that initializes SharedPreferences in onCreate() and a child Activity that safely reads and writes a preference.

Requirements

  • Create a BaseActivity with a protected lateinit SharedPreferences property.
  • Initialize the property in BaseActivity.onCreate().
  • Create a child Activity that calls super.onCreate() before accessing the property.
  • Read a dark_mode Boolean preference with false as its default value.
  • Save the opposite Boolean value back to preferences.
Take Quiz

Keep learning

Related questions

View more