Passing a function that requires parameters as a callback in Kotlin - android

I am using Kotlin (for Android development) and I'm trying to pass a function to another function which I'd like to use as a callback. The code is very basic as this is just a test for now.
Please note that, although you will probably wonder why I'm using a callback like this, it's just for test purposes. In my actual application I would want to assign the callback to a value and call it later on once an asynchronous method has completed.
I cannot use co-routines etc... since this code will be used for a multi-platform solution, hence my interest in making a function callback.
My Kotlin Class that will receive the function (callback)
class SampleApi {
private var counter: Int = 0
fun startCounting(initialValue: Int, counterCallBack: (resultVal: Int) -> Unit) {
counter = initialValue
counter++
counterCallBack(counter)
}
}
The above is a basic class that has a function startCounting which will receive an integer and a function. It will then call that function and pass in a value.
The calling code
class MainActivity : AppCompatActivity() {
private val sampleApi: SampleApi = SampleApi()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
sampleApi.startCounting(5, {counterCallBack()})
}
private fun counterCallBack(counter: Int) {
Toast.makeText(this, counter.toString(), Toast.LENGTH_SHORT).show()
}
}
The Sample code shown above contains the callback method (which is expecting to receive an integer), and contains the call to the startCounting method from the SampleApi class that is expecting to receive a function.
The problem I have is this line:
sampleApi.startCounting(5, {counterCallBack()})
The error within Android Studio is due to the fact that a value is that the function is expecting an integer and hence I receive the error:
No value passed for parameter 'counter'
I tried to look at lambdas but didn't think that was the issue. I have searched to see if an answer to this already existed and, whilst helpful they didn't seem to consider the same use case as mine.
Any help with this would be very much appreciated.

Because counterCallback has exactly the type you need, you can also use a function reference instead of a lambda:
sampleApi.startCounting(5, ::counterCallBack)

I assume what you want to do is create a toast displaying the Int every time your callback lamda is called from the SampleApi.
You just need to make use of the Int that your lamba is called with, using it:
sampleApi.startCounting(5, {counterCallBack(it)})

Related

calling coroutine function from activity returns `should be called only from a coroutine or another suspend function`

