TransmissionRouter
The TransmissionRouter is the central hub that manages communication between all Transformers in your application. It receives Signals from the UI, distributes them to appropriate Transformers, collects their outputs, and provides streams for data consumption.
For Android development, consider using RouterViewModel which provides a simplified, lifecycle-aware wrapper around TransmissionRouter.
Basic Setup
Simple Router Creation
val router = TransmissionRouter {
addTransformerSet(setOf(
UserTransformer(),
AuthTransformer(),
DataTransformer()
))
}
With Custom Configuration
val router = TransmissionRouter {
addTransformerSet(transformers)
addDispatcher(Dispatchers.IO)
setCapacity(Capacity.High)
}
Constructor Parameters
class TransmissionRouter internal constructor(
identity: Contract.Identity,
transformerSetLoader: TransformerSetLoader? = null,
autoInitialization: Boolean = true,
capacity: Capacity = Capacity.Default,
dispatcher: CoroutineDispatcher = Dispatchers.Default,
)
- identity: Unique identifier for the router
- transformerSetLoader: Lazy loading mechanism for transformers
- autoInitialization: Whether to initialize automatically (default: true)
- capacity: Buffer capacity for internal streams
- dispatcher: Coroutine dispatcher for router operations
Builder Configuration
Adding Transformers
// Direct transformer set
val router = TransmissionRouter {
addTransformerSet(setOf(
CounterTransformer(),
LoggingTransformer()
))
}
// Using a loader for lazy initialization
val router = TransmissionRouter {
addLoader(MyTransformerSetLoader())
}
Setting Capacity
Capacity affects the buffer size of internal streams:
val router = TransmissionRouter {
addTransformerSet(transformers)
setCapacity(Capacity.High) // Higher capacity = better performance under load
}
// Available capacity options
enum class Capacity(val value: Int) {
Low(64),
Default(256),
High(1024),
Unlimited(Channel.UNLIMITED)
}
Custom Dispatcher
val router = TransmissionRouter {
addTransformerSet(transformers)
addDispatcher(Dispatchers.IO) // Use IO dispatcher for network/database operations
}
Manual Initialization
val router = TransmissionRouter {
overrideInitialization() // Disable auto-initialization
}
// Later, initialize manually
router.initialize(myTransformerSetLoader)
Processing Transmissions
Processing Signals
// Process signals from UI
router.process(UserLoginSignal(credentials))
router.process(RefreshDataSignal)
router.process(CounterSignal.Increment)
Processing Effects
// Process effects (typically from other transformers)
router.process(LoggingEffect("Manual log entry"))
router.process(NotificationEffect("Custom notification"))
Data Streams
Observing All Data
// Collect all data from all transformers
lifecycleScope.launch {
router.streamData().collect { data ->
when (data) {
is UserData -> updateUserUI(data)
is CounterData -> updateCounterUI(data)
is ErrorData -> showError(data)
}
}
}
Filtering Specific Data Types
// Observe only user data
lifecycleScope.launch {
router.streamData<UserData>()
.collect { userData ->
updateUserProfile(userData)
}
}
// Observe multiple specific types
lifecycleScope.launch {
router.streamData()
.filter { it is UserData || it is ProfileData }
.collect { data ->
when (data) {
is UserData -> handleUserData(data)
is ProfileData -> handleProfileData(data)
}
}
}
Effect Streams
Access to the effect stream for monitoring:
lifecycleScope.launch {
router.streamEffect<LoggingEffect>()
.collect { effect ->
println("Log: ${effect.message}")
}
}
Query Helper
The router provides a query helper for debugging and monitoring:
val queryHelper: QueryHandler = router.queryHelper
// Use for debugging transformer states
// (Specific usage depends on your debugging needs)
Complete Examples
Counter App from Sample
// From samples/counter
class CounterModule {
fun provideTransmissionRouter(): TransmissionRouter {
return TransmissionRouter {
addTransformerSet(setOf(
Worker("1"),
Worker("2"),
Worker("3")
))
}
}
}
// Usage in UI
class CounterViewModel(private val router: TransmissionRouter) {
val counterData = router.streamData<CounterData>()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = CounterData("Initial")
)
fun lookup() {
router.process(CounterSignal.Lookup)
}
}
Components App from Sample
// From samples/components
class ComponentsModule {
fun provideTransmissionRouter(): TransmissionRouter {
return TransmissionRouter {
addTransformerSet(setOf(
InputTransformer(defaultDispatcher),
ColorPickerTransformer(defaultDispatcher),
OutputTransformer(defaultDispatcher),
MultiOutputTransformer()
))
addDispatcher(defaultDispatcher)
}
}
}
// Usage with multiple data types
class ComponentViewModel(private val router: TransmissionRouter) {
val uiState = combine(
router.streamData<InputUiState>(),
router.streamData<OutputCalculationResult>()
) { inputState, outputResult ->
SampleScreenUiState(
inputState = inputState,
outputResult = outputResult
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = SampleScreenUiState()
)
fun updateInput(value: String) {
router.process(InputSignal.InputUpdate(value))
}
fun selectColor(color: Color) {
router.process(ColorPickerSignal.ColorSelected(color))
}
}
Advanced Features
Custom Transformer Set Loader
For complex applications, you might want to load transformers dynamically:
class CustomTransformerSetLoader : TransformerSetLoader {
override suspend fun load(): Set<Transformer> {
return buildSet {
// Load based on configuration
if (isFeatureEnabled("auth")) {
add(AuthTransformer())
}
if (isFeatureEnabled("analytics")) {
add(AnalyticsTransformer())
}
// Always include core transformers
add(CoreTransformer())
}
}
}
val router = TransmissionRouter {
addLoader(CustomTransformerSetLoader())
}
Multiple Routers
For large applications, you might use multiple routers:
// Feature-specific routers
val authRouter = TransmissionRouter {
addTransformerSet(authTransformers)
}
val dataRouter = TransmissionRouter {
addTransformerSet(dataTransformers)
}
// Cross-router communication via effects
authRouter.streamEffect<UserLoggedInEffect>()
.collect { effect ->
dataRouter.process(LoadUserDataSignal(effect.userId))
}
Lifecycle Management
Proper Cleanup
class MyRepository {
private val router = TransmissionRouter {
addTransformerSet(transformers)
}
fun cleanup() {
router.clear() // Cleans up all transformers and coroutines
}
}
// In Android ViewModel
class MyViewModel : ViewModel() {
private val router = TransmissionRouter { /* config */ }
override fun onCleared() {
router.clear()
super.onCleared()
}
}
Integration with Dependency Injection
Hilt Example
@Module
@InstallIn(SingletonComponent::class)
object TransmissionModule {
@Provides
@Singleton
fun provideTransformers(): Set<Transformer> = setOf(
UserTransformer(),
AuthTransformer(),
DataTransformer()
)
@Provides
@Singleton
fun provideTransmissionRouter(
transformers: Set<Transformer>
): TransmissionRouter = TransmissionRouter {
addTransformerSet(transformers)
setCapacity(Capacity.High)
}
}
Koin Example
val transmissionModule = module {
single { UserTransformer() }
single { AuthTransformer() }
single { DataTransformer() }
single<Set<Transformer>> {
setOf(get<UserTransformer>(), get<AuthTransformer>(), get<DataTransformer>())
}
single {
TransmissionRouter {
addTransformerSet(get<Set<Transformer>>())
setCapacity(Capacity.High)
}
}
}
Error Handling
The router includes built-in error handling:
// Transformer errors are isolated and don't crash the router
// Individual transformers can override onError() to handle their own errors
class SafeTransformer : Transformer() {
override fun onError(throwable: Throwable) {
// Handle transformer-specific errors
publish(ErrorEffect("Transformer error: ${throwable.message}"))
}
}
Performance Considerations
Capacity Settings
// For high-throughput applications
val router = TransmissionRouter {
addTransformerSet(transformers)
setCapacity(Capacity.High) // or Capacity.Unlimited for extreme cases
}
// For memory-constrained environments
val router = TransmissionRouter {
addTransformerSet(transformers)
setCapacity(Capacity.Low)
}
Dispatcher Selection
// For CPU-intensive work
val router = TransmissionRouter {
addTransformerSet(transformers)
addDispatcher(Dispatchers.Default)
}
// For IO-intensive work
val router = TransmissionRouter {
addTransformerSet(transformers)
addDispatcher(Dispatchers.IO)
}
// For UI-related work (use sparingly)
val router = TransmissionRouter {
addTransformerSet(transformers)
addDispatcher(Dispatchers.Main.immediate)
}
RouterViewModel
RouterViewModel is a convenience wrapper around TransmissionRouter that extends androidx.lifecycle.ViewModel
. It's fully Kotlin Multiplatform compatible and provides automatic setup, lifecycle management, and simplified stream handling across all platforms.
Key Features
- Automatic Setup: Router is configured and initialized automatically
- Lifecycle Management: Router cleanup is handled in
onCleared()
- Stream Collection: Data and effect streams are automatically collected in
viewModelScope
- Hook Methods: Override
onData
,onEffect
,onProcessSignal
, andonProcessEffect
for custom logic - Query Support: Built-in access to
QueryHandler
for querying transformer states - StateFlow Helpers: Built-in methods for converting streams to StateFlow
- Error Handling: Built-in error handling with
onError
callback
Basic Usage
Simple Implementation
class UserViewModel : RouterViewModel(
setOf(
UserTransformer(),
AuthTransformer(),
ProfileTransformer()
)
) {
// Create StateFlow from data streams
val userState = streamDataAsState<UserData>(UserData.Empty)
val authState = streamDataAsState<AuthData>(AuthData.LoggedOut)
// Handle data updates
override fun onData(data: Transmission.Data) {
when (data) {
is UserData -> logUserUpdate(data)
is AuthData -> handleAuthChange(data)
}
}
// Handle effects
override fun onEffect(effect: Transmission.Effect) {
when (effect) {
is NavigationEffect -> navigateTo(effect.destination)
is ErrorEffect -> showError(effect.message)
}
}
// User actions
fun login(credentials: Credentials) {
processSignal(AuthSignal.Login(credentials))
}
fun updateProfile(profile: Profile) {
processSignal(UserSignal.UpdateProfile(profile))
}
}
Advanced Configuration
class FeatureViewModel : RouterViewModel(
loader = FeatureTransformerSetLoader(),
config = RouterViewModelConfig(
capacity = Capacity.High,
dispatcher = Dispatchers.IO,
identity = Contract.identity("feature-router")
)
) {
// Type-safe stream access
val errorStream = streamEffect<ErrorEffect>()
val loadingData = streamData<LoadingData>()
// Custom error handling
override fun onError(throwable: Throwable) {
logError("Router error", throwable)
processEffect(ErrorEffect("System error occurred"))
}
// Post-processing hooks
override fun onProcessSignal(signal: Transmission.Signal) {
analytics.trackSignal(signal::class.simpleName)
}
// Query transformer states
suspend fun getCurrentUserData(): UserData? {
return queryHandler.getData(UserTransformer.dataContract)
}
}
Configuration Options
RouterViewModelConfig
data class RouterViewModelConfig(
val capacity: Capacity = Capacity.Default, // Buffer capacity
val dispatcher: CoroutineDispatcher = Dispatchers.Default, // Coroutine dispatcher
val identity: Contract.Identity = Contract.identity() // Router identity
)
Usage with Configuration
// High-performance configuration
val config = RouterViewModelConfig(
capacity = Capacity.High,
dispatcher = Dispatchers.IO
)
class MyViewModel : RouterViewModel(transformers, config) {
// Implementation
}
Stream Helpers
StateFlow Creation
class ShoppingViewModel : RouterViewModel(shoppingTransformers) {
// Automatic StateFlow conversion
val cartState = streamDataAsState<CartData>(CartData.Empty)
val productsState = streamDataAsState<ProductListData>(ProductListData.Loading)
val checkoutState = streamDataAsState<CheckoutData>(CheckoutData.Idle)
// Custom sharing behavior
val userPreferences = streamDataAsState<UserPreferencesData>(
initialValue = UserPreferencesData.Default,
started = SharingStarted.Eagerly
)
}
Type-Safe Stream Access
class ChatViewModel : RouterViewModel(chatTransformers) {
// Access specific data types
val messageFlow = streamData<MessageData>()
val userStatusFlow = streamData<UserStatusData>()
// Access specific effect types
val notificationEffects = streamEffect<NotificationEffect>()
val navigationEffects = streamEffect<NavigationEffect>()
init {
// Custom stream handling
viewModelScope.launch {
navigationEffects.collect { effect ->
handleNavigation(effect)
}
}
}
}
Dependency Injection Integration
Hilt Example
@HiltViewModel
class MainViewModel @Inject constructor(
private val transformerSetLoader: MainTransformerSetLoader
) : RouterViewModel(
loader = transformerSetLoader,
config = RouterViewModelConfig(capacity = Capacity.High)
) {
val mainState = streamDataAsState<MainScreenData>(MainScreenData.Loading)
fun refreshData() {
processSignal(RefreshSignal)
}
}
Koin Example
val viewModelModule = module {
viewModel {
UserViewModel(
transformerSet = get<Set<Transformer>>(),
config = RouterViewModelConfig(
dispatcher = get(named("ioDispatcher"))
)
)
}
}
class UserViewModel(
transformerSet: Set<Transformer>,
config: RouterViewModelConfig
) : RouterViewModel(transformerSet, config) {
// Implementation
}
Error Handling
class RobustViewModel : RouterViewModel(transformers) {
override fun onError(throwable: Throwable) {
when (throwable) {
is NetworkException -> handleNetworkError(throwable)
is ValidationException -> handleValidationError(throwable)
else -> {
logError("Unexpected router error", throwable)
processEffect(ErrorEffect("Something went wrong"))
}
}
}
override fun onProcessSignal(signal: Transmission.Signal) {
// Log all processed signals for debugging
if (BuildConfig.DEBUG) {
Log.d("RouterViewModel", "Processed signal: $signal")
}
}
}
Multiplatform Support
RouterViewModel is built using androidx.lifecycle.ViewModel
which is fully multiplatform:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
This means you can use RouterViewModel across all supported platforms:
- Android: Native AndroidX ViewModel support
- iOS: Full lifecycle management via androidx.lifecycle-viewmodel
- Desktop: JVM-based lifecycle management
- Web: Kotlin/JS lifecycle management
Platform-Specific Considerations
While RouterViewModel works on all platforms, you may want different configurations per platform:
// Platform-specific configurations
expect fun defaultRouterConfig(): RouterViewModelConfig
// androidMain
actual fun defaultRouterConfig() = RouterViewModelConfig(
capacity = Capacity.High,
dispatcher = Dispatchers.Main.immediate
)
// iosMain
actual fun defaultRouterConfig() = RouterViewModelConfig(
capacity = Capacity.Default,
dispatcher = Dispatchers.Main
)
// Usage
class MyViewModel : RouterViewModel(
transformers = myTransformers,
config = defaultRouterConfig()
)
## Comparison: Direct Router vs RouterViewModel
### Direct TransmissionRouter
```kotlin
class ManualViewModel : ViewModel() {
private val router = TransmissionRouter {
addTransformerSet(transformers)
}
val dataState = router.streamData<MyData>()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), MyData.Empty)
init {
viewModelScope.launch {
router.streamEffect<MyEffect>().collect { effect ->
handleEffect(effect)
}
}
}
fun processUserAction(action: MySignal) {
router.process(action)
}
override fun onCleared() {
router.clear()
super.onCleared()
}
}
RouterViewModel
class SimpleViewModel : RouterViewModel(transformers) {
val dataState = streamDataAsState<MyData>(MyData.Empty)
override fun onEffect(effect: Transmission.Effect) {
if (effect is MyEffect) {
handleEffect(effect)
}
}
fun processUserAction(action: MySignal) {
processSignal(action)
}
// Cleanup is automatic!
}
Best Practices for RouterViewModel
1. Use StateFlow for UI State
class GoodViewModel : RouterViewModel(transformers) {
// ✅ Good: Direct StateFlow creation
val uiState = streamDataAsState<UiState>(UiState.Loading)
// ❌ Avoid: Manual StateFlow conversion in ViewModels
// val uiState = streamData<UiState>()
// .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), UiState.Loading)
}
2. Handle Effects Properly
class EffectHandlingViewModel : RouterViewModel(transformers) {
override fun onEffect(effect: Transmission.Effect) {
when (effect) {
is NavigationEffect -> {
// Handle navigation - don't process signals here
navigationManager.navigateTo(effect.destination)
}
is ValidationEffect -> {
// Handle validation feedback
showValidationError(effect.message)
}
}
}
}
3. Use Configuration for Performance
// ✅ Good: Configure based on use case
class HighThroughputViewModel : RouterViewModel(
transformers = heavyTransformers,
config = RouterViewModelConfig(
capacity = Capacity.High,
dispatcher = Dispatchers.Default
)
)
class IOIntensiveViewModel : RouterViewModel(
transformers = networkTransformers,
config = RouterViewModelConfig(
dispatcher = Dispatchers.IO
)
)
Best Practices
- Single Router per Feature: Use one router per logical feature or module
- Proper Cleanup: Always call
router.clear()
when done - Appropriate Capacity: Choose capacity based on your app's throughput needs
- Error Handling: Implement proper error handling in transformers
- Stream Management: Use appropriate lifecycle scopes for data stream collection
- Testing: The router is designed to be easily testable - see Testing guide