Skip to content

Instantly share code, notes, and snippets.

@cjbrooks12
Created April 30, 2022 20:34
Show Gist options
  • Save cjbrooks12/3f09ff80b457806fd5c4dba2843cee73 to your computer and use it in GitHub Desktop.
Save cjbrooks12/3f09ff80b457806fd5c4dba2843cee73 to your computer and use it in GitHub Desktop.
Ballast Simple Router
fun main() = singleWindowApplication {
MaterialTheme {
val applicationCoroutineScope = rememberCoroutineScope()
val router = remember(applicationCoroutineScope) { RouterViewModel(applicationCoroutineScope) }
val routerState by router.observeStates().collectAsState()
val handleNavigation = { input: RouterContract.Inputs -> router.trySend(input) }
when(routerState.currentPage) {
"/app/screen1" -> { Screen1(handleNavigation) }
"/app/screen2" -> { Screen2(handleNavigation) }
"/app/screen3" -> { Screen3(handleNavigation) }
else -> { ScreenNotFound() }
}
}
}
@Composable
fun Screen1(navigate: (RouterContract.Inputs)->Unit) {
val screenCoroutineScope = rememberCoroutineScope()
val screen1ViewModel = remember(screenCoroutineScope) { Screen1ViewModel(screenCoroutineScope) }
val screen1State by screen1ViewModel.observeStates().collectAsState()
// do stuff with screen1State, post Inputs to the ViewModel, etc.
// but you can also pass the Navigation inputs up to the parent
Button(onClick = { navigate(RouterContract.Inputs.Navigate("/app/screen2")) }) {
Text("Go To Screen 2")
}
Button(onClick = { navigate(RouterContract.Inputs.Navigate("/app/screen3")) }) {
Text("Go To Screen 3")
}
}
@Composable
fun Screen2(navigate: (RouterContract.Inputs)->Unit) {
val screenCoroutineScope = rememberCoroutineScope()
val screen2ViewModel = remember(screenCoroutineScope) { Screen2ViewModel(screenCoroutineScope) }
val screen2State by screen2ViewModel.observeStates().collectAsState()
// do stuff with screen2State, post Inputs to the ViewModel, etc.
// but you can also pass the Navigation inputs up to the parent
Button(onClick = { navigate(RouterContract.Inputs.Navigate("/app/screen3")) }) {
Text("Go To Screen 3")
}
}
@Composable
fun Screen3(navigate: (RouterContract.Inputs)->Unit) {
val screenCoroutineScope = rememberCoroutineScope()
val screen3ViewModel = remember(screenCoroutineScope) { Screen3ViewModel(screenCoroutineScope) }
val screen3State by screen3ViewModel.observeStates().collectAsState()
// do stuff with screen2State, post Inputs to the ViewModel, etc.
// but you can also pass the Navigation inputs up to the parent
Button(onClick = { navigate(RouterContract.Inputs.Navigate("/app/screen1")) }) {
Text("Go back to Screen 1")
}
}
val LocalRouter = compositionLocalOf<RouterViewModel> { error("RouterViewModel not provided") }
fun main() = singleWindowApplication {
MaterialTheme {
val applicationCoroutineScope = rememberCoroutineScope()
val router = remember(applicationCoroutineScope) { RouterViewModel(applicationCoroutineScope) }
val routerState by router.observeStates().collectAsState()
CompositionLocalProvider(LocalRouter provides router) {
when(routerState.currentPage) {
"/app/screen1" -> { Screen1() }
"/app/screen2" -> { Screen2() }
"/app/screen3" -> { Screen3() }
else -> { ScreenNotFound() }
}
}
}
}
@Composable
fun Screen1() {
val screenCoroutineScope = rememberCoroutineScope()
val screen1ViewModel = remember(screenCoroutineScope) { Screen1ViewModel(screenCoroutineScope) }
val screen1State by screen1ViewModel.observeStates().collectAsState()
// do stuff with screen1State, post Inputs to the ViewModel, etc.
// but you can also interact with the local Router
val router = LocalRouter.current
Button(onClick = { router.trySend(RouterContract.Inputs.Navigate("/app/screen2")) }) {
Text("Go To Screen 2")
}
Button(onClick = { router.trySend(RouterContract.Inputs.Navigate("/app/screen3")) }) {
Text("Go To Screen 3")
}
}
@Composable
fun Screen2() {
val screenCoroutineScope = rememberCoroutineScope()
val screen2ViewModel = remember(screenCoroutineScope) { Screen2ViewModel(screenCoroutineScope) }
val screen2State by screen2ViewModel.observeStates().collectAsState()
// do stuff with screen2State, post Inputs to the ViewModel, etc.
// but you can also interact with the local Router
val router = LocalRouter.current
Button(onClick = { router.trySend(RouterContract.Inputs.Navigate("/app/screen3")) }) {
Text("Go To Screen 3")
}
}
@Composable
fun Screen3() {
val screenCoroutineScope = rememberCoroutineScope()
val screen3ViewModel = remember(screenCoroutineScope) { Screen3ViewModel(screenCoroutineScope) }
val screen3State by screen3ViewModel.observeStates().collectAsState()
// do stuff with screen2State, post Inputs to the ViewModel, etc.
// but you can also interact with the local Router
val router = LocalRouter.current
Button(onClick = { router.trySend(RouterContract.Inputs.Navigate("/app/screen1")) }) {
Text("Go back to Screen 1")
}
}
interface AppInjector {
fun router(coroutineScope: CoroutineScope): RouterViewModel // singleton
fun screen1ViewModel(coroutineScope: CoroutineScope): Screen1ViewModel // factory
fun screen2ViewModel(coroutineScope: CoroutineScope): Screen2ViewModel // factory
fun screen3ViewModel(coroutineScope: CoroutineScope): Screen3ViewModel // factory
}
val LocalInjector = compositionLocalOf<AppInjector> { error("AppInjector not provided") }
fun main() = singleWindowApplication {
MaterialTheme {
val injector = remember { AppInjector.create() }
val applicationCoroutineScope = rememberCoroutineScope()
val router = remember(applicationCoroutineScope) { injector.router(applicationCoroutineScope) }
val routerState by router.observeStates().collectAsState()
CompositionLocalProvider(LocalInjector provides injector) {
when(routerState.currentPage) {
"/app/screen1" -> { Screen1() }
"/app/screen2" -> { Screen2() }
"/app/screen3" -> { Screen3() }
else -> { ScreenNotFound() }
}
}
}
}
@Composable
fun Screen1() {
val screenCoroutineScope = rememberCoroutineScope()
val screen1ViewModel = remember(screenCoroutineScope) { Screen1ViewModel(screenCoroutineScope) }
val screen1State by screen1ViewModel.observeStates().collectAsState()
// do stuff with screen1State, post Inputs to the ViewModel, etc.
// navigation is all local to this screen's ViewModel
Button(onClick = { screen1ViewModel.trySend(Screen1Contract.Inputs.NavigateToScreen2) }) {
Text("Go To Screen 2")
}
Button(onClick = { screen1ViewModel.trySend(Screen1Contract.Inputs.NavigateToScreen3) }) {
Text("Go To Screen 3")
}
}
class Screen1InputHandler : InputHandler<
Screen1Contract.Inputs,
Screen1Contract.Events,
Screen1Contract.State> {
override suspend fun InputHandlerScope<
Screen1Contract.Inputs,
Screen1Contract.Events,
Screen1Contract.State>.handleInput(
input: Screen1Contract.Inputs
) = when (input) {
is Screen1Contract.Inputs.NavigateToScreen2 -> {
postEvent(Screen1Contract.Events.Navigate("/app/screen2"))
}
is Screen1Contract.Inputs.NavigateToScreen3 -> {
postEvent(Screen1Contract.Events.Navigate("/app/screen3"))
}
}
}
class Screen1EventHandler(val router: RouterViewModel) : EventHandler<
Screen1Contract.Inputs,
Screen1Contract.Events,
Screen1Contract.State> {
override suspend fun EventHandlerScope<
Screen1Contract.Inputs,
Screen1Contract.Events,
Screen1Contract.State>.handleEvent(
event: Screen1Contract.Events
) = when (event) {
is Screen1Contract.Events.Navigate -> {
router.send(RouterContract.Inputs.Navigate(event.route))
}
}
}
@Composable
fun Screen2() {
val screenCoroutineScope = rememberCoroutineScope()
val screen2ViewModel = remember(screenCoroutineScope) { Screen2ViewModel(screenCoroutineScope) }
val screen2State by screen2ViewModel.observeStates().collectAsState()
// do stuff with screen2State, post Inputs to the ViewModel, etc. Its input/event handlers would look very similar
// to screen 1
// navigation is all local to this screen's ViewModel
Button(onClick = { screen2ViewModel.trySend(Screen2Contract.Inputs.NavigateToScreen3) }) {
Text("Go To Screen 3")
}
}
@Composable
fun Screen3(navigate: (RouterContract.Inputs)->Unit) {
val screenCoroutineScope = rememberCoroutineScope()
val screen3ViewModel = remember(screenCoroutineScope) { Screen3ViewModel(screenCoroutineScope) }
val screen3State by screen3ViewModel.observeStates().collectAsState()
// do stuff with screen2State, post Inputs to the ViewModel, etc. Its input/event handlers would look very similar
// to screen 1
// navigation is all local to this screen's ViewModel
Button(onClick = { screen3ViewModel.trySend(Screen3Contract.Inputs.NavigateToScreen1) }) {
Text("Go back to Screen 1")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment