Pull to refresh

Comments 18

Switch имеет ровно те же недостатки в данном случае.
Еще один очевидный метод (и правильный с точки зрения ООП) это дать возможность элементу «обработать» себя самому.
Но это потребует расширения всех классов данных определенным интерфейсом, что может быть не очень удобным.
Рассмотренный метод мне нравится больше.
Но это потребует расширения всех классов данных определенным интерфейсом
Не обязательно. Можно обернуть объект с данными в объект реализующий этот интерфейс. При такой реализации можно избавиться от приведения элементов к нужному типу. Да и конечная реализация получится гибче.

Однако, тут «вылезает» другой минус — необходимо оборачивать каждый объект данных в объект обработчик. По сути, адаптер должен работать не со списком данных, а со списком обработчиков этих данных. Выглядит ужасающе, но для небольших списков будет весьма элегантным решением.
Все правильно, именно такой подход позволяет избавиться от проверок на тип и преобразований типов. И это самый очевидный подход для решения этой задачи.

Но как вы правильно заметили, у этого подхода есть один недостаток. Под каждый адаптер нам потребуется столько классов-оберток, сколько разнотипных ячеек планируется использовать. Плюс под каждый такой адаптер (а точнее для классов-оберток под текущий адаптер) в идеале создается еще и интерфейс.

Плюс только лишь оборачивание не решает всех поставленных вопросов.
Я сторонник «тонких» моделей. Т.е. модель здесь понимается в самом узком смысле — это просто DataObject. И вся его обязанность сводится к тому, чтобы предоставлять нам доступ к данным. Если обязать этот объект заниматься обработкой самого себя, то это уже будет нарушением принципа SRP.

Но такой подход возможен. И даже может позволить избежать проверок на тип и приведений типов.

Спасибо за статью.
Подход выглядит неплохо.
Смутило, что метод


abstract RecyclerView.ViewHolder holder(ViewGroup parent);

создает новый холдер, а из названия это совсем неясно. Лучше


abstract RecyclerView.ViewHolder createHolder(Context ctx);

Еще я бы избавился от неявных зависимостей и вынес весь класс CellType наружу.


А еще вы используете в качестве ViewType id соответствующего лейаута, но тогда теоретически для каждой сборки приложения id будет отличаться. В некоторых случаях это может быть проблемой.

Еще я бы избавился от неявных зависимостей и вынес весь класс CellType наружу.


В боевых условиях именно так и делается :)

Цель статьи — максимально просто и доступно донести идею. А как вы это реализуете под себя — вопрос десятый.
UFO just landed and posted this here
В идеале от класса CellType нужно избавляться. Точнее заменять его на не статичный объект. Не секрет, что enum в Java является статичным объектом, а статика может в определенных ситуациях стать головной болью и причиной падений.

В статье enum использован для упрощения материала и простоты восприятия идеи. С этой же цель явно прописаны все методы в контракте enum и локальные переменные.
Я попробовал развить идею и вот что вышло:
Класс CleanAdapter
public class CleanAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
	private final AdapterTypesMap typesMap = new AdapterTypesMap();

	private List itemList; // TODO

	public CleanAdapter() {
		// Здесь компилятор следит чтоб ViewHolder соответствовал классу данных. Т.е. передача AdVo.class, ProgressViewHolder.class вызовет ошибку компиляции
		typesMap.putItem(R.layout.cell_progress, R.layout.cell_progress, ProgressVo.class, ProgressViewHolder.class);
		typesMap.putItem(R.layout.cell_ad, R.layout.cell_ad, AdVo.class, AdViewHolder.class);
		typesMap.putItem(R.layout.cell_user, R.layout.cell_user, UserVo.class, UserViewHolder.class);
	}

	@Override
	public int getItemCount() {
		return itemList.size();
	}

	@Override
	public int getItemViewType(int position) {
		return typesMap.getItemViewType(itemList.get(position));
	}

	@Override
	public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
		return typesMap.createViewHolder(parent, viewType);
	}

	@Override
	public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
		typesMap.bindViewHolder(holder, itemList.get(position));
	}
}



Класс AdapterTypesMap
public class AdapterTypesMap {
	private final Map<Integer /* view type */, AdapterItemType> viewTypesMap = new HashMap<>();
	private final Map<Class /* adapter list item class */, AdapterItemType> listItemTypesMap = new HashMap<>();

	public <T, V extends ViewHolderBase<T>> void putItem(int viewType, int resourceId, Class<T> dataItemClass, Class<V> viewHolderClass) {
		AdapterItemType adapterItemType = new AdapterItemType(viewType, resourceId, viewHolderClass);
		listItemTypesMap.put(dataItemClass, adapterItemType);
		viewTypesMap.put(viewType, adapterItemType);
	}

	public int getItemViewType(Object obj) {
		return listItemTypesMap.get(obj).getViewType();
	}

	public RecyclerView.ViewHolder createViewHolder(ViewGroup parent, int viewType) {
		return viewTypesMap.get(viewType).createViewHolder(parent);
	}

	public void bindViewHolder(RecyclerView.ViewHolder holder, Object obj) {
		listItemTypesMap.get(obj).bind(holder, obj);
	}
}



Интерфейс IAdapterItemType
public interface IAdapterItemType {
	int getViewType();
	RecyclerView.ViewHolder createViewHolder(ViewGroup parent);
	void bind(RecyclerView.ViewHolder holder, Object item);
}



Реализация AdapterItemType
public class AdapterItemType<T extends ViewHolderBase> implements IAdapterItemType {
	private final Class<T> viewHolderClass;
	private final int viewType;
	private final int resourceId;

