Ballast Core
Overview
The Ballast Core module provides all the core capabilities of the entire Ballast MVI framework. The Core framework is
robust and opinionated, but also provides many ways to extend the functionality through Interceptors without impacting
the core MVI model. Any additional functionality outside of Core will typically be implemented as an Interceptor and
provided to the BallastViewModelConfiguration
.
Usage
ViewModels
The Core module provides several ViewModel base classes, so Ballast can integrate natively with a variety of platforms.
AndroidViewModel
: A subclass ofandroidx.lifecycle.ViewModel
IosViewModel
: A custom ViewModel that can be integrated with Combine Publishers for SwiftUIBasicViewModel
: A generic ViewModel for Kotlin targets that don't have their own platform-specific ViewModel, or for anywhere you want to manually control the lifecycle of the ViewModel.BasicViewModel
's lifecycle is controlled by acoroutineScope
provided to it upon creation. When the scope gets cancelled, the ViewModel gets closed and can not be used again.
Interceptors
The Core module comes with only one Interceptor,
LoggingInterceptor
: It will print all Ballast activity to the logger provided in theBallastViewModelConfiguration
. The information logged by this interceptor may be quite verbose, but it can be really handy for quickly inspecting the data in your ViewModel and what happened in what order.
The LoggingInterceptor
writes to a logger installed into the BallastViewModelConfiguration
, which may be used by
InputHandlers or other Ballast features as well.
Ballast offers several logger implementations out-of-the-box:
NoOpLogger
: The default implementation, it simply drops all messages and exceptions so nothing gets logged accidentally. It's recommended to use this in production builds.PrintlnLogger
: Useful for quick-and-dirty logging on all platforms. It just writes log messages to stdout through println.AndroidLogger
: Only available on Android, writes logs to the default LogCat at the appropriate levels.JsConsoleLogger
: Only available on JS, writes logs toconsole.log()
orconsole.error()
NSLogLogger
: Only available on iOS, writes logs toNSLog
OSLogLogger
: Only available on iOS, writes logs toOSLog
class ExampleViewModel(coroutineScope: CoroutineScope) : BasicViewModel<
ExampleContract.Inputs,
ExampleContract.Events,
ExampleContract.State>(
coroutineScope = coroutineScope,
config = BallastViewModelConfiguration.Builder()
.apply {
if(DEBUG) { // some build-time constant
logger = PrintlnLogger()
this += LoggingInterceptor()
}
}
.withViewModel(
initialState = ExampleContract.State(),
inputHandler = ExampleInputHandler(),
name = "Example",
)
.build(),
eventHandler = ExampleEventHandler(),
)
Input Strategies
Ballast offers 3 different Input Strategies out-of-the-box, which each adapt Ballast's core functionality for different applications:
LifoInputStrategy
: A last-in-first-out strategy for handling Inputs, and the default strategy if none is provided. Only 1 Input will be processed at a time, and if a new Input is received while one is still working, the running Input will be cancelled to immediately accept the new one. Corresponds toFlow.collectLatest { }
, best for UI ViewModels that need a highly responsive UI where you do not want to block the user's actions.FifoInputStrategy
: A first-in-first-out strategy for handling Inputs. Inputs will be processed in the same order they were sent and only ever one-at-a-time, but instead of cancelling running Inputs, new ones are queued and will be consumed later when the queue is free. Corresponds to the normalFlow.collect { }
, best for non-UI ViewModels, or UI ViewModels where it is OK to "block" the UI while something is loading.ParallelInputStrategy
: For specific edge-cases where neither of the above strategies works. Inputs are all handled concurrently so you don't have to worry about blocking the queue or having Inputs cancelled. However, it places additional restrictions on State reads/changes to prevent usage that might lead to race conditions.
InputStrategies are responsible for creating the Channel used to buffer incoming Inputs, consuming the Inputs from that
channel, and providing a "Guardian" to ensure the Inputs are handled properly according the needs of that particular
strategy. The DefaultGuardian
is a good starting place if you need to create your own InputStrategy
to
maintain the same level of safety as the core strategies listed above.
Installation
repositories {
mavenCentral()
}
// for plain JVM or Android projects
dependencies {
implementation("io.github.copper-leaf:ballast-core:4.2.1")
}
// for multiplatform projects
kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.copper-leaf:ballast-core:4.2.1")
}
}
}
}