Ballast Sync
Overview
Ballast Sync allows you to share the state of your ViewModel across multiple instances, potentially even over a network. It allows you to build your ViewModels as normal, and then choose one to be the "source of truth" for the other ViewModels will share the synchronized state, and optionally allow those "observing" ViewModels to send changes back to the source. The flow of data within a synchronized ViewModels is all asynchronous, and follows a model of "eventual consistency".
Usage
There are 3 types of ViewModels which may share in the synchronized state:
Source
: The Source ultimately drives the state of the other ViewModels. Anytime its own State gets changed, that updated State will be sent back to all other ViewModels that are observing it. There should only be 1 Source ViewModel in a given Connection, otherwise they will all be competing to be the source of truth, which may lead to infinite recursion. If all synchronization is performed locally, it's up to you to make sure there is only 1 ViewModel registered as Source. If you're connecting over a network, it's best to keep the Source ViewModel on the Server, and only use Replicas or Spectators within the client applications.Replica
: Replicas are ViewModels that share the same Contract and InputHandler as the Source ViewModel, but will ultimately reflect the State of the Source. Any Inputs sent to it will be processed locally, and then sent back to the Source to be processed there as well, at which point the Source's State will eventually overwrite the Replica's own State. You can add as many Replicas as you wish.Spectator
: Spectators work just like Replicas, except that their Inputs are not sent to the Source. Thus, a user can interact with a Spectator ViewModel without those changes impacting any others. But like Replicas, changes to the Source will eventually overwrite the Spectator's own state. You can add as many Spectators as you wish.
This module also uses an abstract SyncConnectionAdapter
to actually perform the synchronization. Currently, only an
in-memory sync adapter is provided, and no serialization is necessary for this as both States and Inputs are
synchronized fully in memory. You may implement your own adapter to sync this data over a network or via some other
mechanism if necessary (for example, websockets, Redis, etc.).
Additionally, the Adapter will be wrapped in a SyncConnection
which handles the logic for determining how States and
Inputs are actually synchronized amongst each other. DefaultSyncConnection
is what actually handles the Source,
Replica, and Spectator behavior as listed above, but you may create your own implementation if you need to perform some
other kind of behavior.
To get started, first build your ViewModel as normal. Next, provide the BallastSyncInterceptor
with your
SyncConnection
and SyncConnectionAdapter
. The Interceptor forwards all ViewModel data to the Connection, which
decides how its data should be synchronized, and then forwards the relevant data to the Adapter, which will send it to
its connected ViewModel Clients.
// InMemorySyncAdapter must shared among all connected ViewModels
private val syncAdapter = InMemorySyncAdapter<
CounterContract.Inputs,
CounterContract.Events,
CounterContract.State>()
class ExampleViewModel(
coroutineScope: CoroutineScope,
syncClientType: DefaultSyncConnection.ClientType,
) : BasicViewModel<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State>(
coroutineScope = coroutineScope,
config = BallastViewModelConfiguration.Builder()
.apply {
this += BallastSyncInterceptor( // connects the ViewModel to the Connection
connection = DefaultSyncConnection( // implements the logic for deciding what to sync
clientType = syncClientType,
adapter = syncAdapter, // perform the actual synchronization among the connected clients
),
)
}
.withViewModel(
initialState = ExampleContract.State(),
inputHandler = ExampleInputHandler(),
name = "Example",
)
.build(),
)
Installation
repositories {
mavenCentral()
}
// for plain JVM or Android projects
dependencies {
implementation("io.github.copper-leaf:ballast-sync:4.2.1")
}
// for multiplatform projects
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.copper-leaf:ballast-sync:4.2.1")
}
}
}
}