首页 文章

Android架构组件:绑定到ViewModel

提问于
浏览
7

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

假设我有一个带有列表,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,以便它可以准备适配器并显示数据:

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中的每个View添加一个Integer,使其控制视图并使用它,如:

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

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

谢谢

2 回答

  • 5

    这是一个简单的步骤:

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

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

    First ,让你的 ViewModel 公开一个 LiveData 对象,你可以用空值启动 LiveData .

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

    Second ,让您的视图(活动/片段)观察 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 不再活动之后接收事件,基本上,这意味着当你的片段活动不再处于活动状态时,你就不会泄漏那个监听器 .

    Third ,由于数据可用,您需要更新 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应该自动更新 .

相关问题