Kotlin views with synthetic binding and nullability - android

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.

Related

Why does ?. is inevitable when calling a method although instance isn't nullable?

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

Kotlin sytax for '?' constant issue

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.

kotlin - prevent lambda from capturing a view accessed using kotlin synthetics

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!

Kotlin Type mismatch, required: x found: x?

I find a lot of arguments have the error
Type mismatch
required: FragmentActivity
found: FragmentActivity?
I'm not sure of what's the best way to address this problem.
Currently, I wrap the line in a variable?.let{ statement }
meViewModel = ViewModelProviders.of((iMainActivity as Fragment).activity, vmf).get(MeViewModel::class.java) }
into
val fragmentActivity = (iMainActivity as Fragment).activity
fragmentActivity?.let
{
meViewModel = ViewModelProviders.of(fragmentActivity, vmf).get(MeViewModel::class.java)
}
is it the right way to approach this
Short answer: Yes.
This means that the compiler is not sure if s.th. is !=null. If you are sure that it is not null you can also use:
val fragmentActivity = (iMainActivity as Fragment).activity!!
That gives you FragmentActivity instead of FragmentActivity? and you dont need the ?.let{}
Keep in mind that that might throw a NPE, while the
fragmentActivity?.let { fragment ->
meViewModel = ViewModelProviders.of(fragment, vmf).get(MeViewModel::class.java)
}
would simply not execute the block within .let{}, which is often less harmful then a NPE. See https://kotlinlang.org/docs/reference/null-safety.html for more.
Short answer: Yes.
With ?.let you can be sure that the value will be non null, so you have the null safety you would expect. Just keep in mind that in some cases you cannot use the smartcast which you did in your code above.
Smart casts [...] don't work on var properties, and they always work on local variables (val and var). They also don't work on val properties with a custom getter, because val doesn't mean final in Kotlin.
Quote from Marko Topolnik in the comments.
This is because, in a rare edge case, the value could be changed by a different thread. You will get a compile error so that is prevented too. In that case you would need to use the implicit it or define an own alias like here:
fragmentActivity?.let { fragment ->
meViewModel = ViewModelProviders.of(fragment, vmf).get(MeViewModel::class.java)
}

Rx-java pass by reference or pass by value?

In java methods everything is passed-by-value so i can change the object attributes passed to the method and expect that the original object attributes are changed. but in this method i get different result:
I have this method:
public Observable<Menu> makeMenu(Menu menu, NumberSettingChanges.MenuChanges changes) {
// Start flow with added and edited extensions
return Observable.from(changes.added.entrySet())
.mergeWith(Observable.from(changes.edited.entrySet()))
//Upload announcement voices or do nothing if extension is not an announcement
.flatMap(e -> {
if (AppTypeContract.APP_TYPE_ANNOUNCEMENT.equals(e.getValue().type)) {
return mMediaManager.uploadAsync(e.getValue().config.localPrompt)
.doOnNext(response -> {
//Update extension prompt with the storage path.
menu.config.extensions.get(e.getKey()).config.prompt = response.mPath;
menu.config.extensions.get(e.getKey()).config.localPrompt = "";
})
.flatMap(response -> Observable.just(e));
} else {
return Observable.just(e);
}
}
)
}
and i manipulate menu attributes in the flatmap:
menu.config.extensions.get(e.getKey()).config.localPrompt = "";
I call the method in the same class:
public Observable<NumberSetting> saveSettings(NumberSetting o, NumberSetting n) {
NumberSettingChanges changes = compareNumberSetting(o, n);
return makeMenu(n.day, changes.day)
.mergeWith(makeMenu(n.night, changes.night));
}
and finally:
saveSettings(ns, mNumberSettingNew).subscribe();
What i expect is that the mNumberSettingNew.menu.config.extensions.get(e.getKey()).config.prompt is changed but no change is happening after this call and the mNumberSettingNew has no change at all.
Note that i am sure that changing prompt line is done in the debug.
I don't think I could explain Java's parameter semantics any better than (or even half as good as) the link you referenced in your first paragraph so I won't try. The main point is: Everything in Java is passed by value (i. e. copied) but with objects what is copied is not the object itself but the reference to the object. So in other words the reference is passed by value.
So with respect to your particular problem: Yes, if you pass a reference to a mutable object to some rx-java code that reference will point to the same instance of the object. If you mutate the instance then the caller code will also be able to see the changes because they were made on the same instance. That's because rx-java is still only Java and cannot change the language semantics on that level.
Without seeing the whole code I am unsure what could be the problem here... When are you checking whether mNumberSettingsNew actually has the changes you were making in your doOnNext? If you check that immediately after saveSettings(ns, mNumberSettingNew).subscribe(); your uploadAsync may not have returned yet. You could try adding an actual Subscriber in your subscribe and check the result there.
On a more general note, I think you should try to avoid side-effects like this as much as you can when using rx-java. Your case - taking an input object, applying a set of (possibly asynchronous) changes to that object, and waiting for the changed output object - is a bit tricky, but I think it could be done with scan. Maybe something vaguely like this:
Observable.from(changes.added.entrySet())
.mergeWith(Observable.from(changes.edited.entrySet()))
.scan(menuBeforeAnyChanges, new Func2<Menu, Change, Menu>() {
public Menu call(final Menu previousVersionOfTheMenu, final Change nextChange) {
// since I don't know of a version of scan that can return
// an Observable you would I think you would have to adapt
// your code in here to be fully synchronous - but of
// course the scan itself could run asynchronously
final newVersionOfTheMenu = previousVersionOfTheMenu.applyChange(nextChange);
return newVersionOfTheMenu;
}
)
This would take the original Version of the menu, consecutively apply all the changes from added and edited and /emit/ every updated version of menu. So you would not have any side effects but simply subscribe to that observable with a Subscriber<Menu> and then take the last() Menu and that would be the one with all changes applied.
EDIT: Oh, I just saw that there is another method called reduce that does just that: first scan and then last or takeLast.

Categories

Resources