Skip to content

Commit 069aa39

Browse files
authored
Merge pull request #1410 from CruGlobal/realmAndroidAdapters
copy RealmRecyclerViewAdapter into gto-support-realm
2 parents a099397 + 3d09bff commit 069aa39

4 files changed

Lines changed: 245 additions & 5 deletions

File tree

build.gradle.kts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ allprojects {
2727
}
2828
google()
2929
mavenCentral()
30-
jcenter {
31-
content { includeModule("io.realm", "android-adapters") }
32-
}
3330
}
3431

3532
afterEvaluate {

gradle/libs.versions.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ okta-auth-foundation-bootstrap = { module = "com.okta.kotlin:auth-foundation-boo
100100
picasso = { module = "com.squareup.picasso:picasso", version = "2.8" }
101101
play-core = "com.google.android.play:core:1.10.3"
102102
realm = "io.realm:realm-android-library:10.12.0"
103-
realm-adapters = { module = "io.realm:android-adapters", version = "4.0.0" }
104103
retrofit = { module = "com.squareup.retrofit2:retrofit", version = "2.9.0" }
105104
robolectric = "org.robolectric:robolectric:4.9"
106105
scarlet = { module = "com.tinder.scarlet:scarlet", version.ref = "scarlet" }

gto-support-realm/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ dependencies {
1515
compileOnly(project(":gto-support-recyclerview"))
1616
compileOnly(libs.androidx.databinding.runtime)
1717
compileOnly(libs.androidx.lifecycle.livedata.core)
18-
compileOnly(libs.realm.adapters)
1918
implementation(libs.weakdelegate)
2019
// endregion RecyclerViewAdapter dependencies
2120
}
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
/*
2+
* Copyright 2016 Realm Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// TODO: this is duplicated from https://github.com/realm/realm-android-adapters to workaround the legacy jcenter
18+
// repository being offline.
19+
// If realm-android-adapters is ever published to mavenCentral we can revert to importing this class from there.
20+
// see: https://github.com/realm/realm-android-adapters/pull/162
21+
package io.realm;
22+
23+
import androidx.annotation.NonNull;
24+
import androidx.annotation.Nullable;
25+
import androidx.recyclerview.widget.RecyclerView;
26+
27+
/**
28+
* The RealmBaseRecyclerAdapter class is an abstract utility class for binding RecyclerView UI elements to Realm data.
29+
* <p>
30+
* This adapter will automatically handle any updates to its data and call {@code notifyDataSetChanged()},
31+
* {@code notifyItemInserted()}, {@code notifyItemRemoved()} or {@code notifyItemRangeChanged()} as appropriate.
32+
* <p>
33+
* The RealmAdapter will stop receiving updates if the Realm instance providing the {@link OrderedRealmCollection} is
34+
* closed.
35+
* <p>
36+
* If the adapter contains Realm model classes with a primary key that is either an {@code int} or a {@code long}, call
37+
* {@code setHasStableIds(true)} in the constructor and override {@link #getItemId(int)} as described by the Javadoc in
38+
* that method.
39+
*
40+
* @param <T> type of {@link RealmModel} stored in the adapter.
41+
* @param <S> type of RecyclerView.ViewHolder used in the adapter.
42+
* @see RecyclerView.Adapter#setHasStableIds(boolean)
43+
* @see RecyclerView.Adapter#getItemId(int)
44+
*/
45+
public abstract class RealmRecyclerViewAdapter<T extends RealmModel, S extends RecyclerView.ViewHolder>
46+
extends RecyclerView.Adapter<S> {
47+
48+
private final boolean hasAutoUpdates;
49+
private final boolean updateOnModification;
50+
private final OrderedRealmCollectionChangeListener listener;
51+
@Nullable
52+
private OrderedRealmCollection<T> adapterData;
53+
54+
private OrderedRealmCollectionChangeListener createListener() {
55+
return new OrderedRealmCollectionChangeListener() {
56+
@Override
57+
public void onChange(Object collection, OrderedCollectionChangeSet changeSet) {
58+
if (changeSet.getState() == OrderedCollectionChangeSet.State.INITIAL) {
59+
notifyDataSetChanged();
60+
return;
61+
}
62+
// For deletions, the adapter has to be notified in reverse order.
63+
OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges();
64+
for (int i = deletions.length - 1; i >= 0; i--) {
65+
OrderedCollectionChangeSet.Range range = deletions[i];
66+
notifyItemRangeRemoved(range.startIndex + dataOffset(), range.length);
67+
}
68+
69+
OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges();
70+
for (OrderedCollectionChangeSet.Range range : insertions) {
71+
notifyItemRangeInserted(range.startIndex + dataOffset(), range.length);
72+
}
73+
74+
if (!updateOnModification) {
75+
return;
76+
}
77+
78+
OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges();
79+
for (OrderedCollectionChangeSet.Range range : modifications) {
80+
notifyItemRangeChanged(range.startIndex + dataOffset(), range.length);
81+
}
82+
}
83+
};
84+
}
85+
86+
/**
87+
* Returns the number of header elements before the Realm collection elements. This is needed so
88+
* all indexes reported by the {@link OrderedRealmCollectionChangeListener} can be adjusted
89+
* correctly. Failing to override can cause the wrong elements to animate and lead to runtime
90+
* exceptions.
91+
*
92+
* @return The number of header elements in the RecyclerView before the collection elements. Default is {@code 0}.
93+
*/
94+
public int dataOffset() {
95+
return 0;
96+
}
97+
98+
/**
99+
* This is equivalent to {@code RealmRecyclerViewAdapter(data, autoUpdate, true)}.
100+
*
101+
* @param data collection data to be used by this adapter.
102+
* @param autoUpdate when it is {@code false}, the adapter won't be automatically updated when collection data
103+
* changes.
104+
* @see #RealmRecyclerViewAdapter(OrderedRealmCollection, boolean, boolean)
105+
*/
106+
public RealmRecyclerViewAdapter(@Nullable OrderedRealmCollection<T> data, boolean autoUpdate) {
107+
this(data, autoUpdate, true);
108+
}
109+
110+
/**
111+
* @param data collection data to be used by this adapter.
112+
* @param autoUpdate when it is {@code false}, the adapter won't be automatically updated when collection data
113+
* changes.
114+
* @param updateOnModification when it is {@code true}, this adapter will be updated when deletions, insertions or
115+
* modifications happen to the collection data. When it is {@code false}, only
116+
* deletions and insertions will trigger the updates. This param will be ignored if
117+
* {@code autoUpdate} is {@code false}.
118+
*/
119+
public RealmRecyclerViewAdapter(@Nullable OrderedRealmCollection<T> data, boolean autoUpdate,
120+
boolean updateOnModification) {
121+
if (data != null && !data.isManaged())
122+
throw new IllegalStateException("Only use this adapter with managed RealmCollection, " +
123+
"for un-managed lists you can just use the BaseRecyclerViewAdapter");
124+
this.adapterData = data;
125+
this.hasAutoUpdates = autoUpdate;
126+
this.listener = hasAutoUpdates ? createListener() : null;
127+
this.updateOnModification = updateOnModification;
128+
}
129+
130+
@Override
131+
public void onAttachedToRecyclerView(final RecyclerView recyclerView) {
132+
super.onAttachedToRecyclerView(recyclerView);
133+
if (hasAutoUpdates && isDataValid()) {
134+
//noinspection ConstantConditions
135+
addListener(adapterData);
136+
}
137+
}
138+
139+
@Override
140+
public void onDetachedFromRecyclerView(final RecyclerView recyclerView) {
141+
super.onDetachedFromRecyclerView(recyclerView);
142+
if (hasAutoUpdates && isDataValid()) {
143+
//noinspection ConstantConditions
144+
removeListener(adapterData);
145+
}
146+
}
147+
148+
@Override
149+
public int getItemCount() {
150+
//noinspection ConstantConditions
151+
return isDataValid() ? adapterData.size() : 0;
152+
}
153+
154+
/**
155+
* Returns the item in the underlying data associated with the specified position.
156+
*
157+
* This method will return {@code null} if the Realm instance has been closed or the index
158+
* is outside the range of valid adapter data (which e.g. can happen if {@link #getItemCount()}
159+
* is modified to account for header or footer views.
160+
*
161+
* Also, this method does not take into account any header views. If these are present, modify
162+
* the {@code index} parameter accordingly first.
163+
*
164+
* @param index index of the item in the original collection backing this adapter.
165+
* @return the item at the specified position or {@code null} if the position does not exists or
166+
* the adapter data are no longer valid.
167+
*/
168+
@SuppressWarnings("WeakerAccess")
169+
@Nullable
170+
public T getItem(int index) {
171+
if (index < 0) {
172+
throw new IllegalArgumentException("Only indexes >= 0 are allowed. Input was: " + index);
173+
}
174+
175+
// To avoid exception, return null if there are some extra positions that the
176+
// child adapter is adding in getItemCount (e.g: to display footer view in recycler view)
177+
if (adapterData != null && index >= adapterData.size()) return null;
178+
//noinspection ConstantConditions
179+
return isDataValid() ? adapterData.get(index) : null;
180+
}
181+
182+
/**
183+
* Returns data associated with this adapter.
184+
*
185+
* @return adapter data.
186+
*/
187+
@Nullable
188+
public OrderedRealmCollection<T> getData() {
189+
return adapterData;
190+
}
191+
192+
/**
193+
* Updates the data associated to the Adapter. Useful when the query has been changed.
194+
* If the query does not change you might consider using the automaticUpdate feature.
195+
*
196+
* @param data the new {@link OrderedRealmCollection} to display.
197+
*/
198+
@SuppressWarnings("WeakerAccess")
199+
public void updateData(@Nullable OrderedRealmCollection<T> data) {
200+
if (hasAutoUpdates) {
201+
if (isDataValid()) {
202+
//noinspection ConstantConditions
203+
removeListener(adapterData);
204+
}
205+
if (data != null) {
206+
addListener(data);
207+
}
208+
}
209+
210+
this.adapterData = data;
211+
notifyDataSetChanged();
212+
}
213+
214+
private void addListener(@NonNull OrderedRealmCollection<T> data) {
215+
if (data instanceof RealmResults) {
216+
RealmResults<T> results = (RealmResults<T>) data;
217+
//noinspection unchecked
218+
results.addChangeListener(listener);
219+
} else if (data instanceof RealmList) {
220+
RealmList<T> list = (RealmList<T>) data;
221+
//noinspection unchecked
222+
list.addChangeListener(listener);
223+
} else {
224+
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
225+
}
226+
}
227+
228+
private void removeListener(@NonNull OrderedRealmCollection<T> data) {
229+
if (data instanceof RealmResults) {
230+
RealmResults<T> results = (RealmResults<T>) data;
231+
//noinspection unchecked
232+
results.removeChangeListener(listener);
233+
} else if (data instanceof RealmList) {
234+
RealmList<T> list = (RealmList<T>) data;
235+
//noinspection unchecked
236+
list.removeChangeListener(listener);
237+
} else {
238+
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
239+
}
240+
}
241+
242+
private boolean isDataValid() {
243+
return adapterData != null && adapterData.isValid();
244+
}
245+
}

0 commit comments

Comments
 (0)