Model-View-ViewModel

MVP — Model-View-Presenter

Для початку — трошки теорії. Все почалося з того, що багато хто думав, як адаптувати шаблон MVC (Model-View-Controller) для написання додатків з інтерфейсом користувача. І в 2006 році в роботі “GUI Architectures” Мартін Фаулер детально розглянув шаблон, який пізніше отримав назву “MVP” (“Model-View-Presenter”).

Отже, MVP — шаблон проектування, похідний від MVC, розроблений переважно для побудови користувацького інтерфейсу. MVP використовується для полегшення автоматичного модульного тестування та покращення розділення логіки і відображення.

В цьому шаблоні є три елементи:

  • View.
  • Presenter.
  • Model (модель).

  1. Елемент View відповідає за показ користувацьких даних і перехоплення користувацьких дій. Все це він надсилає Presenter'у.
  2. Presenter обробляє дії користувача в UI, враховує зміни даних у Model і надсилає цю інформацію View. Presenter — це елемент, який містить всю бізнес-логіку роботи з користувацьким інтерфейсом.
  3. Model містить у собі моделі з предметної області, які відображають знання та дані предметної області вашого додатка. Model надсилає інформацію про зміну даних Presenter і приймає повідомлення від Presenter.

MVP — реалізація в Android

MVP дозволяє створювати абстракцію представлення. Для цього необхідно виокремити інтерфейс представлення з певним набором властивостей і методів.

Тепер подивимося, як це можна реалізувати в Android — для цього напишемо невеликий «велосипед».

Presenter взаємодіє з View за допомогою спеціального інтерфейсу, який описує абстракцію цього View.

Припустимо, у нас є ось така модель View:

public interface SomeScreenView {
    void startLoading(); 
    void stopLoading(); 
    void mapDataItems(final Collection<DataItem> items);
}

Зверніть увагу: не слід плутати цю модель View з тим виглядом (View), який ми бачимо на екрані. View, яка використовується в MVP — це певна абстракція View. Іншими словами, це узагальнення поведінки нашого View. В MVP View не відповідає за те, як саме все буде відображатися на користувацькому інтерфейсі. Вона відповідає за те, як буде поводитися користувацький інтерфейс.

Presenter отримує посилання на реалізацію інтерфейсу, взаємодіє з моделлю нашого View, ініціалізує його, викликає всі його повідомлення, посилає йому якісь повідомлення і т.д. Все взаємодія відбувається напряму: у нас є реалізація View, ми викликаємо її методи і отримуємо певний результат.

Іншими словами, Presenter як би підписується на події View і за необхідності змінює дані в Model.

public class SomeScreenPresenter extends Presenter {
    private SomeScreenView mView;

    public void setView(SomeScreenView view) {
        mView = view;
    }

    @Override
        public void initialize() {
        mView.startLoading();

        mView.mapDataItems(...);
        mView.stopLoading();
    }
}

Як приклад View у нашому випадку виступатиме Activity, що відповідає за реалізацію поведінки SomeScreenView. Роль View може відігравати не тільки Activity, але й Fragment, Dialog або просто Android View. Для цього йому також необхідно реалізувати поведінку SomeScreenView. У зазначеному Activity використовується об'єкт типу SomeScreenPresenter, який і виступає в ролі Presenter у нашому прикладі. Цьому об'єкту ми надаємо посилання на реалізацію вашого View, яке взаємодіє з Presenter шляхом прямого виклику в нього необхідних методів. Своєю чергою, Presenter викликає методи, реалізовані всередині вашої Activity, тому що вона є реалізацією вашого View.

@EActivity(R.layout.activity_some_screen) 
public class SomeScreenActivity extends Activity implements SomeScreenView { 
    private SomeScreenPresenter mPresenter; 

    @ViewById(R.id.drawer_layout) 
    protected ProgressBar mProgressBar;

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 

        mPresenter = new SomeScreenPresenter(this);
        mPresenter.gradletialize(); 
    } 
}

Цей простий приклад демонструє, як MVP дозволяє декомпозувати логіку, яка раніше повністю знаходилася в Activity та була пов'язана з обробкою даних і дій користувача. Ми винесли цю логіку в окремий модуль, і цей модуль, наприклад, можемо перевірити звичайним модульним тестуванням. На мою думку, це набагато простіше, ніж тестування нашої UI-функціональності за допомогою Robotium, запуску емуляторів і т. д. Іншими словами, ми взяли всю нашу логіку з Activity, яка раніше була Controller, винесли її в новий елемент Presenter, і тепер можемо цей елемент спокійно протестувати, не створюючи ніяких Controller і View. Крім того, цей код можна додатково покращити — наприклад, використовуючи впровадження залежностей (скажімо, за допомогою RoboGuice або Dagger).

