Summary of the book Kotlin for Android Developers by Antonio Leiva
class MainActivity {
}
- Class with extra constructor:
class Person(name: String, surname: String)
- Using
init
block to declare the body of constructor
class Person(name: String, surname: String) {
init {
...
}
}
- A class always extends from
Any
(similar to Java Object). - Classes are closed by default (final).
- Can only extend a class if it's exlicitly declared as open or abstract:
open class Animal(name: String)
class Person(name: String, surname: String) : Animal(name)
Note that when using the single constructor nomenclature, we need to specify the parameters we’re using for the parent constructor. That’s the equivalent to super() call in Java.
- Using
fun
keyword:
fun onCreate(savedInstanceState: Bundle?) {
}
- Return
Unit
(kotlin) =void
(Java), any type can be returned
fun add(x: Int, y: Int) : Int {
return x + y
}
- If the result can be calculated using single expression, get rid of brackets and use equal
fun add(x: Int, y: Int) : Int = x + y
- Name First - Type Later (just like Swift :)
fun add(x: Int, y: Int) : Int {
return x + y
}
- Support default value, avoid function overloading
fun niceToast(message: String,
tag: String = MainActivity::class.java.simpleName,
length: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, "[$tag] $message", length).show()
}
Using:
niceToast("Hello")
niceToast("Hello", "MyTag")
niceToast("Hello", "MyTag", Toast.LENGTH_SHORT)
- named arguments can be used, mean you can write the name of the argument preceding the value to specify which one you want:
niceToast(message = "Hello", length = Toast.LENGTH_SHORT)
niceToast(message = "Hello", tag = "MyTag")
- Classes, functions or properties are
public
by default
In Kotlin:
- Everything is an object
- No primivitve types as in Java
Some differences:
- No automatic conversions among numeric types. Cannot assign an Int to a Double variable, must use explicit conversion
val i: Int = 7
val d: Double = i.toDouble()
- Characters (Char) cannot directly be used as numbers.
val c: Char = 'c'
val i: Int = c.toInt()
- Using "
and
" and "or
" for bitwise arithmetical operations
// Java
int bitwiseOr = FLAG1 | FLAG2;
int bitwiseAnd = FLAG1 & FLAG2;
// Kotlin
val bitwiseOr = FLAG1 or FLAG2
val bitwiseAnd = FLAG1 and FLAG2
- Literals can give information about its type. Let the compiler infer the type:
val i= 12 // An Int
val iHex = 0x0f // An Int from hexadecimal literal
val l = 3L // A Long
val d = 3.5 // A Double
val f = 3.5F // A Float
- A String can be accessed as an array and can be iterated:
val s = "Example"
val c = s[2] // This is the Char 'a'
// Iterate over String
val s = "Example"
for(c in s){
print(c)
}
- Variables in Kotlin can be easily defined as mutable (var) or immutable (val).
- The key concept: just use val as much as possible
- Don’t need to specify object types, they will be inferred from the value
val s = "Example" // A String
val i = 23 // An Int
val actionBar = supportActionBar // An ActionBar in an Activity context
However, a type needs to be specified if we want to use a more generic type:
val a: Any = 23
val c: Context = activity
Properties are the equivalent to fields in Java. Properties will do the work of a field plus a getter plus a setter.
In Java:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
...
Person person = new Person();
person.setName("name");
String name = person.getName();
In Kotlin, only a property is required:
public class Person {}
var name: String = ""
}
...
val person = Person()
person.name = "name"
val name = person.name
Custom getter and setter:
public class Person {
var name: String = ""
get() = field.toUpperCase()
set(value) {
field = "Name: $value"
}
}
Notes:
- backing field: if the property needs access to its own value in custom getter and setter
- The backing field can only be used inside property accessors
- Anko is a powerful library developed by JetBrains.
- 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.
Anytime you use something from Anko, it will include an import with the name of the property or function to the file. This is because Anko uses extension functions to add new features to Android framework.
In MainActivity:onCreate
, an Anko extension function can be used to simplify how to find the RecyclerView
:
val forecastList: RecyclerView = find(R.id.forecast_list)
- 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. Think of it as implementing in utility classes which include a set of static methods in Java.
- The advantage of using extension functions in Kotlin is that we don’t need to pass the object as an arguments. The extension function acts as if it belonged to the class, and we can implement it using
this
and all its public methods.
Example: Create a toast
function which doesn’t ask for the context, which could be used by any Context
objects
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
Extension functions can also be properties.
public var TextView.text: CharSequence
get() = getText()
set(v) = setText(v)
Extension functions don’t really modify the original class, but the function is added as a static import where it is used. Extension functions can be declared in any file, so a common practice is to create files which include a set of related functions.
Request class simply receives an url, reads the result and outputs the json in the Logcat
class Request(val url: String) {
fun run() {
val forecastJsonStr = URL(url).readText()
Log.d(javaClass.simpleName, forecastJsonStr)
}
}
async() {
Request(url).run()
uiThread { longToast("Request performed") }
}
async
function will execute its code in another thread, with the option to return to the main thread by callinguiThread
uiThread
is implemented differently depending on the caller object. If it’s used by anActivity
, theuiThread
code won’t be executed ifactivity.isFinishing()
returnstrue
, and it won’t crash if the activity is no longer valid.- use
async
executor:
val executor = Executors.newScheduledThreadPool(4)
async(executor) {
// Some task
}
- To return a future with a result, use
asyncResult
.
Data classes in Kotlin = POJO classes in Java
data class Forecast(val date: Date, val temperature: Float, val details: String)
equals()
hashCode()
copy()
: copy an object, modify the properties.- A set of numbered functions to map an object into variables.
If using immutability, if we want to change the state of an object, a new instance of the class is required
val f1 = Forecast(Date(), 27.5f, "Shiny day")
val f2 = f1.copy(temperature = 30f)
This way, we copy the first forecast and modify only the temperature property without changing the state of the original object.
- This process is known as multi-declaration and consists of mapping each property inside an object into a variable.
- The
componentX
functions are automatically created.
Example:
val f1 = Forecast(Date(), 27.5f, "Shiny day")
val (date, temperature, details) = f1
This multi-declaration is compiled down to the following code:
val date = f1.component1()
val temperature = f1.component2()
val details = f1.component3()
Another example:
for ((key, value) in map) {
Log.d("map", "key:$key, value:$value")
}
Companion objects
In Kotlin, we can’t create static properties or functions, but we need to rely on 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.
class ForecastRequest(val zipCode: String) {
companion object {
private val APP_ID = "15646a06818f61f7b8d7823ca833e1ce"
private val URL = "https://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=metric&cnt=7"
private val COMPLETE_URL = "$URL&APPID=$APP_ID&q="
}
fun execute(): ForecastResult {
val forecastResult = URL(COMPLETE_URL + zipCode).readText()
return Gson().fromJson(forecastResult, ForecastResult::class.java)
}
}
class ForecastDataMapper {
fun convertFromDataModel(forecast: ForecastResult): ForecastList {
return ForecastList(forecast.city.name, forecast.city.country, convertForecastListToDomain(forecast.list))
}
private fun convertForecastListToDomain(list: List<Forecast>): List<ModelForecast> {
return list.map { convertForecastItemToDomain(it) }
}
private fun convertForecastItemToDomain(forecast: Forecast): ModelForecast {
return ModelForecast(
convertDate(forecast.dt), forecast.weather[0].description, forecast.temp.max.toInt(),
forecast.temp.min.toInt(), generateIconUrl(forecast.weather[0].icon)
)
}
private fun convertDate(date: Long): String {
val df = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault())
return df.format(date * 1000)
}
}
- When using two classes with the same name, give a specific name to one of them so that we don’t need to write the complete package:
import com.minhpd.weatherapp.domain.model.Forecast as ModelForecast
- To convert the forecast list from the data to the domain model:
return list.map { convertForecastItemToDomain(it) }
In a single line, we can loop over the collection and return a new list with the converted items.
override fun onBindViewHolder(holder: ViewHolder,
position: Int) {
with(weekForecast.dailyForecast[position]) {
holder.textView.text = "$date - $description - $high/$low"
}
}
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. This means that all the code we define inside the brackets acts as an extension function of the object we specify in the first parameter, and we can use all its public functions and properties, as well as this. Really helpful to simplify code when we do several operations over the same object.
Kotlin has a fixed number of symbolic operators. A function with a reserved name that will be mapped to the symbol.
To overload an operator, functions must be annotated with the operator
modifier
Operator | Function |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a++ | a.inc() |
a- | a.dec() |
Operator | Function |
---|---|
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) |
Operator | Function |
---|---|
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) |
Operator | Function |
---|---|
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.
Operator | Function |
---|---|
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, ..., i_n) | a.invoke(i_1, ..., i_n) |
We can extend existing classes using extension functions to provide new operations to third party libraries. For instance, we could access to ViewGroup views the same way we do with lists:
operator fun ViewGroup.get(position: Int): View
= getChildAt(position)
Now to get a view from a ViewGroup by its position:
val container: ViewGroup = find(R.id.container)
val view = container[2]
- A lambda expression is a simple way to define an anonymous function.
- Prevent us from having to write the specification of the function in an abstract class or interface, and then the implementation of the class.
- In Kotlin, we can use a function as a parameter to another function.
To implement a click listener behaviour in Java, we first need to write the OnClickListener interface:
public interface OnClickListener {
void onClick(View v);
}
And then we write an anonymous class that implements this interface:
view.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "Click", Toast.LENGTH_SHORT).show();
}
});
This would be the transformation of the code into Kotlin (using Anko toast function):
view.setOnClickListener(object : OnClickListener {
override fun onClick(v: View) {
toast("Click")
}
}
Any function that receives an interface with a single function can be substituted by the function.
fun setOnClickListener(listener: (View) -> Unit)
A lambda expression is defined by the parameters of the function to the left of the arrow (surrounded by parentheses), and the return value to the right. In this case, we get a View
and return Unit
(nothing).
So with this in mind, we can simplify the previous code a little:
view.setOnClickListener({ view -> toast("Click")})
We can even get rid of the left part if the parameters are not being used:
view.setOnClickListener({ toast("Click") })
If the function is the last one in the parameters of the function, we can move it out of the parentheses:
view.setOnClickListener() { toast("Click") }
And, finally, if the function is the only parameter, we can get rid of the parentheses:
view.setOnClickListener { toast("Click") }
In Java
interface OnItemClickListener {
operator fun invoke(forecast: Forecast)
}
class ViewHolder(view: View, val itemClick: OnItemClickListener) :
RecyclerView.ViewHolder(view) {
...
}
public class ForecastListAdapter(val weekForecast: ForecastList,
val itemClick: ForecastListAdapter.OnItemClickListener) :
RecyclerView.Adapter<ForecastListAdapter.ViewHolder>() {
...
}
forecastList.adapter = ForecastListAdapter(result,
object : ForecastListAdapter.OnItemClickListener{
override fun invoke(forecast: Forecast) {
toast(forecast.date)
}
})
Using lambda:
public class ForecastListAdapter(val weekForecast: ForecastList,
val itemClick: (Forecast) -> Unit)
The function will receive a forecast and return nothing. The same change can be done to the ViewHolder
:
class ViewHolder(view: View, val itemClick: (Forecast) -> Unit)
Then:
val adapter = ForecastListAdapter(result) { forecast -> toast(forecast.date) }
In functions that only need one parameter, we can make use of the it reference, which prevents us from defining the left part of the function specifically.
val adapter = ForecastListAdapter(result) { toast(it.date) }
Check 13.3 Extending the language in the book for further details
The default modifier in Kotlin language is public
Same as Java.
Same as Java.
- An internal member is visible inside the whole module if it’s a package member. If it’s a member inside another scope, it depends on the visibility of the scope.
- We can use internal classes from any other class in the same module, but not from another module.
According to Jetbrains definition, a module is a discrete unit of functionality which you can compile, run, test and debug independently. It basically refers to the Android Studio modules we can create to divide our project into different blocks. In Eclipse, these modules would refer to the projects inside a workspace.
Same as Java.
- By default, all constructors are public, which means they can be used from any scope where their class is visible.
- We can make a constructor private using this specific syntax:
class C private constructor(a: Int) { ... }
In Kotlin we don’t need to specify the return type of a function if it can be computed by the compiler.
data class ForecastList(...) {
fun get(position: Int) = dailyForecast[position]
fun size() = dailyForecast.size()
}
The plugin substitutes any properties call into a function that requests the view, and a caching function that prevents from having to find the view every time a property is called.
Be aware that this caching mechanism only works if the receiver is an Activity or a Fragment. If it’s used in an extension function, the caching will be skipped, because it could be used in an activity the plugin is not able to modify, so it won’t be able to add the caching function.
Add the dependency to app build.gradle
:
apply plugin: 'kotlin-android-extensions'
...
buildscript {
repositories {
jcenter()
dependencies {
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
}
}
The only thing required by the plugin is the addition of a special “synthetic” import
to the class that
makes use of this feature.
- The views can be accessed as if they were properties of the activity or fragment. The names of the properties are the ids of the views in the XML.
- The
import
we need to use will start withkotlin.android.synthetic
plus the name of the XML we want to bind to the activity. We also have to specify the build variant:
import kotlinx.android.synthetic.main.activity_main.*
From that moment, we can access the views after setContentView
is called.
- If using
include
tag, we’ll need to add a synthetic import for any XMLs we use:
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.content_main.*
To access the views inside an XML, for example, a custom view or an adapter. The only difference is the required import
:
import kotlinx.android.synthetic.main.view_item.view.*
If we were in an adapter, for instance, we could now access the properties from the inflated views:
view.textView.text = "Hello"