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() |