Android Quick Start
This guide will help you build your first Android application with Cuppa and Jetpack Compose.
Creating Your First Screen
Create a new composable in android/app/src/main/kotlin/com/myapp/screens/HomeScreen.kt:
package com.myapp.screens
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import io.mycuppa.ui.components.CuppaButton
@Composable
fun HomeScreen(
viewModel: HomeViewModel = viewModel()
) {
val isLoading by viewModel.isLoading.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "Welcome to Cuppa!",
style = MaterialTheme.typography.headlineLarge
)
Spacer(modifier = Modifier.height(20.dp))
CuppaButton(
text = "Get Started",
onClick = { viewModel.handleGetStarted() },
isLoading = isLoading
)
}
}
Creating a ViewModel
Create the corresponding ViewModel using shared Cuppa logic:
package com.myapp.screens
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.mycuppa.core.CuppaBridge
import io.mycuppa.data.CuppaData
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class HomeViewModel : ViewModel() {
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
private val _items = MutableStateFlow<List<Item>>(emptyList())
val items: StateFlow<List<Item>> = _items
fun handleGetStarted() {
viewModelScope.launch {
_isLoading.value = true
try {
// Use shared Cuppa data fetching
val result = CuppaData.query<List<Item>>("api/items")
_items.value = result
} catch (e: Exception) {
// Handle error
println("Error: ${e.message}")
} finally {
_isLoading.value = false
}
}
}
}
data class Item(
val id: String,
val title: String,
val description: String
)
Setting Up Navigation
Configure navigation in MainActivity.kt:
package com.myapp
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.myapp.screens.HomeScreen
import com.myapp.screens.ProfileScreen
import com.myapp.screens.SettingsScreen
import io.mycuppa.ui.theme.CuppaTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CuppaTheme {
AppNavigation()
}
}
}
}
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home"
) {
composable("home") {
HomeScreen()
}
composable("profile") {
ProfileScreen()
}
composable("settings") {
SettingsScreen()
}
}
}
Using Cuppa Components
Cuppa provides pre-built Compose components:
Buttons
import io.mycuppa.ui.components.*
// Primary button
CuppaButton(
text = "Submit",
onClick = { handleSubmit() },
variant = ButtonVariant.Primary
)
// Secondary button
CuppaButton(
text = "Cancel",
onClick = { handleCancel() },
variant = ButtonVariant.Secondary
)
// Outlined button
CuppaButton(
text = "Learn More",
onClick = { openDocs() },
variant = ButtonVariant.Outlined
)
Form Inputs
@Composable
fun LoginScreen() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
CuppaTextField(
value = email,
onValueChange = { email = it },
label = "Email",
placeholder = "Enter your email",
leadingIcon = Icons.Default.Email
)
CuppaPasswordField(
value = password,
onValueChange = { password = it },
label = "Password",
placeholder = "Enter your password"
)
CuppaButton(
text = "Sign In",
onClick = { handleSignIn(email, password) },
modifier = Modifier.fillMaxWidth()
)
}
}
Lists
@Composable
fun ItemListScreen(items: List<Item>) {
CuppaLazyColumn(
items = items,
emptyState = {
CuppaEmptyState(
icon = Icons.Default.Inbox,
title = "No Items",
message = "Add your first item to get started"
)
}
) { item ->
CuppaListItem(
title = item.title,
subtitle = item.description,
onClick = { navigateToDetail(item) },
trailing = {
Icon(
Icons.Default.ChevronRight,
contentDescription = null
)
}
)
}
}
Cards
CuppaCard(
modifier = Modifier.padding(16.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = "Card Title",
style = MaterialTheme.typography.titleMedium
)
Text(
text = "Card content goes here",
style = MaterialTheme.typography.bodyMedium
)
HorizontalDivider()
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
CuppaButton(
text = "Action",
onClick = { },
modifier = Modifier.weight(1f)
)
CuppaButton(
text = "Cancel",
onClick = { },
variant = ButtonVariant.Secondary,
modifier = Modifier.weight(1f)
)
}
}
}
Integrating with Shared Code
Use your shared TypeScript/JavaScript business logic from Android:
import io.mycuppa.core.CuppaBridge
class DataManager {
suspend fun fetchUserData(): User {
// Call shared TypeScript function
val result = CuppaBridge.call<User>(
function = "getUserData",
args = emptyMap()
)
return result
}
}
Adding Plugins
Integrate Cuppa plugins for common functionality:
Authentication Plugin
import io.mycuppa.plugins.auth.CuppaAuth
// Sign in with email
val user = CuppaAuth.signIn(
email = email,
password = password
)
// Sign out
CuppaAuth.signOut()
// Get current user
val currentUser = CuppaAuth.currentUser
Data Plugin
import io.mycuppa.plugins.data.CuppaData
// Query data
val items = CuppaData.query<List<Item>>(
endpoint = "items",
filters = mapOf("category" to "featured")
)
// Mutate data
CuppaData.mutate(
endpoint = "items/create",
data = newItem
)
Running Your App
Build and run your app:
# Run on Android emulator
pnpm android
# Run on specific device
pnpm android --device="Pixel_7_API_34"
Next Steps
- Learn about Android Architecture to understand how Cuppa works
- Explore the Jetpack Compose Components library
- Browse the full Components Reference