如何用LiveData、ViewModel处理网络请求状态及Retrofit错误通知UI
Hey there! Let's tackle both of your MVVM + Android Architecture Components questions with clear, actionable steps and code examples—this pattern is core to building robust Android apps, so let's break it down.
1. Handling Network Request States with LiveData & ViewModel to Update UI
When dealing with network calls, your UI has multiple states to handle: loading, success with data, and error. The cleanest way to manage this is to create a sealed class that wraps all possible outcomes, then use LiveData to emit these states from your ViewModel to the UI.
Step 1: Define a State Sealed Class
This class acts as a type-safe wrapper for all request outcomes. Sealed classes are perfect here because they force you to handle every state in the UI (no missing cases!).
sealed class Resource<out T> { // Loading state: UI should show a progress indicator object Loading : Resource<Nothing>() // Success state: UI gets the data to display data class Success<out T>(val data: T) : Resource<T>() // Error state: UI gets an error message to show the user data class Error(val message: String) : Resource<Nothing>() }
Step 2: Build Your ViewModel & Repository
Separate your data layer from the ViewModel using a Repository (a key MVVM best practice). Use viewModelScope to launch coroutines—it automatically cancels when the ViewModel is destroyed, preventing memory leaks.
First, the Repository and Retrofit service:
// Repository: Handles data operations (Retrofit calls, local DB, etc.) class UserRepository(private val apiService: ApiService) { suspend fun getUsers(): List<User> { return apiService.fetchUsers() } } // Retrofit Service Interface interface ApiService { @GET("users") suspend fun fetchUsers(): List<User> }
Now the ViewModel:
class UserViewModel(private val userRepository: UserRepository) : ViewModel() { // MutableLiveData is internal to the ViewModel (only it can update it) private val _usersLiveData = MutableLiveData<Resource<List<User>>>() // Expose immutable LiveData to the UI val usersLiveData: LiveData<Resource<List<User>>> = _usersLiveData fun fetchUsers() { // Emit loading state first to show progress _usersLiveData.postValue(Resource.Loading) viewModelScope.launch { try { // Make the Retrofit call (suspend function runs on background thread) val userList = userRepository.getUsers() // Emit success state with the fetched data _usersLiveData.postValue(Resource.Success(userList)) } catch (e: Exception) { // Emit error state with a user-friendly message val errorMsg = e.message ?: "Oops, something went wrong fetching users" _usersLiveData.postValue(Resource.Error(errorMsg)) } } } }
Step 3: Observe LiveData in the UI (Fragment/Activity)
In your UI component, observe the LiveData using viewLifecycleOwner (for Fragments) to ensure you only receive updates when the UI is active. Then update the UI based on each state:
class UserListFragment : Fragment() { private lateinit var viewModel: UserViewModel private lateinit var userAdapter: UserAdapter private lateinit var progressBar: ProgressBar private lateinit var errorTextView: TextView private lateinit var recyclerView: RecyclerView override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // Initialize views and adapter setupViews() // Get ViewModel instance viewModel = ViewModelProvider(this)[UserViewModel::class.java] // Observe the LiveData to react to state changes viewModel.usersLiveData.observe(viewLifecycleOwner) { resource -> when (resource) { is Resource.Loading -> { progressBar.visibility = View.VISIBLE recyclerView.visibility = View.GONE errorTextView.visibility = View.GONE } is Resource.Success -> { progressBar.visibility = View.GONE recyclerView.visibility = View.VISIBLE errorTextView.visibility = View.GONE // Update RecyclerView with new data userAdapter.submitList(resource.data) } is Resource.Error -> { progressBar.visibility = View.GONE recyclerView.visibility = View.GONE errorTextView.visibility = View.VISIBLE errorTextView.text = resource.message // Optional: Show a Snackbar for quick feedback Snackbar.make(view, resource.message, Snackbar.LENGTH_LONG).show() } } } // Trigger the network request viewModel.fetchUsers() } private fun setupViews() { recyclerView = view.findViewById(R.id.recycler_view) userAdapter = UserAdapter() recyclerView.adapter = userAdapter progressBar = view.findViewById(R.id.progress_bar) errorTextView = view.findViewById(R.id.error_text) } }
2. Sending Error Notifications to UI in MVVM (Retrofit Errors)
This is a natural extension of the state pattern above! The key is to catch specific Retrofit exceptions in your ViewModel, craft targeted error messages, and emit the Resource.Error state.
Step 1: Handle Specific Retrofit Exceptions
Retrofit throws different exceptions for different error scenarios—you can catch these to provide more helpful messages to users:
// Update the try/catch block in your ViewModel's fetchUsers() function viewModelScope.launch { try { val userList = userRepository.getUsers() _usersLiveData.postValue(Resource.Success(userList)) } catch (e: HttpException) { // Handle HTTP errors (4xx, 5xx status codes) val errorMsg = when(e.code()) { 404 -> "User list not found on server" 500 -> "Server is down, please try again later" 401 -> "You're not authorized to view this list" else -> "HTTP Error: ${e.message()}" } _usersLiveData.postValue(Resource.Error(errorMsg)) } catch (e: IOException) { // Handle network connectivity errors (no internet) _usersLiveData.postValue(Resource.Error("No internet connection—please check your network")) } catch (e: Exception) { // Catch-all for unexpected errors _usersLiveData.postValue(Resource.Error("Unexpected error: ${e.message ?: "Unknown issue"}")) } }
Step 2: Display the Error in the UI
As we did earlier, the UI observes the usersLiveData and reacts to the Resource.Error state. You can choose to show the error in a TextView, Snackbar, Toast, or dialog—whatever fits your app's design.
The main win here is that the ViewModel never directly interacts with the UI—it only emits state, and the UI decides how to present that state. This keeps your code decoupled, testable, and aligned with MVVM principles.
内容的提问来源于stack exchange,提问作者Sharad

