Anko Commons Tutorial
See how to improve the readability and conciseness of your Android Kotlin code for Intents, Dialogs and more using the Anko library from JetBrains. By Arturo Mejia.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Anko is a library for Android devs that want to achieve more while writing less. It simplifies common tasks that are tedious and generate a lot of boilerplate, making your code enjoyable to read, concise and clean. Neat and Tidy, just what the doctor ordered for my Java headaches. :]
The folks at JetBrains, makers of titles like Kotlin (A New Hope) and the IntelliJ Platform (the foundation of Android Studio), have created and maintain Anko.
Anko consists of a number of different components:
- Anko Commons: helpers for intents, dialogs, logging;
- Anko Layouts: lets you quickly and programattically create type-safe Android layouts;
- Anko SQLite: helpers for working with Android SQLite;
- Anko Coroutines: utilities for using Kotlin coroutins.
In this tutorial, we are going to be focused on Anko Commons. Future tutorials will cover other parts of Anko.
You’ll become an Anko Commons master by updating an existing app that doesn’t use Anko, and then comparing how to do the same things with and without Anko. This will help you be able to make a conscious decision about whether or not to use the library.
Note: This tutorial assumes you have previous experience with developing for Android in Kotlin. If you are unfamiliar with the language, have a look at this tutorial. If you’re just beginning with Android, check out some of our Getting Started and other Android tutorials.
Getting Started
Kanime is the app that we are going to use to showcase Anko’s power. It shows a list of my top 10 animes that you should watch. It’s built without using Anko, but not for long, along the way you’re going to update it.
Meet Kanime. :]
Download the Kanime source code using the download button at the top or bottom of the tutorial. Open the starter project up by starting Android Studio 3.1.2 or later and selecting Open an existing Android Studio project:
Before going on with the tutorial, take a look at the existing starter code.
Kanime Structure
The Kotlin source code packages in the starter project appear as follow:
-
Activities
-
MainActivity.kt
Here is where you show the top 10 best anime list. -
AnimeDetailActivity.kt
When you tap an Anime, this activity opens up. It shows all the info about the selected anime. -
AboutActivity.kt
A screen to show information about the app.
-
MainActivity.kt
-
Adapters
-
AnimeAdapter.kt
Contains all the code to show each anime item on the list.
-
AnimeAdapter.kt
-
data
-
AnimeDataSource.kt
Provides all the anime data.
-
AnimeDataSource.kt
-
model
-
Anime.kt
Anime model data class.
-
Anime.kt
Now you have a clear picture of how Kanime is structured. Let’s Rock!
Anko
Anko is a set of helpers functions (Kotlin extension functions) that help you to get something done with the least amount of boilerplate code. It’s subdivided into modules that help you to deal with Layouts, SQLite, and Coroutines. This modularization allows you to pick and choose only what you need.
Extension Function Fundamentals (Optional)
To understand how Anko works, you need to understand Kotlin Extension Functions. They allow you to add a function to an existing class without modifying the class.
For example, say you had a Dog
class:
Dog.kt
class Dog(val name: String, val breed: String)
In another Kotlin file you could add a function to Dog
without modifying the original file:
Extensions.kt
package com.raywenderlich.doggy
fun Dog.bark(): Unit{
println("woof woof")
}
To create the extension function, after fun type the class, then a dot, then the name of the extension function.
You could test your extension function in another file as follows”
Main.kt
//Importing bark extension function :]
import com.raywenderlich.doggy.bark
fun main(args: Array<String>) {
var myPuppy = Dog("Max", "Pug")
myPuppy.bark()
}
To use the extension function, you only import bark
, and then every Dog
object will be able to use the bark()
function.
Let’s see what happen if you don’t import the bark
:
Main.kt
//import com.raywenderlich.doggy.bark
fun main(args: Array<String>) {
var myPuppy = Dog("Max", "Pug")
myPuppy.bark() // Compile error ¯\_(ツ)_/¯
}
After this small introduction to Kotlin extension functions, now you are ready to become an Anko rock star!
Setting Up Anko Commons
Let’s spice up Kanime by adding Anko Commons!
To set up Anko in your project you only need to include the Gradle dependency. The dependency comes in two flavors:
1.The full Anko artifacts
implementation "org.jetbrains.anko:anko:$anko_version"
This dependency contains all Anko components. If you are only going to use some of them then this is not the way to go, because it will add unnecesary size to your APK for code you are not going to use.
2. Specific Artifacts
Anko allows you to pick and choose only the dependencies that you need:
implementation "org.jetbrains.anko:anko-commons:$anko_version"
This only brings the specific classes for Anko Commons. We are going go with this one for Kanime because it is more lightweight.
Open the app module build.gradle file and add this line to your dependencies :
implementation "org.jetbrains.anko:anko-commons:0.10.5"
Click “Sync Now” to sync your Gradle files and wait a bit until it finishes.
Hooray!! Now you can use Anko Commons in your project.
Using Anko Commons
Let’s do a deep dive to see what Anko Commons can do for you.
Intents
Intents help you to send data from one Android Component to another. Code to use them can be cumbersome and long. Anko give you a more concise and pleasant way to use intents.
For example, in Kanime you show a list of animes in MainActivity
, and when an anime is tapped, it will open AnimeDetailActivity
with all the information about the anime.
For this reason, you need an Intent to open AnimeDetailActivity
to pass it the information about the selected anime.
Lets see some code!
In the MainActivity.kt file find the function openDetailActivity()
that opens the detail screen.
Here is what the code looks like without Anko. :[
private fun openDetailActivity(anime: Anime) {
// 1 Create the intent and specify the target activity
val intent = Intent(this, AnimeDetailActivity::class.java)
// 2 Add anime details
intent.putExtra("TITLE_KEY", anime.name)
intent.putExtra("DESCRIPTION_KEY", anime.description)
intent.putExtra("IMDB_LINK_KEY", anime.imdbLink)
intent.putExtra("IMAGE_KEY", anime.imageDrawable)
// 3. open the detail activity
startActivity(intent)
}
You create an Intent, then have a number of lines to add extras onto the Intent. There are a lot of steps and the end code is not pretty readable.
Replace the function with the equivalent code from Anko Commons:
private fun openDetailActivity(anime: Anime) {
startActivity<AnimeDetailActivity>(
"TITLE_KEY" to anime.name,
"DESCRIPTION_KEY" to anime.description,
"IMDB_LINK_KEY" to anime.imdbLink,
"IMAGE_KEY" to anime.imageDrawable
)
}
You need only one statement to achieve the same end and the code is completely readable!
You may need to hit Option+Return on Mac or Alt+Enter on PC to pull in the Anko extension function import.
To specify the target activity, you put the name of the class after the startActivity
as <TargetActivity>
. If you want to know more about how this works, read about Generics here.
To specify the additional data, you pass as arguments one or more Kotlin Pair objects to the function startActivity<TargetActivity>()
. Each Pair is an extra that you are sending to the activity that you want to call.
A Pair is a class in Kotlin that helps you to store two values, and is similar to a Tuple in other languages.
If you got goosebumps, when you saw the to
operator, don’t worry since it’s a shortcut infix function for creating Pair. The first parameter goes before the to
and the second after to
. For example:
val myPair = "TITLE_KEY" to "Berserk"
println("${myPair.first} ${myPair.second}")
// prints TITLE_KEY Berserk
This is the same as if you were to write:
val myPair2 = Pair("TITLE_KEY","Berserk")
println("${myPair2.first} ${myPair2.second}")
// Prints TITLE_KEY Berserk
Now you are a pair master :]
Intent without passing data
When you tap the menu icon, Kanime opens AboutActivity
.
In MainActivity
, find the function openAboutActivity
and replace it with the Anko version:
private fun openAboutActivity() {
startActivity<AboutActivity>()
}
Here is the code prior to the switch to Anko:
private fun openAboutActivity() {
val intent = Intent(this, AboutActivity::class.java)
startActivity(intent)
}
Using Anko, with one line and a lot less code, you are able to open an activity without worrying about creating an Intent object.
Common Intents
Anko Commons offers a great set of “common” intents that are pretty easy to use. Take a look at the table below.
In AnimeDetailActivity
, there are a lot of Intents like “share the anime with a friend” the anime and “open the IMDB profile of the anime in a web browser”.
Let’s see how you can improve AnimeDetailActivity
using Anko Commons.
Share Anime
In AnimeDetailActivity
, search for the function shareAnime()
and update it with the Anko version:
private fun shareAnime() {
share("$title \n $imdbLink", "Give a checkout to $title")
}
The non-Anko code looked like this:
private fun shareAnime() {
val text = "$title \n $imdbLink"
val subject = "Give a checkout to $title"
//1. Creating the Intent
val intent = Intent(android.content.Intent.ACTION_SEND)
//2. Add extra data
intent.type = "text/plain"
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject)
intent.putExtra(android.content.Intent.EXTRA_TEXT, text)
//3. open a list of possible apps that can handle this intent
startActivity(Intent.createChooser(intent, null))
}
Nope, your eyes are not lying to you, this is for real!
With just one line, you are saving your self going off to search Google on the specific constants for the Intent for sharing with all the right parameters.
Open a Web browser
In AnimeDetailActivity
, find the function openAnimeInTheBrowser()
and update it to the Anko Version:
private fun openAnimeInTheBrowser(){
browse(imdbLink)
}
Here is the code without Anko:
private fun openAnimeInTheBrowser(){
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(imdbLink)
startActivity(intent)
}
Again, a huge savings in code and improvement in readability!
Send an email
In AnimeDetailActivity
, find the function sendAnimeByEmail()
and modify it to the cool Anko version:
private fun sendAnimeByEmail(emailAddress: String, subject: String, body: String) {
email(emailAddress, subject, body)
}
Here is the code without Anko:
private fun sendAnimeByEmail(emailAddress: String, subject: String, body: String) {
//1. Creating the Intent
val intent = Intent(Intent.ACTION_SENDTO)
//2. Add extra data
intent.data = Uri.parse("mailto:")
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(emailAddress))
intent.putExtra(Intent.EXTRA_SUBJECT, subject)
intent.putExtra(Intent.EXTRA_TEXT, body)
//3. Open the email app
startActivity(intent)
}
You’ve again gone done to just one line of code with Anko!
Toasts, Snackbars and Dialogs
Dialogs are pretty important widgets when you want to communicate with your users, and Anko Commons make your life easier when working with them. It covers most of the more common widgets like Toast and SnackBars, and many flavors of Dialogs.
Toast
In AnimeDetailActivity
, find the functions showShortToast
and showLongToast
and replace them with the Anko versions.
private fun showShortToast(message:String){
toast(message)
}
private fun showLongToast(message: String) {
longToast(message)
}
Here is the code without Anko:
private fun showShortToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
private fun showLongToast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
You’ve reduced a lot of the boilerplate by switching to Anko.
The best part of using toast()
and longToast()
is that you don’t have to call .show()
, after creating the Toast, a million dollar mistake.
Also, toast()
and longToast()
offer helpful overloaded versions, accepting string resources and String objects, for example:
toast("message")
toast(R.string.message)
longToast("message")
longToast(R.string.message)
Alerts
Alerts in Anko Commons are a quick solution to show Alert Dialogs:
In AnimeDetailActivity
, find the function showAlert
and update it with the Anko version.
private fun showAlert(messageResource: Int, onYesTapped: () -> Unit, onNoTapped: () -> Unit) {
alert(messageResource) {
//1. Creating the alert
yesButton {
//2. Handling yes button (This is optional)
onYesTapped()
}
noButton {
//3. Handling no button (This is optional)
onNoTapped()
}
}.show() //4. Showing the alert
}
alert
is a pretty handy function, with which you can specify the title and the message of the alert with String objects or String resources.
Also, within the alert
function you can specify handlers for yesButton
and noButton
. And if you want to have full control over the alert, it has an init
function. Here are some examples:
alert("message").show()
alert("message", "title").show() //The title is optional
alert(R.string.message, R.string.title).show()
alert("message") {
yesButton { } //Adds the default android.R.string.yes text to the button
noButton { } //Adds the default android.R.string.no text to the button
}.show()
alert { //the init function where you can configure the alert as you please
title = "title"
message = "message"
//Changing the default title
positiveButton("Yes") {
//Do something
}
//Changing the default title
negativeButton("No") {
//Do something
}
}.show()
Selectors
A selector is a special type of Dialog that allows you to show a list of items.
When you tap either the thumbs up or thumbs down buttons, it shows a Dialog with a list of options in it.
In AnimeDetailActivity
, search for the function showSelector()
and update it with a shorter one (Anko :] ).
private fun showSelector(
title: CharSequence, items: List<CharSequence>, onClick: (DialogInterface, Int) -> Unit) {
selector(title,items,onClick)
}
Here is the code without Anko:
private fun showSelector(
title: CharSequence, items: List<CharSequence>, onClick: (DialogInterface, Int) -> Unit) {
val context = this
//1. Creating the AlertDialog
val alertBuilder = AlertDialog.Builder(context)
//2. Setting the title
alertBuilder.setTitle(title)
//3. Setting click handlers for each item of the list
alertBuilder.setItems(Array(items.size) { itemIndex -> items[itemIndex].toString() }) { dialog, which ->
onClick(dialog, which)
}.show()
}
As you can see, the Anko version is again much more condensed, reducing the noise in your code, and it also conveys the intention of the code just by simply reading it, instead of trying to infer the meaning of all the non-Anko lines needed to build the selector.
Progress dialogs
A ProgressDialog allows you to indicate to your users that you’re doing something that is going to take a bit to process.
In MainActivity
, find the function showLoadingDialog()
and update it to an Anko version:
private fun showLoadingDialog(message: String, title: String): ProgressDialog {
val dialog = indeterminateProgressDialog(message, title) {
//Do any customization to the dialog here
show()
}
return dialog
}
Compare this with the non-Anko version:
private fun showLoadingDialog(message: String, title: String): ProgressDialog {
val dialog = ProgressDialog(this)
dialog.setMessage(message)
dialog.setTitle(title)
dialog.show()
return dialog
}
You can create different types of progress dialogs switching between progressDialog
and indeterminateProgressDialog
.
Also, within the progressDialog
you can specify the drawable to display the progress value, for example:
progressDialog("message","title").show() //The title is optional
progressDialog("message") {
//configure the alert as you please
val drawable = getDrawable(R.drawable.spinner)
setTitle("title")
// Set the drawable to be used to display the progress value.
setProgressDrawable(drawable)
show()
}
indeterminateProgressDialog("message","title").show()
AnkoLogger
Anko improves significantly on the Android Log class. One of the Anko advantages is that you don’t have to include a TAG name on each call to the logger, since by default it takes the name of the class as a tag.
Another positive aspect of Anko logging is that the name of the functions are nicer and more straightforward. The table below compares the Anko logger and the Android logger:
Let’s see some examples in Kanime.
In order to start using Anko Logger, first you must implement AnkoLogger
in your class. In this case, have MainActivity
implement AnkoLogger
:
class MainActivity : AppCompatActivity(), OnAnimeClickListener, AnkoLogger {
In MainActivity
, search for the functions logError()
and logWTF()
and update them with the Anko versions.
private fun logError() {
error("Log Error") //Will log E/MainActivity: Log Error
}
private fun logWTF() {
// What a Terrible Failure
wtf("Log WTF" ) // //Will log E/MainActivity: Log WTF
}
If you run into any trouble, make sure the imports for Anko are setup correctly:
import org.jetbrains.anko.AnkoLogger
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.error
import org.jetbrains.anko.wtf
Here are the non-Anko versions for comparison:
private fun logError() {
val tag = "MainActivity"
val message = "Log Error"
if (Log.isLoggable(tag, Log.ERROR))
Log.e(tag, message) //Will log I/MainActivity: Log Error
}
private fun logWTF() {
val tag = "MainActivity"
// What a Terrible Failure
Log.wtf(tag, message) //Will log I/MainActivity: Log WTF
}
You’ve again reduced the boilerplate quite a bit.
An alternative to implement AnkoLogger
is to have a reference to the AnkoLogger
class for example:
val logger = AnkoLogger<YourActivity>()
logger.error("This is an error") //Will Print E/YourActivity: This is an error
Also, you can change the tag name like this:
val logger = AnkoLogger("YourTag")
logger.error("This is an error") //Will Print E/YourTag: This is an error
In the same file MainActivity.kt, search for the functions
logInfo()
,logVerbose()
,logDebug()
and logWarn()
, and update them with the Anko versions.
private fun logInfo() {
info("Log Info") //Will log I/MainActivity: Log Info
info {
"Log Info" //Will log I/MainActivity: Log Info
}
}
private fun logVerbose() {
verbose("Log Verbose") //Will log I/MainActivity: Log Verbose
verbose {
"Log Verbose" //Will log I/MainActivity: Log Verbose
}
}
private fun logDebug() {
debug("Log Debug") //Will log D/MainActivity: Log Debug
debug {
"Log Debug" //Will log D/MainActivity: Log Debug
}
}
private fun logWarn() {
warn("Log Warn") //Will log W/MainActivity: Log Warn
warn {
"Log Warn" //Will log W/MainActivity: Log Warn
}
}
You see here that each of the logging functions in AnkoLogger have an alternative version that takes a Kotlin lambda as a parameter.
One important detail about AnkoLogger
is that every call to the logger is checking if Log.isLoggable(yourTag, Log.SELECTED_LOG_LEVEL)
is true. If not it won’t log the statement. Also, in the lambda versions, the lambda result will not be calculated if the particular log level is false.
Go ahead and build and run the final project with all the concise Anko code. You’ll see the app works just like before, but your app code is much more readablee! :]
Where to Go From Here?
You can download the final project with all the Anko changes using the download button at the top and bottom of the tutorial.
Now that you have become a master of using Anko Commons and added new items to your toolbox, you should check out the other parts of Anko: Layouts, SQLite, and Coroutines.
Remember that Anko is an open source project and you can contribute to it in many ways: proposing new features, reporting bugs or submitting a pull request.
If you have any comments or questions, please comment in the forum discussion below.
I hope this tutorial has been helpful for you. If it was remember to share it with your friends! :]