I use the Kotlin Android Extensions plugin for accessing views from XML. If I understand that properly, the synthetic properties generated for the views are nullable, to be more precise - their type is View!, meaning that they can be treated as non-null in Kotlin, but actually they can still be null because under the hood findViewById is called, which is from Java, so Kotlin can't ensure null-safety. This is okay because we can use the ?. operator.
The problem comes when you need to use these in a lambda because the null value gets captured and you never get the chance to handle the non-null value.
See these two snippets.
In the moment these are executed, view1 is non null, but view2 is null:
//this one crashes
view1.setOnClickListener {
view2.doStuff()
}
//this one works
view1.setOnClickListener {
findViewById<View>(R.id.view2).doStuff()
}
What happens in the first case is that when the line is executed, view2 is null and the lambda captures it as null. So even if view2 is not null anymore when the lambda runs (view1 was clicked), the app crashes.
In the second case, the lambda doesn't capture anything, so even though view2 is null at first, it is retrieved again every time the lambda runs (view1 is clicked).
So the question is: How can I use the synthetic property in lambdas, without the initial value being captured?
Thank you!
Related
This question already has answers here:
Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time
(12 answers)
Closed 1 year ago.
I am studying Android and I am also studying Kotlin.
While writing Android code, I was curious about using it in a let function.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var curFrag: Fragment? = null
curFrag = fm.primaryNavigationFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// curFrag?.let { transaction.hide(curFrag) } // error.
curFrag?.let { transaction.hide(it) }
}
}
ERROR
Smart cast to 'Fragment' is impossible, because 'curFrag' is a mutable property that could have been changed by this time
In the lambda expression of let(), T is curFrag and the type is Fragment? is.
And T(curFrag) can be replaced by it.
But the moment I used curFrag instead of it, the IDE displayed an error message.
Later, when I checked the type of it, it was Fragment? It was not a Fragment type.
Honestly, I don't understand well.
I don't know why it is automatically smart cast and should only be used for immutable variables.
Kotlin is a null safe language, it tries to eliminate every possible null references from the code. You can perform a nullability check on the variable and then can use it like this
if(curfrag != null) { transaction.hide(curFrag)
This too will only work if variable curfrag is immutable (that means a local variable which is not modified between the check and the usage or a member val which has a backing field and is not overridable), because otherwise it might happen that curfrag changes to null after the check from some other thread.
But Safe calls ?. with let always gives us non nullable result, what Safe calls operator ?. does is, it only performs any operation following it, only if the variable is not-null otherwise it returns null.
It works with all mutable types or member var, It check for the null once and then provides the result. If value is non null it performs the defined operation otherwise skips it. it refers to the copy of that non-null value.
So when you do this
curFrag?.let { transaction.hide(curFrag) }
curFrag can be null as you are directly passing a nullable value.
But in this case
curFrag?.let { transaction.hide(it) }
it only passes value if it's a non-null value.
The let function basically creates a new variable with the same value as whatever you called it on, so it is not really smart-casting the original property.
If you use ?.let, let isn't even called if the value was null. The safe call means the receiver let is being called on is not a nullable value to begin with because otherwise let isn't called at all. The it inside let is just a reference to what it was called on.
Effectively, though it is conceptually similar to smart-casting. There is not really a way to write equivalent Kotlin code that does what ?.let is doing because the ?. safe call is a special operator that has no expanded form.
I use findViewById to get references to views in a custom DialogClass outside the view hierarchy. Since I want these references throughout the dialog class, and since they are not available until the call to setContentView, I use lazy field initialization in the dialog class definition:
private val balanceView : TextView? by lazy { dialog?.findViewById(R.id.balanceTextView)}
Later, in a separate function that runs after the call to setContentView, I have two commands related to balanceView:
private fun setupBalance(){
balanceView!!.visibility = View.VISIBLE
balanceView.text = presentation.balance
}
I expected the second command to compile since we are ensuring balanceView is not null in the first command. However, the compiler underlines the second command with this message:
Smart cast to 'TextView' is impossible, because 'balanceView' is a property that has open or custom getter
I was wondering how to interpret this message - to my knowledge the lazy keyword doesn't make a variable open.
The use of a property delegate like lazy means it has a custom getter. A custom getter prevents the compiler from being able to ensure that the value returned by the property will be the same in your two consecutive calls, so it cannot smart-cast.
Since balanceView is nullable, you shouldn't be using !! either, or your app will crash when balanceView is null. The correct way to write your function (if you don't know if balanceView is currently null) would be like this:
private fun setupBalance(){
balanceView?.apply {
visibility = View.VISIBLE
text = presentation.balance
}
}
If there's no chance of it being null, don't use a nullable property, and then you won't have to worry about null-safe calls and smart-casting. But you seem to also have a nullable dialog that it is dependent on. I don't know what's going on higher-up-stream, so I can't help with that.
I have to following variable declaration:
var baseItemList: MutableList<BaseDataItem>? = null
when writing the line:
baseDataItemsList?.get(position).getObjectTypeNum()
I'm getting an error saying that:
Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type BaseDataItem?
but, get method doesn't return a BaseDataItem?, only a BaseDataItem since the BaseDataItem inside the brackets is without a question mark.
Can someone explain me this error, and why i have to add this question mark?
Looking at this code:
baseDataItemsList?.get(position)?.getObjectTypeNum()
The call ?.get(position) returns the position if baseDataItemsList is not null, but otherwise returns null. So even though baseDataItemsList.get() would return a non-nullable BaseDataItem (only possible to call if baseDataItemsList is not nullable), the null-safe baseDataItemsList?.get() call returns a nullable BaseDataItem?, where the null condition indicates that baseDataItemsList is null. So you must use ?.getObjectTypeNum() to account for this.
Side note: in my opinion combining var with a mutable collection is often a code smell, because you're making something mutable in two different ways, which makes it more error-prone to work with.
Make use of Kotlins scope functions, for example the let scope to avoid that warning:
baseDataItemsList?.let { baseDataItemList ->
baseDataItemList.get(position).getObjectTypeNum()
}
That way you assert that baseDataItemList cannot be null inside the let scope. If you want to read more about that topic, take a look into the documentation
I must be doing something wrong with Kotlin implementation of view models
I have a view model that has a function to retrieve youtube video id from url.
fun getYoutubeVideoId(url: String): String?{
return "([a-zA-Z0-9_-]{11})".toRegex().find(url)?.value
}
I feel like I'm always in catch 22 because I use this function in a fragment inside with LiveData observable, which forces me to to ? on objects, which then forces me to have return type with ?, which then tirggers if statements to check if objects aren't null.
Here is the vm var
val streamUrl= mainState.getOrNull { it?.account?.streamUrl ?: 0}.distinctUntilChanged()
Here is my shortened observable
streamUrl.observe{
playVideo(getYoutubeVideoId(it))
}
The error from above statement is that it
Requires a String and I'm passing Any
Return should be String and its String?
I'm running around to make sure the types match and its always something not matching or being right. I think I could setup another streamUrl variable under the viewModel besides the observable, but I feel like I should be able to just do it of a single variable.
I hope this makes sense.
So the first thing to embrace with kotlin is: Null Safety.
Null Safety does not mean that you do not get nulls.
It means, that if something is possibly null, the compiler forces you to think about it and handle it at a point that makes sense. If you don't, you potentially get the notorious NullPointerException at an unexpected and possibly ugly point of execution.
So, to eliminate the ? think about where you want to handle the possibility of it being null -> check it -> handle it in an elegant way, and then safely pass the checked result without a ? to the rest of your code.
I've noticed that when using Kotlin's synthetic binding, the view returned is non null (Kotlin will return View!). But this doesn't make much sense to me, since findCachedViewById can actually return null results, meaning that views can actually be null.
public View _$_findCachedViewById(int var1) {
if(this._$_findViewCache == null) {
this._$_findViewCache = new HashMap();
}
View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
if(var2 == null) {
View var10000 = this.getView();
if(var10000 == null) {
return null;
}
var2 = var10000.findViewById(var1);
this._$_findViewCache.put(Integer.valueOf(var1), var2);
}
return var2;
}
So why are they not optional in this case? Why doesn't Kotlin simply return View? when using synthetic binding, so that developers would be forced to check nullability when dealing with views?
Maybe it's just because I'm new to Kotlin, but I think this is a bit counter intuitive, since the variable is not optional but we are still supposed to check if the View is in fact not null.
So in this case, does it make sense to do something like the code below?
view?.let {
// handle non null view here
}
I figured it out, I always find the correct SO question right after I post mine :)
The single exclamation point following the View does not actually mean that the view can not be null like I expected.
This answer to another question essentially answers my exact question. The View, when using synthetic binding, can actually be null, but we can't know for sure, hence the single exclamation mark.
So it's safe to assume that the code I posted above - using ?.let{...} is perfectly acceptable way of dealing with views when you are not sure if they are already initialised when accessing them.
The cases where views might be null are very rare, but it can happen.
As you pointed out already, a single exclamation mark does not mean that it's not null, but rather that it's a Java platform type and the compiler doesn't know if it's nullable or not.
I think what you have suggested is fine, although it fails silently in the actual case of a null which might not actually be what you want.
Let's say you tried to call your view in onCreateView and forgot that it will not be initialised yet. The fragment will not behave as expected but it won't produce a meaningful error to help you debug the issue.
I'm still trying to settle on one solution or another myself but I would suggest either explicitly handling the case of a null:
view?.let {
//...
} ?: throwExceptionIfDebugElseLogToCrashlytics()
Or decide that this time you actually want it to throw the NullPointerException in which case I would suggest:
view!!.let {
//...
}
The latter doesn't bloat your code for what "should" be an impossible edge case and it doesn't fail silently, but it still makes it clear to a reader that view could be null. Obviously the !! is not needed by the compiler, it is just there to make the chosen strategy for dealing with platform types more explicit.
Actualy null pointer exception can happen for synthetic view bindings, if you try to access view from listener out of context of an activity or view, or in lambdas.
The problem is in lambda, and Frantisek have post about it here:
https://stackoverflow.com/posts/comments/115183445?noredirect=1
The idea is that xml layouts in Android are pretty static and in order to use synthetic views, you must create a direct import of the parsed layout:
import kotlinx.android.synthetic.main.activity_main.*
So there are no real-life, non-magic scenarios where the View would be null. Unless you choose the wrong synthetic layout, but then you will get the crash on first run.
That said, it will of course break if you modify the view on runtime, removing Views etc. But again, this is not the default usage for synthetic Views and requires a different approach.