More about Clean Architecture in Android. Example with Cross-cutting features.
Source: Dev.to
Introduction
Now we’ll discuss features that span the entire app—exceptions, dialogs, global events, etc. These features depend on one another.
The core idea is to create a single source of truth (SSOT) for global events and subscribe to it from the main module. Kotlin Flows help us achieve this, and we’ll use SharedFlow, which is designed for this scenario.
Requirements
- Platform: Android only (no CMP/KMP)
- Project: multi‑module
- Each feature has its own screens
- Any feature can send a trigger
- Features are independent of each other
Structure
General scheme

Domain module
The domain module is independent and contains the core contracts:
interface GlobalRepository {
val eventFlow: SharedFlow
fun sendTrigger(trigger: Trigger)
}
enum class Trigger {
SOME_TRIGGER_1,
SOME_TRIGGER_2,
// …
}
enum class GlobalEvent {
SOME_EVENT_1,
SOME_EVENT_2,
// …
}
Domain‑shared module
This module depends on :domain:core and provides the implementation and DI for sending triggers:
interface SendTrigger {
operator fun invoke(trigger: Trigger) // or suspend fun if needed
}
class SendGlobalEventImpl @Inject constructor(
private val repository: GlobalRepository
) : SendTrigger {
override fun invoke(trigger: Trigger) = repository.sendTrigger(trigger)
}
Data module
Implementation of GlobalRepository using a MutableSharedFlow:
class GlobalRepositoryImpl @Inject constructor(
// injected entities such as dispatcher or scope if needed
) : GlobalRepository {
private val _eventFlow = MutableSharedFlow()
override val eventFlow = _eventFlow.asSharedFlow()
override fun sendTrigger(trigger: Trigger) {
when (trigger) {
// some logic
// example:
// someScope.launch { _eventFlow.emit(event) }
// other logic
}
}
// …
}
Flow parameters (replay, buffer, etc.) can be tuned per case.
App and feature modules
- App module: inject
GlobalRepositorydirectly from:domain:coreor expose an interface for obtaining the event flow. - Feature modules: inject
SendTriggerfrom:domain:shared.
How a feature module works is described in the previous article.
Summary
The approach follows the same principles as earlier articles:
- Single independent
:domain:corecontaining shared interfaces. - Data module acts as the SSOT for global events.
This pattern is similar to the one described in the article Sharing data between ViewModels, but applied on a larger scale.
Warning: The example fits the specific requirements outlined above. It may not be suitable for other project types. Do not copy without thinking.
Thanks for reading!