I am working with this Kotlin function. I know that we have a function called mPasswordView!!.setOnEditorActionListener, that take parameter TextView.OnEditorActionListener, but what's that after it? we have curly brackets inside parameter?
mPasswordView!!.setOnEditorActionListener(TextView.OnEditorActionListener { textView, id, keyEvent ->
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin()
return#OnEditorActionListener true
}
false
})
The feature used in your example is a SAM constructor. The setOnEditorActionListener listener takes an OnEditorActionListener as its parameter. This interface only has a single method that you have to implement, which makes it a Single Abstract Method (SAM) interface.
The full syntax for using this method in Java would be:
mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
attemptLogin();
return true;
}
});
The one-to-one conversion to Kotlin would give you:
mPasswordView.setOnEditorActionListener(object: TextView.OnEditorActionListener{
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
attemptLogin()
return true
}
})
Kotlin, however, allows you to use methods that take SAM interfaces as their parameter with a more concise syntax, by passing in a lambda instead. This is called a SAM conversion:
mPasswordView.setOnEditorActionListener { v, actionId, event ->
attemptLogin()
true
}
SAM conversions automatically determine which interface this lambda corresponds to, but you can specify it explicitly by using something called a SAM constructor, this is what's in your sample code. A SAM constructor returns an object that implements the given interface, and makes the lambda you've passed to it its single method's implementation.
mPasswordView.setOnEditorActionListener( TextView.OnEditorActionListener { v, actionId, event ->
attemptLogin()
true
})
This is redundant in this specific situation, because there is only one method called setOnEditorActionListener. But if there were multiple methods with this same name, that took different interfaces as parameters, you could use the SAM constructor to specify which overload of the method you want to call.
Official docs about SAM conversions
Related
I'm struggling with Kotlin anonymous functions. The syntax seems to vary (sometimes parenthesis are used, sometimes not) and I'm not sure where they can vs. cannot be used.
In this code for example, what exactly is the anonymous function replacing? It just seems to suddenly pop up in the middle of a builder and it's not at all clear what it's doing.
// Choose between gallery and camera
val builder = AlertDialog.Builder(this)
val sources = arrayOf("Use Camera", "View Gallery")
builder.setItems(sources) { _, which ->
when (which) {
0 -> { cameraIntentLauncher.launch(cameraIntent) }
1 -> { galleryIntentLauncher.launch(galleryIntent) }
}
}.create().show().
Is it associated with setItems()? Not really. It appears to be defining the callback function that occurs after an item is selected. But there's no ".setCallback()" to attach it to the builder.
In regular Java, I'd expect something like:
builder.setItems(sources, new callbackFunc {...})
But in the above Kotlin, the anonymous function is defined outside of the setItems() parenthesis. It's very confusing.
So what can I read or watch to understand this Kotlin syntax implicitly when reading it?
The concept used here is called Trailing lambdas.
According to Kotlin convention, if the last parameter of a function is
a function, then a lambda expression passed as the corresponding
argument can be placed outside the parentheses
So your code,
builder
.setItems(sources) { _, which ->
when (which) {
0 -> {
cameraIntentLauncher.launch(cameraIntent)
}
1 -> {
galleryIntentLauncher.launch(galleryIntent)
}
}
}
.create()
.show()
And the below code would be considered as same.
builder
.setItems(sources, { _, which ->
when (which) {
0 -> {
cameraIntentLauncher.launch(cameraIntent)
}
1 -> {
galleryIntentLauncher.launch(galleryIntent)
}
}
})
.create()
.show()
Adding more details as suggested in the comments.
If the lambda is the only argument in that call, the parentheses can be omitted entirely.
Example
run { println("...") }
Lambdas and anonymous functions are not the same.
Difference in return type
The return type inference for anonymous functions works just like for normal functions: the return type is inferred automatically for anonymous functions with an expression body, but it has to be specified explicitly (or is assumed to be Unit) for anonymous functions with a block body.
Difference in return statement
A difference between lambda expressions and anonymous functions is the behavior of non-local returns.
A return statement without a label always returns from the function declared with the fun keyword.
This means that a return inside a lambda expression will return from the enclosing function, whereas a return inside an anonymous function will return from the anonymous function itself.
When passing anonymous functions as parameters, place them inside the parentheses. The shorthand syntax that allows you to leave the function outside the parentheses works only for lambda expressions.
The following are two blocks of code that should essentially do the same thing. But the second one does not execute the onEditorAction while the first one does. What is it about the second one that is different that prevents it from executing the code? NOTE: Only one of these is present in the code and not both.
// This one works
this.setOnEditorActionListener { v, actionId, event ->
if(actionId == EditorInfo.IME_ACTION_SEARCH){
mOnRunSearchCallback()
true
} else {
false
}
}
// This one does not work
this.setOnEditorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent): Boolean {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
mOnRunSearchCallback()
return true
}
return false
}
})
Change the second example with this
this.setOnEditorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
return true;
}
})
Basically, you have wrong arguments type for v and event both v & event are nullable.
I am confusing about merit of writing code as following 2 case:
class TestA {
private val foo: Boolean by lazy {
// Here is logic that return true or false
}
Case 1:
fun main() {
TestB({foo})
}
Case 2:
fun main() {
TestB(foo)
}
}
Case 1:
class TestB(private val isFoo: () -> Boolean ) {
fun checkFoo(): Boolean {
return isFoo.invoke()
}
}
Case 2:
class TestB(private val isFoo: Boolean ) {
fun checkFoo(): Boolean {
return isFoo
}
}
When should I use case 1 or case 2 ?
By the way, please let me know how does invoke() method work?
You only pass lambdas into other class constructors if you want something to be invoked on the other end, that might make sense if used as a callback, or if you need to have a function that creates objects again and again rather than being static. In this case, you'd store the lambda for later referral and invoke it whenever needed. If you just pass a static instance around, that is for example foo in your code, there's no reason for a lambda. You should always prefer not to use lambdas for constructors; scenarios in which they are useful or necessary are rather rare IMO.
As to your question regarding invoke: Kotlin has a number of functions that work "by convention", e.g. rangeTo, equals, contains, compareTo, the index operators and also invoke. Learn about conventions here.
Now, whenever a class provides the invoke operator, you can call instances of that class as if they were functions:
class InvokeMe(){
operator fun invoke(value: Int) = println("invoked with $value")
}
val obj = InvokeMe()
//both are compiled to the same code
obj(10)
obj.invoke(5)
Since every lambda is being compiled into a Function instance (see kotlin.jvm.functions) which comes with an implementation of invoke, you can call lambdas as shown above, i.e., using lambda(args) or lambda.invoke(args)
.invoke() will simply call your lambda and give your result, same as calling a function.
As for when you should pass a lambda or an actual value, it very depends.
Personally, I would only suggest using lambdas in very specific situations, overusing them can make your code very confusing and hard to refactor. If you just want a result to be passed into the function, just pass the actual value. Don't make someone else call .invoke().
But a few good example for a lambda are callsbacks, or onClickListeners.
// A login network request with a lambda handling the result.
fun login( username: String, password: String, onResult: (Result) -> Unit ) {
// do some network call, and return a Result.
}
// note: if the last param is a lambda, you can simply move it outside the function call like this.
login( username, password ) { result ->
// use the result of the network request.
}
Hopefully that helps.
Back in java I used to write only return for a void method... But kotlin doesn't seem to allow just return, instead it uses return#methodname?
Can someone explain what this is and how does it add value?
bAddLine.setOnClickListener {
val selectedSeries = getSelectedSeries()
if (selectedSeries.isEmpty()) {
Toast.makeText(this, getString(R.string.toast_channel_mandatory), Toast.LENGTH_LONG).show()
return#setOnClickListener
}
}
From kotlinlang website:
Return at Labels
With function literals, local functions and object expression, functions can be nested in Kotlin. Qualified returns allow us to return from an outer function. The most important use case is returning from a lambda expression. Recall that when we write this:
fun foo() {
ints.forEach {
if (it == 0) return // nonlocal return from inside lambda directly to the caller of foo()
print(it)
}
}
The return-expression returns from the nearest enclosing function, i.e. foo. (Note that such non-local returns are supported only for lambda expressions passed to inline functions.) If we need to return from a lambda expression, we have to label it and qualify the return:
fun foo() {
ints.forEach lit# {
if (it == 0) return#lit
print(it)
}
}
Now, it returns only from the lambda expression. Oftentimes it is more convenient to use implicits labels: such a label has the same name as the function to which the lambda is passed.
fun foo() {
ints.forEach {
if (it == 0) return#forEach
print(it)
}
}
When inside a lambda, you have to specify which scope you wish to return from, because it might be ambiguous. See the official docs about returning at labels.
In this specific case, if you'd be returning at the end of a function that returns nothing, you can omit the return statement altogether.
I am trying to use the done button of the soft keyboard to activate a method via databinding. Just like onClick. Is there a way to do that?
example:
<EditText
android:id="#+id/preSignUpPg2EnterPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
onOkInSoftKeyboard="#{(v) -> viewModel.someMethod()}"
/>
onOkInSoftKeyboard doesn't exists... Is there something to create this behavior?
Thanks!
I won't claim to be an expert in onEditorAction() or soft keyboard. That said, assuming you use the solution to the stack overflow question Firoz Memon suggested, you can make it happen. Even if there is another solution that works better, this can give you an idea on how to add your own event handlers.
You'd need a binding adapter that takes some kind of handler. Let's assume you have an empty listener like this:
public class OnOkInSoftKeyboardListener {
void onOkInSoftKeyboard();
}
Then you need a BindingAdapter:
#BindingAdapter("onOkInSoftKeyboard") // I like it to match the listener method name
public static void setOnOkInSoftKeyboardListener(TextView view,
final OnOkInSoftKeyboardListener listener) {
if (listener == null) {
view.setOnEditorActionListener(null);
} else {
view.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public void onEditorAction(TextView v, int actionId, KeyEvent event) {
// ... solution to receiving event
if (somethingOrOther) {
listener.onOkInSoftKeyboard();
}
}
});
}
}
Using Kotlin, kapt produces:
e: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding errors.
****/ data binding error ****msg:Listener class kotlin.jvm.functions.Function1 with method invoke did not match signature of any method viewModel::signIn
(because viewModel::signIn is of type KFunction1) so we can't use a method reference. However, if we create a variable within the viewModel that is explicit about the type, then we can pass that variable as the binding's param. (or just use a class)
Bindings.kt:
#BindingAdapter("onEditorEnterAction")
fun EditText.onEditorEnterAction(f: Function1<String, Unit>?) {
if (f == null) setOnEditorActionListener(null)
else setOnEditorActionListener { v, actionId, event ->
val imeAction = when (actionId) {
EditorInfo.IME_ACTION_DONE,
EditorInfo.IME_ACTION_SEND,
EditorInfo.IME_ACTION_GO -> true
else -> false
}
val keydownEvent = event?.keyCode == KeyEvent.KEYCODE_ENTER
&& event.action == KeyEvent.ACTION_DOWN
if (imeAction or keydownEvent)
true.also { f(v.editableText.toString()) }
else false
}
}
MyViewModel.kt:
fun signIn(password: String) {
Toast.makeText(context, password, Toast.LENGTH_SHORT).show()
}
val signIn: Function1<String, Unit> = this::signIn
layout.xml:
<EditText
android:id="#+id/password"
app:onEditorEnterAction="#{viewModel.signIn}"
android:imeOptions="actionDone|actionSend|actionGo"
android:singleLine="true"/>
Just as i was looking at this myself, here a simpler version where the function is directly called from the data binding:
In your ViewModel use this function:
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
return false; // if you want the default action of the actionNext or so on
return true; // if you want to intercept
}
And in the layout:
android:onEditorAction="#{(view,actionId,event) -> viewModel.onEditorAction(view,actionId,event)}"
Kotlin, without writing custom binding adapter
In Layout,
<EditText
...
android:onEditorAction="#{(view, actionId, event) -> viewModel.onDoneClicked(view, actionId, event)}" />
ViewModel
fun onDoneClicked(view: View, actionId: Int, event: KeyEvent?): Boolean {
if(actionId == EditorInfo.IME_ACTION_DONE) {
// handle here
return true
}
return false
}
Note: event can be null, so make KeyEvent nullable by putting ? there.
You can directly call login method what inside ViewModel by implementing setOnEditorActionListener to the Edittext, taking reference from binging class
loginFragmentBinding.etPassword.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
loginViewModel.login()
return#OnEditorActionListener true
}
false
})
Android framework already have this implemented. Take a look at TextViewBindingAdapter
You'll see those attributes, the documentation kind of glosses over what this means, but in a nutshell:
attribute = when this attribute appears in a layout file
type = then look for the implementation in this class
method = of a method with this name in the class defined in type
For more on this take a look at this blog post.