Kotlin DSL - let's express code in "mini-language" - Part 4 of 5
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 and onCategory 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.