ListAdapter renewed

ListAdapter renewed

Every Android developer has worked with the RecyclerView when a list of content needs to be shown. Before the RecyclerView Android offered the widget ListAdapter, with bad performance and several other issues.

Since the Android support lib version 27.1.0 Google added a new ListAdapter which extends RecyclerView.Adapter to make life even easier. Yes it has the name ListAdapter again :(, but for the rest it is completely different!


The simplified ListAdapter

To create a custom ListAdapter override the methods onCreateViewHolder() and onBindViewHolder(). All other methods are already handled by the ListAdapter and don't need any attention anymore.
  • onCreateViewHolder():
    Called when RecyclerView needs a new RecyclerView.ViewHolder of the given type to represent an item.
  • onBindViewHolder():
    Called by RecyclerView to display the data at the specified position.
The ListAdapter constructor requests a DiffUtil.ItemCallback, keep reading to found out what it does.

package com.test.myapplication.recyclerview;
public class CarAdapter extends ListAdapter<Car, CarViewHolder> {
public CarAdapter() {
super(Car.DIFF_CALLBACK);
}
@NonNull
@Override
public CarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.bool_list_row, parent, false);
return new CarViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CarViewHolder holder, int position) {
Car car = getItem(position);
if (car != null) {
holder.bindTo(car);
}
}
}
view raw CarAdapter.java hosted with ❤ by GitHub

How about DiffUtil.ItemCallback?

When working with a RecyclerView the adapter is responsible to validate if items where added/changed/removed. Previously all actions needed to be controlled manual, these days are over. This task has been taken over by the AsyncListDiffer, a helper for computing differences between two lists with on DiffUtil. Another advantage is that the AsyncListDiffer runs by default on a background thread.

Simply implement a DiffUtil.ItemCallback and provide it as parameter with the constructor as in below example.

The DiffUtil.ItemCallback contains two important functions which need to be implemented:
  • areItemsTheSame():
    Called to check whether two items have the same data.
  • areContentsTheSame():
    Called to check whether two objects represent the same item.
package com.test.myapplication.recyclerview;
public class Car {
private String name;
private String manufacturer;
public Car(String name, String author) {
this.name = name;
this.manufacturer = author;
}
static DiffUtil.ItemCallback<Car> DIFF_CALLBACK = new DiffUtil.ItemCallback<Car>() {
@Override
public boolean areItemsTheSame(@NonNull Car oldItem, @NonNull Car newItem) {
return oldItem.name.equals(newItem.name);
}
@Override
public boolean areContentsTheSame(@NonNull Car oldItem, @NonNull Car newItem) {
return oldItem.equals(newItem);
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Objects.equals(name, car.name) &&
Objects.equals(manufacturer, car.manufacturer);
}
@Override
public int hashCode() {
return Objects.hash(name, manufacturer);
}
}
view raw Car.java hosted with ❤ by GitHub
package com.test.myapplication.recyclerview;
public class CarAdapter extends ListAdapter<Car, CarViewHolder> {
public CarAdapter() {
super(Car.DIFF_CALLBACK);
}
}
view raw CarAdapter.java hosted with ❤ by GitHub

Let's go to the total image

You have learned about the ListAdapter and DiffUtil.ItemCallback. Now it's time to use them to populate a RecyclerView.

This example populates a list of cars. Every two seconds one list item is modified to show the animations.

