/* * Copyright 2016 Realm Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.realm; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import com.mikepenz.fastadapter.IExpandable; import com.mikepenz.fastadapter.IItem; import com.mikepenz.fastadapter.ISubItem; import com.mikepenz.fastadapter.adapters.ItemAdapter; import com.mikepenz.fastadapter.utils.IdDistributor; import java.util.Iterator; import java.util.List; /** * The RealmBaseRecyclerAdapter class is an abstract utility class for binding RecyclerView UI elements to Realm data. * <p> * This adapter will automatically handle any updates to its data and call notifyDataSetChanged() as appropriate. * Currently there is no support for RecyclerView's data callback methods like notifyItemInserted(int), notifyItemRemoved(int), * notifyItemChanged(int) etc. * It means that, there is no possibility to use default data animations. * <p> * The RealmAdapter will stop receiving updates if the Realm instance providing the {@link OrderedRealmCollection} is * closed. * * @param <Item> type of {@link RealmModel} {@link IItem} stored in the adapter. */ public class RealmItemAdapter<Item extends RealmModel & IItem> extends ItemAdapter<Item> { private final boolean hasAutoUpdates; private final RealmChangeListener listener; @Nullable private OrderedRealmCollection<Item> adapterData; private int notified = 0; public RealmItemAdapter(@Nullable OrderedRealmCollection<Item> data, boolean autoUpdate) { this.adapterData = data; this.hasAutoUpdates = autoUpdate; // Right now don't use generics, since we need maintain two different // types of listeners until RealmList is properly supported. // See https://github.com/realm/realm-java/issues/989 this.listener = hasAutoUpdates ? new RealmChangeListener() { @Override public void onChange(Object results) { if (results instanceof List) { List<Item> items = (List<Item>) results; if (isUseIdDistributor()) { IdDistributor.checkIds(items); } mapPossibleTypes(items); if (hasAutoUpdates || notified < 1) { getFastAdapter().notifyAdapterDataSetChanged(); notified++; } } } } : null; } @Override public void onAttachedToRecyclerView(final RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); if (hasAutoUpdates && isDataValid()) { //noinspection ConstantConditions addListener(adapterData); } } @Override public void onDetachedFromRecyclerView(final RecyclerView recyclerView) { super.onDetachedFromRecyclerView(recyclerView); if (hasAutoUpdates && isDataValid()) { //noinspection ConstantConditions removeListener(adapterData); } } @Override public int getAdapterItemCount() { //noinspection ConstantConditions return isDataValid() ? adapterData.size() : 0; } /** * @return the items within this adapter */ @Override public List<Item> getAdapterItems() { //noinspection ConstantConditions return isDataValid() ? adapterData.subList(0, adapterData.size()) : null; } @Override public int getAdapterPosition(Item item) { if (!isDataValid()) { return -1; } //noinspection ConstantConditions Iterator<Item> iter = adapterData.iterator(); int count = 0; while (iter.hasNext()) { if (iter.next().getIdentifier() == item.getIdentifier()) { return count; } count++; } return -1; } /** * Returns the item associated with the specified position. * Can return {@code null} if provided Realm instance by {@link OrderedRealmCollection} is closed. * * @param index index of the item. * @return the item at the specified position, {@code null} if adapter data is not valid. */ @Nullable public Item getAdapterItem(int index) { //noinspection ConstantConditions return isDataValid() ? adapterData.get(index) : null; } /** * Returns data associated with this adapter. * * @return adapter data. */ @Nullable public OrderedRealmCollection<Item> getData() { return adapterData; } /** * Updates the data associated to the Adapter. Useful when the query has been changed. * If the query does not change you might consider using the automaticUpdate feature. * * @param data the new {@link OrderedRealmCollection} to display. */ public void updateData(@Nullable OrderedRealmCollection<Item> data) { if (hasAutoUpdates) { if (adapterData != null) { removeListener(adapterData); } if (data != null) { addListener(data); } } this.adapterData = data; notifyDataSetChanged(); } private void addListener(@NonNull OrderedRealmCollection<Item> data) { if (data instanceof RealmResults) { RealmResults realmResults = (RealmResults) data; //noinspection unchecked realmResults.addChangeListener(listener); } else if (data instanceof RealmList) { RealmList realmList = (RealmList) data; //noinspection unchecked realmList.realm.handlerController.addChangeListenerAsWeakReference(listener); } else { throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass()); } } private void removeListener(@NonNull OrderedRealmCollection<Item> data) { if (data instanceof RealmResults) { RealmResults realmResults = (RealmResults) data; realmResults.removeChangeListener(listener); } else if (data instanceof RealmList) { RealmList realmList = (RealmList) data; //noinspection unchecked realmList.realm.handlerController.removeWeakChangeListener(listener); } else { throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass()); } } private boolean isDataValid() { return adapterData != null && adapterData.isValid(); } @Override public <T extends IItem & IExpandable<T, S>, S extends IItem & ISubItem<Item, T>> T setSubItems(T collapsible, List<S> subItems) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> set(List<Item> items) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> setNewList(List<Item> items) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> add(List<Item> items) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> add(int position, List<Item> items) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> set(int position, Item item) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> move(int fromPosition, int toPosition) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> remove(int position) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> removeRange(int position, int itemCount) { throw new UnsupportedOperationException("this is not supported by the RealmRecyclerViewAdapter"); } @Override public ItemAdapter<Item> clear() { adapterData.clear(); return this; } }