Learn Kotlin gist for android developers part- 2
This is the second part of learn kotlin gist series. You can read the part-1 also.
Anko and Extension Functions — 7
What is Anko?
Its main purpose is the generation of UI layouts by using code instead of XML. Anko includes a lot of extremely helpful functions and properties that will avoid lots of boilerplate.
Anko uses extension functions to add new features to Android framework.
Extension functions
An extension function is a function that adds a new behaviour to a class, even if we don’t have access to the source code of that class.
The advantage of using extension functions in Kotlin is that we don’t need to pass the object as an argument. The extension function acts as if it belonged to the class.
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
This function can be used inside an activity, for instance:
toast("Hello world!")
toast("Hello world!", Toast.LENGTH_LONG)
Extension functions can also be properties. So you can create extension properties too in a very similar way.
Retrieving data from API — 8
Performing a request
General way:
class Request(val url: String) {
fun run() {
val forecastJsonStr = URL(url).readText()
Log.d(javaClass.simpleName, forecastJsonStr)
}
}
This method is not recommended for huge responses, An HttpURLConnection , a BufferedReader and an iteration over the result would have been necessary to get the same result.
Performing the request out of the main thread
Anko provides a very easy DSL to deal with asynchrony. It basically provides an async function that will execute its code in another thread, with the option to return to the main thread by calling uiThread
async() {
Request(url).run()
uiThread { longToast("Request performed") }
}
If it’s used by an Activity , the uiThread code won’t be executed if activity.isFinishing() returns true , and it won’t crash if the activity is no longer valid.
You also can use your own executor:
val executor = Executors.newScheduledThreadPool(4)
async(executor) {
// Some task
}
And if you need it to return a future with a result, you can use asyncResult .
Data Classes — 9
Copying a data class
if we want to change the state of an object, a new instance of the class is required, with one or more of its properties modified. However, data classes include the copy() method.
val f1 = Forecast(Date(), 27.5f, "Shiny day")
val f2 = f1.copy(temperature = 30f)
We copy the first forecast and modify only the temperature property without changing the state of the original object.
Mapping an object into variables
This process is known as multi-declaration and consists of mapping each property inside an object into a variable.
val f1 = Forecast(Date(), 27.5f, "Shiny day")
val (date, temperature, details) = f1
The logic behind this feature is very powerful. For instance, Map class has some extension functions implemented that allow to recover its keys and values in an iteration:
for ((key, value) in map) {
Log.d("map", "key:$key, value:$value")
}
Parsing data — 10
Converting json to data classes
A good practice explained in most software architectures is to use different models for the different layers in our app to decouple them from each other.
Companion objects
If we need some static properties, constants or functions in a class, we can use a companion object. This object will be shared among all instances of the class, the same as a static field or method would do in Java.
Shaping the domain layer
First, a definition of a Command is required:
public interface Command<T> {
fun execute(): T
}
It’s interesting to know that every function in Kotlin returns a value. By default, if nothing is specified, it will return an object of the Unit class. So if we want our Command to return nothing, we can specify Unit as its type.
Interfaces in Kotlin are more powerful than Java (prior to Java 8), because they can contain code.
Another interesting thing about this code is the way to convert the forecast list from the data to the domain model:
return list.map { convertForecastItemToDomain(it) }
Drawing the data in the UI
with function
with is a useful function included in the standard Kotlin library. It basically receives an object and an extension function as parameters, and makes the object execute the function.
Operator overloading — 11
Operators tables
Here you can see a set of tables that include an operator and its corresponding function.
Unary operations
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a– a.dec()
Binary operations
a+b a.plus(b)
a-b a.minus(b)
a*b a.times(b)
a/b a.div(b)
a%b a.mod(b)
a..b a.rangeTo(b)
a in b b.contains(a)
a !in b !b.contains(a)
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.modAssign(b)
Array-like operations
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, …, i_n] a.get(i_1, …, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, …, i_n] = b a.set(i_1, …, i_n, b)
Equals operation
a == b a?.equals(b) ?: b === null
a != b !(a?.equals(b) ?: b === null)
Operators === and !== do identity checks (they are == and != in Java respectively) and can’t be overloaded.
Operators in extension functions
We could access to ViewGroup views the same way we do with lists:
operator fun ViewGroup.get(position: Int): View
= getChildAt(position)
Now it’s really simple to get a view from a ViewGroup by its position:
val container: ViewGroup = find(R.id.container)
val view = container[2]