Kotlin smart cast not working for LinearLayout.LayoutParams - android

I have implemented a function that I used to pass to anko`s applyRecursively.
Inside this function, I would like to add a marginEnd is the view is inside an LinearLayout, so I wrote the following code:
when(view.layoutParams) {
is LinearLayout.LayoutParams -> {
view.layoutParams.marginEnd = view.resources.getDimensionPixelSize(R.dimen.min_spacing)
}
}
And I receive the error that the view.layoutParams is a mutable property that could have been changed. So I had to force the cast:
when(view.layoutParams) {
is LinearLayout.LayoutParams -> {
(view.layoutParams as LinearLayout.LayoutParams).marginEnd = view.resources.getDimensionPixelSize(R.dimen.min_spacing)
}
}
Looking here at stackoverflow I saw that Kotlin don't smart cast in variables that can be nullable, but the view.layoutParams is not nullable, so why the smart cast can't infer the type?

Smart cast won't work in this case, because the type of the variable you did the type check on could have changed since that check passed successfully (for example, by a different thread), and if it did, you'd get a runtime exception when you attempt to cast it.
The solution is either to do the cast manually as you did, or to introduce a temporary val to your function, which smart cast will work on, since we know its type won't change:
val params = view.layoutParams
when(params) {
is LinearLayout.LayoutParams -> {
params.marginEnd = view.resources.getDimensionPixelSize(R.dimen.min_spacing)
}
}
Edit: As an additional note, you could also introduce this variable by using let:
view.layoutParams.let { params ->
when(params) {
is LinearLayout.LayoutParams -> {
params.marginEnd = view.resources.getDimensionPixelSize(R.dimen.min_spacing)
}
}
}

Related

How to create a View extension function in Kotlin

I'm trying to create an extension function in Kotlin. I did try several tutorials, but didn't quite understand, how to implement this one.
I'm trying to create a setWidth() function as such
//Find my_view in the fragment
val myView = v.findViewById<RelativeLayout>(R.id.my_view)
//Then use the extension function
myView.setNewWidth(500)
This is how I've defined my extension function
private fun View?.setNewWidth(i: Int) {
val layoutParams: ViewGroup.LayoutParams = View.layoutParams
layoutParams.width = i
View.layoutParams = layoutParams
}
I don't understand what I need to do here.
I want to call the extension function as myView.ExtensionFunction(), but I don't know how to do that. The tutorials, were un-informative.
I think the main problem here is how the extension function is defined, in particular, the lines that have View.layoutParams - this is calling a static property on View that doesn't exist. You need to use the one from the instance. If you'd write the extension function like so:
private fun View?.setNewWidth(i: Int) {
val layoutParams = this?.layoutParams
layoutParams?.width = i
this?.layoutParams = layoutParams
}
Then you can call the method like you want. Personally, I don't find this so readable and I'd remove the nullability here and write it as:
private fun View.setNewWidth(i: Int) {
val newLayoutParams = layoutParams
newLayoutParams?.width = i
layoutParams = newLayoutParams
}
The only difference is that now you need ?. to call the method if the view is nullable, which I personally find fine - myView?.setNewWidth(123). I assume most of the time you won't have a nullable view.
Ok, So my issue was that I didn't know how to get reference to the calling View. i.e., I didn't know how to call myView and set its property inside the extension function setNewWidth()
So, I tried using this? and it worked.
Then, I did a few changes to the extension function to work for myView which is a Relative Layout.
This is what I worked out:
private fun RelativeLayout?.setWidth(i: Int) {
val layoutParams: ViewGroup.LayoutParams? = this?.layoutParams
layoutParams?.width = i
this?.layoutParams = layoutParams
}

How to supply side effect for RxBinding.textChanges() and preserve it's functionality on client side?

Suppose that i'm writing my own View class, that represents editable field and contains EditText tv_input inside:
class EditTextIconItemView : LinearLayout {
fun setInputText(text: String?) {
with (tv_input) {
setText(text)
setCharsCount(text?.length ?: 0)
setSelection(text?.length ?: 0)
}
}
fun setCharsCount(count: Int) {
tv_chars_count?.text = "$count/$MAX_LENGTH"
}
}
I'd like to delegate textChanges() to that internal EditText tv_input, so i'm writing the following code in my custom EditTextIconItemView:
fun EditTextIconItemView.textChanges() =
tv_input.textChanges().doOnNext { setCharsCount(it.length) }
That works well, but now i want my client code to actually skip initial value, so in client code i have:
val et = EditTextIconItemView()
et.textChanges().skipInitialValue().subscribe { ... }
This requires me to explicitly specify the return type in EditTextIconItemView for textChanges():
fun EditTextIconItemView.textChanges(): InitialValueObservable<CharSequence> =
tv_input.textChanges().doOnNext { setCharsCount(it.length) }
But this won't compile since doOnNext returns Observable which cannot be cast to InitialValueObservable.
But i actually don't want the client code to handle that side effect and set up chars count on that View, this is the responsibility of EditTextIconItemView itself. But i' like to still be able to tell, whether to skip initial value or not on client's side.
How could i make it work?
Thank You!

Getting error "Smart cast to 'EditText!' is impossible, because 'usernameET' is a mutable property that could have been changed by this time" [duplicate]

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

Kotlin Multiple Layer it Reference

How do you reference the second layer "it" from the third layer without creating a new val/var? I know you can do val mydata = it and then do mydata.id.toString() I was just wondering is there something in Kotlin that can let me reference an it from a higher level?
data.arrayresults.forEach {
val result = it
result.myData.let {
val itemView - inflater.inflate(R.layout.somelayout)
itemView.setOnClickListener(View.OnClickListener {
// the it references the view but I want it to reference the result.myData
SomeActivity.startActivity(context, it.id.toString())
})
}
}
No, the it symbol always references the innermost implicit single lambda parameter.
To resolve this, and also to improve the code readability, use named lambda parameters every time when you have nested lambdas with parameters, as suggested in the Coding conventions:
data.arrayresults.forEach { result ->
result.myData.let { myData ->
val itemView - inflater.inflate(R.layout.somelayout)
itemView.setOnClickListener(View.OnClickListener { view ->
// the it references the view but I want it to reference the result.myData
SomeActivity.startActivity(context, myData.id.toString())
})
}
}

Unresolved reference inside anonymous Kotlin listener

I have the code below. It is Kotlin. Any idea why textToSpeech from textToSpeech.setLanguage(Locale.UK) is telling that there is no reference resolved for textToSpeech?
val textToSpeech = TextToSpeech(
applicationContext,
object : TextToSpeech.OnInitListener {
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.UK)
}
}
})
At first I assumed it is an Idea kotlin plugin bug, but it seems that it actually can't be compiled
Kotlin has hardened the variables initialization policy, and it's now prohibited to reference the variable inside its initializer, even in lambdas and object expressions, which seems reasonable: imagine that a lambda is called immediately before the variable assignment.
For your case, I can suggest as a workaround using an object expression in this quite cumbersome construct:
val textToSpeech = object {
val value: TextToSpeech get() = inner
private val inner = TextToSpeech(
applicationContext,
{ value.setLanguage(Locale.UK) }
)
}.value
This will initialize an anonymous object with inner inside that is acceptable through value property. Note that the inner initializer uses value property. Then the value is extracted and can be used.
But please keep in mind that this trick is unsafe: in runtime, using value before inner is assigned (e.g. in TextToSpeech constructor) will throw NullPointerException.
Also, I've replaced the OnInitListener with a lambda using SAM conversion to be short, but object expression can still be used there.
UPD: check this question for my effort to generalize this approach. Using it, you can write
val textToSpeech = selfReference {
TextToSpeech(
applicationContext,
{ self.setLanguage(Locale.UK) }
)
}
See the sources on Github.
This is a very readable and clear way to face that problem. First you should define this:
fun <T> selfReferenced(initializer: () -> T) = initializer.invoke()
operator fun<T> T.getValue(any: Any?, property: KProperty<*>) = this
and later use
val valueName: ValueType by selfReferenced{
//here you can create and use the valueName object
}
Using your problem as example you can do:
val textToSpeech:TextToSpeech by selfReferenced {
TextToSpeech(
applicationContext,
TextToSpeech.OnInitListener { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.UK)
}
})
}
Inside the selfReferenced block you can use the outer object with no restrictions. The only thing you should take care of, is declaring the type explicitly to avoid recursive type checking issues.

Categories

Resources