I have a variable in my fragment class:
private lateinit var dManager: DataManager
And I'm initializing it before the first using here:
override fun onResume() {
super.onResume()
dManager = MyApp.gManager.getDataManager(sp,level,test)
if (dManager.hp< 1) {
...
...
...
}
}
This code works ok for me and most users (99.5%), but sometimes i get crash report
lateinit property dManager has not been initialized
How can this happen? What should I do to prevent it?
lateinit var makes compiler aware that’s not null
If your property is lifecycle-driven (e.g. a reference to a TextView
or ImageView which gets inflated during Android Activity lifecycle)
or it is initialized through injection, you cannot supply a non-null
initializer and you must declare its type as nullable. This in turn
will require you to use null checks every time you reference the
property, which may be a bit inconvenient, especially if you are
absolutely sure the property will get initialized at some point,
before you access it for the first time.
Kotlin has a simple solution for such a scenario, allowing you to mark the property with the lateinit modifier.
If you access the property before initialization, you’ll get
an UninitializedPropertyAccessException.
getDataManager(sp,level,test) may return sometimes null so for safe sides your solution would be like as :-
override fun onResume() {
super.onResume()
dManager = MyApp.gManager.getDataManager(sp,level,test)
if (::dbManager.isInitialized && dManager.hp< 1) {
...
...
...
}
}
May be your getDataManager(sp,level,test) return null value
OR
As per the document you have to check object with .isInitialized property.
Returns true if this lateinit property has been assigned a value, and false otherwise.
Check lateinit var is initialized
lateinit var file: File
if (::file.isInitialized) { ... }
Related
And the Kotlin newbie asks, "why won't the following code compile?":
var left: Node? = null
fun show() {
if (left != null) {
queue.add(left) // ERROR HERE
}
}
Smart cast to 'Node' is impossible, because 'left' is a mutable
property that could have been changed by this time
I get that left is mutable variable, but I'm explicitly checking left != null and left is of type Node so why can't it be smart-casted to that type?
How can I fix this elegantly?
Between execution of left != null and queue.add(left) another thread could have changed the value of left to null.
To work around this you have several options. Here are some:
Use a local variable with smart cast:
val node = left
if (node != null) {
queue.add(node)
}
Use a safe call such as one of the following:
left?.let { node -> queue.add(node) }
left?.let { queue.add(it) }
left?.let(queue::add)
Use the Elvis operator with return to return early from the enclosing function:
queue.add(left ?: return)
Note that break and continue can be used similarly for checks within loops.
1) Also you can use lateinit If you are sure you will do your initialization later on onCreate() or elsewhere.
Use this
lateinit var left: Node
Instead of this
var left: Node? = null
2) And there is other way that use !! end of variable when you use it like this
queue.add(left!!) // add !!
There is a fourth option in addition to the ones in mfulton26's answer.
By using the ?. operator it is possible to call methods as well as fields without dealing with let or using local variables.
Some code for context:
var factory: ServerSocketFactory = SSLServerSocketFactory.getDefault();
socket = factory.createServerSocket(port)
socket.close()//smartcast impossible
socket?.close()//Smartcast possible. And works when called
It works with methods, fields and all the other things I tried to get it to work.
So in order to solve the issue, instead of having to use manual casts or using local variables, you can use ?. to call the methods.
For reference, this was tested in Kotlin 1.1.4-3, but also tested in 1.1.51 and 1.1.60. There's no guarantee it works on other versions, it could be a new feature.
Using the ?. operator can't be used in your case since it's a passed variable that's the problem. The Elvis operator can be used as an alternative, and it's probably the one that requires the least amount of code. Instead of using continue though, return could also be used.
Using manual casting could also be an option, but this isn't null safe:
queue.add(left as Node);
Meaning if left has changed on a different thread, the program will crash.
The practical reason why this doesn't work is not related to threads. The point is that node.left is effectively translated into node.getLeft().
This property getter might be defined as:
val left get() = if (Math.random() < 0.5) null else leftPtr
Therefore two calls might not return the same result.
Change var left: Node? = null to lateinit var left: Node. Problem solved.
Your most elegant solution must be:
var left: Node? = null
fun show() {
left?.also {
queue.add( it )
}
}
Then you don't have to define a new and unnecessary local variable, and you don't have any new assertions or casts (which are not DRY). Other scope functions could also work so choose your favourite.
Do this:
var left: Node? = null
fun show() {
val left = left
if (left != null) {
queue.add(left) // safe cast succeeds
}
}
Which seems to be the first option provided by the accepted answer, but that's what you're looking for.
For there to be a Smart Cast of the properties, the data type of the property must be the class that contains the method or behavior that you want to access and NOT that the property is of the type of the super class.
e.g on Android
Be:
class MyVM : ViewModel() {
fun onClick() {}
}
Solution:
From: private lateinit var viewModel: ViewModel
To: private lateinit var viewModel: MyVM
Usage:
viewModel = ViewModelProvider(this)[MyVM::class.java]
viewModel.onClick {}
GL
Try using the not-null assertion operator...
queue.add(left!!)
How I would write it:
var left: Node? = null
fun show() {
val left = left ?: return
queue.add(left) // no error because we return if it is null
}
Perform as below :-
var left: Node? = null
Use a null safe call
left?.let { node -> queue.add(node) } // The most preferred one
This worked for me:
private lateinit var varName: String
Lets assume, there is a non-null enum MainFlag object ( like enum class MainFlag { PARAM1, PARAM2 }) in an Activity by lateinit var:
private lateinit var flag: MainFlag
Furtheron, I get that later in the onCreate() like:
flag = intent?.extras?.getSerializable(ARG_FLAG) as MainFlag
and I used to use this flag in that activity on several places with null-checks
flag?.let{
//..
}
but then Android Studio complains:
Unnecessary safe call on a non-null receiver of type
MainActivity.Companion.MainFlag
I am not sure using it without null checks, because if intent?.extras? fails, flag will not be set, thus null?
yes it can be null. You are getting the warning because you are down casting to a not nullable type
intent?.extras?.getSerializable(ARG_FLAG) as MainFlag
should be
intent?.extras?.getSerializable(ARG_FLAG) as? MainFlag
First of all you have to declare the variable as
private lateinit var flag: MainFlag?
or
private var flag: MainFlag? = null
then you can set the value of flag like this
flag = intent?.extras?.getSerializable(ARG_FLAG) as? MainFlag
Now the flag variable is nullable. So you need to check for nullable everywhere in the code like this:
flag?.let{
//..
}
If you are sure that intent?.extras?.getSerializable(ARG_FLAG) will never be null (and also none of the intermediate objects: intent and intent.extras), then you can keep everything as is and just use flag.let { ... instead of flag?.let { ....
If there is a chance that any of those is null, then you have to use a MainFlag?-type for your flag, e.g.:
private var flag : MainFlag? = null // having null in place basically makes the lateinit meaningless and rightfully lateinit does not allow nullable types
flag = intent?.extras?.getSerializable(ARG_FLAG) as? MainFlag
... or risk a TypeCastException when keeping as MainFlag.
The reason why Android Studio complains about the unnecessary safe call, is, that you specified the type as non-nullable MainFlag (indirectly via your as MainFlag-cast). So if you access flag it must already be there and it must be not-null (you said it so). The code however will fail at the cast, if there was a null-value involved, i.e. the cast to the non-nullable type could not succeed.
I want to create a singleton class, but unfortunately, Android needs Context for almost anything so I need it to create an instance. So I just assumed the user called init(), and then return the instance. As you see below, if the _instance is null, an exception will be thrown, so the get method cannot return null.
But Kotlin says I must initialise instance. The things is, that MyClass cannot be created without a context. So I would like not to specify an initial value. How can I do that?
companion object
{
protected var _instance:MyClass? = null;
fun init(context:Context)
{
_instance = MyClass(context)
}
var instance:MyClass //<---This causes a compile error.
get()
{
if(_instance==null) throw RuntimeException("Call init() first.");
return _instance!!;
}
}
Change the var to val and it should work:
....
val instance: MyClass
....
A variable property (var) not only assumes a getter, but also a setter. Since you provided no setter, a default one was generated set(value) { field = value }. Despite is uselessness in this situation, the default setter uses field, thus requires its initialization.
Use lateinit property
public class MyTest {
lateinit var subject: TestSubject
fun setup() {
subject = TestSubject()
}
fun test() {
subject.method()
}
}
And the Kotlin newbie asks, "why won't the following code compile?":
var left: Node? = null
fun show() {
if (left != null) {
queue.add(left) // ERROR HERE
}
}
Smart cast to 'Node' is impossible, because 'left' is a mutable
property that could have been changed by this time
I get that left is mutable variable, but I'm explicitly checking left != null and left is of type Node so why can't it be smart-casted to that type?
How can I fix this elegantly?
Between execution of left != null and queue.add(left) another thread could have changed the value of left to null.
To work around this you have several options. Here are some:
Use a local variable with smart cast:
val node = left
if (node != null) {
queue.add(node)
}
Use a safe call such as one of the following:
left?.let { node -> queue.add(node) }
left?.let { queue.add(it) }
left?.let(queue::add)
Use the Elvis operator with return to return early from the enclosing function:
queue.add(left ?: return)
Note that break and continue can be used similarly for checks within loops.
1) Also you can use lateinit If you are sure you will do your initialization later on onCreate() or elsewhere.
Use this
lateinit var left: Node
Instead of this
var left: Node? = null
2) And there is other way that use !! end of variable when you use it like this
queue.add(left!!) // add !!
There is a fourth option in addition to the ones in mfulton26's answer.
By using the ?. operator it is possible to call methods as well as fields without dealing with let or using local variables.
Some code for context:
var factory: ServerSocketFactory = SSLServerSocketFactory.getDefault();
socket = factory.createServerSocket(port)
socket.close()//smartcast impossible
socket?.close()//Smartcast possible. And works when called
It works with methods, fields and all the other things I tried to get it to work.
So in order to solve the issue, instead of having to use manual casts or using local variables, you can use ?. to call the methods.
For reference, this was tested in Kotlin 1.1.4-3, but also tested in 1.1.51 and 1.1.60. There's no guarantee it works on other versions, it could be a new feature.
Using the ?. operator can't be used in your case since it's a passed variable that's the problem. The Elvis operator can be used as an alternative, and it's probably the one that requires the least amount of code. Instead of using continue though, return could also be used.
Using manual casting could also be an option, but this isn't null safe:
queue.add(left as Node);
Meaning if left has changed on a different thread, the program will crash.
The practical reason why this doesn't work is not related to threads. The point is that node.left is effectively translated into node.getLeft().
This property getter might be defined as:
val left get() = if (Math.random() < 0.5) null else leftPtr
Therefore two calls might not return the same result.
Change var left: Node? = null to lateinit var left: Node. Problem solved.
Your most elegant solution must be:
var left: Node? = null
fun show() {
left?.also {
queue.add( it )
}
}
Then you don't have to define a new and unnecessary local variable, and you don't have any new assertions or casts (which are not DRY). Other scope functions could also work so choose your favourite.
Do this:
var left: Node? = null
fun show() {
val left = left
if (left != null) {
queue.add(left) // safe cast succeeds
}
}
Which seems to be the first option provided by the accepted answer, but that's what you're looking for.
For there to be a Smart Cast of the properties, the data type of the property must be the class that contains the method or behavior that you want to access and NOT that the property is of the type of the super class.
e.g on Android
Be:
class MyVM : ViewModel() {
fun onClick() {}
}
Solution:
From: private lateinit var viewModel: ViewModel
To: private lateinit var viewModel: MyVM
Usage:
viewModel = ViewModelProvider(this)[MyVM::class.java]
viewModel.onClick {}
GL
Try using the not-null assertion operator...
queue.add(left!!)
How I would write it:
var left: Node? = null
fun show() {
val left = left ?: return
queue.add(left) // no error because we return if it is null
}
Perform as below :-
var left: Node? = null
Use a null safe call
left?.let { node -> queue.add(node) } // The most preferred one
This worked for me:
private lateinit var varName: String
I am confuse for lateinit and nullable variable, which one to use for variable.
lateinit var c: String
var d: String? = null
c = "UserDefinedTarget"
// if not added initialisation for c than throws UninitializedPropertyAccessException
if (c == "UserDefinedTarget") {
//do some stuff.
}
//not throws any exception whether d is initialise or not.
if(d == "UserDefinedTarget") {
//do some stuff
}
A type that is nullable is just that, a thing that has a valid state that is null.
A non-nullable late init var represents something where null is an invalid state, but for some reason you can't populate it in the constructor.
Android Activities are a good example of a use of lateinit. Activities must have a no args constructor and their lifecycle only really starts with onCreate().
These are two completely different concepts.
You can use lateinit to avoid null checks when referencing the property. It's very convenient in case your properties are initialized through dependency injection, or, for example, in the setup method of a unit test.
However, you should keep in mind that accessing a lateinit property before it has been initialized throws an exception. That means you should use them only if you are absolutely sure, they will be initialized.
Nullable types, on the other hand, are used when a variable can hold null.
class A {
lateinit var a: String
fun cat() {
print(a.length) // UninitializedPropertyAccessException is thrown
a = "cat"
print(a.length) // >>> 3
}
}
class B {
var b: String? = null
fun dog() {
print(b.length) // won't compile, null check is obligatory here
print(b?.length) // >>> null
b = "dog"
print(b?.length) // >>> 3
}
}
For more information:
Late-initialized properties
Nullable types
It depends.
Nullable variable means that variable can hold value or null. lateinit means that variable must be initialised later. It should be initialized before accessing it. If you attempt accessing uninitialized lateinit variable UninitializedPropertyAccessException will be thrown.
It's always better to avoid using nulls in your app. Nulls are evil. So if you can initialize variable in onCreate then it's better to use lateinit. Also if you use dependency injection in your app and fields should be injected it's also a valid case to use lateinit instead of handling nulls.
If for some reason you can't initialize variable, initializing code can result to null, or null can be assigned to this variable later than you should use nullable variable. Generally speaking if null is a valid value for the variable.
Use lateinit for properties that cannot be initialized in a constructor.