import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class UserListViewModel : ViewModel() {
var loading by mutableStateOf(false)
private set
var users by mutableStateOf(listOf<String>())
private set
var error by mutableStateOf<String?>(null)
private set
fun fetchUsers() {
loading = true
error = null
viewModelScope.launch {
try {
delay(2000) // Simulate network delay
// Simulate fetched user names
users = listOf("Alice", "Bob", "Charlie", "Diana")
} catch (e: Exception) {
error = "Failed to load users"
} finally {
loading = false
}
}
}
}
class UserListActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = UserListViewModel()
setContent {
UserListScreen(viewModel)
}
viewModel.fetchUsers()
}
}
@Composable
fun UserListScreen(viewModel: UserListViewModel) {
val loading = viewModel.loading
val users = viewModel.users
val error = viewModel.error
Surface(modifier = Modifier.fillMaxSize()) {
when {
loading -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center) {
CircularProgressIndicator()
}
}
error != null -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center) {
Text(text = error, color = MaterialTheme.colorScheme.error)
}
}
users.isNotEmpty() -> {
LazyColumn(modifier = Modifier.fillMaxSize().padding(16.dp)) {
items(users) { user ->
Text(text = user, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(vertical = 8.dp))
}
}
}
else -> {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = androidx.compose.ui.Alignment.Center) {
Text(text = "No users found.")
}
}
}
}
}This solution uses Kotlin coroutines inside the ViewModel to fetch user data asynchronously. The fetchUsers() function launches a coroutine in the viewModelScope, simulating a network delay with delay(2000). It updates the loading, users, and error state variables accordingly.
The UI is built with Jetpack Compose. The UserListScreen composable observes these state variables and shows a loading spinner while loading, an error message if there is an error, or a scrollable list of user names when data is loaded. This separation keeps UI reactive and simple.
This approach teaches beginners how to use coroutines for async networking, manage UI state with Compose, and handle loading and error states gracefully.