Home > Software engineering >  lateinit property has not been initialized - Android Lifecycle issue
lateinit property has not been initialized - Android Lifecycle issue

Time:03-25

I get an occasional app crash report which shouldn't occur based on my understanding of the Android lifecycle. Obviously I'm wrong somehow.

I have an Activity which initializes an API interface in the onCreate and provides a method called getApi()

class MainActivity : AppCompatActivity(), IApiProvider...{
...
    private lateinit var mApiInterface: ApiInterface
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        mApiInterface = ApiInterface.invoke(
            ConnectivityInterceptor(applicationContext, this),
            AuthorizationInterceptor(this),
            prefs,
            authenticator
        )
        ...
    }

    override fun getApi(): ApiInterface {
        return mApiInterface // THIS IS WHERE THE CRASH OCCURS
    }

}

I also have a Fragment which attempts to grab this Api interface from the activity in the following fashion

class MyFragment()...{

    private lateinit var mApiProvider: IApiProvider

    override fun onAttach(context: Context) {
        super.onAttach(context)

        if (context is IApiProvider) {
            mApiProvider = context
        } else {
            throw RuntimeException("$context must implement IApiProvider")
        }
    }

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

        setHasOptionsMenu(true)

        if (activity != null) {
            mViewModel = ViewModelProvider(
                this,
                MyViewModelFactory(requireActivity().application, mApiProvider.getApi()) // This call causes the crash in the Activity
            )
                .get(LapTimingViewModel::class.java)
        }

}

So even if the Activity gets recycled due to some Android memory management, shouldn't the Activity onCreate method be executed before the Fragment onCreate method and therefore the mApiInterface should be initialized?

Edit #1: Adding stacktrace

2022-03-24 12:45:32.600 23305-23305/hidden.package.name.debug E/AndroidRuntime: FATAL EXCEPTION: main
    Process: hidden.package.name.debug, PID: 23305
    java.lang.RuntimeException: Unable to start activity ComponentInfo{hidden.package.name.debug/hidden.package.name.MainActivity}: android.view.InflateException: Binary XML file line #11 in hidden.package.name.debug:layout/activity_main: Binary XML file line #47 in hidden.package.name.debug:layout/app_bar_main: Error inflating class fragment
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3527)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3702)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2151)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:250)
        at android.app.ActivityThread.main(ActivityThread.java:7844)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:958)
     Caused by: android.view.InflateException: Binary XML file line #11 in hidden.package.name.debug:layout/activity_main: Binary XML file line #47 in hidden.package.name.debug:layout/app_bar_main: Error inflating class fragment
     Caused by: android.view.InflateException: Binary XML file line #47 in hidden.package.name.debug:layout/app_bar_main: Error inflating class fragment
     Caused by: kotlin.UninitializedPropertyAccessException: lateinit property mApiInterface has not been initialized
        at hidden.package.name.MainActivity.getApi(MainActivity.kt:536)
        at hidden.package.name.ui.laptiming.LapTimingFragment.onCreate(LapTimingFragment.kt:112)
        at androidx.fragment.app.Fragment.performCreate(Fragment.java:2684)
        at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:280)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1175)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
        at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2625)
        at androidx.fragment.app.FragmentManager.dispatchCreate(FragmentManager.java:2571)
        at androidx.fragment.app.Fragment.restoreChildFragmentState(Fragment.java:1707)
        at androidx.fragment.app.Fragment.onCreate(Fragment.java:1683)
        at hidden.package.name.ui.home.BottomNavigationFragment.onCreate(BottomNavigationFragment.kt:62)
        at androidx.fragment.app.Fragment.performCreate(Fragment.java:2684)
        at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:280)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1175)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
        at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2625)
        at androidx.fragment.app.FragmentManager.dispatchCreate(FragmentManager.java:2571)
        at androidx.fragment.app.Fragment.restoreChildFragmentState(Fragment.java:1707)
        at androidx.fragment.app.Fragment.onCreate(Fragment.java:1683)
        at androidx.navigation.fragment.NavHostFragment.onCreate(NavHostFragment.java:264)
        at androidx.fragment.app.Fragment.performCreate(Fragment.java:2684)
        at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:280)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1175)
2022-03-24 12:45:32.601 23305-23305/hidden.package.name.debug E/AndroidRuntime:     at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
        at androidx.fragment.app.FragmentLayoutInflaterFactory.onCreateView(FragmentLayoutInflaterFactory.java:135)
        at androidx.fragment.app.FragmentController.onCreateView(FragmentController.java:135)
        at androidx.fragment.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:356)
        at androidx.fragment.app.FragmentActivity.onCreateView(FragmentActivity.java:335)
        at android.view.LayoutInflater.tryCreateView(LayoutInflater.java:1067)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:995)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:959)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1121)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1082)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1124)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1082)
        at android.view.LayoutInflater.parseInclude(LayoutInflater.java:1261)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1117)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1082)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:680)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:532)
        at hidden.package.name.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:52)
        at hidden.package.name.databinding.ActivityMainBinding.inflate(ActivityMainBinding.java:46)
        at hidden.package.name.MainActivity.onCreate(MainActivity.kt:83)
        at android.app.Activity.performCreate(Activity.java:8114)
        at android.app.Activity.performCreate(Activity.java:8098)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3500)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3702)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2151)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:250)
        at android.app.ActivityThread.main(ActivityThread.java:7844)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:958)

CodePudding user response:

After a config change, fragments get restored when you call activity super.onCreate(savedInstanceState). You should have the activity ready for your fragments init at that point. One way to do that is to postpone the super.onCreate() call to a later point in your onCreate().

CodePudding user response:

Before returning the lateinit property where a crash occurs, you can simply check if that property is initialized or not like this:

this::propertyName.isInitialized

If the property has not been initialized, you can simply return something else or null which will avoid the crash.

CodePudding user response:

Found an answer thanks to @laalto

I changed my Activity onCreate from this:

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

        mBinding = ActivityMainBinding.inflate(layoutInflater)
        val view = mBinding.root
        setContentView(view)

        val toolbar: Toolbar = findViewById(R.id.toolbar)
        toolbar.visibility = View.GONE
        setSupportActionBar(toolbar)
        setupDrawerNav()

        ...

        mApiInterface = ApiInterface.invoke(
            ConnectivityInterceptor(applicationContext, this),
            AuthorizationInterceptor(this),
            prefs,
            authenticator
        )
     ...
     }

to this

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

        ...

        mApiInterface = ApiInterface.invoke(
            ConnectivityInterceptor(applicationContext, this),
            AuthorizationInterceptor(this),
            prefs,
            authenticator
        )
        

        mBinding = ActivityMainBinding.inflate(layoutInflater)
        val view = mBinding.root
        setContentView(view)

        val toolbar: Toolbar = findViewById(R.id.toolbar)
        toolbar.visibility = View.GONE
        setSupportActionBar(toolbar)
        setupDrawerNav()

Basically, I only bind the view after the init is done...

  • Related