← Назад к вопросам

Как написать приложение с сетевым запросом с помощью ViewModel

2.0 Middle🔥 253 комментариев
#Android компоненты#UI и вёрстка

Комментарии (3)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектура приложения с сетевым запросом в ViewModel

Для написания приложения с сетевым запросом с использованием ViewModel в Android рекомендуется следовать современным архитектурным подходам, таким как MVVM (Model-View-ViewModel) с компонентами Jetpack. Это обеспечивает разделение ответственности, тестируемость и корректную работу с жизненным циклом.

Основные компоненты:

  1. ViewModel — хранит и управляет данными, связанными с UI, переживает изменения конфигурации.
  2. LiveData/StateFlow — обеспечивает наблюдение за изменениями данных в UI-слое.
  3. Repository — абстрагирует источник данных (сеть, база данных).
  4. Retrofit/OkHttp — для сетевых запросов.
  5. Coroutines/RxJava — для асинхронных операций.

Пошаговая реализация:

1. Настройка зависимостей (build.gradle)

// Модуль app/build.gradle.kts
dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
}

2. Создание модели данных

data class Post(
    val id: Int,
    val title: String,
    val body: String,
    val userId: Int
)

3. Реализация API-интерфейса с Retrofit

interface ApiService {
    @GET("posts/{id}")
    suspend fun getPost(@Path("id") postId: Int): Post
    
    @GET("posts")
    suspend fun getAllPosts(): List<Post>
}

object RetrofitClient {
    private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
    
    val instance: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(
                OkHttpClient.Builder()
                    .addInterceptor(HttpLoggingInterceptor().apply {
                        level = HttpLoggingInterceptor.Level.BODY
                    })
                    .build()
            )
            .build()
            .create(ApiService::class.java)
    }
}

4. Создание Repository

class PostRepository {
    private val apiService = RetrofitClient.instance
    
    suspend fun fetchPost(postId: Int): Post {
        return apiService.getPost(postId)
    }
    
    suspend fun fetchAllPosts(): List<Post> {
        return apiService.getAllPosts()
    }
}

5. Реализация ViewModel с LiveData/StateFlow

class PostViewModel : ViewModel() {
    private val repository = PostRepository()
    
    // Использование LiveData
    private val _post = MutableLiveData<Post>()
    val post: LiveData<Post> = _post
    
    private val _loading = MutableLiveData<Boolean>()
    val loading: LiveData<Boolean> = _loading
    
    private val _error = MutableLiveData<String?>()
    val error: LiveData<String?> = _error
    
    // Альтернатива с StateFlow (более современный подход)
    private val _postState = MutableStateFlow<UiState<Post>>(UiState.Loading)
    val postState: StateFlow<UiState<Post>> = _postState
    
    sealed class UiState<T> {
        data class Success<T>(val data: T) : UiState<T>()
        data class Error<T>(val message: String) : UiState<T>()
        class Loading<T> : UiState<T>()
    }
    
    fun loadPost(postId: Int) {
        viewModelScope.launch {
            _loading.value = true
            _error.value = null
            
            try {
                val result = repository.fetchPost(postId)
                _post.value = result
                _postState.value = UiState.Success(result)
            } catch (e: Exception) {
                _error.value = "Ошибка загрузки: ${e.message}"
                _postState.value = UiState.Error(e.message ?: "Неизвестная ошибка")
            } finally {
                _loading.value = false
            }
        }
    }
}

6. Наблюдение за данными в Activity/Fragment

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: PostViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // Инициализация ViewModel
        viewModel = ViewModelProvider(this)[PostViewModel::class.java]
        
        // Наблюдение за LiveData
        viewModel.post.observe(this) { post ->
            // Обновление UI с полученными данными
            textViewTitle.text = post.title
            textViewBody.text = post.body
        }
        
        viewModel.loading.observe(this) { isLoading ->
            progressBar.isVisible = isLoading
        }
        
        viewModel.error.observe(this) { errorMessage ->
            errorMessage?.let {
                Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
            }
        }
        
        // Альтернатива с StateFlow
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.postState.collect { state ->
                    when (state) {
                        is UiState.Loading -> progressBar.isVisible = true
                        is UiState.Success -> {
                            progressBar.isVisible = false
                            textViewTitle.text = state.data.title
                        }
                        is UiState.Error -> {
                            progressBar.isVisible = false
                            showError(state.message)
                        }
                    }
                }
            }
        }
        
        // Запуск запроса
        buttonLoad.setOnClickListener {
            viewModel.loadPost(1)
        }
    }
}

Ключевые преимущества подхода:

  • Разделение ответственности: ViewModel не знает о UI, Activity/Fragment не управляют данными
  • Сохранение состояния: ViewModel переживает повороты экрана
  • Реактивное программирование: LiveData/StateFlow автоматически уведомляют UI об изменениях
  • Отмена корутин: viewModelScope автоматически отменяет корутины при очистке ViewModel
  • Тестируемость: ViewModel и Repository легко тестируются отдельно от Android-компонентов

Дополнительные улучшения:

  • Dependency Injection (Hilt/Dagger) для внедрения зависимостей
  • Кэширование данных в базе данных (Room)
  • Пагинация с помощью Paging Library
  • Обработка повторов запросов и таймаутов
  • Кэширование HTTP-ответов на уровне OkHttp

Такой подход обеспечивает чистую, поддерживаемую архитектуру, которая масштабируется по мере роста приложения.

Как написать приложение с сетевым запросом с помощью ViewModel | PrepBro