In Kotlin, we have lateinit modifier to lazy variable initialization instead of var something: Something? = null.
In my situation, I have a list of the Element, and I want to assign it to the lateinit variable when I having the first object.
So, I tried several methods to achieve this.
First, using 'firstOrNull()' methods
lateinit var applicationHolder: ApplicationHolder
applicationHolder = env.getElementsAnnotatedWith(InjectApplication::class.java)
.map {
ApplicationHolder(it, (it as TypeElement).asClassName(), it.simpleName.toString()).apply {
val component = it.getAnnotation(InjectApplication::class.java).getComponent()
componentClass = component
}
}.firstOrNull()
The first solution was failed because applicationHolder doesn't accept the Nullable type of ApplicationHolder. (Type inference failed. Expected type mismatch: inferred type is ApplicationHolder? but ApplicationHolder was expected.)
Although I can use first instead of firstOrNull to achieve this, it's too dangerous because the list can be empty.
Second, using if-condition
val item = env.getElementsAnnotatedWith(InjectApplication::class.java)
.map {
ApplicationHolder(it, (it as TypeElement).asClassName(), it.simpleName.toString()).apply {
val component = it.getAnnotation(InjectApplication::class.java).getComponent()
componentClass = component
}
}.firstOrNull()
if (item != null) {
applicationHolder = item
}
The second solution was succeeded to compile and working well.
Third, using backing properties (Actually, this solution don't use lateinit modifier)
val applicationHolder: ApplicationHolder
get() {
return _applicationHolder ?: throw NullPointerException("Not initialized")
}
private var _applicationHolder: ApplicationHolder? = null
_applicationHolder = env.getElementsAnnotatedWith(InjectApplication::class.java)
.map {
ApplicationHolder(it, (it as TypeElement).asClassName(), it.simpleName.toString()).apply {
val component = it.getAnnotation(InjectApplication::class.java).getComponent()
componentClass = component
}
}.firstOrNull()
The third solution was succeeded to compile and working well.
In short, my question is as follows.
Is there a better solution to achieve my goal than these solutions?
If another solution doesn't exist, which is a clean or better solution? I can use the second or third solution but I have no confidence which is clean or better.
Why are you using lateinit over a nullable type here?
This strikes warning bells to me :
it's too dangerous because the list can be empty.
If you try to access a lateinit object without initialising it, your application will crash. Lateinit should be use if it will definitely be initialised.
I would change the code to what you were avoiding : var something: Something? = null
Then use the firstOrNull() method. The kotlin null safety type system will enforce you deal with nulls, leading to safe code!
It is possible to check whether a lateinit has been initialised, by calling this::applicationHolder.isInitialized. (This was introduced in Kotlin 1.2; info here.)
However, as per #user8159708's answer, it still smells a bit. Reworking to make the property nullable will make it slightly more awkward to access, but much safer.
Related
I'd like to pass a function reference on a nullable object. To take an Android example, say I want to use Activity#onBackPressed from a fragment that is a child of that actvity.
If I wanted to invoke this function, I could easily do
activity?.onBackPressed()
However, say I wanted to pass that as a reference instead:
val onBackPressedRef = activity::onBackPressed
This gives the familiar null safe error of Only safe or non null assserted calls are allowed...
I can get the error to go away with the following, but using !! is obviously not ideal:
val onBackPressedRef = activity!!::onBackPressed
Attemping activity?::onBackPressed was my first instinct, but this also breaks with several errors, where the interpreter seems confused.
val onBackPressedRef = activity?.let { it::onBackPressed }
This last variation works, but it's a lot more ugly than just using ?::. I checked all the docs I could find, but I feel like I'm missing something. Any ideas?
You are right, there is no ?:: operator in Kotlin.
You have several alternatives:
1. let and run
Thus, you have to use a helper function. Instead of let(), you can also use run(), making the expression a tiny bit shorter:
val onBackPressedRef = activity?.let { it::onBackPressed }
val onBackPressedRef = activity?.run { ::onBackPressed }
But keep in mind that either way, the invocation will be more verbose, too:
onBackPressedRef?.invoke(args)
Thus you should ask yourself, if this is really what you want, or if a no-op function call is also acceptable.
2. Closures
You could use a closure -- this will change semantics however:
val onBackPressedRef = { activity?.onBackPressed() }
Here, onBackPressedRef is not nullable anymore, so you can call it using the () operator, and in case of null activity it will have no effect.
3. Helper function
If function references with nullable objects are something you encounter a lot, you can write your own little abstraction:
// Return type: () -> Unit
fun <T> funcRef(obj: T?, function: T.() -> Unit) = { obj?.function() }
This trades a different syntax for a non-null function variable:
// activity can be null
val onBackPressedRef = funcRef(activity, Activity::onBackPressed)
// Callable directly
onBackPressedRef()
I need to know if this line code is right, my teacher told me it's correct, but I disagree 'cause "lateinit" can't be with a variable that could be null or not.
Line code:
lateinit var text : String?
Code:
val cadena = null
lateinit var text : String?
text = null
text = cadena ?: "Hola"
text?.let { println(text) }
You are correct and your teacher is wrong. Proof: lateinit var text : String? results in compilation error with Kotlin 1.3.50:
'lateinit' modifier is not allowed on properties of nullable types
How any teacher can possibly claim such code is correct is beyond me...
I would like to add a little deep dive into lateinit properties in Kotlin.
'lateinit' modifier is not allowed on properties of nullable type - this can be found in Kotlin documentation. This kind of modifier is for special kind of constructions. It's for fields that will be initialized somewhen after object creation. For example, via DI framework or mocking framework.
But, what is under that field? If we would check it, we will simply find that before initialization property has null value. Nothing more, nothing less, just null. But, if we would like to access that property before initialization UninitializedPropertyAccessException is thrown.
In Kotlin 1.3 lateinit properties got new property - isInitialized (to use it: ::lateinitiProperty.isInitilized). So, before we access that property we are able to check if under that field is null or something else, without throwing exception.
But, lateinit means that object will be initialized later as not null property. And a programmer guarantee that this value is not null after intialization. If it could be, why just not use nullable type?
If there is a way to uninialize lateinit property? Yes, it is. Via reflection we can set that value to null again (JVM is not null-safe). And accessing that field will not finish with NPE execption, but with UninitializedPropertyAccessException. And .isInitialized will return false for field that refers to null.
And how does it work?
class MyClass {
lateinit var lateinitObject: Any
fun test() {
println("Is initialized: ${::lateinitObject.isInitialized}") // false
lateinitObject = Unit
println("Is initialized: ${::lateinitObject.isInitialized}") // true
resetField(this, "lateinitObject")
println("Is initialized: ${::lateinitObject.isInitialized}") // false again
lateinitObject // this will throw UninitializedPropertyAccessException
}
}
fun resetField(target: Any, fieldName: String) {
val field = target.javaClass.getDeclaredField(fieldName)
with (field) {
isAccessible = true
set(target, null)
}
}
Ofc, using lateinit that way is probably not what you want, and treat is as a curio about lateinit design in JVM.
And due to your teacher - he wasn't right. Even if lateinit may refer to null (and it does actually), you cannot declare it as a nullable type. If you need to, you don't need lateinit modifier.
I have a lateinit var as
lateinit var someVariable: SomeVariable
I initialize this value like someVariable = SomeVariable() and use it whenever I need.
At a certain point, I want to set everything to default and want to "uninitialize" someVariable. How can I do that?
I am not looking for changing its type to a nullable object and set null. I need to keep it a Non-Null type.
I don't think it's possible without some kind of wrapper (or reflection, but about it in a moment).
In fact, lateinit is design for compatibility with DI frameworks etc. If you know the value can be uninitialized in any moment then you should use nullable type.
So, what about that reflection thing? lateinit is in fact a kind of smart wrapper that makes nullable value to act like not nullable, and instead of throwing NullPointerException throws UninitializedPropertyAccessException. lateinit property at the moment of declaration in JVM is null, so, let's make it null again ;)
So...
class MyClass {
lateinit var lateinitObject: Any
fun test() {
println("Is initialized: ${::lateinitObject.isInitialized}") // false
lateinitObject = Unit
println("Is initialized: ${::lateinitObject.isInitialized}") // true
resetField(this, "lateinitObject")
println("Is initialized: ${::lateinitObject.isInitialized}") // false
lateinitObject // this will throw UninitializedPropertyAccessException
}
}
fun resetField(target: Any, fieldName: String) {
val field = target.javaClass.getDeclaredField(fieldName)
with (field) {
isAccessible = true
set(target, null)
}
}
fun main() {
MyClass().test()
}
So, setting that field to null (and it's possible only via reflection) makes this filed uninitialized again. And one important thing - treat it as a curiosity, not like thing that should be in your production code.
In Kotlin, we have val that is final and can't be change. e.g.
val something = "Something"
If a value that is is initialized later, we use lateinit var.
lateinit var something: String
But this is var instead of val. I wanted to set something once (not in constructor), and have it as final. How could I achieve this?
Reading into the conventions of Kotlin, a late-initialized variable which is final is impossible.
Consider its use case:
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
lateinit var is providing relative sanity when dealing with a variable that may not have yet been initialized, such as the case with injected fields (like Spring and #Autowired). Then, speaking strictly in the context of dependency injection, if you don't have a way to concretely instantiate the variable at compile time, then you cannot leave it as a final field.
From a Java to Kotlin world, having a late initialized variable come in as final would look as paradoxical as this from Spring:
#Autowired
private final Interface something;
What do you think the behavior should be when you attempt to set it again? Do you expect this to be enforced at compile time? Should it cause a crash at runtime or just do nothing?
If you expect it to happen at compile time, I'm pretty sure it's not possible for a compiler to catch something like that.
If you want some other behavior, you can make it a private variable with a public set method that does whatever you want if it's been already set.
Or you could encapsulate it in an instance of a custom class that does whatever behavior you want.
You can use following delegate class:
import kotlin.reflect.KProperty
class WriteOnce<T> {
private var holder = holdValue<T>()
private var value by holder
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (!holder.hasValue) {
throw IllegalStateException("Property must be initialized before use")
}
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (holder.hasValue) {
throw RuntimeException("Write-once property already has a value")
}
this.value = value
}
}
fun <T> holdValue() = ValueHolder<T>()
class ValueHolder<T> {
var value: T? = null
var hasValue: Boolean = false
private set
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
hasValue = true
}
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return this.value!!
}
}
Usage:
var example by WriteOnce<String>()
If you try to write a to the variable a second time it will produce a RuntimeException:
java.lang.RuntimeException: Write-once property already has a value
Not having any value also produces an exception, similar to as if you were using lateinit:
java.lang.IllegalStateException: Property must be initialized before use
Which means this is val and lateinit combined because you can set the value at any time, only once ever.
The downside to this implementation is that this is not checked at compile time, meaning that you will only ever see errors in runtime. If that's acceptable in your use case, it certainly would be a good solution for what you're looking for.
For me this is more of a way to make sure that a variable is only every assigned once by code I control – something I can catch during testing as well as in production as a way to improve security by preventing foreign code from changing a variable.
It is possible you can use You can create a custom delegate for the property that is a combination of the existing notNull delegate and your own idea of set once. Read more about property delegates for information on how to create a custom one that can do whatever you want, including the use case you want here. You would then not use lateinit but instead this delegate.
Just ver confused about casting and how to set up class variables. In java it was possible to do
private var mSectionsStatePageAdapter : SectionsStatePagerAdapter? = null
private val mViewPager : ViewPager? = null
now we're in kotlin
class MainActivity : AppCompatActivity() {
private var mSectionsStatePageAdapter : SectionsStatePagerAdapter? = null
private val mViewPager : ViewPager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mytoolbar:Toolbar = findViewById(R.id.top_toolbar)
setSupportActionBar(mytoolbar)
mSectionsStatePageAdapter = SectionsStatePagerAdapter(getSupportFragmentManager())
mViewPager = findViewById(R.id.viewpager1)
setupViewPager(mViewPager)
}
fun setupViewPager(viewPager :ViewPager):Unit {
var adapter : SectionsStatePagerAdapter = SectionsStatePagerAdapter(getSupportFragmentManager())
adapter.addFragment(Fragment1(),"Fragment1")
viewPager.setAdapter(adapter)
}
I'm getting val can't be reassigned...
Error:(65, 24) Smart cast to 'ViewPager!' is impossible, because 'mViewPager' is a mutable property that could have been changed by this time
For Android, it is common to use lateinit var because you create the object outside of constructor (in onCreate, etc). You can go with two route:
lateinit var variable:Type
var variable:Type?
I would recommend if your variable should be available when you are ready to use it. You do not need to do null check. lateinit mean late initialization. Kotlin use null to represent not yet initialized, so you cannot use nullable type and assign null on it.
If your variable is nullable, then you should go the second way.
Beside, if you are handling views, you should use Android extension to do it. You don't need to findViewById in Activity or Fragment yourself.
There are two things happening here.
First, as #gyosida points out, you have defined your mPager property as val instead of var so it cannot be reassigned in the line mViewPager = findViewById(R.id.viewpager1). As #Joshua points out, you either have to make it a var or you need to make it a lateinit val to solve the problem of it not being initialised with the class instance.
The second is represented by the actual error you describe of, 'mutable property that could have been changed', and you will continue to see this if you make it a var. The approach of using lateinit is most likely the better idea.
However, to explain this error, in your method declaration of:
fun setupViewPager(viewPager: ViewPager): Unit {
you have said that the argument for viewPager cannot be null. If it were, it would be viewPager: ViewPager?. So, if you pass something that could be null into it, you will get a compile error.
What Kotlin is telling you is that in between the lines:
mViewPager = findViewById(R.id.viewpager1)
and
setupViewPager(mViewPager)
something - imagine another method on another thread - could potentially have changed the value of mViewPager from that assigned instance to null. Therefore it's not safe to pass it in.
The only way to solve this without changing the method is supply a value that is guaranteed to be non-null. There are a few ways you could do that:
assign your value to a method-level variable that can't be interfered with, and supply that as the argument
only call your function if the value is non-null, e.g. mViewPager?.let{ pager -> setupViewPager(pager)}
assert that mViewPager will not be null, leaving any violations to fail at runtime, e.g. setupViewPager(mViewPager!!)