I'm trying to use coroutines in my code since in one of my function, I need to do multiple network calls and wait for its result. Below is a portion of my activity code:
class SellerDeliveryRegister : AppCompatActivity() {
lateinit var sellerDeliveryVM:SellerDeliveryVM
sellerDeliveryVM = ViewModelProviders.of(this).get(SellerDeliveryVM::class.java)
var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url)
}
And below is a portion of my sellerDeliveryVM ViewModel code:
class SellerDeliveryVM: ViewModel() {
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): Unit = withContext(Dispatchers.IO){
var uploadMotorImage1Result = "-1"; var uploadMotorImage2Result = "-1";
viewModelScope.launch(Dispatchers.IO) {
uploadMotorImage1Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage1Url)
}
uploadMotorImage2Result = withContext(Dispatchers.Default) {
NetworkRepository.instance!!.uploadFile(uploadMotorImage2Url)
}
return#launch;
}
return#withContext
}
}
Please take note that previously uploadVehiclesImages is a 'normal' function that doesn't use coroutine, so now I'm converting it to use coroutine.
Below are the problems I'm facing:
Line var uploadVehicleImageResult = sellerDeliveryVM.uploadVehiclesImages(uploadMotorImage1Url,uploadMotorImage2Url) inside my SellerDeliveryRegister class returns this error:
Suspend function 'uploadVehiclesImages' should be called only from a coroutine or another suspend function
Initially I want to return Boolean from uploadVehiclesImages, so I have return true in place of the return#launch and return false in place of the return#withContext, but then I will get the error return is not allowed here, and Android Studio suggested me to make the changes above, although I really have no idea what the changes meant there.
So what should I do to fix this problem 1, and can anyone enlighten me more on what's really happening on the problem 2?
So what should I do to fix this problem 1
Remove the property. uploadVehiclesImages() returns Unit; there is no value in having Unit in a property. If your objective is to call uploadVehiclesImages() when the viewmodel is created, put a call to it in an init block, wrapped in a suitable coroutine launcher (e.g., viewModelScope.launch {}).
This assumes that you are going to keep the function in its current form — your next question suggests that this function may not be the right solution.
Initially I want to return Boolean from uploadVehiclesImages,
More importantly, you seem to want it to return values more than once. That is not how functions work in Kotlin. One call to uploadVehiclesImages() can return one Boolean value, but not one now and one sometime in the future.
If you want to be emitting a stream of results, a suspend function on its own is not the correct solution. For example, you could:
Use LiveData, with the suspend function updating the backing MutableLiveData, or
Use a function that returns a StateFlow or SharedFlow
For part 1, you cannot use a coroutine to initialize a property. Coroutines return some time in the future, but properties have to be initialized immediately at class instantiation time. You'll have to change the strategy so you launch a coroutine that calls the suspend function, and then does something with the result when it arrives.
For part 2, you have an awkwardly composed suspend function. A proper suspend function typically isn't launching other coroutines unless it is using them to break down multiple simultaneous asynchronous actions and then waiting for them.
The convention for a suspend function is that it is safe to call from any Dispatcher. It's not proper to be sending off these background actions by launching a coroutine in a specific coroutine scope. Usually, a coroutine that calls a suspend function should not have to worry that the suspend function is going to launch some other coroutine in another scope, because this breaks support for cancellation.
Also, you can use async instead of launch to run suspend functions that you need a result from. That will avoid the awkward variables you've created to store the results (and you neglected to wait for).
Assuming you want to return both of these image results, you'll have to wrap them in another class, such as List. So your function could look like below. It returns something, not Unit. It uses aysnc to run the two requests simultaneously.
suspend fun uploadVehiclesImages(uploadMotorImage1Url: String, uploadMotorImage2Url: String): List<ImageUploadResult> {
return listOf(uploadMotorImage1Url, uploadMotorImage2Url)
.map { aysnc { NetworkRepository.instance!!.uploadFile(it) }
.awaitAll()
}
I just put ImageUploadResult to stand in for whatever this uploadFile function returns. Maybe it's just Boolean.
Whenever you do want to call it, you would use either lifecycleScope (from an Activity or Fragment) or viewModelScope (from a ViewModel) to launch a coroutine that calls it. For example:
fun doUpload(url1: String, url2: String) {
lifecycleScope.launch {
val results = uploadVehiclesImages(url1, url2)
// do something with results here
}
}

why dont set this value?

I want set my gorterilecekkart variable in code, but it will not change. It always sees the first value instead. How can I solve this problem? Please help me. I don't know English very well sorry. But in my code I have added comments.
class Sahnem : AppCompatActivity() {
var gorterilecekkart=5
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sahnem)
val parsUser = ParseUser.getCurrentUser().email
val queryTercih = ParseQuery.getQuery<ParseObject>("Tercih")
queryTercih.whereEqualTo("user", parsUser.toString())
queryTercih.findInBackground { objects, e ->
if (objects.isEmpty()){
}else{
var bas:String=objects[0].get("basla").toString()
var bit:String=objects[0].get("bitir").toString()
txvBasla.setText(bas)
txvBitir.setText(bit)
bitir=bit.toInt()
baslat=bas.toInt()
gorterilecekkart=bitir-baslat // i see log 3
}
println("116 "+bitir)
gorterilecekkart=bitir-baslat
println("119 "+gorterilecekkart) // i see log 3
}
println("124 "+gorterilecekkart) // why i dont see log 3, i see 5
It looks like ParseQuery#findInBackground(...) runs the callback lambda in a background thread. Documentation here.
This means that:
println("124 "+gorterilecekkart) // why i dont see log 3, i see 5
Actually gets executed first, before the lambda is done executing.
In order to fix this, you need to use ParseQuery#find(...) instead, or somehow find a way to wait until the background thread is done executing your lambda before checking the value of gorterilecekkart.

Kotlin: Force inline method to preserve the amount of defined lines

So I have this method that I want to use for logging while also returning the logged object:
inline fun <T> T.btwLog(prefix:String="")=apply { Timber.d("$prefix->${toString()}") }
The Timber class is from a logging Library and also (when set up this way) shows the line at which it was called, which is also why the method is inline (to show the line and correct class of where it was called)
For example:
val b = someThingComplex().btwLog()
This basically works, the only problem is that the line number is wrong. This basically has to be because Kotlin inserts the method and while doing so adds some lines to the class (4)?.
So the example from before probably looks similar to this:
val b = someThingComplex()
.apply {
Timber.d("$prefix->${toString()}")
}
An alternative is this method where Kotlin doesn't add additional lines:
inline fun Any.log(s:String="")= Timber.d(s+this)
Is there any way that I could force Kotlin to just inline it exactly as I wrote it and not add linebreaks and stuff? Or is there a better way that I could define the method in general?
Thanks.
I suspect that the shifting of line numbers might be due to the call to apply. Try it this way.
inline fun <T> T.btwLog(prefix: String = ""): T {
Timber.d(prefix + "->" + this)
return this
}

Return type is 'Unit?', which is not a subtype of overridden

Today while programming I found some odd behaviour in Kotlin. I could easily go around it, but I wonder if there is some reason to it or if it is a bug in Kotlin.
I have the following interface of a delegate which delegates the showing of a dialog to the Activity.
interface ViewModelDelegate {
fun showWarningDialog(textResource: Int)
}
I want to implement it as following in the Activity. Since I know I can only do it with a context and the Activity.getContext() may return null, I wrap the code in context?.let
override fun showWarningDialog(textResource: Int) = context?.let {
//show dialog
}
However this gives me a compile error:
Return type of 'showWarningDialog' is not a subtype of the return type of the overridden member 'public abstract fun showWarningDialog(textResource: Int): Unit defined in com.some.class.path'
Which really confused me, because I don't want to return anything. So since let returns whatever the function inside returns, I was wondering if I could fix it by writing a version of let which does not return anything.
fun <T, R> T.myLet(block: (T) -> R) {
let(block)
}
However this did not remove the compiler error. I found then that the mouseover text over the error gives more information (would be nice if the compiler did). It says:
Return type is 'Unit?', which is not a subtype of overridden
Now that tells me more about the problem. Because the function context?let call may not happen, it could return null. Now there are multiple ways to go around this. I could add ?: Unit too the end of the function call or I could define showWarningDialog to return Unit? which will allow me to call it just fine in most cases. However none of these solutions are desireable. I will probably just make a normal method and call the let inside of that instead of delegating the call to it. Costs me another level of indentation and an extra vertical line:
override fun showWarningDialog(textResource: Int) {
context?.let {
//show dialog
}
}
My question is, is this behaviour intended? Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call. I am very confused by this behaviour.
Single expression function
fun foo() = <expression>
by language design is equivalent to
fun foo(): <ReturnType> {
return <expression>
}
And because Unit? is not a not a subtype of Unit, you can't return it in from a function, which returns Unit. In this sense Unit just another type in the type system, it's not something magical. So it works just as it's supposed to work with any other type.
Why or when would this be useful that a function that returns Unit cannot be delegated to an optional function call.
So basically the question is why language designers did not created a special handling to accept Unit? from a function declaring Unit as a return type. I can think about a few reasons:
It requires to create this special handling in the compiler. Special cases lead to bugs, break slim language design and complicate documentation.
As it had to be a special case, it would be not really clear and predictable for programmers. Currently it works in the same way for all types, no special treatments. It makes the language predictable, you don't need to check the documentation for every type to see if it's treated specially.
It also adds some additional safety, so to make you notice that your expression can actually skip the calculation.
So trying to summarize, I would say making this case work does not add much of value but can potentially bring some issues. That's probably why they did not add it to the language.
lets discuss this case when you have return type for example String
interface someInterface{
fun somFun():String
}
class someClass : someInterface {
var someString:String? = null
override fun somFun()=someString?.let {
//not working
it
}
override fun somFun()=someString?.let {
//working
it
}?:""
}
so what we see that when parents return type is String you cannot return Strin? it is jus kotlins nullSafety ,
what is different when you don't have return type ? lets change the code above a little
interface someInterface{
fun somFun():String
fun unitFun()
}
class someClass : someInterface {
var someString:String? = null
override fun unitFun() {
//if it is possible to return null in here
}
override fun somFun()=someString?.let {
val someresult = unitFun().toString() //you will get crash
it
}?:""
}
now we have another function without return type (unitFun Unit)
so if you can return Unit? in your subclass it will cause a crash when you want to use the result of method because it is defined asUnit and you dont need any null checks.
generally it means Unit is also type and you need to keep it null safe .

Lifecycle methods in statically typed languages

In the last year I've become a mobile developer and a functional programming admirer.
In each of the mobile arenas there are components with lifecycle methods that make up the meat of the app. The following will use Android and Kotlin as examples, but the same applies to iOS and Swift.
In Android, there are Activity's with lifecycle methods like onCreate(). You might also define a function, onButtonClicked(), which will do exactly what the name describes.
For the purposes of the question, let's say there's a variable defined in onCreate() that is used in a button click handler onButtonClickedPrintMessageLength() (This is usually the case - onCreate() is essentially Activity's setup method).
The example class would look like this:
class ExampleActivity: Activity() {
var savedStateMessage: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedStateMessage = "Hello World!"
}
fun onButtonClickedPrintMessageLength() {
System.out.println(savedStateMessage?.length)
}
}
Notice the declaration of savedStateMessage as a String? (nullable string) and the use of ?. (null safe call). These are required because the compiler cant guarantee that onCreate() will be called before onButtonClickedPrintMessageLength(). As developers though, we know that onCreate will always be called first* **.
My question is how can I tell the compiler about the guaranteed order of these methods and eliminate the null checking behavior?
* I suppose it's possible to new up our ExampleActivity and call onButtonClickedPrintMessageLength() directly, thus sidestepping the Android framework and lifecycle methods, but the compiler/JVM would likely run into an error before anything interesting happened.
** The guarantee that onCreate is called first is provided by the Android framework, which is an external source of truth and might break/function differently in the future. Seeing that all Android apps are based on this source of truth though, I believe it's safe to trust.
Although this won't answer your actual question, in Kotlin you can use lateinit to tell the compiler that you'll initialize a var at a later point in time:
lateinit var savedStateMessage: String
You'll get a very specific UninitializedPropertyAccessException if you try to use this variable before initializing it. This feature is useful in use cases like JUnit, where you'd usually initialize variables in #Before-annotated method, and Android Activitys, where you don't have access to the constructor and initialize stuff in onCreate().
As mentioned in another answer, lateinit is available as an option to defer initialization to a later point in a guaranteed lifecycle. An alternative is to use a delegate:
var savedStateMessage: String by Delegates.notNull()
Which is equivalent, in that it will report an error if you access the variable before initializing it.
In Swift this is where you would use an implicitly-unwrapped Optional:
class Example: CustomStringConvertible {
var savedStateMessage: String! // implicitly-unwrapped Optional<String>
var description: String { return savedStateMessage }
init() {
savedStateMessage = "Hello World!"
}
}
print(Example()) // => "Hello World!\n"
By using the operator ! at the end of String in the second line of the example you are promising that the variable will be set before it can be used. This is accomplished in the init method of the example. It's still an Optional but code can treat it as a String since it will be automatically unwrapped before each use. You must take care that the variable is never set to nil when it might be accessed or a runtime exception may be generated.

Categories

Resources