【问题标题】:Android Architecture Components: bind to ViewModelAndroid 架构组件:绑定到 ViewModel
【发布时间】:2018-05-12 08:19:05
【问题描述】:

我对在使用新的架构组件时数据绑定应该如何工作感到有些困惑。

假设我有一个带有列表、ProgressBar 和 TextView 的简单 Activity。 Activity 应该负责控制所有视图的状态,而 ViewModel 应该保存数据和逻辑。 例如,我的 Activity 现在看起来像这样:

@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 只负责加载数据并在列表准备好时通知 Activity,以便它可以准备 Adapter 并显示数据:

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);
}

我的问题是:显示/隐藏列表、进度条和错误文本的正确方法是什么?

我是否应该为 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<>();

在需要时更新它们的值并让 Activity 观察它们的值?

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);
    }
});

我真的很难理解这一点。如果有人能澄清这一点,那就太好了。

谢谢

【问题讨论】:

  • 好问题……我也有同样的困惑……

标签: android android-activity mvvm viewmodel android-livedata


【解决方案1】:

以下是实现您的观点的简单步骤。

首先,让您的ViewModel 公开一个LiveData 对象,然后您可以使用空值启动LiveData

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

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

其次,让您的视图(活动/片段)观察LiveData 并相应地更改 UI。

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 不再活动之后收到事件,基本上,这意味着当你的片段活动不再活动时,你赢了不要泄露那个监听器。

第三,由于数据可用,您需要更新您的LiveData 对象。

@Override
public void onResponse(Call<GamesResponse> call, Response<GamesResponse> response) {
    if(response.body().response.equals("success")){
        List<Game> newGames = response.body().data; // Assuming this is a list
        list.setValue(newGames); // Update your LiveData object by calling setValue
    }
}

通过在您的LiveData 上调用setValue(),这将导致您视图的侦听器上的onChanged 被调用并且您的UI 应该会自动更新。

【讨论】:

  • 不使用数据绑定的BU,我错了吗?实际上,问题是:如何将数据绑定与架构组件一起使用?
  • 如果你想使用数据绑定,也许你可以使用 ObservableFeild 而不是 LiveData,但我看不出将它们组合起来有什么价值,因为它们基本上做同样的事情。我猜 LiveData 具有生命周期感知的优势
【解决方案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 只是描述逻辑,而 View 观察所有需要的。一旦 View 不存在,就不会出现 NPE。我理解你的问题正确吗?哦,我写错代码了,它不应该包含setList()调用,而只是在ViewModel中更新LiveData,并且activity观察实时数据并调用setList方法
  • 或多或少是的。仍然必须尝试更好地理解一切:)
  • onResponse 从何而来?如果您能详细说明此功能,那就太好了。
  • @David 这个方法是提问者方法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-17
  • 2021-01-06
  • 2017-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多