I've been writing in Kotlin for a while, and I get used to use next pattern:
variable.addSomething(object: TargetType() { ...code... })
or
var variable = object: TargetType() { ...code... }
(if i'm not missing something)
Is it possible to somehow use this pattern in Swift ? And how is it called ? :)
Edit:
What I actually want to do - to store preconfigured RxSwift.SingleEvent in a let / var inside object and reuse it later multiple times.
In code, as I imagine, it should look like that:
private var observer = SingleEvent<Response>(ok_callback, error_callback) {
override success(el: Element) {
ok_callback(el)
super.success(el)
}
override error(er: Error) {
self.onErrorRetry(er, callback)
}
}
And if retry after some magic works - simply call my callback and return back :)
Its seems to be trailing closure. Adapted from Swift programming language - Closures:
If you need to pass a closure expression to a function as the
function’s final argument and the closure expression is long, it can
be useful to write it as a trailing closure instead. A trailing
closure is written after the function call’s parentheses, even though
it is still an argument to the function. When you use the trailing
closure syntax, you don’t write the argument label for the closure as
part of the function call.
Let's code it:
Simply, all you have to do is just to create a function which its last argument is a closure:
func doSomething(firstParameter: Any, closure: () -> Void) { }
thus you could call it as:
doSomething(firstParameter: "whatever") {
// ...
}
Nothing special goes here, it is a cool feature from the Swift language to "trail" closure parameter if its the last one in the function signature.
In case of initialization, it is almost the same:
struct MyObject {
init(firstParameter: Any, closure: () -> Void) { }
}
let variable = MyObject(firstParameter: "whatever") { }
Certainly, this pattern is followed by many other functions in the language, but here is an example of the merge method for the Dictionary, you could recognize how you could type it in more than one way as mentioned in the answers of: Map Dictionary Keys to add values - Swift.
Update:
If you are aiming to use it as constant/variable -to be passed for a function for example-, you could do it like this:
let variable: (String) -> Void = { name in
print("The name is: \(name)!")
}
At this point, variable type is (String) -> Void which means that its a constant that could be passed somewhere else; Consider the following method:
func doSomething(closure: (String) -> Void) {
closure("Nikita")
}
Because doSomething takes a parameter of type (String) -> Void, you could do:
doSomething(closure: variable) // The name is: Nikita!
instead of calling it as:
doSomething { name in
print("The name is: \(name)!")
}
which prevents the boilerplate code.
Related
In the code below, i'd like to generalize it so I instead of viewBinding.editText.text and viewModel.property.price can use the same method for e.g viewBinding.secondEditText.text and viewModel.property.income.
I'm thinking exchanging viewBinding.editText.text for a variable defined in the primary constructor, but then I'd need the variable to contain a reference to viewBinding.editText.text/viewBinding.secondEditText.text etc. instead of containing a value.
Is this possible? I've looked at lengths for this but can't find anything useful.
fun updateProperty() {
//... other irrelevant code
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
//... other irrelevant code
}
You can pass parameters into a function, yeah!
This is the easy one:
fun updateProperty(editText: EditText) {
val contents = editText.text.toString()
}
simple enough, you just pass in whatever instance of an EditText and the function does something with it.
If you're just using objects with setters and getters, you can just define the type you're going to be using and pass them in. Depending on what viewmodel.property is, you might be able to pass that in as well, and access price and income on it. Maybe use an interface or a sealed class if there are other types you want to use - they need some commonality if you're going to be using a generalised function that works with them all.
Properties are a bit tricker - assuming viewmodel.property contains a var price: Double, and you didn't want to pass in property itself, just a Double that exists somewhere, you can do it like this:
import kotlin.reflect.KMutableProperty0
var wow: Double = 1.2
fun main() {
println(wow)
setVar(::wow, 6.9)
println(wow)
}
fun setVar(variable: KMutableProperty0<Double>, value: Double) {
variable.set(value)
}
>> 1.2
>> 6.9
(see Property references if you're not familiar with the :: syntax)
KMutableProperty0 represents a reference to a mutable property (a var) which doesn't have any receivers - just a basic var. And don't worry about the reflect import, this is basic reflection stuff like function references, it's part of the base Kotlin install
Yes, method parameters can also be references to classes or interfaces. And method parameters can also be references to other methods/functions/lambdas.
If you are dealing with cases that are hard to generalize, consider using some kind of inversion of control (function as parameter or lambda).
You add a lambda parameter to your updateProperty function
fun updateProperty(onUpdate: (viewBinding: YourViewBindingType, viewModel: YourViewModelType) -> Unit) {
//... other irrelevant code
// here you just call the lambda, with any parameters that might be useful 'on the other side'
onUpdate(viewBinding, viewModel)
//... other irrelevant code
}
Elsewhere in code - case 1:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
Elsewhere in code - case 2:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Elsewhere in code - case 3:
updateProperty() { viewBinding, viewModel ->
// I am a totally different case, because I have to update two properties at once!
viewModel.property.somethingElse1 = viewBinding.thirdEditText.text.toString().toBoolean()
viewModel.property.somethingElse2 = viewBinding.fourthEditText.text
.toString().replaceAll("[- ]*", "").toInt()
}
You could then go even further and define a function for the first 2 cases, since those 2 can be generalized, and then call it inside the lambda (or even pass it as the lambda), which would save you some amount of code, if you call updateProperty() in many places in your code or simply define a simple function for each of them, and call that instead, like this
fun updatePrice() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
fun updateIncome() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Then elsewhere in code you just call it in a really simple way
updatePrice()
updateIncome()
Learning Functional Programming in Kotlin, I've run into some problems in understanding
fun exampleFunction(name : String, paramFun : (String) -> Unit){
...some body code...
}
When I declare such function, as seen above, function is taking 2 parameters - String type 'name' and function type 'paramFun'.
how can paramFun, which is a function and passed as an input for exampleFunction, use name (which is parameter for exampleleFunction) as its parameter
like
fun exampleFunction(name : String, paramFun : (String) -> Unit){
paramFun(name)
}
When you call a function in a function or whatever, try to remember that the fcuntion you call (paramFun(name)), is just a copy of that same function inside exempleFunction.
So basically, when you call exampleFunction, you have 2 variable you can use inside this function, after that, when you call paramFun(name), you can use name inside as you want.
When you call a function with parameter, you can use theses parameter as many time as you want in any order you want.
Ex : This is the same thing as this.
fun functionOne(a: Int, b: Int) {
a = b //or// b = a
}
the function paramFun is the same parameter as a or b , but it's a function
I'm not entirely sure I understood your problem, because you already accomplished what you seem to have trouble with:
fun exampleFunction(name : String, paramFun : (String) -> Unit){
paramFun(name)
}
If your problem is at the call site, here is how you would call exampleFunction:
exampleFunction(name = "the name param") { someParam ->
// do what you want with someParam here
}
With this call, someParam in the lambda would get the value provided as name parameter to exampleFunction, because the body of exampleFunction actually passes name as argument to the lambda when doing paramFun(name) (just like a regular function call).
I had 2 functions that does basically the same thing, so I thought to create an extension function.
The problem is that I need to extract the data of each class in order to get the correct result, I thought of creating a Generic function, and inside this function decide which class member to access inside when statement.
But when I try to call T inside the when statement, I'm getting Type parameter 'T' is not an expression
What am I doing wrong?
My function:
fun <T: Any> List<T>.extractWithSepreation(errorString: String): String {
var errorString = "There is no available $errorString"
if (this.isEmpty()) {
return errorString
}
errorString = ""
this.forEachIndexed { index, item ->
when(T)
}
}
The error message pretty much says it all. T is not an expression, so it cannot be used inside when(...). T just refers to the class of the item of the List.
Didn't you mean using something like:
when(item) {
is ClassA -> doSomething()
is ClassB -> doSomethingElse()
}
?
This is just a sample code I wrote. The below is the data class for the Comment object.
data class Comment(
val id: Int,
val text: String?
)
And I'm performing a filter operation on a list of Comment objects.
commentList.filter { comment -> comment.text != null }
.map { comment -> comment.text.length }
This leads to the compiler error on comment.text stating Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver. So my question is why doesn't the filter method, do a smart-cast on the text object and convert it into a non-nullable String type?
Also is there a better way where smart-casting is possible?
p.s. I know that I can always do this, but just out of curiosity would like to know if there is some default Kotlin extension function which can get the job done.
commentList.filter { comment -> comment.text != null }
.map { comment -> comment.text?.let { text ->
text.length
}
}
To start with,
...?.let { text ->
text.length
}
can be simplified to
...?.length
I'd further combine filter and map using mapNotNull:
commentList.mapNotNull { comment -> comment.text?.length }
It could have different result from your original code if length could be null on a non-null input, but it can't.
It's because smart cast works within a method boundary.
filter() and map() are 2 different methods.
Check this answer for a better understanding
Based on the comment and the answer I got here, I can understand that smart-casts works only within a method boundary.
Alternatively I was able to find a different solution, which makes the code a bit elegant to my eyes.
commentList.mapNotNull { comment -> comment.text }
.map { text -> text.length }
Android studio is giving me a squiggly saying that I should/could turn this into a lambda. I'm just getting back into my android.
popup.setOnMenuItemClickListener(object : PopupMenu.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean {
if (item.itemId === R.id.action_vitals) {
val vitalsIntent = Intent(this#DashboardActivity, VitalsActivity::class.java)
vitalsIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(vitalsIntent)
}
if (item.itemId === R.id.action_devices) {
val devicesIntent = Intent(this#DashboardActivity, DevicesActivity::class.java)
devicesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(devicesIntent)
}
return false
}
})
This is the squiggly "object : PopupMenu.OnMenuItemClickListener"
There are a couple of comments and answers suggesting to use alt + enter in IntelliJ/Android Studio and that will handle converting your code into a Lambda for you. However, it might be worth covering why it is suggesting that to you.
A lambda is a function that isn't declared e.g. fun someFunction() but instead is immediately passed as a parameter to another function. This lambda will then be executed by some other code elsewhere in the app. Essentially a Lambda is a shorthand function e.g:
val lambda: () -> Unit = {
// Some code could go inside this Lambda here
}
An important concept for you here is a SAM (Single Abstract Method) type. This simply refers to an interface that defines a single abstract function that needs to be implemented. In your example PopupMenu.OnMenuItemClickListener is a Java interface that has a single abstract method void onMenuItemClick(MenuItem item). SAM types can be written in shorthand with the body of the lambda becoming the body of abstract function.
You've correctly written this as an anonymous object which is fine; but it could be written more concisely with a lambda which Android studio is suggesting. Another helpful tidbit, in Kotlin if a function or lambda is the sole or final parameter in a list of parameters it can be moved outside of the braces of the function or they can be removed entirely.
So your code will have been converted to something like this:
enterpopup.setOnMenuItemClickListener {
if (item.itemId === R.id.action_vitals) {
val vitalsIntent = Intent(this#DashboardActivity, VitalsActivity::class.java)
vitalsIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(vitalsIntent)
}
if (item.itemId === R.id.action_devices) {
val devicesIntent = Intent(this#DashboardActivity, DevicesActivity::class.java)
devicesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(devicesIntent)
}
return false
}
You can use Alt+Enter to show a list of "suggestions" (called inspections in JetBrains language). Selecting the suggestion will perform the conversion automatically for you.
IntelliJ 2019.2 brings an improvement to this feature (which should come to Android Studio soon), showing more info about the top suggestion and adding Alt+Shift+Enter as a shortcut to directly apply it (bypassing the list)
https://www.jetbrains.com/idea/whatsnew/#v2019-2