Trainto.log()

IT, music, movie, travel, life.. random log of my life

Android - Replace EventBus with LiveData

Google announced a new set of architecture libraries. One of the new compoenents is LiveData, which can be used to manage propagating data to the views, while respecting the view’s lifecycles. It means you don’t have to care about view’s lifecycles anymore.

Using this LiveData, an event bus can be implemented without any 3rd party libraries like Otto. It also can be achieved using Rx, but with Rx, it can easily lead to memory leaks unless subscriptions managed carefully. With LiveData, we don’t have to worry about memory leaks or view’s lifecycles.

Let’s start with implementation. (Kotlin will be used)

To use LiveData in your application, dependencies should be applied to your gradle configuration.

// This includes LiveDdata and ViewModel
// If you want to add just LiveData, then use 
// "android.arch.lifecycle:livedata:x.x.x"
implementation "android.arch.lifecycle:extensions:1.1.0"

Ok, everything’s set to start.

We need a class holds livedata(event) first.

object EventProvider {

    private val liveDataEvent = MutableLiveData<Event>()

    fun post(event: Any) {
        this.liveDataEvent.postValue(Event(event))
    }

    fun getEventLiveData(): MutableLiveData<Event> {
        return liveDataEvent
    }


    class Event(private val value:Any) {
        private var isConsumed = false

        fun setConsumed() {
            isConsumed = true
        }

        fun isConsumed(): Boolean {
            return isConsumed
        }

        fun getValue(): Any {
            return this.value
        }
    }
}

This class is singleton and holds MutalbeLiveData. The Event class's value field has Any type so that any type of data can be sent through this EvnetProvider. And Event can be submmited through post function. Also note that the Event class has isConsumed field. This is due to the behaviour of LiveData which it sends out when new observer is registered. This is totally fine if it is for propagating values, but here LiveData is used for Event Bus, so we don't want to receive events every time activities created and registered.

Ok, now we prepared event bus. Now let’s subscribe this event bus from the view(activity).

abstract class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        EventProvider.getEventLiveData().observe(this, Observer {
            it?.let { 
                if (!it.isConsumed()) handleEventBus(it.getValue())
                it.setConsumed()
            }
        })
    }
    
    protected fun handleEventBus(value: Any) {
        when (value) {
            is String -> Log.d("Event", value)
        }
    }
}

I mentioned that with LiveData, we don’t have to care about view’s lifecycles. To take this advantage, activity should extend android.support.vy.app.AppCompatActivity.(For fragment, it should extends android.support.v4.app.Fragment) Because these support libraries are already integrated with LifecycleOwners.

This BaseActivity subscribes the event bus through call observe(LifecyclerOwner, Observer) of the LiveData inside of EventProvider on its onCreate() lifecycle. When event occurred, Observer checks if this event consumed before, if it’s not, then call handleEventBus function.

Now you can create activities extends this BaseActivity, and override the handleEventBus to make the activity has its own behaviour to the specific event.

Kotlin - Kotlin's magic with let, apply, run and with

Kotlin’s Standard.kt provides some higher-order functions implementing idiomatic patterns like let, apply, run and with.

With the help of these functions, your code can be more simple and elegant.

The only challenge of using these functions(let, apply, run and with) is that it feels they are very much similar.

Let’s go through how to handle this Kotlin’s magic.

let

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

let() passes the object, which calls it, to the block, and returns the result of the block.

val student = Student("David", 20)
val result = student.let { it.age * 2 }

println(result) // 40

This is a very simple example, but consider using let with safe calls(?.). You don’t have to bother with if (obj != null) {….} anymore. Check below.

// without safe calls and let
var strRes: String? = null
if (context != null) {
    strRes = context.getString(R.string.app_name)
}

// with safe calls and let
var strRes:String? = context?.let { it.getString(R.string.app_name) }

This is quite powerful, isn’t it?


apply

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

apply() passes the object, which calls it, to the block’s receiver, and returns the object itself.

Let’s check out how apply() can be used.

class Student(val name: String, val age: Int) {
    var major: String? = null
    var mobileNumber: String? = null
    var address: String? = null
}

val newStudent = Student("Joon", 20).apply { 
    major = "Computer Science"
    mobileNumber = "+82-10-1111-1111"
    address = "Seoul"
}

With apply, new Student instance initialized in a convenient way.


run

/**
 * Calls the specified function [block] and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

There are 2 types of run() function.

Using run() independently without object, it will be treated like anonymous function without parameters. Just like any other functions, it can return nothing, or return something.

When run() called from objects, the object will be passed to the block, and returns result of the block.

Maybe it is confused with apply(), but remember that they return different type.(apply returns an object which it is called, and run returns the result of the block.)

class Student(val name: String, val age: Int) {
    var major: String? = null
    var mobileNumber: String? = null
    var address: String? = null
}

val newStudent = Student("Joon", 20).apply { 
    major = "Computer Science"
    mobileNumber = "+82-10-1111-1111"
    address = "Seoul"
}

val homework = newStudent.run {
    if (major === "Computer Science") {
        "Implementing LinkedList"
    } else {
        "No homework!!"
    }
}

println(homework) // "Implementing LinkedList"


with

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

with() passes its parameter to the block as a receiver, and returns the result of the block.

Actually with() is almost same with run(), except where the object passed to the block is. And run() can be used with safe calls, but with() can’t.

class Student(val name: String, val age: Int) {
    var major: String? = null
    var mobileNumber: String? = null
    var address: String? = null
}

val newStudent = Student("Joon", 20).apply { 
    major = "Computer Science"
    mobileNumber = "+82-10-1111-1111"
    address = "Seoul"
}

val homework = with(newStudent) {
    if (major === "Computer Science") {
        "Implementing LinkedList"
    } else {
        "No homework!!"
    }
}


There are another useful funtions in Standard.kt like also, takeIf, takeUnless and repeat. Refer to Standard.kt, and examine them with simple code. I believe it will make your kotlin code more beautiful.

Android - Lambda with Retrolambda

Since Java 8 release Java programmers can take advantages of lambda expression. However it can’t be applied directly to Android development.

There are two options to use lambda expression for Android development.

Using Jack tool-chain supports more features of Java 8 than using Retrolambda, but I recommend Retrolambda here. Because Jack has a few disadvantages.

  • Does not support Lint
  • Does not support Instant Run
  • Does not support full features for every API level


Let’s dive into lambda expression with Retrolambda.

First, add the following to your build.gradle

buildscript {
  repositories {
    mavenCentral()
  }

  dependencies {
    classpath 'me.tatarka:gradle-retrolambda:3.5.0'
  }
}

repositories {
  mavenCentral()
}

apply plugin: 'com.android.application'
apply plugin: 'me.tatarka.retrolambda'

alternatively, new plugin syntax for gradle 2.1+ can be used

plugins {
  id "me.tatarka.retrolambda" version "3.5.0"
}

Click gradle sync button in AndroidStudio.

Now coding with lambda expression on android is available.

// Traditional
button.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
    log("Clicked");
  }
});

// With lambda expression
button.setOnClickListener(v -> log("Clicked"));


// Traditional
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
  @Override
  public void run() {
    handlePushEvent(pushEvent);
  }
});

// With lambda expression
Handler handler = new Handler(Looper.getMainLooper());
handler.post(() -> handlePushEvent(pushEvent));

How simple and beautiful(?) it is!!

Not only lambda expression, but also method reference is available.

button.setOnClickListener(this::printHello);

private void printHello() {
  log("Hello");
}