Приклад

Опис бізнес-сценарію:

  • Користувач може переглядати список товарів.
  • Користувач може додавати товар у кошик.
  • Користувач може оформити покупку.
  • Ми використовуємо Retrofit для мережевих запитів, щоб отримати список товарів із серверу та виконати покупку.

Крок 1: Модель (Model)

1.1. Структура товару (Product)

public class Product {
    private String id;
    private String name;
    private double price;

    public Product(String id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}

1.2. Інтерфейс для отримання товарів із мережі

import retrofit2.Call;
import retrofit2.http.GET;

public interface ApiService {
    @GET("products")
    Call<List<Product>> getProducts();
}

1.3. Клас для обробки покупки

public class Purchase {
    private String userId;
    private List<Product> products;

    public Purchase(String userId, List<Product> products) {
        this.userId = userId;
        this.products = products;
    }

    public String getUserId() {
        return userId;
    }

    public List<Product> getProducts() {
        return products;
    }
}

Крок 2: Presenter

2.1. Презентер для роботи з продуктами та кошиком

import android.util.Log;
import java.util.List;

public class ProductPresenter {

    private ProductView view;
    private ApiService apiService;
    private List<Product> products;

    public ProductPresenter(ProductView view, ApiService apiService) {
        this.view = view;
        this.apiService = apiService;
    }

    public void loadProducts() {
        apiService.getProducts().enqueue(new retrofit2.Callback<List<Product>>() {
            @Override
            public void onResponse(Call<List<Product>> call, retrofit2.Response<List<Product>> response) {
                if (response.isSuccessful()) {
                    products = response.body();
                    view.showProducts(products);
                } else {
                    view.showError("Failed to load products");
                }
            }

            @Override
            public void onFailure(Call<List<Product>> call, Throwable t) {
                view.showError("Network error");
            }
        });
    }

    public void addToCart(Product product) {
        // Логіка додавання товару в кошик
        view.showProductAddedToCart(product);
    }

    public void makePurchase(String userId) {
        if (products == null || products.isEmpty()) {
            view.showError("Your cart is empty");
            return;
        }
        Purchase purchase = new Purchase(userId, products);
        // Можна реалізувати мережевий запит для оформлення покупки
        view.showPurchaseSuccess(purchase);
    }
}

Крок 3: View

3.1. Інтерфейс для View

public interface ProductView {
    void showProducts(List<Product> products);
    void showError(String error);
    void showProductAddedToCart(Product product);
    void showPurchaseSuccess(Purchase purchase);
}

3.2. Реалізація View в Activity

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;

import java.util.List;

public class MainActivity extends AppCompatActivity implements ProductView {

    private ProductPresenter presenter;
    private ListView productsListView;
    private Button buyButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        productsListView = findViewById(R.id.productsListView);
        buyButton = findViewById(R.id.buyButton);

        // Ініціалізація Retrofit
        ApiService apiService = RetrofitClient.getRetrofitInstance().create(ApiService.class);
        presenter = new ProductPresenter(this, apiService);

        // Завантаження продуктів
        presenter.loadProducts();

        buyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Оформлення покупки (потрібно передати userId)
                presenter.makePurchase("user123");
            }
        });
    }

    @Override
    public void showProducts(List<Product> products) {
        // Налаштовуємо адаптер для відображення товарів
        ProductAdapter adapter = new ProductAdapter(this, products);
        productsListView.setAdapter(adapter);
    }

    @Override
    public void showError(String error) {
        Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showProductAddedToCart(Product product) {
        Toast.makeText(this, "Product added to cart: " + product.getName(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showPurchaseSuccess(Purchase purchase) {
        Toast.makeText(this, "Purchase successful! " + purchase.getProducts().size() + " items bought.", Toast.LENGTH_SHORT).show();
    }
}

Крок 4: Адаптер для відображення списку продуктів

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import java.util.List;

public class ProductAdapter extends BaseAdapter {

    private Context context;
    private List<Product> products;

    public ProductAdapter(Context context, List<Product> products) {
        this.context = context;
        this.products = products;
    }

    @Override
    public int getCount() {
        return products.size();
    }

    @Override
    public Object getItem(int position) {
        return products.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.product_item, parent, false);
        }

        Product product = products.get(position);

        TextView nameTextView = convertView.findViewById(R.id.productName);
        TextView priceTextView = convertView.findViewById(R.id.productPrice);

        nameTextView.setText(product.getName());
        priceTextView.setText("$" + product.getPrice());

        return convertView;
    }
}

Крок 5: Ретрофіт клієнт для мережевих запитів

public class RetrofitClient {

    private static Retrofit retrofit;
    private static final String BASE_URL = "https://api.example.com/";

    public static Retrofit getRetrofitInstance() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

Крок 6: XML для Activity

<!-- activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/productsListView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toTopOf="@+id/buyButton" />

    <Button
        android:id="@+id/buyButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Buy"
        app:layout_constraintTop_toBottomOf="@+id/productsListView"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

MVVM

Шаблон MVP непоганий, але Microsoft придумала шаблон ще кращий — MVVM (Model-View-ViewModel). Цей шаблон дуже полюбляють .NET-розробники, він використовується в Silverlight, WPF, WinUI 3, MAUI, його реалізація є в AngularJS. MVVM — дуже зручний шаблон.

Чим MVVM відрізняється від MVP?

MVVM дозволяє зв'язувати елементи View з властивостями та подіями ViewModel. При цьому ViewModel — абстракція представлення. У MVVM є:

  • View — містить поля, що відповідають інтерфейсу користувача.
  • ViewModel — містить такі ж поля, але в предметній області.
  • Той же, Model.

Властивості View збігаються з властивостями ViewModel/Model. При цьому ViewModel не має посилання на інтерфейс представлення. Зміна стану ViewModel автоматично змінює View, і навпаки. Для цього використовується механізм зв'язування даних. Також характерною рисою MVVM є двостороння комунікація з View.

MVVM у розробці Android: Як використати шаблон із Java та Data Binding

Вступ

MVVM (Model-View-ViewModel) — це один із найпопулярніших архітектурних шаблонів для Android-додатків. Він забезпечує чіткий поділ відповідальності, що робить код більш підтримуваним, тестованим і зрозумілим. У цій статті ми розглянемо, як реалізувати MVVM у Android із використанням Java та Data Binding.


Що таке MVVM?

MVVM розділяє додаток на три основні компоненти:

  1. Model:
    • Відповідає за управління даними (логіка бізнесу, доступ до API, бази даних тощо).
  2. View:
    • Це інтерфейс користувача (наприклад, XML-макет).
    • Відображає дані, які отримує від ViewModel.
  3. ViewModel:
    • Зв'язує View і Model.
    • Містить всю логіку відображення даних і викликає Model для отримання або збереження даних.

Чому MVVM краще?

Переваги:

  • Чіткий поділ відповідальності: Легше підтримувати й розширювати додаток.
  • Легке тестування: ViewModel легко тестується, оскільки не має прямої залежності від UI.
  • Data Binding: Позбавляє необхідності оновлювати UI вручну.

Недоліки:

  • Додаткова складність у малих проєктах.
  • Потребує більшої кількості класів та налаштувань.

Data Binding: Що це?

Data Binding дозволяє зв'язати UI-елементи напряму з даними у ViewModel, що усуває потребу в пошуку View через findViewById і дозволяє автоматично оновлювати UI при зміні даних.


Реалізація MVVM із Data Binding

Створимо простий приклад програми, що показує привітання користувачу за введеним ім'ям.


1. Налаштування проєкту

Додайте Data Binding у build.gradle:

android {
    ...
    buildFeatures {
        dataBinding true
    }
}

Залежності:

dependencies {
    implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.1'
    implementation 'androidx.lifecycle:lifecycle-livedata:2.4.1'
}

2. Створіть ViewModel

ViewModel зберігає введені дані та генерує повідомлення.

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class GreetingViewModel extends ViewModel {

    private MutableLiveData<String> name = new MutableLiveData<>("");
    private MutableLiveData<String> greetingMessage = new MutableLiveData<>("");

    public LiveData<String> getName() {
        return name;
    }

    public void setName(String newName) {
        name.setValue(newName);
        updateGreetingMessage();
    }

    public LiveData<String> getGreetingMessage() {
        return greetingMessage;
    }

    private void updateGreetingMessage() {
        String currentName = name.getValue();
        if (currentName == null || currentName.isEmpty()) {
            greetingMessage.setValue("Привіт, незнайомцю!");
        } else {
            greetingMessage.setValue("Привіт, " + currentName + "!");
        }
    }
}

3. Налаштуйте XML (View)

У XML ми використовуємо Data Binding для зв’язування UI-елементів із ViewModel.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="com.example.mvvmexample.GreetingViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="16dp">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="Введіть своє ім'я"
            android:text="@={viewModel.name}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{viewModel.greetingMessage}"
            android:layout_marginTop="16dp"
            android:textSize="20sp" />

    </LinearLayout>
</layout>

4. Створіть Activity (View)

У MainActivity ми ініціалізуємо ViewModel і зв’язуємо його з XML.

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;

import com.example.mvvmexample.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Використовуємо Data Binding
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        // Ініціалізуємо ViewModel
        GreetingViewModel viewModel = new ViewModelProvider(this).get(GreetingViewModel.class);

        // Прив'язуємо ViewModel до Binding
        binding.setViewModel(viewModel);

        // Дозволяємо LiveData автоматично оновлювати UI
        binding.setLifecycleOwner(this);
    }
}

