Skip to content

Instantly share code, notes, and snippets.

@cjbrooks12
Last active March 11, 2023 18:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cjbrooks12/b242f1735a16f00301231fcd1ab0b377 to your computer and use it in GitHub Desktop.
Save cjbrooks12/b242f1735a16f00301231fcd1ab0b377 to your computer and use it in GitHub Desktop.
A sample master-detail view with Ballast and Compose

Data flow diagram

graph TD
    AppVM
    ListVM
    EditorVM
    
    AppVM -->|AppContract.State| ListVM
    ListVM -->|AppContract.Inputs| AppVM
    ListVM -->|ListContract.State| ListScreen
    ListScreen -->|ListContract.Inputs| ListVM

    AppVM -->|AppContract.State| EditorVM
    EditorVM -->|AppContract.Inputs| AppVM
    EditorVM -->|EditorContract.State| EditorScreen
    EditorScreen -->|EditorContract.Inputs| EditorVM
// AppVM
// ---------------------------------------------------------------------------------------------------------------------
public object AppContract {
public data class State(
val selectedFile: String? = null
)
public sealed class Inputs {
public data class SetSelectedFile(val selectedFile: String) : Inputs()
public object CloseSelectedFile : Inputs()
}
public sealed class Events {
}
}
public class AppInputHandler : InputHandler<AppContract.Inputs, AppContract.Events, AppContract.State> {
override suspend fun InputHandlerScope<AppContract.Inputs, AppContract.Events, AppContract.State>.handleInput(
input: AppContract.Inputs
): Unit = when(input) {
is AppContract.Inputs.SetSelectedFile -> {
updateState { it.copy(selectedFile = input.selectedFile) }
}
is AppContract.Inputs.CloseSelectedFile -> {
updateState { it.copy(selectedFile = null) }
}
}
}
public fun getAppViewModel(coroutineScope: CoroutineScope): BallastViewModel<AppContract.Inputs, AppContract.Events, AppContract.State> {
TODO()
}
// ListVM
// ---------------------------------------------------------------------------------------------------------------------
public object ListContract {
public data class State(
val selectedFile: String? = null,
val allFiles: List<String> = emptyList(),
)
public sealed class Inputs {
public object Initialize: Inputs()
public data class AppStateUpdated(val appState: AppContract.State) : Inputs()
public data class FilesListUpdated(val files: List<String>) : Inputs()
public data class OpenFile(val file: String) : Inputs()
public data class CreateFile(val fileName: String) : Inputs()
public data class DeleteFile(val file: String) : Inputs()
}
public sealed class Events { }
}
public class ListInputHandler(
private val appViewModel: BallastViewModel<AppContract.Inputs, AppContract.Events, AppContract.State>
) : InputHandler<ListContract.Inputs, ListContract.Events, ListContract.State> {
override suspend fun InputHandlerScope<ListContract.Inputs, ListContract.Events, ListContract.State>.handleInput(
input: ListContract.Inputs
): Unit = when(input) {
is ListContract.Inputs.Initialize -> {
observeFlows(
"AppStateUpdated",
appViewModel.observeStates().map { ListContract.Inputs.AppStateUpdated(it) }m
)
}
is ListContract.Inputs.AppStateUpdated -> {
updateState { it.copy(selectedFile = input.appState.selectedFile) }
}
is ListContract.Inputs.FilesListUpdated -> {
updateState { it.copy(allFiles = input.files) }
}
is ListContract.Inputs.OpenFile -> {
sideJob("OpenFile") {
appViewModel.send(AppContract.Inputs.SetSelectedFile(input.file))
}
}
is ListContract.Inputs.CreateFile -> TODO()
is ListContract.Inputs.DeleteFile -> TODO()
}
}
public fun getListViewModel(coroutineScope: CoroutineScope, appViewModel: BallastViewModel<AppContract.Inputs, AppContract.Events, AppContract.State>): BallastViewModel<ListContract.Inputs, ListContract.Events, ListContract.State> {
TODO()
}
// EditorVM
// ---------------------------------------------------------------------------------------------------------------------
public object EditorContract {
public data class State(
val selectedFile: String? = null
)
public sealed class Inputs {
public object Initialize: Inputs()
public data class AppStateUpdated(val appState: AppContract.State) : Inputs()
public object CloseFile : Inputs()
}
public sealed class Events { }
}
public class EditorInputHandler(
private val appViewModel: BallastViewModel<AppContract.Inputs, AppContract.Events, AppContract.State>
) : InputHandler<EditorContract.Inputs, EditorContract.Events, EditorContract.State> {
override suspend fun InputHandlerScope<EditorContract.Inputs, EditorContract.Events, EditorContract.State>.handleInput(
input: EditorContract.Inputs
): Unit = when(input) {
is EditorContract.Inputs.Initialize -> {
observeFlows("AppStateUpdated", appViewModel.observeStates().map { EditorContract.Inputs.AppStateUpdated(it) })
}
is EditorContract.Inputs.AppStateUpdated -> {
updateState { it.copy(selectedFile = input.appState.selectedFile) }
}
is EditorContract.Inputs.CloseFile -> {
sideJob("CloseFile") {
appViewModel.send(AppContract.Inputs.CloseSelectedFile)
}
}
}
}
public fun getEditorViewModel(coroutineScope: CoroutineScope): BallastViewModel<EditorContract.Inputs, EditorContract.Events, EditorContract.State> {
TODO()
}
// Compose UI
// ---------------------------------------------------------------------------------------------------------------------
@Composable
public fun App() {
val appCoroutineScope = rememberCoroutineScope()
val appVm = remember(appCoroutineScope) { getAppViewModel(appCoroutineScope) }
Row {
Column {
ListView(appVm)
}
Column {
EditorView(appVm)
}
}
}
@Composable
public fun ListView(appViewModel: BallastViewModel<AppContract.Inputs, AppContract.Events, AppContract.State>) {
val listCoroutineScope = rememberCoroutineScope()
val listVm = remember(listCoroutineScope, appViewModel) { getListViewModel(listCoroutineScope, appViewModel) }
val listVmState by listVm.observeStates().collectAsState()
// display files list
}
@Composable
public fun EditorView(appViewModel: BallastViewModel<AppContract.Inputs, AppContract.Events, AppContract.State>) {
val editorCoroutineScope = rememberCoroutineScope()
val editorVm = remember(editorCoroutineScope, appViewModel) { getEditorViewModel(editorCoroutineScope, appViewModel) }
val editorVmState by editorVm.observeStates().collectAsState()
// display selected file in the editor
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment