Компоненты архитектуры Android: привязка к ViewModel

Я немного смущен тем, как привязка данных должна работать при использовании новых компонентов архитектуры.

предположим, у меня есть простое действие со списком, ProgressBar и TextView. действие должно отвечать за управление состоянием всех представлений, но ViewModel должен содержать данные и логику. Например, моя деятельность теперь выглядит так:

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

    binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);

    binding.setViewModel(listViewModel);

    list = findViewById(R.id.games_list);

    listViewModel.getList().observeForever(new Observer<List<Game>>() {
        @Override
        public void onChanged(@Nullable List<Game> items) {
            setUpList(items);
        }
    });

    listViewModel.loadGames();
}

private void setUpList(List<Game> items){
    list.setLayoutManager(new LinearLayoutManager(this));
    GameAdapter adapter = new GameAdapter();
    adapter.setList(items);
    list.setAdapter(adapter);
}

и ViewModel он отвечает только за загрузку данных и уведомление о деятельности когда список будет готов, чтобы он мог подготовить адаптер и показать данные:

public int progressVisibility = View.VISIBLE;

private MutableLiveData<List<Game>> list;

public void loadGames(){

    Retrofit retrofit = GamesAPI.create();

    GameService service = retrofit.create(GameService.class);

    Call<GamesResponse> call = service.fetchGames();

    call.enqueue(this);
}


@Override
public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
    if(response.body().response.equals("success")){
        setList(response.body().data);

    }
}

@Override
public void onFailure(Call<GamesResponse> call, Throwable t) {

}

public MutableLiveData<List<Game>> getList() {
    if(list == null)
        list = new MutableLiveData<>();
    if(list.getValue() == null)
        list.setValue(new ArrayList<Game>());
    return list;
}

public void setList(List<Game> list) {
    this.list.postValue(list);
}

мой вопрос: какой правильный способ показать / скрыть список, progressbar и текст ошибки?

должен ли я добавить целое число для каждого представления в ViewModel, чтобы управлять представлениями и использовать его как:

<TextView
    android:id="@+id/main_list_error"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{viewModel.error}"
    android:visibility="@{viewModel.errorVisibility}" />

или ViewModel должен создать экземпляр объекта LiveData для каждого свойства:

private MutableLiveData<Integer> progressVisibility = new MutableLiveData<>();
private MutableLiveData<Integer> listVisibility = new MutableLiveData<>();
    private MutableLiveData<Integer> errorVisibility = new MutableLiveData<>();

обновите их значение при необходимости и сделайте деятельность наблюдать их ценность?

viewModel.getProgressVisibility().observeForever(new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer visibility) {
        progress.setVisibility(visibility);
    }
});

viewModel.getListVisibility().observeForever(new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer visibility) {
        list.setVisibility(visibility);
    }
});

viewModel.getErrorVisibility().observeForever(new Observer<Integer>() {
    @Override
    public void onChanged(@Nullable Integer visibility) {
        error.setVisibility(visibility);
    }
});

Я действительно пытаюсь понять. Если кто-то сможет это прояснить, было бы здорово.

спасибо

2 ответов


вот простые шаги:

public class MainViewModel extends ViewModel {

    MutableLiveData<ArrayList<Game>> gamesLiveData = new MutableLiveData<>();
    // ObservableBoolean or ObservableField are classes from  
    // databinding library (android.databinding.ObservableBoolean)

    public ObservableBoolean progressVisibile = new ObservableBoolean();
    public ObservableBoolean listVisibile = new ObservableBoolean();
    public ObservableBoolean errorVisibile = new ObservableBoolean();
    public ObservableField<String> error = new ObservableField<String>();

    // ...


    // For example we want to change list and progress visibility
    // We should just change ObservableBoolean property
    // databinding knows how to bind view to changed of field

    public void loadGames(){
        GamesAPI.create().create(GameService.class)
            .fetchGames().enqueue(this);

        listVisibile.set(false); 
        progressVisibile.set(true);
    }

    @Override
    public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
        if(response.body().response.equals("success")){
            gamesLiveData.setValue(response.body().data);

            listVisibile.set(true);
            progressVisibile.set(false);
        }
    }

}

а то

<data>
    <import type="android.view.View"/>

    <variable
        name="viewModel"
        type="MainViewModel"/>
</data>

...

<ProgressBar
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:visibility="@{viewModel.progressVisibile ? View.VISIBLE : View.GONE}"/>

<ListView
    android:layout_width="32dp"
    android:layout_height="32dp"
    android:visibility="@{viewModel.listVisibile ? View.VISIBLE : View.GONE}"/>

<TextView
    android:id="@+id/main_list_error"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{viewModel.error}"
    android:visibility="@{viewModel.errorVisibile ? View.VISIBLE : View.GONE}"/>

Также обратите внимание, что это ваш выбор, чтобы сделать вид наблюдения

ObservableBoolean : false / true 
    // or
ObservableInt : View.VISIBLE / View.INVISIBLE / View.GONE

но ObservableBoolean лучше подходит для тестирования ViewModel.

также вы должны наблюдать LiveData, учитывая жизненный цикл:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    listViewModel.getList().observe((LifecycleOwner) this, new Observer<List<Game>>() {
        @Override
        public void onChanged(@Nullable List<Game> items) {
            setUpList(items);
        }
    });
}

вот простые шаги для достижения вашей точки.

первый, у своего ViewModel подвергнуть LiveData "объект", и вы можете начать LiveData С пустым значением.

private MutableLiveData<List<Game>> list = new MutableLiveData<>();

public MutableLiveData<List<Game>> getList() {
    return list;
}

второй, имейте свой взгляд (активность/фрагмент), обратите внимание, что LiveData и изменить пользовательский интерфейс соответственно.

listViewModel = ViewModelProviders.of(this).get(ListViewModel.class);
listViewModel.data.observe(this, new Observer<List<Game>>() {
    @Override
    public void onChanged(@Nullable final List<Game> games) {
        setUpList(games);
    }
});

здесь важно, чтобы вы использовали observe(LifecycleOwner, Observer) вариант, чтобы ваш наблюдатель не получал события после этого LifecycleOwner больше не активный, в основном, это означает, что, когда ваша активность фрагмента больше не активна, вы не будете сливать этот слушатель.

третий, в результате получения данных вам необходимо обновить свой