Android Architecture Components: Binding to ViewModel

I'm a little confused about how data binding should work when using new architecture components.

let's say I have a simple activity with a list, ProgressBar and TextView. the action should be responsible for managing the state of all views, but the ViewModel must contain data and logic. For example, my activity now looks like this:

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

and ViewModel it is only responsible for loading the data and notifies Activity when the list is ready so that it can prepare the adapter and show the data:

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

My question is: what is the correct way to show / hide the list, progress bar and error text?

you should add an Integer for each view in the ViewModel so that it manages the views and uses it like:

 <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}" /> 

or if the ViewModel instantiates a LiveData object for each property:

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

update their value when necessary, and force activity to observe their value?

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

I'm really trying to figure it out. If someone can clarify this, that would be great.

thanks

+5
source share
2 answers

Here are the simple steps:

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

And then

 <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}"/> 

Also note that it is your choice to watch the viewing.

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

but ObservableBoolean is better for testing ViewModel.

You should also observe LiveData, taking into account the life cycle:

 @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); } }); } 
+2
source

Here are simple steps to reach your point.

First , you have the ViewModel set the LiveData object, and you can start LiveData with an empty value.

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

Second , so that your view (activity / fragment) LiveData that LiveData and change the interface accordingly.

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

It’s important here to use the observe(LifecycleOwner, Observer) option observe(LifecycleOwner, Observer) so that your observer will NOT accept events after this LifecycleOwner no longer active, basically this means that when your fragment activity is no longer active, you won this listener's leak.

Third , as a result of receiving the data, you need to update the LiveData object.

 @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 } } 

By calling setValue() on LiveData , this will call the onChanged call in your viewer, and your user interface should be updated automatically.

0
source

Source: https://habr.com/ru/post/1273725/


All Articles