You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:52:21