------------------------- | User List | ------------------------- | Loading... | | | | | | | | | -------------------------
Coroutines for async networking in Android Kotlin - Mini App: Build & Ship
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 }
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.
------------------------- | User List | ------------------------- | • Alice | | • Bob | | • Charlie | | • Diana | | | -------------------------
Practice
Solution
Step 1: Understand what happens during network calls
Network calls can take time and if done on the main thread, they freeze the UI.Step 2: Role of coroutines in network calls
Coroutines run network tasks in the background, keeping the UI responsive.Final Answer:
They prevent the app's screen from freezing during network tasks. -> Option DQuick Check:
Coroutines keep UI responsive = D [OK]
- Thinking coroutines speed up CPU usage
- Believing coroutines cache data automatically
- Assuming coroutines remove all threading needs
Solution
Step 1: Identify dispatcher for background network tasks
Dispatchers.IO is designed for blocking IO tasks like network calls.Step 2: Match correct coroutine launch syntax
launch(Dispatchers.IO) { fetchData() } runs fetchData() on IO thread pool.Final Answer:
launch(Dispatchers.IO) { fetchData() } -> Option CQuick Check:
Network calls use Dispatchers.IO = B [OK]
- Using Dispatchers.Main for network calls
- Confusing Dispatchers.Default with IO
- Omitting dispatcher and running on main thread
fun main() = runBlocking {
launch(Dispatchers.IO) {
println("Network call started")
}
println("Main thread continues")
}Solution
Step 1: Understand runBlocking and launch behavior
runBlocking blocks main thread until all coroutines complete. launch starts coroutine asynchronously.Step 2: Order of print statements
"Main thread continues" prints immediately; "Network call started" prints shortly after from IO dispatcher.Final Answer:
Main thread continues\nNetwork call started -> Option AQuick Check:
launch is async, main prints first = C [OK]
- Assuming launch blocks main thread
- Expecting network print before main print
- Ignoring coroutine dispatchers
GlobalScope.launch(Dispatchers.IO) {
val data = fetchData()
textView.text = data
}Solution
Step 1: Check thread for UI update
UI elements like textView must be updated on the main thread.Step 2: Identify misuse of Dispatchers.IO
Dispatchers.IO runs on background thread; updating UI here causes error.Final Answer:
Cannot update UI from Dispatchers.IO thread. -> Option BQuick Check:
UI updates require Dispatchers.Main = A [OK]
- Updating UI on background thread
- Misusing GlobalScope without lifecycle awareness
- Calling fetchData() on main thread unnecessarily
Solution
Step 1: Identify correct dispatcher for network call
Network call fetchUser() should run on Dispatchers.IO to avoid blocking UI.Step 2: Ensure UI update runs on main thread
updateUI(data) must run on Dispatchers.Main to safely update UI elements.Step 3: Analyze launch(Dispatchers.Main) { val data = withContext(Dispatchers.IO) { fetchUser() }; updateUI(data) } structure
launch on Main dispatcher, inside withContext(Dispatchers.IO) runs fetchUser(), then UI updates on Main.Final Answer:
launch(Dispatchers.Main) { val data = withContext(Dispatchers.IO) { fetchUser() }; updateUI(data) } -> Option AQuick Check:
Network on IO, UI on Main = A [OK]
- Updating UI on IO or Default dispatcher
- Running network call on Main dispatcher directly
- Not switching context for network call
Ready to prove it?
Pass the test and this pattern moves unknown
