6

I have write my first Kotlin and android app and i'm facing a lot of crashes on this app.

All of them are related to the lateinitkeyword.

i get crashes like :

Caused by e.y: lateinit property coordinator has not been initialized

and:

Fatal Exception: java.lang.RuntimeException
Unable to start activity ComponentInfo{app.myapp/mypackage.myapp.Controllers.MainActivity}: e.y: lateinit property coordinator has not been initialized

Caused by e.y
lateinit property coordinator has not been initialized
myapp.com.myapp.Controllers.Fragments.Parents.MyParentFragment.getCoordinator


Last one trace for example is related to a variable i set on my fragment once i initialize it like this :

        fun newInstance(mainObject: MyObject, anotherObject: AnotherObject, coordinator: FragmentCoordinator): MyFragment {
            val fragment = MyFragment()
            fragment.mainObject = mainObject
            fragment.anotherObject = ticket
            fragment.coordinator = coordinator
            return fragment
        }

Fragment side looks like :

class MyFragment: MyParentFragment() {

     companion object {
        fun newInstance(mainObject:...)
     }

    lateinit var mainObject: MainObject
    lateinit var anotherObject: AnotherObject
    ... 
}

What i understand is that when app changes its state (background...) this lateinit properties reference get lost, once the code call this variable property is null and app crash... Please understand that once they fragment is created all this variable are not null, they get allocated.

i have found this article : https://www.bignerdranch.com/blog/kotlin-when-to-use-lazy-or-lateinit/ which indicates what happened and can be fixed linking the variable to the app lifecycle using by lifecycleAwareLazy(lifecycle) thing is at the ends of the article he adds: These two property delegates solve one problem, but not completely. They still contain a memory leak. Why does he says "they still contains memory leaks ?"

So how on android i can be sure that my variable always be set wether the app comes back from background or whatever, because this happen when the fragment is displayed by the app, some codes run good and then needs to call this lateinit variable and crash because this variable does not exist anymore but existed once the fragment where create.

Thank you for any help.

user2335528
  • 161
  • 1
  • 3
  • 13
  • you must have to initialize your lateinit var in kotlin else it throws error in fragment you can use onViewCreated(...) function to initialize your views ie. lateinit var imgAddress: AppCompatImageView imgAddress = view.findViewById(R.id.imgSelectAddress) as AppCompatImageView. Also there is no need to declare var. in activity you can directly use it with XML id name. – Madhav Sep 17 '19 at 11:10

3 Answers3

6

When your activity gets recreated, FragmentManager invokes 0-argument constructor of each attached Fragment to recreate them as well.

Your newInstance method should only create a Bundle and use it in Fragment.setArguments(Bundle) as this is the only way to ensure those arguments survive configuration changes. Then inside onCreate you can getArguments to retrieve that Bundle.

If you need arguments that cannot be put in a Bundle, you have to inject / recreate / fetch them inside Fragments onCreate or other method.

Pawel
  • 15,548
  • 3
  • 36
  • 36
0

lateinit indicates that you are not assign the variable value at declaration time but you have to assign the value late in your java class before uses it.

If you tries to get the value of lateinit variable before assignment than lateinit property coordinator has not been initialized exception is occur.

If you are not sure that you lateinit varibale is assign or not you can check the value using isInitialized method given for lateinit variable to avoid crashes.

Example :

if(::varibleName.isInitialized){
// do something
}

If you want to save the variables values after configuration changes you have to use ViewModel.

If you not uses ViewModel than pass the values by bundle and get the values again from it and re-assign the variables, but note that if you are passing too many values using bundle than you will get the TooLargeTransition exception. You can also used onSaveInstanceState to store the updated values and retrieve it in onCreate method.

Mahavir Jain
  • 1,448
  • 10
  • 23
-1
    fun newInstance(mainObject: MyObject, anotherObject: AnotherObject, coordinator: FragmentCoordinator): MyFragment {
        val fragment = MyFragment()
        fragment.mainObject = mainObject
        fragment.anotherObject = ticket
        fragment.coordinator = coordinator
        return fragment
    }

You cannot create a Fragment like this, you cannot set field variables like this.

The system recreates fragments after low memory condition using no-arg constructor via reflection. Your code, here, newInstance and stuff - that never runs. Never.

On another hand, setArguments(Bundle arguments are retained by the system, and you would be able to get them using getArguments().

So currently, if your app is put to background, killed by Android, and you open the app from launcher again, then you'll crash.

See related questions:

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428