// Copyright 2004-present Facebook. All Rights Reserved. package com.facebook.litho.widget; import java.util.ArrayList; import java.util.List; import android.support.v4.util.Pools.SynchronizedPool; import android.support.v7.util.ListUpdateCallback; import com.facebook.litho.Component; import com.facebook.litho.ComponentInfo; /** * An implementation of {@link ListUpdateCallback} that generates the relevant {@link Component}s * when an item is inserted/updated. * * The user of this API is expected to provide a ComponentRenderer implementation to build a * Component from a generic model object. * */ public class RecyclerBinderUpdateCallback<T> implements ListUpdateCallback { private static final SynchronizedPool<RecyclerBinderUpdateCallback> sUpdatesCallbackPool = new SynchronizedPool<>(4); public interface ComponentRenderer<T> { ComponentInfo render(T t, int idx); } public interface OperationExecutor { void executeOperations(List<Operation> operations); } private List<T> mData; private List<Operation> mOperations; private List<ComponentContainer> mPlaceholders; private ComponentRenderer mComponentRenderer; private OperationExecutor mOperationExecutor; public static<T> RecyclerBinderUpdateCallback<T> acquire( int oldDataSize, List<T> data, ComponentRenderer<T> componentRenderer, RecyclerBinder recyclerBinder) { return acquire( oldDataSize, data, componentRenderer, new RecyclerBinderOperationExecutor(recyclerBinder)); } public static<T> RecyclerBinderUpdateCallback<T> acquire( int oldDataSize, List<T> data, ComponentRenderer<T> componentRenderer, OperationExecutor operationExecutor) { RecyclerBinderUpdateCallback instance = sUpdatesCallbackPool.acquire(); if (instance == null) { instance = new RecyclerBinderUpdateCallback(); } instance.init(oldDataSize, data, componentRenderer, operationExecutor); return instance; } public static<T> void release(RecyclerBinderUpdateCallback<T> updatesCallback) { final List<Operation> operations = updatesCallback.mOperations; for (int i = 0, size = operations.size(); i < size; i++) { operations.get(i).release(); } updatesCallback.mOperations = null; updatesCallback.mData = null; for (int i = 0, size = updatesCallback.mPlaceholders.size(); i < size; i++) { updatesCallback.mPlaceholders.get(i).release(); } updatesCallback.mComponentRenderer = null; updatesCallback.mOperationExecutor = null; sUpdatesCallbackPool.release(updatesCallback); } private RecyclerBinderUpdateCallback() { } private void init( int oldDataSize, List<T> data, ComponentRenderer<T> componentRenderer, OperationExecutor operationExecutor) { mData = data; mComponentRenderer = componentRenderer; mOperationExecutor = operationExecutor; mOperations = new ArrayList<>(); mPlaceholders = new ArrayList<>(); for (int i = 0; i < oldDataSize; i++) { mPlaceholders.add(ComponentContainer.acquire()); } } @Override public void onInserted(int position, int count) { final List<ComponentContainer> placeholders = new ArrayList<>(); for (int i = 0; i < count; i++) { final int index = position + i; final ComponentContainer componentContainer = ComponentContainer.acquire(); componentContainer.mNeedsComputation = true; mPlaceholders.add(index, componentContainer); placeholders.add(componentContainer); } mOperations.add(Operation.acquire(Operation.INSERT, position, -1, placeholders)); } @Override public void onRemoved(int position, int count) { for (int i = 0; i < count; i++) { final ComponentContainer componentContainer = mPlaceholders.remove(position); componentContainer.release(); } mOperations.add(Operation.acquire(Operation.DELETE, position, count, null)); } @Override public void onMoved(int fromPosition, int toPosition) { mOperations.add(Operation.acquire(Operation.MOVE, fromPosition, toPosition, null)); ComponentContainer placeholder = mPlaceholders.remove(fromPosition); mPlaceholders.add(toPosition, placeholder); } @Override public void onChanged(int position, int count, Object payload) { final List<ComponentContainer> placeholders = new ArrayList<>(); for (int i = 0; i < count; i++) { final int index = position + i; final ComponentContainer placeholder = mPlaceholders.get(index); placeholder.mNeedsComputation = true; placeholders.add(placeholder); } mOperations.add(Operation.acquire(Operation.UPDATE, position, -1, placeholders)); } public void applyChangeset() { for (int i = 0, size = mPlaceholders.size(); i < size; i++) { if (mPlaceholders.get(i).mNeedsComputation) { mPlaceholders.get(i).mComponentInfo = mComponentRenderer.render(mData.get(i), i); } } mOperationExecutor.executeOperations(mOperations); } public static class Operation { private static final SynchronizedPool<Operation> sOperationsPool = new SynchronizedPool<>(8); public static final int INSERT = 0; public static final int UPDATE = 1; public static final int DELETE = 2; public static final int MOVE = 3; private int mType; private int mIndex; private int mToIndex; private List<ComponentContainer> mComponentContainers; private Operation() { } private void init( int type, int index, int toIndex, List<ComponentContainer> placeholder) { mType = type; mIndex = index; mToIndex = toIndex; mComponentContainers = placeholder; } private static Operation acquire( int type, int index, int toIndex, List<ComponentContainer> placeholder) { Operation operation = sOperationsPool.acquire(); if (operation == null) { operation = new Operation(); } operation.init(type, index, toIndex, placeholder); return operation; } private void release() { if (mComponentContainers != null) { mComponentContainers.clear(); mComponentContainers = null; } sOperationsPool.release(this); } public int getType() { return mType; } public int getIndex() { return mIndex; } public int getToIndex() { return mToIndex; } public List<ComponentContainer> getComponentContainers() { return mComponentContainers; } } public static class ComponentContainer { private static final SynchronizedPool<ComponentContainer> sComponentContainerPool = new SynchronizedPool<>(8); private ComponentInfo mComponentInfo; private boolean mNeedsComputation = false; public static ComponentContainer acquire() { ComponentContainer componentContainer = sComponentContainerPool.acquire(); if (componentContainer == null) { componentContainer = new ComponentContainer(); } return componentContainer; } public void release() { mComponentInfo = null; mNeedsComputation = false; sComponentContainerPool.release(this); } public ComponentInfo getComponentInfo() { return mComponentInfo; } } }