Important notes of 'Kotlin for Android Developers' book by Antonio Leiva.
This book can help you understand the various ways how Kotlin can take you one step ahead and make your code much better.
And the support for this language comes from the company who develops the IDE, so we Android developers are first-class citizens.
-
More expressive: Write more with less code.
No need to getters, setters and toString(), equals(), hashCode().
-
Safer: Null safe. Possible null situations in compile time. Explicitly specify an object can be null. Save a lot of time debugging null pointer exceptions.
Java: Big amount of code is defensive.
var artist: Artist? = null artist?.print() val name = artist?.name ?: "empty"
-
Functional: Uses many concepts from functional programming: lambda exp, dealing with collections.
lambda exp:
view.setOnClickListener { toast("Hello world!") }
-
Extension functions: Extend any class without having access to the source code.
fun Fragment.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){ Toast.makeText(getActivity(), message, duration).show() } fragment.toast("Hi")
-
Interoperable with java libs and code.
Automated convert from Java to Kotlin:
Code -> Convert Java File to Kotlin File.
Kotlin Android Extensions' magic: No need to findViewById()
<TextView
android:id="@+id/message"
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
message.text = "Hello Kotlin!"
}
We can use message.text
instead of message.setText
for free.
- Class and its constructor:
class Person(name: String, surname: String) { init { ... } }
- Class inheritance:
- A class always extends from
Any
(similar to JavaObject
). - Classes are closed by default (final).
- We can only extend a class if declared
open
orabstract
:open class Animal(name: String) class Person(firstName: String, lastName: String) : Animal(firstName)
- A class always extends from
- Functions:
fun onCreate(savedInstanceState: Bundle?){ }
- Always return a value. default =
Unit
similar tovoid
in Java. - Single expression functions:
fun add(x: Int, y: Int) : Int = x + y
- Default value for parameters (preventing need of function overloadings):
fun niceToast(message: String, length: Int = Toast.LENGTH_SHORT) { Toast.makeText(this, message, length).show() }
- Named arguments in function calls:
niceToast(message = "Hello", length = Toast.LENGTH_SHORT)
- String templates: just write the $ symbol.
"[$className] $message" "Your name is ${user.name}"
- Always return a value. default =
- Defining variables and casting with
val
andas
:val forecastList = findViewById(R.id.forecast_list) as RecyclerView
- Instantiation: "
Class(this)
" in kotlin instead of "new Class(this)
" in java. - Default visibility for classes, functions or properties is
public
. - Creating constant lists (immutable lists) by using function
listOf()
which infers type of arguments.
- Everything is an object. There is
no primitive type. - No automatic conversion b/w numertic types.
val i: Int = 7 val d: Double = i.toDouble() val c: Char = 'c' val i: Int = c.toInt()
- Bitwise operations:
val bitwiseOr = FLAG1 or FLAG2 // FLAG1 | FLAG2; in java val bitwiseAnd = FLAG1 and FLAG2 // FLAG1 & FLAG2; in java
- Compiler can infer the type from the literals:
But for using more generic types (polymorphism), a type must be specified.
val i = 12 // An Int val iHex = 0x0f // Still an Int val l = 3L // A Long val d = 3.5 // A Double val f = 3.5F // A Float val actionBar = supportActionBar // An ActionBar
val a: Any = 23 val c: Context = someActivity
- A
String
can be accessed as an array and also iterated:val s = "Example" val c = s[2] // Char 'a' for (c in s){ print(c) }
- Variables: mutable (
var
) or immutable (val
)val
in kotlin is similar tofinal
in java- immutability: If you need a new version, a new object needs to be created. Makes software much more robust and predictable.
- Java: most objects are mutable -> Any part of code changing object may affect rest of the app.
- Immutable objects are thread-safe by definition.
- Just use
val
as much as possible (except when class constructor is not available).
- Properties: equivalent to fields in Java + getters + setters.
- Modifying default getters and setters:
class Person { var name: String = "" get() = field.toUpperCase() set(value){ field = "Name: $value" } }
- Kotlin can use the property syntax when dealing with code written in Java where a getter is defined in Java.
- Modifying default getters and setters:
- Anko: Generation of UI layouts by code instead of XML.
- Using XML should be easier.
- Anko uses extension functions to add new features to Android framework. Example:
val forecastList: RecyclerView = find(R.id.forecast_list)
- Features: instantiation of intents, navigation b/w activities, creating fragments, db access, alerts creation, etc.
- Extension function: adding new behavior to class without access to its source
- In java, this is usually implemented in utility classes with static methods.
- Example:
fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_SHORT){ Toast.makeText(this, message, duration).show() }
- Anko includes its own
toast()
andlongToast()
extension functions. - We also have extension properties:
val ViewGroup.childViews: List<View> get() = (0 until childCount).map { getChildAt(it) }
- Extension functions don't modify the original class but are add as static import.
- Common practice: create files including a set of related functions.
- OpenWeatherMap API can be used to retrieve weather data.
- Kotlin can use Retrofit (written in Java) for server requests.
- Performing a request: basics
class Request(val url: String) { fun run() { val forecastJsonStr = URL(url).readText() Log.d(javaClass.simpleName, forecastJsonStr) } }
- Implementation is easy using
readText()
, an extension function from Kotlin standard library. - In Java we needed:
HttpURLConnection
,BufferedReader
& many other checkings.
- Implementation is easy using
- Performing the request: executing out of the main thread
- HTTP requests are not allowed in the main thread. A common solution in Android =
AsyncTask
. AsyncTask
s may be dangerous: When reachespostExecute()
, activity may be destroyed.- Anko:
doAsync(){}
function for executing in another thread withuiThread{}
option for returning to the main thread:doAsync() { Request(url).run() uiThread { longToast("Request performed") } }
uiThread
is safe: it checks finishing or existence of the callerActivity
.doAsync
returns a javaFuture
. For getting a future with a result usedoAsyncResult
.
- HTTP requests are not allowed in the main thread. A common solution in Android =
A powerful kind of classes instead of POJO classes in Java.
data class Forecast(val date: Date, val temperature: Float, val details: String)
- Data class extra functions:
equals()
,hashCode()
,copy()
, etc. - Use of
copy
when using immutability:val f1 = Forecast(Date(), 27.5f, "Shiny day") val f2 = f1.copy(temperature = 30f)
- Java classes (e.g.
Date
class above) are not immutable by default. To force immutability you can wrap them (e.g.ImmutableDate
wrappingDate
). - Declaration destructuring = Mapping each property inside an object into a variable:
equals to:val f1 = Forecast(Date(), 27.5f, "Shiny day") val (date, temperature, details) = f1
Another example: Iterating overval date = f1.component1() val temperature = f1.component2() val details = f1.component3()
Map
's keys and values:for ((key, value) in map){ Log.d("map", "key:$key, value:$value") }
- Java classes (e.g.
- Unlike Java, each
*.kt
file can contain more than 1 Kotlin class. - Gson: to parse json to our classes. Properties must have the same name as the ones in the json, or specify a serialised name.
companion object
s of kotlin instead ofstatic
properties, constants and functions of Java:class ForecastRequest(val zipCode: String) { companion object { private val APP_ID = "15646a06818f61f7b8d7823ca833e1ce" private val URL = "http://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 forecastJsonStr = URL(COMPLETE_URL + zipCode).readText() return Gson.fromJson(forecastJsonStr, ForecastResult::class.java) } }
- Every function in Kotlin returns a value. If nothing specified, returns an object of
Unit
class. - Definition of a
Command
interface:public interface Command<out T>{ fun execute(): T }
- In this architecture, 1st we retrieve data from API and fill
data class
es in data package using Gson, and then convert needed info into domaindata class
es. - We can convert a list easily without directly using loops by using functional operations over lists:
return list.mapIndexed {i, forecast -> ...}
with
function in standard Kotlin library:- Gets an object and an extension function as parameters, runs the function on the object.
- Really helpful when doing several operations overt the same object.
- Implement a function with a reserved name mapped to the symbol.
a++ -> a.unaryPlus() a..b -> a.rangeTo(b) a[i] -> a.get(i) a[i]=b -> a.set(i,b) a==b -> a?.equals(b)?:b===null a(i) -> a.invoke(i)
- Improves code readability and simplicity.
- The reserved function must be annotated with
operator
modifier. - Types of operators: unary, binary, array-like, equals, function invocation
- equal operator (==) must be implemented exactly like this"
operator fun equals(other: Any?): Boolean
- Kotlin lists have the array-like operations.
- We could extend existing classes using extension functions to provide new operators on them.
- Picasso: An image loader library, like glide.
- Defining a setOnClickListener interface with
invoke()
operator can simplify call of the listener:interface OnItemClickListener{ operator fun invoke(forecast: Forecast) } itemclick.invoke(forecast) itemclick(forecast)
- Implementing an anonymous class in Kotlin: Create an
object
(notObject
) implementing the desired interfaceview.setOnClickListener( object: OnItemClickListener{ override fun invoke(item: Item){ toast(item.text) } })
- It's not nice. We should instead make use of functional programming with lambdas.
Lambda expression = Simple way to define an anonymous function (a function w/o name).
- In Kotlin a function behaves as a type, so it can be:
- passed as an argument
- returned by a function
- saved into a var or property
- Any function that receives an interface with a single function can be substituted by a lambda.
- Like
setOnClickListener()
is defined as:fun setOnClickListener(listener: (View) -> Unit) // a higher order fun accepting another fun
- Unlike Java, a SAM (Single Abstract Method) Interface
val
can't be passedinstead of a lambda functionval
.
- Like
- Without lambda expression:
With lambda expression:
view.setOnClickListener(object: OnClickListener{ override fun onClick(v: View){ toast("Click") } })
Unlike Java, in Kotlin we must useview.setOnClickListener({ view -> toast("Click")}) // or view.setOnClickListener({toast("Click")}) //input values are of no use // or view.setOnClickListener(){toast("Click")} //if the last argument is a (lambda) function // or view.setOnClickListener{ toast("Click") } //if function is the only parameter
{}
for defining lambda expressions. - In lambdas with only one argument, we can ignore the argument and use
it
.val adapter = ForecastListAdapter(result) { toast(it.date) }
- Unusable knowledge: A simple Implementation of
with
in Kotlin:inline fun <T> with(t: T, body: T.() -> Unit) { t.body() } // Gets an object of type T and a function that will be used as an extension function.
- Benefit of an inline function: Reduces memory allocations and runtime overhead in some situations.
- Useful example:
inline fun supportsLollipop(code: () -> Unit){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ code() } } supportsLollipop{ window.setStatusBarColor(color.BLACK) }
- Modifiers:
- private
private
class: Not accessible outside the file where it was defined.
- protected
- internal: visible inside the whole module if it's a package member. We can use internal classes from any other class in the same module.
- module = Android Studio modules = can be compiled, run, tested and debugged independently.
- public
- Default modifier in Kotlin
- private
- Constructors: public by default
- Note: No need to specify the return type of a function if can be computed by the compiler.
operator fun get(position: Int) = dailyForecast[position] // instead of operator fun get(position: Int): Forecast = dailyForecast[position]
- View binder: Automatically creates a set of properties giving access to all views in a XML. So we don't need to explicitly find all the views in the layout before use.
- Names come from ids of the views. So XML
id
s should not have_
and must be in CamelCase format. - Types also taken from the XML.
- Names come from ids of the views. So XML
- Benefit: Doesn't add any extra libs to our code. Instead consists of a plugin generating the code.
- Plugin substitutes properties call into a function requesting the view.
How to add it to our project:
apply plugin: 'kotlin-android-extensions'
How to use it in our code:
- In Activities or Fragments (regular):
import kotlinx.android.synthetic
+ name of the XML. ex:import kotlinx.android.synthetic.main.activity_main
- We can access teh views after
setContentView
.
- In Views: we may need to access XML views in many other parts of the code.
import kotlinx.android.synthetic.main.view_item.view.*
- Access by
itemView.textView.text = "Hello"
. - It won't cache the views.
- Every bind will do
findViewById
under the hood for all subviews. - If your layout is complex, this may make it slower, specially the
RecyclerView
performance.
- Every bind will do
- Old (not recommended) way of creating a singleton, like in Java:
class App : Application(){ companion object{ private var instance: Application? = null fun instance() = instance!! } override fun onCraete(){ super.onCreate() instance = this } }
- Reimplementing App singleton in Kotlin (using concepts below):
class App : Application(){ companion object{ lateinit var instance: App private set } override fun onCreate(){ super.onCreate() instance = this } }
- Interesting behaviors in a property: Lazy values and Observable properties.
- Property Delegate: Instead of having to declare the same code over and over again, delegate the code a property needs to another class (property delegate).
- Ex: Delegating
get
orset
of a property to another class:class Delegate<T> { operator fun getValue(thisRef: Any?, property: KProperty<*>): T { return ... } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { ... } } class Example{ var p: String by Delegate() }
T
: type of the property delegating its behaviorthisRef
: reference to class of the propertyproperty
: metadata of the propertyby
: reserved word to specify the delegation.
- Ex: Delegating
- Standard delegates in Kotlin Standard Library:
- lazy: takes a lambda executed first time
getValue
is called.- ex:
class App : Application() { val database: SQLiteOpenHelper by lazy { MyDatabaseHelper(applicationContext) } override fun onCreate() { super.onCreate() val db = database.writableDatabase } }
- Subsequent calls will return the same value.
- Interesting when the property is not always required.
- saves memory
- Needed when some other parts must be ready before this one.
- in ex above we are sure
applicationContext
exists during initialization.
- in ex above we are sure
lazy
is thread safe by default.
- ex:
- observable: takes a lambda executed after every time
set
function is called.- Helps us detect changes on any property we need to observe.
- In the observable we receive 'delegated property', 'old value' and 'new value'.
- ex:
class ViewModel(val db: MyDatabase) { var myProperty by Delegates.observable("") { _, _, new -> db.saveChanges(this, new) } }
- Note: you can use
_
for unused lambda arguments.
- vetoable: a special kind of observable deciding whether the value must be saved?
- ex:
var positiveNumber = Delegates.vetoable(0){ _, _, new -> new >= 0 }
- Can be used to check some conditions before saving a value.
- ex:
- lateinit: not a delegate, but a property modifier.
- A non abstract property must have a value before finish of the constructor, which may not be available in Activities, etc. Solutions:
- Define property as nullable and set it to null. Drawback: we need to check nullity every time. If property won't be null when used -> unnecessary code. (Java & Kotlin)
lateinit
: Property should have non-nullable value but its assignment will be delayed. If the value is requested before being assigned -> throws an exception.
- ex:
class App : Application() { companion object { lateinit var instance: App } override fun onCreate() { super.onCreate() instance = this } }
- NOTE:
lateinit
can only be used forvar
.
- A non abstract property must have a value before finish of the constructor, which may not be available in Activities, etc. Solutions:
- by map: delegates value of a property to be filled from a map (taken in constructor); name of the property = key of the map.
- ex:
class Configuration(map: Map<String, Any?>) { val width: Int by map val height: Int by mapped val dp: Int by mapped val deviceName: String by map } val conf = Configuration(mapOf( "width" to 1080, "height" to 720, "dp" to 240, "deviceName" to "mydevice" ))
- Easily creates an object from a dynamic map.
- ex:
- lazy: takes a lambda executed first time
- Old way to get objects from DB: Write SQL sentences, then map objects into
ContentValues
or take them fromCursors
. - New way: Use ORMs for Android like GreenDAO or Room (google jetpack).
Instead of instantiating collabators inside the class, we provide them via constructor.
- More testable, easy to extend and maintain.
- Substitute collaborators with other objects (same interface) or make use of mocks in tests.
Dagger: Most popular dependency injector for Android.
- Simpler alternative = use of default values in constructors.
class ForecastDbHelper(ctx: Context = App.instance){ ... } val dbHelper1 = ForecastDbHelper() // It will use App.instance val dbHelper2 = ForecastDbHelper(mockedContext) // For tests, for example
Functional programming advantage: We just say what we want to do, not explaining how to do it.
- Ex. filtering a list:
- Non-functional: creat a list, iterate over original list and add selected items to the new list.
- Functional: use a filter function with a specified filter.
- Say a lot more using less code.
Kotlin native collection-related interfaces:
- Iterable: the parent interface.
- MutableIterable
- Collection: including functions for size, is empty, contains & etc.
- MutableCollection: plus add, remove, clear, etc.
- List: ordered. Get an item by
get(position)
function. - MutableList
- Set: unordered, no duplicate elements allowed.
- MutableSet
- Map: a collection of key-value pairs.
- MutableMap
Functional operations available over different collections:
- any{condition}:
true
if any member matches condition. - all{condition}:
true
if all members match condition. - count{condition}: number of elements matching condition.
- fold(initial_value){total, next->total+next}: applies an operation from 1st to last element, putting result in total.
- foldRight: same as fold but from last to 1st element.
- forEach{operation}: performs operation to each element.
- forEachIndexed{index, value -> function of index and value}: same as forEach but operation is function of index and value.
- max(): largest element or
null
. - maxBy{function}: largest value of the function on the collection.
- min()
- minBy(function)
- none{condition}:
true
if no element match the condition. - reduce{total, next->total+next}: same as fold but without initial_value.
- reduceRight: same as reduce but form last to 1st element.
- sumBy{operation}: sum of transformation of all elements. Filtering operations:
- drop(n): a list containing all but 1st n elements.
- dropWhile{condition}: a list containing all except 1st elements satisfying the condition.
- dropLastWhile{condition}: a list containing all except last elements satisfying the condition.
- filter: