0
0
Android Kotlinmobile~20 mins

Coroutines for async networking in Android Kotlin - Mini App: Build & Ship

Choose your learning style9 modes available
Build: User List Screen
This screen fetches a list of users from a network API asynchronously using Kotlin coroutines and displays them in a scrollable list.
Target UI
-------------------------
|      User List        |
-------------------------
| Loading...            |
|                       |
|                       |
|                       |
|                       |
-------------------------
Use Kotlin coroutines to fetch data asynchronously from a fake API endpoint.
Show a loading indicator while fetching data.
Display the list of user names in a vertical scrollable list after loading.
Handle errors by showing a simple error message on the screen.
Use ViewModel and LiveData or StateFlow to manage UI state.
Starter Code
Android Kotlin
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.launch

class UserListViewModel : ViewModel() {
    // TODO: Add state variables for loading, users list, and error message

    fun fetchUsers() {
        // TODO: Implement coroutine to fetch users asynchronously
    }
}

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) {
    // TODO: Observe state and display loading, error or user list
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Android Kotlin
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.

Final Result
Completed Screen
-------------------------
|      User List        |
-------------------------
| • Alice               |
| • Bob                 |
| • Charlie             |
| • Diana               |
|                       |
-------------------------
When the screen opens, a loading spinner appears for 2 seconds.
After loading, the list of user names is shown in a scrollable list.
If an error occurs during fetch, an error message is displayed instead.
Stretch Goal
Add a retry button that appears when an error occurs, allowing the user to try fetching the users again.
💡 Hint
Show a Button below the error message that calls fetchUsers() again when clicked.