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

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

When choosing a first cosplay outfit, it is worth considering how details related to photoshoot preparation will affect the complete look. When comparing costume options, it is sensible to balance details related to cosplay costumes with care and storage needs. For choices related to details related to cosplay costume sets, men's cosplay costumes for conventions can help narrow the options for a specific purpose. To preserve the character's visual identity, it is useful to check details related to photoshoot preparation against the wearer's own needs.

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;}