Data flow diagram
graph TD
AppVM
ListVM
EditorVM
AppVM -->|AppContract.State| ListVM
ListVM -->|AppContract.Inputs| AppVM| @file:OptIn(ExperimentalBallastApi::class) | |
| @file:Suppress("UNCHECKED_CAST") | |
| package com.copperleaf.ballast.ktor | |
| import com.copperleaf.ballast.BallastViewModel | |
| import com.copperleaf.ballast.BallastViewModelConfiguration | |
| import com.copperleaf.ballast.EventHandler | |
| import com.copperleaf.ballast.ExperimentalBallastApi | |
| import com.copperleaf.ballast.InputHandler |
| enum class AppScreen( | |
| routeFormat: String, | |
| override val annotations: Set<RouteAnnotation> = emptySet(), | |
| ) : Route { | |
| Home("/app/home"), | |
| PostList("/app/posts?sort={?}"), | |
| PostDetails("/app/posts/{postId}"), | |
| ; | |
| override val matcher: RouteMatcher = RouteMatcher.create(routeFormat) |
Data flow diagram
graph TD
AppVM
ListVM
EditorVM
AppVM -->|AppContract.State| ListVM
ListVM -->|AppContract.Inputs| AppVM| package com.copperleaf.ballast.clicker | |
| import com.copperleaf.ballast.InputHandler | |
| import com.copperleaf.ballast.InputHandlerScope | |
| import kotlinx.coroutines.delay | |
| import kotlinx.datetime.Clock | |
| import kotlinx.datetime.Instant | |
| import kotlin.time.Duration | |
| import kotlin.time.Duration.Companion.milliseconds |
| object Injector { | |
| private val singletonCoroutineScope = CoroutineScope(Dispatchers.Default + SupervisorJob()) | |
| private val repository = Repository( | |
| coroutineScope = singletonCoroutineScope, | |
| config = BallastViewModelConfiguration.Builder() | |
| .withViewModel( | |
| inputHandler = RepositoryInputHandler(), | |
| initialState = RepositoryContract.State(), | |
| name = "Repository", | |
| ) |
| import kotlin.random.Random | |
| /** | |
| * Denotes an instance of [Random] that does not mutate its own internal state. Unlike standard [Random], | |
| * [ImmutableRandom] will always return the same value for subsequent calls to [nextBits], [nextInt], etc. One must | |
| * call [nextRandom] to get a new instance of [ImmutableRandom] with a state that would typically have been updated | |
| * internally. | |
| * | |
| * For the same seed, any sequence of `next*(), nextRandom()` should yield the same result as a normal [Random] calling |
| public class ExampleViewModel : ViewModel() { | |
| private val state = mutableStateFlowOf(ExampleFragmentState()) | |
| public fun observeStates(): StateFlow<ExampleFragmentState> = state.asStateFlow() | |
| public fun button1Clicked() = viewModelScope.launch { | |
| // ... | |
| } | |
| public fun button2Clicked() = viewModelScope.launch { |
| 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) } |
| class LoginActivity : AppCompatActivity(), LoginView { | |
| val vm: LoginViewModel by lazy { | |
| LoginViewModel(this, LoginService.getInstance(BuildConfig.DEBUG)) | |
| } | |
| override fun onCreate(savedInstanceState: Bundle?) { | |
| super.onCreate(savedInstanceState) | |
| setContentView(R.layout.activity_main) |
| class MainActivity : AppCompatActivity() { | |
| var username: String? = null | |
| var passwordValue: String? = null | |
| var passwordField: EditText? = null | |
| val listener = object : TextWatcher { | |
| override fun afterTextChanged(s: Editable?) {} | |
| override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} | |
| override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { | |
| username = s.toString() |