Commit a61066f9 authored by Joey's avatar Joey

core: add event-based architecture components

Change-Id: I116985fea1992a085079a5dc32a19b22b65b222b
Signed-off-by: Joey's avatarJoey <bevilacquajoey@gmail.com>
parent dcd4b520
......@@ -52,6 +52,11 @@ spotless {
}
}
// Experimental coroutines APIs
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi"]
}
ext {
appVersionCode = 100 + Integer.parseInt('git rev-list --count HEAD'.execute([], project.rootDir).text.trim())
}
\ No newline at end of file
......@@ -32,8 +32,8 @@ dependencies {
// Kotlin
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.31"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0-M2"
// Architecture
api "androidx.lifecycle:lifecycle-extensions:2.1.0-beta01"
......
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.core.arch
/**
* An event to which an [UiComponent] can subscribe to and / or eventually create
*/
interface ComponentEvent
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.core.arch
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BroadcastChannel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass
/**
* It implements a Factory pattern generating [BroadcastChannel]s based on [ComponentEvent]s.
* It maintain a map of [BroadcastChannel]s, one per type per instance of EventBusFactory.
*
* Based on Netflix' EventBusFactory.
*
* @param lifecycleOwner is a LifecycleOwner used to auto dispose based on destroy observable
*/
@UseExperimental(ExperimentalCoroutinesApi::class)
class EventBusFactory private constructor(private val lifecycleOwner: LifecycleOwner) {
@ExperimentalCoroutinesApi
private val map = HashMap<Class<*>, BroadcastChannel<*>>()
@Suppress("unused")
internal val observer = object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
map.forEach { entry ->
entry.value.cancel()
}
buses.remove(lifecycleOwner)
}
}
fun <T> create(clazz: Class<T>): BroadcastChannel<T> {
val subject = BroadcastChannel<T>(1)
map[clazz] = subject
return subject
}
@Suppress("unchecked_cast")
fun <T : ComponentEvent> emit(
clazz: KClass<T>,
event: T,
context: CoroutineContext = Dispatchers.Default
) {
val channel = map[clazz.java] ?: create(clazz.java)
CoroutineScope(context).launch {
channel as BroadcastChannel<T>
channel.send(event)
}
}
@Suppress("unchecked_cast")
fun <T : ComponentEvent> subscribe(
clazz: KClass<T>,
scope: CoroutineScope,
onEvent: (T) -> Unit
) {
val channel = map[clazz.java] ?: create(clazz.java)
channel as BroadcastChannel<T>
val subscription = channel.openSubscription()
scope.launch {
subscription.consumeEach(onEvent)
}
}
companion object {
private val buses = mutableMapOf<LifecycleOwner, EventBusFactory>()
@JvmStatic
operator fun get(lifecycleOwner: LifecycleOwner): EventBusFactory {
var bus = buses[lifecycleOwner]
if (bus == null) {
bus = EventBusFactory(lifecycleOwner)
buses[lifecycleOwner] = bus
lifecycleOwner.lifecycle.addObserver(bus.observer)
}
return bus
}
}
}
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.core.arch
/**
* A component that contains a [UiView] and manages its logic.
*
* A component can be active or passive:
* a passive component can only listen to [ComponentEvent]s, while an active
* component can also post them.
*/
interface UiComponent
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.core.arch
import android.view.View
/**
* Manages one or more [View]s.
*
* This component can only be aware of a [ViewStatus] that
* holds the information used to set up the inner views.
*
* If the component has views that store informations
* that we want to retrive at a certain point
* (e.g. when a "Send" button is pressed we read the
* message from the text field), we can return a [ViewStatus]
* so that the component can later use it in its business logic.
*/
@Suppress("unused")
abstract class UiView<in I : ViewStatus, out O : ViewStatus>(protected val container: View) {
open fun setStatus(status: I) = Unit
open fun getStatus(): O {
throw UnsupportedOperationException("This UiView does not return any ViewStatus")
}
}
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.core.arch
/**
* Status holder an [UiView]
*/
interface ViewStatus
\ No newline at end of file
/*
* Copyright (c) 2019 Bevilacqua Joey
*
* Licensed under the GNU GPLv3 license
*
* The text of the license can be found in the LICENSE file
* or at https://www.gnu.org/licenses/gpl.txt
*/
package it.diab.core.util.extensions
import androidx.lifecycle.LifecycleOwner
import it.diab.core.arch.EventBusFactory
val LifecycleOwner.bus: EventBusFactory
get() = EventBusFactory[this]
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment