← Назад к вопросам
Как написать приложение с сетевым запросом с помощью ViewModel
2.0 Middle🔥 253 комментариев
#Android компоненты#UI и вёрстка
Комментарии (3)
🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектура приложения с сетевым запросом в ViewModel
Для написания приложения с сетевым запросом с использованием ViewModel в Android рекомендуется следовать современным архитектурным подходам, таким как MVVM (Model-View-ViewModel) с компонентами Jetpack. Это обеспечивает разделение ответственности, тестируемость и корректную работу с жизненным циклом.
Основные компоненты:
- ViewModel — хранит и управляет данными, связанными с UI, переживает изменения конфигурации.
- LiveData/StateFlow — обеспечивает наблюдение за изменениями данных в UI-слое.
- Repository — абстрагирует источник данных (сеть, база данных).
- Retrofit/OkHttp — для сетевых запросов.
- 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
Такой подход обеспечивает чистую, поддерживаемую архитектуру, которая масштабируется по мере роста приложения.