Kotlin DSL - let's express code in "mini-language" - Part 4 of 5

dsl•Mar 16, 2019

In this post, we take a look at building a simpler API to work with Broadcast receiver, which will automatically unregister itself when the Activity pauses, and register again when it resumes.

Broadcast Receiver

To build this , we first create a class which observes  lifecycle events

class BroadcastReceiver<T>(
  context: T,
  constructor: Builder.() -> Unit
) : LifecycleObserver where T : Context, T : LifecycleOwner {

  
  @OnLifecycleEvent(ON_START)
  fun start() {
    appContext.registerReceiver(broadcastReceiver, filter)
  }

  @OnLifecycleEvent(ON_DESTROY)
  fun stop() = appContext.unregisterReceiver(broadcastReceiver)
}

To attach lifecycle events to our custom class BroadcastReceiver , we extend our class with LifecycleObserver and LifecycleOwner

context.lifecycle.addObserver(this)

During initialize, we init our Builder :

init {
    val builder = Builder()
    constructor(builder)
    filter = builder.filter()
    instructions = builder.instructions()

    context.lifecycle.addObserver(this)
  }

Builder is another class

class Builder internal constructor() {

  private val filter = IntentFilter()
  private val instructions = mutableListOf<Instructions>()

  fun onAction(
    action: String,
    execution: Execution
  ) {
    filter.addAction(action)
    instructions.add(OnAction(action, execution))
  }

  fun onDataScheme(
    scheme: String,
    execution: Execution
  ) {
    filter.addDataScheme(scheme)
    instructions.add(OnDataScheme(scheme, execution))
  }

  fun onCategory(
    category: String,
    execution: Execution
  ) {
    filter.addCategory(category)
    instructions.add(OnCategory(category, execution))
  }

  internal fun filter() = filter

  internal fun instructions() = instructions
}

in which we have 3 main functions : onAction , onDataScheme andonCategory where we do operation on the filter for this Broadcast receiver

Instructions are set of data classes where the checks for action , category are handled.

typealias Execution = (Intent) -> Unit

sealed class Instructions {

  abstract fun matches(intent: Intent): Boolean

  abstract fun execution(): Execution

  data class OnAction(
    val action: String,
    val execution: Execution
  ) : Instructions() {

    override fun matches(intent: Intent): Boolean {
      return intent.action == action
    }

    override fun execution() = execution
  }

  data class OnDataScheme(
    val scheme: String,
    val execution: Execution
  ) : Instructions() {
    override fun matches(intent: Intent): Boolean {
      return intent.data?.scheme == scheme
    }

    override fun execution() = execution
  }

  data class OnCategory(
    val category: String,
    val execution: Execution
  ) : Instructions() {
    override fun matches(intent: Intent): Boolean {
      return intent.hasCategory(category)
    }

    override fun execution() = execution
  }
}

Here , typealias is allowing us to refer to type (Intent)-> Unit as Execution

Now to connect it all :

class BroadcastReceiver<T>(
  context: T,
  constructor: Builder.() -> Unit
) : LifecycleObserver where T : Context, T : LifecycleOwner {

  private val appContext = context.applicationContext
  private val filter: IntentFilter
  private val instructions: List<Instructions>

  init {
    val builder = Builder()
    constructor(builder)
    filter = builder.filter()
    instructions = builder.instructions()

    context.lifecycle.addObserver(this)
  }

  private val broadcastReceiver = object : BroadcastReceiver() {
    override fun onReceive(
      context: Context,
      intent: Intent
    ) {
      for (ins in instructions) {
        if (ins.matches(intent)) {
          ins.execution()
              .invoke(intent)
          break
        }
      }
    }
  }

  @OnLifecycleEvent(ON_START)
  fun start() {
    appContext.registerReceiver(broadcastReceiver, filter)
  }

  @OnLifecycleEvent(ON_DESTROY)
  fun stop() = appContext.unregisterReceiver(broadcastReceiver)
}

Our Broadcast receiver DSL can now be called in the following way :

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Unregister when onPause, and register again when it resumes.
    BroadcastReceiver(this) {
      onAction("app.SOME_ACTION") {
        // Do something
      }
      onCategory("messages") {
        // Do something
      }
      onDataScheme("file://") {
        // Do something
      }
    }

(credits : Aidan Follestad https://goo.gl/Mi7Z9x)

In Part 5 of this series , we will take a look at how to apply DSLs in writing easier to understand tests for android.

Adit Lal

Recommended for you

json[

        Ad-hoc polymorphism in JSON with Kotlin

a year ago•4 min read](/web/20200807213717/https://www.aditlal.dev/polymorphic-json/)dsl[

        Kotlin DSL - let's express code in "mini-language" - Part 5 of 5

a year ago•3 min read](/web/20200807213717/https://www.aditlal.dev/kotlin-dsl-part-5/)dsl[

        Kotlin DSL - let's express code in "mini-language" - Part 3 of 5

a year ago•3 min read](/web/20200807213717/https://www.aditlal.dev/kotlin-dsl-part-3/)

  No results for your search, please try with something else.

Adit Lal © 2020  •  Published with Ghost

JavaScript license information

cookie-bar {background:#090a0b; height:auto; line-height:24px; color:#fff; text-align:center; padding:3px 0;}

cookie-bar.fixed {position:fixed; top:0; left:0; width:100%;}

cookie-bar.fixed.bottom {bottom:0; top:auto;}

cookie-bar p {margin:0; padding:0;}

cookie-bar a {color:#ffffff; display:inline-block; border-radius:3px; text-decoration:none; padding:0 6px; margin-left:8px;}

cookie-bar .cb-enable {background:#26a8ed;}

cookie-bar .cb-enable:hover {background:#26a8ed;}

cookie-bar .cb-disable {background:#26a8ed;}

cookie-bar .cb-disable:hover {background:#26a8ed;}

cookie-bar .cb-policy {background:#26a8ed;}

cookie-bar .cb-policy:hover {background:#26a8ed;}