	public AdapterItemType(int viewType, int resourceId, Class<T> viewHolderClass) {
		this.viewHolderClass = viewHolderClass;
		this.viewType = viewType;
		this.resourceId = resourceId;
	}

	@Override
	public int getViewType() {
		return viewType;
	}

	@Override
	public ViewHolderBase createViewHolder(ViewGroup parent) {
		try {
			View view = LayoutInflater.from(parent.getContext()).inflate(resourceId, parent, false);
			// здесь не обойтись без рефлексии
			return viewHolderClass.getConstructor(View.class).newInstance(view);
		}
		catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	@Override
	public void bind(RecyclerView.ViewHolder holder, Object item) {
		ViewHolderBase holder2 = (ViewHolderBase) holder;
		holder2.bind(item);
	}
}



Базовый клас ViewHolderBase
public abstract class ViewHolderBase<T> extends RecyclerView.ViewHolder {
	public ViewHolderBase(View itemView) {
		super(itemView);
	}

	public abstract void bind(T item);
}



И по одному наследнику классу ViewHolderBase для соответствующих классов данных. Типа такого:
public class UserViewHolder extends ViewHolderBase<UserVo> {
	public UserViewHolder(View itemView) {
		super(itemView);
	}

	@Override
	public void bind(UserVo item) {
		// TODO
	}
}


Там есть некоторые ошибки, но я думаю не составит труда их исправить тому кто надумает использовать :)
О — даже один минус есть. Т.е. кто-то предпочел-бы чтоб этого комментария здесь не было.
Хорошо если этот кто-то хоть что-то сделал для развития общества.

Про Adapter Delegates автор не слышал? По-моему, второй вариант смотрится тоже не особо красиво.

По-моему, второй вариант смотрится тоже не особо красиво


Ребят, ну надо понимать что в статье используется максимально упрощенная модель. Все описано в одном классе, используется enum и т.д. Это сделано умышленно для упрощения восприятия идеи. Как вы реализуете эту идею — уже другой вопрос.

Вот как выглядит моя боевая реализация:

Вариант боевой реализации идеи
public class UsersArbitraryCellAdapter extends ArbitraryCellAdapter {

    public UsersArbitraryCellAdapter() {
        this.arbitraryCellSelector.addCell(new ProgressArbitraryCell());
        this.arbitraryCellSelector.addCell(new AdArbitraryCell());
        this.arbitraryCellSelector.addCell(new UserArbitraryCell());
    }

    public void setUsers(List<UserVo> userList) {
		// Set users, ads, progress...
    }
}

public abstract class ArbitraryCellAdapter
		extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    protected ArbitraryCellSelector arbitraryCellSelector = new ArbitraryCellSelector();
    protected List itemList = new ArrayList();

    @Override
    public final int getItemViewType(int position) {
        return arbitraryCellSelector.getCell(itemList.get(position)).type();
    }

    @Override
    public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return arbitraryCellSelector.getCell(viewType).holder(parent);
    }

    @Override
    public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        Object item = itemList.get(position);
        arbitraryCellSelector.getCell(item).bind(holder, item);
    }

    @Override
    public final int getItemCount() {
        return itemList.size();
    }
}

public abstract class ArbitraryCellHolder<T> extends RecyclerView.ViewHolder {

    public ArbitraryCellHolder(View itemView) {
        super(itemView);
        ButterKnife.bind(this, itemView);
    }

    public abstract void bind(T item);
}

public final class ArbitraryCellSelector {
    private List<Cell> cellList = new ArrayList<>();

    public void addCell(Cell cell) {
        cellList.add(cell);
    }

    public void removeCell(Cell cell) {
        cellList.remove(cell);
    }

    public Cell getCell(Object item) {
        for (Cell cell : cellList) {
            if (cell.is(item)) {
                return cell;
            }
        }
        throw new NoSuchRecyclerRowException();
    }

    public Cell getCell(int viewType) {
        for (Cell cell : cellList) {
            if (cell.type() == viewType) {
                return cell;
            }
        }
        throw new NoSuchRecyclerRowException();
    }

    public interface Cell {

        boolean is(Object item);

        int type();

        RecyclerView.ViewHolder holder(ViewGroup parent);

        void bind(RecyclerView.ViewHolder holder, Object item);
    }
}

public class AdArbitraryCell implements ArbitraryCellSelector.Cell {

    @Override
    public boolean is(Object item) {
        return item instanceof AdVo;
    }

    @Override
    public int type() {
        return R.layout.cell_ad;
    }

    @Override
    public RecyclerView.ViewHolder holder(ViewGroup parent) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view = inflater.inflate(R.layout.cell_ad, parent, false);
        return new AdViewHolder(view);
    }

    @Override
    public void bind(RecyclerView.ViewHolder holder, Object item) {
        try {
            AdViewHolder adViewHolder = (AdViewHolder) holder;
            AdVo ad = (AdVo) item;
            adViewHolder.bind(ad);
        } catch (ClassCastException e) {
            L.printStackTrace(e);
        }
    }

    protected class AdViewHolder extends ArbitraryCellHolder<AdVo> {
        @BindView(R.id.ad_text_view)
        protected TextView adTextView;

        public AdViewHolder(View itemView) {
            super(itemView);
        }

        @Override
        public void bind(AdVo item) {
            adTextView.setText(item.getTitle());

            itemView.setOnClickListener(view -> adPublishSubject.onNext(item));
        }
    }
}

// Other ArbitraryCells...



Про Adapter Delegates автор не слышал?


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

Решение изящное, признаю. Что использовать у себя в проекте — импортировать библиотеку или написать один вспомогательный класс ArbitraryCellSelector — дело личного каждого.
Sign up to leave a comment.

Articles