Skip to content

Quick Start — Android

This walkthrough builds a simple login screen end-to-end using Arkitekt with Jetpack Compose and Hilt.

Define the ViewState

The view state holds all observable UI fields using Compose mutableStateOf.

class LoginViewState @Inject constructor() : ViewState {
    val name = mutableStateOf("")
    val surname = mutableStateOf("")
    val fullName = mutableStateOf("")
    val isLoading = mutableStateOf(false)
}

Create a Use Case

Use cases extend UseCase<Args, Result> from the cr-usecases module. They encapsulate a single unit of business logic.

class LoginUseCase @Inject constructor(
    private val loginStore: LoginStore,
) : UseCase<LoginData, Unit>() {

    override suspend fun build(args: LoginData) {
        loginStore.login(args.name, args.surname)
    }
}

Build the ViewModel

The ViewModel extends BaseViewModel<LoginViewState> from the compose module. BaseViewModel extends BaseCoreViewModel and adds CoroutineScopeOwner, which is needed to execute use cases with the execute() extension.

@HiltViewModel
class LoginViewModel @Inject constructor(
    override val viewState: LoginViewState,
    private val loginUseCase: LoginUseCase,
) : BaseViewModel<LoginViewState>() {

    fun logIn() {
        val args = LoginData(
            name = viewState.name.value,
            surname = viewState.surname.value,
        )
        loginUseCase.execute(args) {
            onStart {
                viewState.isLoading.value = true
            }
            onSuccess {
                viewState.isLoading.value = false
                viewState.fullName.value = "${args.name} ${args.surname}"
            }
            onError { error ->
                viewState.isLoading.value = false
                sendEvent(LoginEvent.ShowError(error.message.orEmpty()))
            }
        }
    }
}

Compose the Screen

The screen composable obtains the ViewModel via hiltViewModel(), observes state, and calls ViewModel functions on user interaction.

@Composable
fun LoginScreen(
    viewModel: LoginViewModel = hiltViewModel(),
) {
    val name by viewModel.viewState.name
    val surname by viewModel.viewState.surname
    val fullName by viewModel.viewState.fullName
    val isLoading by viewModel.viewState.isLoading

    Column {
        TextField(
            value = name,
            onValueChange = { viewModel.viewState.name.value = it },
            label = { Text("Name") },
        )
        TextField(
            value = surname,
            onValueChange = { viewModel.viewState.surname.value = it },
            label = { Text("Surname") },
        )

        Button(
            onClick = { viewModel.logIn() },
            enabled = !isLoading,
        ) {
            Text("Log In")
        }

        if (fullName.isNotEmpty()) {
            Text(text = "Welcome, $fullName!")
        }
    }

    viewModel.EventsEffect {
        onEvent<LoginEvent.ShowError> { event ->
            // Show error snackbar or toast
        }
    }
}

Use EventsEffect to handle one-shot events dispatched by the ViewModel via sendEvent(). Events are collected only while the composable is in the composition.