Task results are very important in a program, because they determine if the next task will be executed. The best programs are the ones that are able to poll and fetch results automatically and synchronously.
Android has a mechanism called optional callback that allows your methods and classes to receive the failure and success results of another task without having to call another class time and again just to get results.
In this tutorial, we will learn about optional callbacks and how to implement them in Kotlin. In addition, we will get to explore libraries that implement this functionality for you.
Android callbacks allow your method to fetch results from another method synchronously. Callbacks act as messengers; they are the center of communication in a program, and get passed into functions as arguments. They help in completing the task of a function by providing results, either negative or positive. An optional callback explicitly states what will happen when different results are fetched from another method or function.
Optional callbacks also allow functions that rely on a callback to continue even if the callback returns an error, which supports high level maintenance and reliability.
Optional callbacks are important, because they make sure that users can enjoy your app without interruption by preventing the application from freezing and blocking. A freezing UI can lead to many people uninstalling your app.
As an Android developer, I use callbacks on a daily basis, starting from the onCreate()
method, which creates an activity when an application is launched. This is a typical example of a callback you will use a million times in your Android development journey.
When your app crashes at start time, the problems can be found in this callback:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }
Here is another example of a typical callback that is called when the activity is paused:
override fun onPause() { super.onPause() print("onPause") }
The above callbacks do a great job in the Android activity lifecycle and fragment lifecycle. But, the problem arises when the method implemented in the onPause()
callback does not work. An optional callback would state what would happen if that error occurs.
Using optional callbacks in your program should not be optional. Optional callbacks keep your activity flowing even when exceptions occur and tasks output negative results.
Now, here is an example of an optional callback:
Result.of<T, Throwable>(doTask()) .flatMap { normalizedData(it) } .map { createRequestFromData(it) } .flatMap { database.updateFromRequest(it) }
Add the failure and success cases:
//if successful result.success { } //if failed result.failure { }
The above optional callback has conditions: the failure and success switch. You have to state what happens if the results from another class are negative or positive. In this way, you will be able to close processes and functions that may crash the app when the callback returns a negative result.
Here is another example of an optional callback that does not use a library. The optional callback shown below provides three cases that will be triggered when results are passed. You are given the opportunity to do something if the animation repeats, ends, or starts:
animation.setAnimationListener(object : Animation.AnimationListener { override fun onAnimationRepeat(animation: Animation?) { } override fun onAnimationEnd(animation: Animation?) { doSomething() } override fun onAnimationStart(animation: Animation?) { } })
An optional callback structure mainly consists of a code block that fetches results, which is followed by the failure and success case (or any case you would like to be executed when certain results are received). However, you can add other cases if you are implementing them starting from scratch without an intervention of a library.
A library (like Result) makes it easy by giving you two cases: failure and success. All you have to do is add code that will be added when a success or failure instance occurs.
The failure and success cases are what makes optional callbacks different from typical callbacks:
//if successful result.success { } //if failed result.failure { }
Result is a Kotlin library that allows you to implement optional callbacks and helps you handle errors swiftly.
To get started, add the following dependencies to the gradle.build
file and synchronize Gradle to download the third-party library:
implementation("com.github.kittinunf.result:result-jvm:5.2.1") implementation ("com.github.kittinunf.result:result:5.2.1")
Let’s see how Result works in a case where we want to open a text file and read it. To get the result of opening the text file task we use the Kotlin Result object, which fetches the success and failure value of the readText task
:
val operation = { File("/path/to/file/foo.txt").readText() } Result.of { operation() }
Next, we create a normalizedData(foo)
function that sorts and simplifies data collected from the operation. Normalize the data by calling the normalize()
function:
fun normalizedData(foo): Result<Boolean, NormalizedThrowable> { Result.of { foo.normalize() } }
Next, create a request from the data we normalized above:
fun createRequestFromData(foo): Request { return createRequest(foo) }
After requesting data, create a database function as shown below that will update the values of the Result object. If the updating transaction fails then an exception will be thrown:
fun database.updateFromRequest(request): Result<Boolean, DBThrowable> { val transaction = request.transaction return Result.of { db.openTransaction { val success = db.execute(transaction) if (!success) { throw DBThrowable("Error") } return success } } }
After you have fetched information using the Result object, it’s time to add the success and failure cases. Start by declaring the value:
val (value, error) = result
Now, get the result value:
val value: Int = result.get()
Now add the success and failure cases:
result.success { //Add code that does something when the task is successful here } result.failure { //add code that warns the user that an error has occurred here. }
The callback-ktx library wraps and transforms callback-based APIs into suspending extension functions. Currently, callback-ktx only supports the following APIs:
Use the following code to download the callback-ktx dependency:
implementation("io.github.sagar-viradiya:callback-core-ktx:1.0.0")
After you have downloaded the library, You can track animations by invoking the lifecycleScope
object to get the scope of the life cycle. Next, call the awaitStart()
method, which will be triggered when the animation.
Now, you have the opportunity to state and specifically add code that will be executed only when the animation starts:
viewLifecycleOwner.lifecycleScope.launch { animator.awaitStart() // insert code that does something when the animation starts here. }
You can check out more use cases of the callback-ktx library here. If you still have an Android codebase that uses Java you can use Vulture to implement callbacks safely.
In this article we have learned about the benefits of optional callbacks, the difference between optional and regular callbacks, and how to implement callbacks using Result and callback-ktx.
Optional callbacks will surely give you more ability and choice when it comes to stating what should happen when errors and success results are returned by the callback.
If you have any questions or comments, feel free to leave them in the comment section below.
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps — try LogRocket for free.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.