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

基于Android架构组件与MVVM的错误处理方案咨询

Error Propagation in Jetpack Architecture: Repository → ViewModel → View

Hey there! Handling error flow from your repository up to the View layer is a key part of making your app robust—let’s walk through a clean, scalable pattern that fits right into the Jetpack architecture you’re using.

1. Define a Sealed Class for State Wrapping

First, we need a consistent way to represent successful results, loading states, and errors. A sealed class is perfect here because it enforces exhaustive handling in your View layer:

sealed class Resource<out T> {
    // For successful data retrieval
    data class Success<out T>(val data: T) : Resource<T>()
    // For errors, carrying the message to display
    data class Error(val errorMessage: String) : Resource<Nothing>()
    // For loading states (optional but super useful for UX)
    object Loading : Resource<Nothing>()
}

2. Handle Errors in the Repository

Your repository is where you’ll catch network exceptions, parse error responses, and wrap everything into the Resource class. This keeps all data-fetching logic centralized:

class UserRepository(private val apiService: UserApiService) {
    suspend fun fetchUser(userId: Int): Resource<User> {
        return try {
            val response = apiService.getUser(userId)
            if (response.isSuccessful) {
                // Return success if we have valid data
                response.body()?.let { Resource.Success(it) } 
                    ?: Resource.Error("Failed to load user: Empty response")
            } else {
                // Handle HTTP errors (4xx, 5xx)
                Resource.Error("Request failed: ${response.message()}")
            }
        } catch (e: IOException) {
            // Catch network-related issues (no internet, timeouts)
            Resource.Error("Network error: ${e.localizedMessage ?: "Check your connection"}")
        } catch (e: Exception) {
            // Catch-all for unexpected errors
            Resource.Error("Oops! Something went wrong: ${e.localizedMessage}")
        }
    }
}

3. Propagate State to ViewModel

In your ViewModel, you’ll call the repository method and expose the state to the View layer using a StateFlow (or LiveData if you prefer). Use viewModelScope to handle coroutines safely:

class UserViewModel(private val repository: UserRepository) : ViewModel() {
    // Mutable state for internal updates
    private val _userState = MutableStateFlow<Resource<User>>(Resource.Loading)
    // Immutable state exposed to the View
    val userState: StateFlow<Resource<User>> = _userState.asStateFlow()

    fun loadUser(userId: Int) {
        viewModelScope.launch {
            _userState.value = Resource.Loading // Show loading first
            val result = repository.fetchUser(userId)
            _userState.value = result // Update with success or error
        }
    }

    // Optional: Add a retry method if you want users to refresh
    fun retryLoadUser(userId: Int) {
        loadUser(userId)
    }
}

4. Observe State and Show Errors in the View

Finally, in your Activity or Fragment, observe the ViewModel’s state and update the UI accordingly. Use repeatOnLifecycle to ensure you only collect state when the View is active:

class UserProfileFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Observe the user state
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.userState.collect { state ->
                    when (state) {
                        is Resource.Loading -> showLoadingIndicator(true)
                        is Resource.Success -> {
                            showLoadingIndicator(false)
                            displayUserProfile(state.data)
                        }
                        is Resource.Error -> {
                            showLoadingIndicator(false)
                            showErrorMessage(state.errorMessage)
                        }
                    }
                }
            }
        }

        // Trigger initial load
        viewModel.loadUser(123)
    }

    private fun showLoadingIndicator(isLoading: Boolean) {
        // Toggle visibility of your progress bar
        binding.progressBar.isVisible = isLoading
    }

    private fun displayUserProfile(user: User) {
        // Populate your UI with user data
        binding.userName.text = user.name
        binding.userEmail.text = user.email
    }

    private fun showErrorMessage(message: String) {
        // Show error to the user—Snackbar, Toast, or an error TextView
        Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG)
            .setAction("Retry") { viewModel.retryLoadUser(123) }
            .show()
    }
}

Quick Best Practices

  • Localize Error Messages: Instead of passing raw strings, use string resource IDs (e.g., R.string.network_error) so your app supports multiple languages.
  • Granular Error Types: Extend the Resource.Error class into subclasses (like NetworkError, AuthError) if you need to handle specific errors differently (e.g., redirect to login for auth issues).
  • Debounce Retries: If adding retry logic, consider adding a delay or limiting retries to avoid spamming the network.

内容的提问来源于stack exchange,提问作者Hayk Galstyan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 03:58:57