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

Как реализовать MVVM не используя Android Architecture Components?

2.7 Senior🔥 122 комментариев
#Android компоненты#Архитектура и паттерны

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

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

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

Реализация MVVM без Android Architecture Components

Реализация паттерна Model-View-ViewModel (MVVM) без использования Android Architecture Components (LiveData, ViewModel, Room и т.д.) требует создания собственных аналогов ключевых компонентов. Это отличный способ глубоко понять архитектурные принципы.

Основные компоненты кастомной реализации

В классической реализации нам нужно создать:

  1. Собственный ViewModel - для хранения и управления UI-данными
  2. Механизм наблюдения за данными - аналог LiveData/StateFlow
  3. Связь между View и ViewModel - обычно через биндинг или колбэки
  4. Репозиторий и источники данных - для работы с Model слоем

Реализация Observable (аналог LiveData)

// Базовый класс для наблюдаемых данных
open class Observable<T>(initialValue: T) {
    private var value: T = initialValue
    private val observers = mutableListOf<(T) -> Unit>()
    
    fun getValue(): T = value
    
    fun setValue(newValue: T) {
        if (value != newValue) {
            value = newValue
            notifyObservers()
        }
    }
    
    fun observe(observer: (T) -> Unit) {
        observers.add(observer)
        observer(value) // Немедленно уведомляем текущим значением
    }
    
    fun removeObserver(observer: (T) -> Unit) {
        observers.remove(observer)
    }
    
    private fun notifyObservers() {
        observers.forEach { it(value) }
    }
}

Кастомный ViewModel

// Базовый класс ViewModel с учетом жизненного цикла
abstract class BaseViewModel {
    private val compositeDisposable = mutableListOf<() -> Unit>()
    
    fun addDisposable(disposable: () -> Unit) {
        compositeDisposable.add(disposable)
    }
    
    fun clear() {
        compositeDisposable.forEach { it() }
        compositeDisposable.clear()
        onCleared()
    }
    
    protected open fun onCleared() {
        // Переопределить для очистки ресурсов
    }
}

Пример конкретной реализации

// ViewModel для экрана пользователя
class UserViewModel : BaseViewModel() {
    private val _userName = Observable("")
    private val _isLoading = Observable(false)
    private val _errorMessage = Observable<String?>(null)
    
    // Публичные свойства для наблюдения
    val userName: Observable<String> get() = _userName
    val isLoading: Observable<Boolean> get() = _isLoading
    val errorMessage: Observable<String?> get() = _errorMessage
    
    private val userRepository = UserRepository()
    
    fun loadUserData(userId: String) {
        _isLoading.setValue(true)
        _errorMessage.setValue(null)
        
        // Имитация асинхронной загрузки
        Thread {
            try {
                Thread.sleep(1000) // Имитация сетевого запроса
                val user = userRepository.getUser(userId)
                
                // Возвращаемся в главный поток для обновления UI
                Handler(Looper.getMainLooper()).post {
                    _userName.setValue(user.name)
                    _isLoading.setValue(false)
                }
            } catch (e: Exception) {
                Handler(Looper.getMainLooper()).post {
                    _errorMessage.setValue("Ошибка загрузки: ${e.message}")
                    _isLoading.setValue(false)
                }
            }
        }.start()
    }
}

// Модель репозитория
class UserRepository {
    fun getUser(id: String): User {
        // В реальном приложении здесь будет работа с API или БД
        return User(id, "Иван Иванов")
    }
}

data class User(val id: String, val name: String)

Интеграция с Activity/Fragment

class UserActivity : AppCompatActivity() {
    private lateinit var viewModel: UserViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        
        // Создаем ViewModel
        viewModel = UserViewModel()
        
        // Наблюдаем за изменениями
        observeViewModel()
        
        // Загружаем данные
        viewModel.loadUserData("123")
    }
    
    private fun observeViewModel() {
        // Подписываемся на изменения userName
        viewModel.userName.observe { name ->
            userNameTextView.text = name
        }
        
        // Подписываемся на состояние загрузки
        viewModel.isLoading.observe { isLoading ->
            progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
        }
        
        // Подписываемся на ошибки
        viewModel.errorMessage.observe { error ->
            if (error != null) {
                showError(error)
            }
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // Важно очистить ViewModel для избежания утечек памяти
        viewModel.clear()
    }
    
    private fun showError(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
}

Ключевые аспекты реализации

Управление жизненным циклом:

  • ViewModel должен очищать ресурсы при уничтожении
  • Нужно отписываться от наблюдений в onDestroy
  • Важно учитывать поворот экрана (можно сохранять ViewModel в Bundle)

Потокобезопасность:

  • Observable должен быть потокобезопасным при многопоточном доступе
  • Обновление UI должно происходить в главном потоке

Преимущества кастомной реализации:

  1. Полный контроль над поведением компонентов
  2. Глубокое понимание принципов MVVM
  3. Минимальные зависимости от сторонних библиотек
  4. Гибкая настройка под конкретные требования проекта

Недостатки:

  1. Больше boilerplate-кода
  2. Необходимость самостоятельно решать проблемы жизненного цикла
  3. Отсутствие готовых решений для сложных сценариев
  4. Потенциальные ошибки при реализации механизмов наблюдения

Альтернативные подходы

Для упрощения можно использовать:

  • RxJava/RxAndroid для реактивного программирования
  • Kotlin Flow (требует корутины)
  • Событийная шина (EventBus, Otto) для коммуникации
  • Кастомные Callback-интерфейсы

Данный подход демонстрирует фундаментальные принципы MVVM и полезен для образовательных целей, но в production-приложениях рекомендуется использовать проверенные решения из Android Architecture Components или других стабильных библиотек для избежания потенциальных ошибок и упрощения поддержки кода.

Как реализовать MVVM не используя Android Architecture Components? | PrepBro