I'm a bit confused about how data binding should work when using the new Architecture Components.
let's say I have a simple Activity with a list, a ProgressBar and a TextView. the Activity should be responsible for controlling the state of all the views, but the ViewModel should hold the data and the 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 the ViewModel it's only responsible for loading the data and notify the Activity when the list is ready so 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: which is the correct way to show/hide the list, progressbar and error text?
should I add an Integer for each View in the ViewModel making it control the views and using 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 should the ViewModel instantiate 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 needed and make the Activity 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 struggling to understand that. If someone can clarify that, it would be great.
Thanks
There are four components, each with a specific role: Room , ViewModel , LiveData , and Lifecycle . All of those parts have their own responsibilities, and they work together to create a solid architecture.
ViewModel overview Part of Android Jetpack. Stay organized with collections Save and categorize content based on your preferences. The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
Using ViewModel components with the Data Binding Library allows you to move UI logic out of the layouts and into the components, which are easier to test. The Data Binding Library ensures that the views are bound and unbound from the data source when needed.
In android, we can use ViewModel to share data between various fragments or activities by sharing the same ViewModel among all the fragments and they can access everything defined in the ViewModel. This is one way to have communication between fragments or activities.
Here are 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 notice that it's your choice to make view observe
ObservableBoolean : false / true
// or
ObservableInt : View.VISIBLE / View.INVISIBLE / View.GONE
but ObservableBoolean is better for ViewModel testing.
Also you should observe LiveData considering lifecycle:
@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);
}
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With