Що таке LiveData?

LiveData — це об'єкт із бібліотеки Android Architecture Components, який спостерігає за даними і забезпечує автоматичне оновлення UI при зміні даних.

Переваги LiveData:

  • Автоматичне керування життєвим циклом.
  • Безпечне оновлення UI з фонових потоків.
  • Мінімізація витоків пам'яті.

Приклад із LiveData:

Створимо об'єкт LiveData для зберігання списку елементів:

private MutableLiveData<List<String>> items = new MutableLiveData<>(new ArrayList<>());

public LiveData<List<String>> getItems() {
    return items;
}

public void addItem(String item) {
    List<String> currentItems = items.getValue();
    if (currentItems != null) {
        currentItems.add(item);
        items.setValue(currentItems);
    }
}

Висновок

Шаблон MVVM у поєднанні з Data Binding і LiveData — це потужний інструмент для створення модульних і підтримуваних Android-додатків. У цьому прикладі ми створили простий додаток із привітанням, використовуючи найкращі практики. Цей підхід легко адаптується до складніших задач, таких як робота з API, базами даних або іншими джерелами даних.

ObservableArrayList в Android

Що таке ObservableArrayList?

ObservableArrayList — це клас із бібліотеки Android Data Binding, який дозволяє створювати списки, зміни в яких автоматично відслідковуються та повідомляються підписникам. Це робить його ідеальним для використання у патерні MVVM разом із Data Binding.

Клас ObservableArrayList особливо корисний, коли потрібно інтегрувати оновлення UI з бізнес-логікою, оскільки будь-яка зміна в списку автоматично тригерить оновлення прив’язаного представлення (View).


Як використовувати ObservableArrayList?

import androidx.databinding.ObservableArrayList;

public class CalculatorViewModel extends ViewModel {
    // Список операцій у калькуляторі
    public ObservableArrayList<String> operations = new ObservableArrayList<>();

    // Додати операцію
    public void addOperation(String operation) {
        operations.add(operation);
    }

    // Видалити операцію
    public void removeOperation(String operation) {
        operations.remove(operation);
    }
}

У цьому прикладі список operations автоматично повідомляє прив’язаний UI, якщо змінюється його вміст.


Інтеграція з Data Binding

XML-макет

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="com.example.calculator.CalculatorViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!-- Список операцій -->
        <ListView
            android:id="@+id/operationsListView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:entries="@{viewModel.operations}" />

        <!-- Додати операцію -->
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Додати операцію"
            android:onClick="@{() -> viewModel.addOperation(`Нова операція`)}" />
    </LinearLayout>
</layout>

Що відбувається?

  1. Прив’язка (Binding): Список operations із ViewModel прив’язаний до ListView.
  2. Автоматичне оновлення: Будь-яка зміна в ObservableArrayList автоматично оновлює ListView.

Особливості ObservableArrayList

  1. Автоматичні повідомлення: Усі зміни в списку автоматично тригерять оновлення прив’язаного представлення.
  2. Простота використання: Не потрібно вручну сповіщати підписників про зміни.
  3. Інтеграція з Data Binding: Клас створений спеціально для роботи з Data Binding.
  4. Немає підтримки складних змін: ObservableArrayList не підтримує подій на рівні окремих елементів (наприклад, оновлення властивості об’єкта в списку).

