Created
April 30, 2022 20:34
-
-
Save cjbrooks12/3f09ff80b457806fd5c4dba2843cee73 to your computer and use it in GitHub Desktop.
Ballast Simple Router
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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