вторник, 30 июля 2019 г.

Использование DiffUtil в RecyclerView

DiffUtil это класс-утилита который может вычислять разницу между двумя списками и выводить список операций обновления, который конвертирует первый лист во второй. Это может использоваться для вычисления обновлений адаптера RecyclerView.
В большинстве случаев наш список полностью меняется, и мы устанавливаем новый список в адаптер RecyclerView. И мы вызываем дорогостоящий notifyDataSetChanged для обновления адаптера. Класс DiffUtil решает эту проблему.

Давайте рассмотрим пример. Допустим, у нас есть список людей, в который мы добавляем случайные строки данных. Затем мы сортируем список по возрасту и обновляем RecyclerView.

Я собираюсь использовать статические данные для упрощения реализации.

public class DataProvider {

    public static List getOldPersonList(){
        List persons = new ArrayList<>();
        persons.add(new Person(1, 20, "John"));
        persons.add(new Person(2, 12, "Jack"));
        persons.add(new Person(3, 8, "Michael"));
        persons.add(new Person(4, 19, "Rafael"));
        return persons;
    }

    public static List sortByAge(List oldList){
        Collections.sort(oldList, new Comparator() {
            @Override
            public int compare(Person person, Person person2) {
                return person.age - person2.age;
            }
        });
        return oldList;
    }
}

DiffUtil.Callback является абстрактным классом и имеет 4 абстрактных метода и 1 неабстрактный. Давайте их рассмотрим.


Методы DiffUtil.Callback

getOldListSize() : возвращает размер старого списка

getNewListSize() : возвращает размер нового списка

areItemsTheSame(int oldItemPosition, int newItemPosition) : Вызывается DiffUtil чтобы решить являются ли два объекта представлениями одного и того же элемента. Если ваши элементы содержат уникальные идентификаторы, этот метод должен проверить их равенство

areContentsTheSame(int oldItemPosition, int newItemPosition) : Проверяет содержат ли два элемента одинаковые данные. Вы можете изменить это поведение в зависимости от вашего UI. Этот метод вызывается DiffUtil только если areItemsTheSame возвращает true.

getChangePayload(int oldItemPosition, int newItemPosition) : Если areItemTheSame возвращает true и areContentsTheSame возвращает false, DiffUtil вызывает этот метод, чтобы получить информацию об изменениях. В моем примере я не использую изменяемые объекты, но если вы хотите это использовать, вы может посмотреть этот пример.

public class MyDiffCallback extends DiffUtil.Callback {

    List oldPersons;
    List newPersons;

    public MyDiffCallback(List newPersons, List oldPersons) {
        this.newPersons = newPersons;
        this.oldPersons = oldPersons;
    }

    @Override
    public int getOldListSize() {
        return oldPersons.size();
    }

    @Override
    public int getNewListSize() {
        return newPersons.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldPersons.get(oldItemPosition).id == newPersons.get(newItemPosition).id;
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return oldPersons.get(oldItemPosition).equals(newPersons.get(newItemPosition));
    }

    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        // вы можете вернуть определенное поле для измененного элемента
        return super.getChangePayload(oldItemPosition, newItemPosition);
    }
}

Метод обновления данных в моем RecyclerViewAdapter

public void updateList(ArrayList newList) {
    DiffUtil.DiffResult diffResult = 
            DiffUtil.calculateDiff(new MyDiffCallback(this.persons, newList));
    diffResult.dispatchUpdatesTo(this);
}

Это все. Мы вызываем метод dispactUpdatesTo (RecyclerView.Adapter), и адаптер будет уведомлен об изменении.

четверг, 25 июля 2019 г.

Kotlin: Деструктуризация

Если вы Android разработчик, которые переходит с Java на Kotlin, одной из наиболее интересных функций, с которой придется столкнуться, является деструктуризация.

Деструктуризация позволяет разбить объект на несколько переменных. Например, у вас может быть класс Person, который состоит из имени и поля возраста:

data class Person (val name: String, val age: Int)

Деструктуризация экземпляров этого класса позволяет эффективно инициализировать переменные внутренними полями объекта Person, как показано ниже:

(1) val person = Person("Thomas", 40)(2) val (name, age) = person // деструктуризация(3) println("$name is $age years old")



>> Tomas is 40 years old


Деструктуризация здесь происходит во второй строке. За сценой происходит следующее:

val name = person.component1()
val age = person.component2()


Пример с Person это типичный пример, который вы можете найти в интернете. Выглядит красиво, но не совсем понятно как это можно использовать на практике.

Для ответа на этот вопрос посмотрим на функционал типичного приложения, допустим нам необходимо иметь дело с данными о местоположении.
Допустим, нам необходимо обрабатывать коллекцию истории объекта Location. Мы по возможности упростим пример, наш Project Manager просто попросил чтобы мы писали широту и долготу каждой позиции в лог.


Наш первый кусок кода может выглядеть примерно так:

fun processLocations(locations: List) {
    for (location in locations) {
        Log.d("TAG", "Lat: ${location.latitude}, Lon: ${location.longitude}")
    }
}


Ну или что-то вроде этого:
fun processLocations(locations: List) { 
    locations.forEach { 
        location -> Log.d("TAG", "Lat: ${location.latitude}, Lon: ${location.longitude}") 
    }
}

Однако, благодаря деструктуризации и расширений языка Kotlin, мы можем записать это кратко, устранив необходимость явного указания имени объекта местоположения. Давайте взглянем.

Сначала мы создадим методы расширения для существующего класса Android Location, чтобы мы могли использовать деструктуризацию:

operator fun Location.component1() = latitude
operator fun Location.component2() = longitude

Теперь, с этими доработками, мы можем изменить наш пример чтобы использовать деструктуризацию. Вот один из примеров:

fun processLocations(locations: List) {
    for ((lat, lon) in locations) {
        Log.d("TAG", "Lat: $lat, Lon: $lon")
    }
}

Или даже еще более краткий:

fun processLocations(locations: List) {
    locations.forEach { (lat, lon) -> println("Lat: $lat, Lon: $lon") }
}

Надеюсь, что эта короткая статья поможет доказать, насколько полезной и откровенно говоря, насколько крутой может быть деструктуризация в Kotlin.