Обмеження ObservableArrayList

  • Масштабованість: Для великих списків або складних змін краще використовувати інші підходи, наприклад, LiveData чи PagedList.

Прив’язка даних (Data Binding) в XML

Прив’язка даних у XML — це ключова особливість Android Data Binding Library, яка дозволяє напряму з'єднати дані з інтерфейсом користувача. Цей підхід спрощує зв’язок між логікою (ViewModel) і представленням (View), що є особливо зручним у патерні MVVM.


Основний синтаксис прив’язки

1. Декларація прив’язки в макеті

Щоб використовувати прив’язку даних у XML, макет файлу повинен починатися з <layout>.

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <!-- Оголошення змінних -->
        <variable
            name="viewModel"
            type="com.example.app.MyViewModel" />
    </data>
    <!-- Основний макет -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!-- Елементи UI -->
    </LinearLayout>
</layout>

2. Оголошення змінних у <data>

Вкладений тег <data> використовується для визначення змінних, які будуть доступні для прив’язки в XML. Змінна визначається за допомогою тега <variable>.

  • name — ім’я змінної, яка буде доступна в XML.
  • type — клас об’єкта, який буде прив’язаний.
<data>
    <variable
        name="viewModel"
        type="com.example.app.MyViewModel" />
</data>

Типи прив’язки

1. Прив’язка до властивостей

Використовуйте синтаксис @{} для прив’язки до властивостей об’єкта.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{viewModel.userName}" />

У цьому прикладі userName — це властивість у класі MyViewModel.


2. Прив’язка подій

Для подій (наприклад, натискань кнопок) використовується синтаксис @{} із лямбда-виразами.

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click Me"
    android:onClick="@{() -> viewModel.onButtonClick()}" />

3. Прив’язка зі значеннями за замовчуванням

Ви можете вказати значення за замовчуванням, якщо значення прив’язки порожнє.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{viewModel.age != null ? viewModel.age : `Немає даних`}" />

4. Прив’язка до методів

Можна викликати методи класу ViewModel напряму.

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@{viewModel.getProfileImageUrl()}" />

Умовна прив’язка

Можна використовувати умовні вирази для динамічної зміни атрибутів.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="@{viewModel.isVisible ? View.VISIBLE : View.GONE}" />

Прив’язка з форматуванням

Використовуйте String.format для форматування тексту.

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text='@{String.format("Привіт, %s!", viewModel.userName)}' />

Двостороння прив’язка

Двостороння прив’язка дозволяє автоматично оновлювати дані у ViewModel при зміні UI. Для цього використовується атрибут @=.

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={viewModel.userInput}" />
  • Якщо користувач вводить текст у EditText, значення userInput у ViewModel автоматично оновлюється.
  • Зміни в userInput також автоматично відображаються в EditText.

Робота з LiveData

LiveData — це об'єкт, який використовується для спостереження за даними, які змінюються з часом. У Data Binding LiveData підтримується автоматично.

Прив’язка LiveData в XML

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{viewModel.liveUserName}" />

ViewModel з LiveData

public class MyViewModel extends ViewModel {
    public MutableLiveData<String> liveUserName = new MutableLiveData<>();

    public void updateName(String name) {
        liveUserName.setValue(name);
    }
}

У цьому прикладі:

  1. Значення liveUserName автоматично відображається у TextView.
  2. Зміни у liveUserName викликають оновлення UI.

Додаткові приклади з LiveData

Прив’язка списків

<ListView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:entries="@{viewModel.liveItems}" />

У ViewModel:

public class MyViewModel extends ViewModel {
    public MutableLiveData<List<String>> liveItems = new MutableLiveData<>();

    public void addItem(String item) {
        List<String> currentItems = liveItems.getValue();
        if (currentItems == null) {
            currentItems = new ArrayList<>();
        }
        currentItems.add(item);
        liveItems.setValue(currentItems);
    }
}

Висновок

Прив’язка даних у XML за допомогою Data Binding Library — це потужний інструмент для створення сучасних Android-додатків. Використовуючи Observable об’єкти, LiveData і двосторонню прив’язку, можна значно спростити код та покращити його підтримку, особливо у великих проектах.

Table of Contents