### Web UI Command Source: https://github.com/copper-leaf/ballast/blob/main/examples/compose_sharedui_kmm/README.md Command to start the web UI using Kobweb. ```bash ./gradlew :web:kobwebStart -t ``` -------------------------------- ### Browser Hash Routing Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/snippets/navigationFaqs.md Example of setting up hash-based routing using `withBrowserHashRouter` in a `BallastViewModelConfiguration`. ```kotlin class RouterViewModel( viewModelCoroutineScope: CoroutineScope ) : BasicRouter( config = BallastViewModelConfiguration.Builder() .withBrowserHashRouter(RoutingTable.fromEnum(AppScreens.values()), AppScreens.Home) .build(), eventHandler = eventHandler { }, coroutineScope = viewModelCoroutineScope, ) ``` -------------------------------- ### Koin DI for Android Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/usage/architecture.md Example of Dependency Injection setup using Koin on Android. ```kotlin val platformModule = module { factory { LoginApiImpl() } single { LoginRepositoryImpl( loginApi = get() ) } factory { BallastViewModelConfiguration.Builder() .apply { this += LoggingInterceptor() logger = { AndroidBallastLogger(it) } } } factory { LoginScreenInputHandler( loginRepository = get() ) } viewModel { LoginScreenViewModel( config = get() .withViewModel( initialState = LoginScreenContract.State(), inputHandler = get(), name = "LoginScreen", ) .build(), ) } } class LoginActivity : AppCompatActivity(), KoinComponent { private val viewModel: LoginScreenViewModel by viewModel() } ``` -------------------------------- ### Koin DI for JS with Assisted Injection Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/usage/architecture.md Example of Dependency Injection setup using Koin on JS, demonstrating assisted injection for parameters like CoroutineScope. ```kotlin val platformModule = module { factory { LoginApiImpl() } single { LoginRepositoryImpl( loginApi = get() ) } factory { BallastViewModelConfiguration.Builder() .apply { this += LoggingInterceptor() logger = { JsConsoleBallastLogger(it) } } } factory { LoginScreenInputHandler( loginRepository = get() ) } factory { (coroutineScope: CoroutineScope) -> LoginScreenViewModel( config = get() .withViewModel( initialState = LoginScreenContract.State(), inputHandler = get(), name = "LoginScreen", ) .build(), ) } } class LoginPage : KoinComponent { @Composable fun LoginContent() { val viewModelScope = rememberCoroutineScope() val viewModel: LoginScreenViewModel = remember(viewModelScope) { get { parametersOf(viewModelScope) } } } } ``` -------------------------------- ### BasicViewModel Configuration Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/feature-overview.md An example of configuring a BasicViewModel using BallastViewModelConfiguration.Builder. ```kotlin public class ExampleViewModel( viewModelScope: CoroutineScope ) : BasicViewModel( config = BallastViewModelConfiguration.Builder() .apply { // set configuration common to all ViewModels, if needed } .withViewModel( initialState = State(), inputHandler = ExampleInputHandler(), name = "Example" ) .build(), eventHandler = ExampleEventHandler(), coroutineScope = viewModelScope, ) ``` -------------------------------- ### Browser History Routing Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/snippets/navigationFaqs.md Example of setting up routing using the Browser History API with `withBrowserHistoryRouter`, including a base path. ```kotlin class RouterViewModel( viewModelCoroutineScope: CoroutineScope ) : BasicRouter( config = BallastViewModelConfiguration.Builder() .withBrowserHistoryRouter(RoutingTable.fromEnum(AppScreens.values()), basePath = "/app", initialRoute = AppScreens.Home) .build(), eventHandler = eventHandler { }, coroutineScope = viewModelCoroutineScope, ) ``` -------------------------------- ### Desktop Run Command Source: https://github.com/copper-leaf/ballast/blob/main/examples/compose_sharedui_kmm/README.md Command to run the desktop application. ```bash ./gradlew :shared:run ``` -------------------------------- ### XML Views Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/platforms/android.md An example of how to use Ballast with XML Views in an Android Fragment. ```kotlin @AndroidEntryPoint class ExampleFragment : ComposeFragment() { @Inject lateinit var eventHandler: ExampleEventHandler.Factory private val viewModel: ExampleViewModel by viewModels() private var binding: FragmentExampleBinding? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return FragmentExampleBinding .inflate(inflater, container, false) .also { binding = it } .root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // events are sent back to the screen during the Fragment's Lifecycle RESUMED state viewModel.attachEventHandlerOnLifecycle( this, eventHandler.create(this, findNavController()), ) // Collect the state on the Fragment's Lifecycle RESUMED state, updating the entire UI with each change vm.observeStatesOnLifecycle(this) { state -> binding?.updateWithState(state) { viewModel.trySend(it) } } } override fun onDestroyView() { super.onDestroyView() binding = null } private fun FragmentExampleBinding.updateWithState( state: ExampleContract.State, postInput: (ExampleContract.Inputs) -> Unit ) { tvCounter.text = "${state.count}" btnDec.setOnClickListener { postInput(ExampleContract.Inputs.Decrement(1)) } btnInc.setOnClickListener { postInput(ExampleContract.Inputs.Increment(1)) } } } ``` -------------------------------- ### RouterViewModel Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-navigation.md An example of creating a ViewModel class to be used as a Router. ```kotlin class RouterViewModel( viewModelCoroutineScope: CoroutineScope ) : BasicRouter( config = BallastViewModelConfiguration.Builder() .withRouter(RoutingTable.fromEnum(AppScreens.values()), AppScreens.Home) .build(), eventHandler = eventHandler { }, coroutineScope = viewModelCoroutineScope, ) ``` -------------------------------- ### ViewModel Setup with Undo Interceptor Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-undo.md Example of how to set up a ViewModel with the BallastUndoInterceptor and a default UndoController. ```kotlin class ExampleViewModel(coroutineScope: CoroutineScope, controller: UndoController< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>, ) : BasicViewModel< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>( coroutineScope = coroutineScope, config = BallastViewModelConfiguration.Builder() .apply { this += BallastUndoInterceptor(controller) } .withViewModel( initialState = ExampleContract.State(), inputHandler = ExampleInputHandler(), name = "Example", ) .build(), ) @Composable fun mainUi() { val controller = remember { DefaultUndoController() } val applicationCoroutineScope = rememberCoroutineScope() val viewModel = remember(applicationCoroutineScope, controller) { ExampleViewModel(applicationCoroutineScope, controller) } val uiState by viewModel.observeStates().collectAsState() // buttons to undo/redo val isUndoAvailable by undoController.isUndoAvailable.collectAsState(false) val isRedoAvailable by undoController.isRedoAvailable.collectAsState(false) Button(onClick = { controller.undo() }, enabled = isUndoAvailable) { Text("Undo") } Button(onClick = { controller.redo() }, enabled = isRedoAvailable) { Text("Redo") } // the normal content for this screen, which will be updated via undo/redo as prompted by the user Content(uiState) { viewModel.trySend(it) } } ``` -------------------------------- ### Side-job Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/feature-overview.md An example demonstrating how to use sideJobs for concurrent work within an Input Handler. ```kotlin override suspend fun InputHandlerScope.handleInput( input: Inputs ) = when (input) { is InfiniteSideJob -> { sideJob("ShortSideJob") { infiniteFlow() .map { Inputs.SomeInputType() } .onEach { postInput(it) } .launchIn(this) } } } ``` -------------------------------- ### Input Handler Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/feature-overview.md An example of an Input Handler implementation in Kotlin, demonstrating how to handle different input types. ```kotlin import LoginScreenContract.* class LoginScreenInputHandler : InputHandler { override suspend fun InputHandlerScope.handleInput( input: Inputs ) = when (input) { is UsernameChanged -> { } is PasswordChanged -> { } is LoginButtonClicked -> { } is RegisterButtonClicked -> { } } } ``` -------------------------------- ### ExampleRepositoryImpl Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-repository.md An example implementation of BallastRepository. ```kotlin class ExampleRepositoryImpl( coroutineScope: CoroutineScope, eventBus: EventBus, ) : BallastRepository< ExampleRepositoryContract.Inputs, ExampleRepositoryContract.State>( coroutineScope = coroutineScope, eventBus = eventBus, config = BallastViewModelConfiguration.Builder() .apply { initialState = ExampleRepositoryContract.State() inputHandler = ExampleRepositoryInputHandler() name = "Example Repository" }.build() ) ``` -------------------------------- ### Logging Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/feature-overview.md Demonstrates how to use the Ballast logger within an InputHandler. ```kotlin import LoginScreenContract.* class LoginScreenInputHandler : InputHandler { override suspend fun InputHandlerScope.handleInput( input: Inputs ) = when (input) { is UsernameChanged -> { } is PasswordChanged -> { } is LoginButtonClicked -> { logger.info("Attempting Logging In...") val loginSuccessful = attemptLogin() if(loginSuccessful) { logger.info("Login success") } else { logger.info("Login failed") } } is RegisterButtonClicked -> { } } } ``` -------------------------------- ### Example ViewModel with Moshi Debugger Adapter Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-debugger.md An example of how to integrate the MoshiReflectionDebuggerAdapter into a Ballast ViewModel configuration. ```kotlin class ExampleViewModel(coroutineScope: CoroutineScope) : BasicViewModel< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>( coroutineScope = coroutineScope, config = BallastViewModelConfiguration.Builder() .withViewModel( initialState = ExampleContract.State(), inputHandler = ExampleInputHandler(), name = "Example", ) .apply { if(DEBUG) { this += BallastDebuggerInterceptor( debuggerConnection, adapter = MoshiReflectionDebuggerAdapter() ) } } .build(), eventHandler = ExampleEventHandler(), ) ``` -------------------------------- ### Event Handler Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/feature-overview.md An example of an Event Handler implementation in Kotlin, showing how to process events. ```kotlin import LoginScreenContract.* class LoginScreenEventHandler : EventHandler { override suspend fun EventHandlerScope.handleEvent( event: Events ) = when (event) { is Events.Notification -> { } } } ``` -------------------------------- ### KVision-Ballast Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/platforms/kvision.md Example of binding a Ballast ViewModel's State to the KVision UI. ```kotlin class KVisionBallastExample : Application(), KoinComponent { private val exampleViewModel: ExampleViewModel by inject() override fun start() { root("ballast-example") { section().bind(todoViewModel) { state -> // ... } } } } ``` -------------------------------- ### Example ViewModel with Ballast Sync Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-sync.md This example demonstrates how to configure a ViewModel to use Ballast Sync with an in-memory adapter and a default sync connection. ```kotlin 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(), ) ``` -------------------------------- ### Basic ScheduleAdapter Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-schedules.md An example of how to define a ScheduleAdapter with two schedules: one that runs every 30 minutes and another that runs daily at 2 AM. ```kotlin public class BallastSchedulerExampleAdapter : SchedulerAdapter< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State> { override suspend fun SchedulerAdapterScope< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>.configureSchedules() { onSchedule( key = "Every 30 Minutes", schedule = EveryHourSchedule(0, 30), scheduledInput = { SchedulerExampleContract.Inputs.Increment(1) }, ) onSchedule( key = "Daily at 2am", schedule = EveryDaySchedule(LocalTime(2, 0)), scheduledInput = { ExampleContract.Inputs.Increment(1) }, ) } } ``` -------------------------------- ### MVP Example in Kotlin Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/usage/mental-model.md An example of the Model-View-Presenter (MVP) pattern in Android using Kotlin, demonstrating how to separate business logic from the UI. ```kotlin class CounterPresenter(private val view: CounterView) { private var currentCount = 0 fun onButtonClick() { val incrementedCount = currentCount + 1 view.setCounterText("$incrementedCount") currentCount = incrementedCount } } interface CounterView { fun setCounterText(text: String) } class CounterActivity : AppCompatActivity(), CounterView { override fun onCreate() { setContentView(R.layout.activity_main) val button: Buttoon = findViewById(R.id.button) val presenter = CounterPresenter(this) button.setOnClickListener { presenter.onButtonClick() } } override fun setCounterText(text: String) { val counterText: TextView = findViewById(R.id.counter_text) counterText.setText("$incrementedCount") } } ``` -------------------------------- ### JavaScript ViewModel Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/usage/workflow.md Example of a ViewModel definition for a JavaScript platform, extending BasicViewModel and configuring BallastViewModelConfiguration with logging, initial state, and event handling. ```kotlin // jsMain/ui/login/LoginScreenViewModel.kt class LoginScreenViewModel( viewModelCoroutineScope: CoroutineScope ) : BasicViewModel< LoginScreenContract.Inputs, LoginScreenContract.Events, LoginScreenContract.State>( config = BallastViewModelConfiguration.Builder() .apply { this += LoggingInterceptor() logger = { JsConsoleBallastLogger(it) } } .withViewModel( initialState = LoginScreenContract.State(), inputHandler = LoginScreenInputHandler(), name = "LoginScreen", ) .build(), eventHandler = LoginScreenEventHandler(), coroutineScope = viewModelCoroutineScope, ) ``` -------------------------------- ### Basic Navigation Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-navigation.md Example of sending a GoToDestination input to the router. ```Kotlin router.trySend( RouterContract.Inputs.GoToDestination("/app/posts/12345") ) ``` -------------------------------- ### Full Code Snippet Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-navigation.md A complete example of using Ballast for routing in a Compose application. This snippet can be directly copied and pasted into a project to get started. ```kotlin // Define your routes enum class AppScreen(routeFormat: String, override val annotations: Set = emptySet()) : Route { Home("/app/home"), PostList("/app/posts?sort={?}"), PostDetails("/app/posts/{postId}"), ; override val matcher: RouteMatcher = RouteMatcher.create(routeFormat) } @Composable fun MainContent() { val applicationScope = rememberCoroutineScope() // Set up the Router, which is just a normal Ballast ViewModel val router: Router = remember(applicationScope) { BasicRouter( coroutineScope = applicationScope, config = BallastViewModelConfiguration.Builder() .apply { // log all Router activity to inspect the backstack changes this += LoggingInterceptor() logger = ::PrintlnLogger // You may add any other Ballast Interceptors here as well, to extend the router functionality } .withRouter(RoutingTable.fromEnum(AppScreen.values()), initialRoute = AppScreen.Home) .build(), eventHandler = eventHandler { if (it is RouterContract.Events.BackstackEmptied) { exitProcess(0) } }, ) } // collect the Router's StateFlow as a Compose State val routerState: Backstack by router.observeStates().collectAsState() routerState.renderCurrentDestination( route = { appScreen -> // the last entry in the backstack was matched to a route. We will switch on which route was matched, // and pull path and query parameters from the destination when (appScreen) { AppScreen.Home -> { HomeScreen() } AppScreen.PostList -> { val sort: String? by optionalStringQuery() PostListScreen( sort = sort, onPostSelected = { postId: Long -> // The user selected a post within the PostListScreen. Generate a URL which will match // to the PostDetails route, by using its directions to ensure the right parameters are // provided in the URL router.trySend( RouterContract.Inputs.GoToDestination( AppScreen.PostDetails .directions() .pathParameter("postId", postId.toString()) .build() ) ) }, ) } AppScreen.PostDetails -> { val postId: Long by longPath() PostDetailsScreen( postId = postId, onBackClicked = { // The user clicked the back button, notify the router to pop the latest destination off // the backstack router.trySend( RouterContract.Inputs.GoBack() ) }, ) } } }, notFound = { // the last entry in the backstack could not be matched to a route NotFoundScreen(mismatchedUrl = it) }, ) } @Composable fun HomeScreen() { // omitted for brevity } @Composable fun PostListScreen(sort: String?, onPostSelected: (Long) -> Unit) { // omitted for brevity } @Composable fun PostDetailsScreen(postId: Long, onBackClicked: () -> Unit) { // omitted for brevity } @Composable fun NotFoundScreen(mismatchedUrl: String) { // omitted for brevity } ``` -------------------------------- ### ExampleFragment with Compose and Hilt Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/platforms/android.md Demonstrates how to integrate `ExampleViewModel` and `ExampleEventHandler` within a `ComposeFragment` using Hilt for dependency injection and `viewModels()` for ViewModel instantiation. It shows setting up the Compose UI, observing states, and attaching the event handler. ```kotlin @AndroidEntryPoint class ExampleFragment : ComposeFragment() { @Inject lateinit var eventHandler: ExampleEventHandler.Factory private val viewModel: ExampleViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return ComposeView(requireContext()).apply { setContent { MaterialTheme { val uiState by viewModel.observeStates().collectAsState() LaunchedEffect(viewModel, eventHandler) { viewModel.attachEventHandler( this, eventHandler.create(this, findNavController()) ) viewModel.trySend(ExampleContract.Inputs.Initialize) } ExampleContent(uiState) { viewModel.trySend(it) } } } } } @Composable fun ExampleContent( uiState: ExampleContract.State, postInput: (ExampleContract.Inputs) -> Unit, ) { // ... } } ``` -------------------------------- ### ViewModel Setup with BallastSavedStateInterceptor Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-saved-state.md Example of configuring a ViewModel to use the BallastSavedStateInterceptor with a custom SavedStateAdapter. ```kotlin class ExampleViewModel( coroutineScope: CoroutineScope, database: ExampleDatabase, ) : BasicViewModel< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>( coroutineScope = coroutineScope, config = BallastViewModelConfiguration.Builder() .apply { this += BallastSavedStateInterceptor( ExampleSavedStateAdapter(database) ) } .withViewModel( initialState = ExampleContract.State(), inputHandler = ExampleInputHandler(), name = "Example", ) .build(), ) ``` -------------------------------- ### Delay Mode Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-schedules.md Example of configuring a schedule with DelayMode.Suspend. ```kotlin public class BallastSchedulerExampleAdapter : SchedulerAdapter< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State> { override suspend fun SchedulerAdapterScope< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>.configureSchedules() { onSchedule( key = "Daily at 2am", delayMode = ScheduleExecutor.DelayMode.Suspend, schedule = EveryDaySchedule(LocalTime(2, 0)), ) { ExampleContract.Inputs.Increment(1) } } } ``` -------------------------------- ### ExampleRepositoryInputHandler Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-repository.md The InputHandler for the ExampleRepository, demonstrating how to handle inputs and manage cached data. ```kotlin class ExampleRepositoryInputHandler( private val exampleApi: ExampleApi, ) : InputHandler< ExampleRepositoryContract.Inputs, Any, ExampleRepositoryContract.State> { override suspend fun InputHandlerScope< ExampleRepositoryContract.Inputs, Any, ExampleRepositoryContract.State>.handleInput( input: ExampleRepositoryContract.Inputs ) = when (input) { is ExampleRepositoryContract.Inputs.ClearCaches -> { updateState { ExampleRepositoryContract.State() } } is ExampleRepositoryContract.Inputs.Initialize -> { val previousState = getCurrentState() if (!previousState.initialized) { updateState { it.copy(initialized = true) } // start observing flows here logger.debug("initializing") observeFlows( key = "Observe account changes", params.eventBus .observeInputsFromBus(), ) } else { logger.debug("already initialized") noOp() } } is ExampleRepositoryContract.Inputs.RefreshAllCaches -> { // refresh all the caches in this repository val currentState = getCurrentState() if (currentState.examplePropertyInitialized) { postInput(ExampleRepositoryContract.Inputs.RefreshExampleProperty(true)) } Unit } is ExampleRepositoryContract.Inputs.RefreshExampleProperty -> { updateState { it.copy(examplePropertyInitialized = true) } fetchWithCache( input = input, forceRefresh = input.forceRefresh, getValue = { it.exampleProperty }, updateState = { ExampleRepositoryContract.Inputs.ExamplePropertyUpdated(it) }, doFetch = { exampleApi.fetchValue() }, ) } is ExampleRepositoryContract.Inputs.ExamplePropertyUpdated -> { updateState { it.copy(value = input.value) } } } } ``` -------------------------------- ### Run Development Server Source: https://github.com/copper-leaf/ballast/blob/main/examples/compose_sharedui_kmm/web/README.md Commands to start the Kobweb development server and access the running application. ```bash $ cd site $ kobweb run ``` -------------------------------- ### ExampleScreen SwiftUI View Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/platforms/ios.md Demonstrates how to observe a Ballast ViewModel in SwiftUI using BallastObservable, managing its lifecycle with .onAppear and .onDisappear, and initializing the ViewModel. ```swift import Combine import SwiftUI import shared struct ExampleScreen: View { @ObservedObject var vm = BallastObservable< ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>( viewModelFactory: { ExampleViewModel() }, // create directly or pass it in via DI eventHandlerFactory: { ExampleEventHandler() } // optional, create directly or pass it in via DI ) var body: some View { ExampleContent( vmState: observableModel.vmState, postInput: observableModel.postInput ) .onAppear(perform: { observableModel.activate() observableModel.postInput(ExampleContract.InputsInitialize()) }) .onDisappear(perform: { observableModel.deactivate() }) } } struct ExampleContent: View { var vmState: ExampleContract.State var postInput: (ExampleContract.Inputs) -> Void var body: some View { // ... } } ``` -------------------------------- ### Example State and Input Serialization with kotlinx.serialization Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-debugger.md Demonstrates how to annotate State and Input classes for serialization using kotlinx.serialization. ```kotlin object ExampleContract { @Serializable data class State( val count: Int = 0 ) @Serializable sealed interface Inputs { @Serializable data class Increment(val amount: Int) : Inputs @Serializable data class Decrement(val amount: Int) : Inputs } @Serializable sealed interface Events { } } ``` -------------------------------- ### Inputs Sealed Class Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/feature-overview.md Example of modeling user intents as a sealed class for Inputs in Ballast. ```kotlin sealed interface Inputs { data class UsernameChanged(val newValue: TextFieldValue) : Inputs data class PasswordChanged(val newValue: TextFieldValue) : Inputs data object LoginButtonClicked : Inputs data object RegisterButtonClicked : Inputs } ``` -------------------------------- ### Login Screen Contract Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/usage/mental-model.md An example of a filled-out contract for a login screen, detailing state, inputs, and events. ```kotlin object LoginScreenContract { data class State( val username: String, val password: String, ) sealed interface Inputs { data class UsernameChanged(val newValue: String) : Inputs data class PasswordChanged(val newValue: String) : Inputs data object LoginButtonClicked : Inputs data object RegisterButtonClicked : Inputs } sealed interface Events { data object NavigateToDashboard : Events data object NavigateToRegistration : Events } } ``` -------------------------------- ### Invalid Traffic Light State Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/orchid/resources/wiki/usage/mental-model.md An example of a data class for traffic light state that can have invalid states. ```kotlin data class TrafficLightState( val isLoading: Boolean, val isError: Boolean, val color: Color? ) ``` -------------------------------- ### Example ViewModel with LoggingInterceptor Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/wiki/modules/ballast-core.md Demonstrates how to configure a BasicViewModel with a PrintlnLogger and LoggingInterceptor, conditionally included for debug builds. ```kotlin 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(), ) ``` -------------------------------- ### Events Sealed Class Example Source: https://github.com/copper-leaf/ballast/blob/main/docs/src/doc/docs/pages/feature-overview.md Example of modeling one-off UI actions as a sealed class for Events in Ballast. ```kotlin sealed interface Events { data object NavigateToDashboard : Events data object NavigateToRegistration : Events } ```