  • activity_main.xml: Main view used within MainActivity.java
  • car_list_row.xml: RecyclerView item used within CarAdapter.java
  • Car.java: The Car object
  • CarAdapter.java: CarAdapter extends the ListAdapter
  • Download.javaData provider, this can Room, Http request or another dataprovider
  • MainActivity.javaThe MainActivity connects all items together and shows all cars
When you want to build the project locally, download both layout files and copy the java classes from below.
package com.test.myapplication.recyclerview;
public class Car {
public String name;
public String manufacturer;
public Car(String name, String author) {
this.name = name;
this.manufacturer = author;
}
static DiffUtil.ItemCallback<Car> DIFF_CALLBACK = new DiffUtil.ItemCallback<Car>() {
@Override
public boolean areItemsTheSame(@NonNull Car oldItem, @NonNull Car newItem) {
return oldItem.name.equals(newItem.name);
}
@Override
public boolean areContentsTheSame(@NonNull Car oldItem, @NonNull Car newItem) {
return oldItem.equals(newItem);
}
};
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Objects.equals(name, car.name) &&
Objects.equals(manufacturer, car.manufacturer);
}
@Override
public int hashCode() {
return Objects.hash(name, manufacturer);
}
}
view raw Car.java hosted with ❤ by GitHub
package com.test.myapplication.recyclerview;
public class CarAdapter extends ListAdapter<Car, CarViewHolder> {
public CarAdapter() {
super(Car.DIFF_CALLBACK);
}
@NonNull
@Override
public CarViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.car_list_row, parent, false);
return new CarViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CarViewHolder holder, int position) {
Car car = getItem(position);
if (car != null) {
holder.bindTo(car);
}
}
static class CarViewHolder extends RecyclerView.ViewHolder {
private TextView name;
private TextView manufacturer;
CarViewHolder(View view) {
super(view);
name = view.findViewById(R.id.name);
manufacturer = view.findViewById(R.id.manufacturer);
}
void bindTo(Car car) {
name.setText(car.name);
manufacturer.setText(car.manufacturer);
}
}
}
view raw CarAdapter.java hosted with ❤ by GitHub
package com.test.myapplication.data;
public class Download {
public List<Car> getDefaultList() {
List<Car> cars = new ArrayList<>();
cars.add(new Car("V40", "Unknown"));
cars.add(new Car("Cayenne coupe", "Porsche"));
cars.add(new Car("Model 3", "Tesla"));
cars.add(new Car("SL 55 AMG ", "Mercedes"));
cars.add(new Car("911 GT3", "Porsche"));
cars.add(new Car("AMG GT C Roadster", "Mercedes"));
cars.add(new Car("DB11 ", "Aston Martin"));
return cars;
}
public List<Car> getListChangedCarItem() {
List<Car> carList = getDefaultList();
carList.get(0).manufacturer = "Volvo";
return carList;
}
public List<Car> getListAddCarItem() {
List<Car> carList = getListChangedCarItem();
carList.add(2, new Car("TT Coupe", "Audi"));
return carList;
}
public List<Car> getListRemoveCarItem() {
List<Car> carList = getListAddCarItem();
carList.remove(1);
return carList;
}
}
view raw Download.java hosted with ❤ by GitHub
package com.test.myapplication;
public class MainActivity extends AppCompatActivity {
private CarAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
setRecyclerView();
}
private void setRecyclerView() {
RecyclerView recyclerView = findViewById(R.id.list);
mAdapter = new CarAdapter();
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.addItemDecoration(new DividerItemDecoration(this,LinearLayoutManager.VERTICAL));
recyclerView.setAdapter(mAdapter);
Download download = new Download();
mAdapter.submitList(download.getDefaultList());
Disposable d = Observable.just(true)
.delay(2000, TimeUnit.MILLISECONDS)
.doOnNext(aBoolean -> mAdapter.submitList(download.getListChangedCarItem()))
.delay(2000, TimeUnit.MILLISECONDS)
.doOnNext(aBoolean -> mAdapter.submitList(download.getListAddCarItem()))
.delay(2000, TimeUnit.MILLISECONDS)
.doOnNext(aBoolean -> mAdapter.submitList(download.getListRemoveCarItem()))
.subscribe();
}
}
With all parts in place the app can run and will show the RecyclerView using the ListAdapter.



Good to know is that the ListAdapter keeps a reference towards the provided list in submitList(). Always make sure to provide an other list with other item instances. If the references to the items are the same, DiffUtil compares the same items which are always equal, and the RecyclerView will not be updated.

Within the MainAcitivty RxJava is used to re-populate the list with modifications. With the ListAdapter Google created another simplified tool for developers to provide the users the best Android experience with the least of costs.
With this road to App Bundle you've learned to create and use the App Bundle.

Thank you for reading this article, If you liked the article, help others find this article by sharing it.

Reacties

Populaire posts van deze blog

Android-x86 virtual machine configuration tips

Android and SonarQube with code coverage

Road to App Bundle and Bundletool