Skip to content

Commit

Permalink
Fine grained notification for RecyclerViewAdapter (#83)
Browse files Browse the repository at this point in the history
* Fine grained notification for RecyclerViewAdapter.
* Improved example
* Removed deprecated methods and fields.
  • Loading branch information
beeender authored and cmelchior committed Feb 28, 2017
1 parent 51a67e9 commit 2f1c060
Show file tree
Hide file tree
Showing 18 changed files with 238 additions and 451 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
## 2.0.0 (YYYY-MM-DD)

Works with Realm Java 3.x

### Breaking changes

* Removed `RealmBaseAdapter(@Nonnull Context context, @Nullable OrderedRealmCollection<T> data)`.
* Removed `RealmRecyclerViewAdapter(@NonNull Context context, @Nullable OrderedRealmCollection<T> data, boolean autoUpdate)`.
* Removed `RealmBaseAdapter.inflater`.
* Removed `RealmBaseAdapter.context`.
* Removed `RealmRecyclerViewAdapter.inflater`.
* Removed `RealmRecyclerViewAdapter.contex`.

### Enhancements

* Added fine grained notification support to `RealmRecyclerViewAdapter`.

## 1.5.0

### Deprecated
Expand Down
58 changes: 15 additions & 43 deletions adapters/src/main/java/io/realm/RealmBaseAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@

package io.realm;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.widget.BaseAdapter;

/**
Expand All @@ -33,48 +31,18 @@
* closed. Trying to access Realm objects will at this point also result in a {@code IllegalStateException}.
*/
public abstract class RealmBaseAdapter<T extends RealmModel> extends BaseAdapter {
@Nullable
@Deprecated
protected LayoutInflater inflater;
@Nullable
protected OrderedRealmCollection<T> adapterData;
@Nullable
@Deprecated
protected Context context;
private final RealmChangeListener<? extends BaseRealm> listener;

@Deprecated
public RealmBaseAdapter(@NonNull Context context, @Nullable OrderedRealmCollection<T> data) {
if (data != null && !data.isManaged())
throw new IllegalStateException("Only use this adapter with managed list, " +
"for un-managed lists you can just use the BaseAdapter");
//noinspection ConstantConditions
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
this.context = context;
this.adapterData = data;
this.inflater = LayoutInflater.from(context);
this.listener = new RealmChangeListener<BaseRealm>() {
@Override
public void onChange(BaseRealm results) {
notifyDataSetChanged();
}
};

if (data != null) {
addListener(data);
}
}
private final RealmChangeListener<OrderedRealmCollection<T>> listener;

public RealmBaseAdapter(@Nullable OrderedRealmCollection<T> data) {
if (data != null && !data.isManaged())
throw new IllegalStateException("Only use this adapter with managed list, " +
"for un-managed lists you can just use the BaseAdapter");
this.adapterData = data;
this.listener = new RealmChangeListener<BaseRealm>() {
this.listener = new RealmChangeListener<OrderedRealmCollection<T>>() {
@Override
public void onChange(BaseRealm results) {
public void onChange(OrderedRealmCollection<T> results) {
notifyDataSetChanged();
}
};
Expand All @@ -86,23 +54,27 @@ public void onChange(BaseRealm results) {

private void addListener(@NonNull OrderedRealmCollection<T> data) {
if (data instanceof RealmResults) {
RealmResults realmResults = (RealmResults) data;
realmResults.realm.handlerController.addChangeListenerAsWeakReference(listener);
RealmResults<T> results = (RealmResults<T>) data;
//noinspection unchecked
results.addChangeListener((RealmChangeListener) listener);
} else if (data instanceof RealmList) {
RealmList realmList = (RealmList) data;
realmList.realm.handlerController.addChangeListenerAsWeakReference(listener);
RealmList<T> list = (RealmList<T>) data;
//noinspection unchecked
list.addChangeListener((RealmChangeListener) listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
}

private void removeListener(@NonNull OrderedRealmCollection<T> data) {
if (data instanceof RealmResults) {
RealmResults realmResults = (RealmResults) data;
realmResults.realm.handlerController.removeWeakChangeListener(listener);
RealmResults<T> results = (RealmResults<T>) data;
//noinspection unchecked
results.removeChangeListener((RealmChangeListener) listener);
} else if (data instanceof RealmList) {
RealmList realmList = (RealmList) data;
realmList.realm.handlerController.removeWeakChangeListener(listener);
RealmList<T> list = (RealmList<T>) data;
//noinspection unchecked
list.removeChangeListener((RealmChangeListener) listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
Expand Down
96 changes: 41 additions & 55 deletions adapters/src/main/java/io/realm/RealmRecyclerViewAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,63 +16,57 @@

package io.realm;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;

/**
* 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.
* This adapter will automatically handle any updates to its data and call {@code notifyDataSetChanged()},
* {@code notifyItemInserted()}, {@code notifyItemRemoved()} or {@code notifyItemRangeChanged(} as appropriate.
* <p>
* The RealmAdapter will stop receiving updates if the Realm instance providing the {@link OrderedRealmCollection} is
* closed.
*
* @param <T> type of {@link RealmModel} stored in the adapter.
* @param <VH> type of RecyclerView.ViewHolder used in the adapter.
* @param <S> type of RecyclerView.ViewHolder used in the adapter.
*/
public abstract class RealmRecyclerViewAdapter<T extends RealmModel, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
public abstract class RealmRecyclerViewAdapter<T extends RealmModel, S extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<S> {

@Nullable
@Deprecated
protected LayoutInflater inflater;
@Nullable
@Deprecated
protected Context context;
private final boolean hasAutoUpdates;
private final RealmChangeListener listener;
private final OrderedRealmCollectionChangeListener listener;
@Nullable
private OrderedRealmCollection<T> adapterData;

@Deprecated
public RealmRecyclerViewAdapter(@NonNull Context context, @Nullable OrderedRealmCollection<T> data, boolean autoUpdate) {
if (data != null && !data.isManaged())
throw new IllegalStateException("Only use this adapter with managed list, " +
"for un-managed lists you can just use the BaseAdapter");
//noinspection ConstantConditions
if (context == null) {
throw new IllegalArgumentException("Context can not be null");
}

this.context = context;
this.adapterData = data;
this.inflater = LayoutInflater.from(context);
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() {
private OrderedRealmCollectionChangeListener createListener() {
return new OrderedRealmCollectionChangeListener() {
@Override
public void onChange(Object results) {
notifyDataSetChanged();
public void onChange(Object collection, OrderedCollectionChangeSet changeSet) {
// null Changes means the async query returns the first time.
if (changeSet == null) {
notifyDataSetChanged();
return;
}
// For deletions, the adapter has to be notified in reverse order.
OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges();
for (int i = deletions.length - 1; i >= 0; i--) {
OrderedCollectionChangeSet.Range range = deletions[i];
notifyItemRangeRemoved(range.startIndex, range.length);
}

OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges();
for (OrderedCollectionChangeSet.Range range : insertions) {
notifyItemRangeInserted(range.startIndex, range.length);
}

OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges();
for (OrderedCollectionChangeSet.Range range : modifications) {
notifyItemRangeChanged(range.startIndex, range.length);
}
}
} : null;
};
}

public RealmRecyclerViewAdapter(@Nullable OrderedRealmCollection<T> data, boolean autoUpdate) {
Expand All @@ -81,16 +75,7 @@ public RealmRecyclerViewAdapter(@Nullable OrderedRealmCollection<T> data, boolea
"for un-managed lists you can just use the BaseRecyclerViewAdapter");
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) {
notifyDataSetChanged();
}
} : null;
this.listener = hasAutoUpdates ? createListener() : null;
}

@Override
Expand Down Expand Up @@ -177,26 +162,27 @@ public void updateData(@Nullable OrderedRealmCollection<T> data) {

private void addListener(@NonNull OrderedRealmCollection<T> data) {
if (data instanceof RealmResults) {
RealmResults realmResults = (RealmResults) data;
RealmResults<T> results = (RealmResults<T>) data;
//noinspection unchecked
realmResults.addChangeListener(listener);
results.addChangeListener(listener);
} else if (data instanceof RealmList) {
RealmList realmList = (RealmList) data;
RealmList<T> list = (RealmList<T>) data;
//noinspection unchecked
realmList.realm.handlerController.addChangeListenerAsWeakReference(listener);
list.addChangeListener(listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
}

private void removeListener(@NonNull OrderedRealmCollection<T> data) {
if (data instanceof RealmResults) {
RealmResults realmResults = (RealmResults) data;
realmResults.removeChangeListener(listener);
RealmResults<T> results = (RealmResults<T>) data;
//noinspection unchecked
results.removeChangeListener(listener);
} else if (data instanceof RealmList) {
RealmList realmList = (RealmList) data;
RealmList<T> list = (RealmList<T>) data;
//noinspection unchecked
realmList.realm.handlerController.removeWeakChangeListener(listener);
list.removeChangeListener(listener);
} else {
throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
}
Expand Down
17 changes: 14 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ buildscript {
ext {
sdkVersion = 25
buildTools = '25.0.0'
realmVersion = '2.1.1'
supportLibraryVersion = '25.0.0'
realmVersion = '3.0.0-SNAPSHOT'
supportLibraryVersion = '25.2.0'
}

repositories {
jcenter()
maven {
url 'http://oss.jfrog.org/artifactory/oss-snapshot-local'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2' // Android Build tools
classpath 'com.android.tools.build:gradle:2.2.3' // Android Build tools
classpath 'ch.netzwerg:gradle-release-plugin:1.2.0' // Manage Release tags / versioning
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' // Allows you to install AAR's locally
classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.0.1' // OJO integration
Expand All @@ -22,11 +25,19 @@ buildscript {
}
}

// Don't cache SNAPSHOT (changing) dependencies.
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}

allprojects {
group = 'io.realm'
version = file("${rootDir}/version.txt").text.trim();
repositories {
jcenter()
maven {
url 'http://oss.jfrog.org/artifactory/oss-snapshot-local'
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,30 @@
*/
package io.realm.examples.adapters.model;

import java.util.concurrent.atomic.AtomicInteger;

import io.realm.RealmObject;

public class TimeStamp extends RealmObject {
public class Counter extends RealmObject {
public static final String FIELD_COUNT = "count";

private static AtomicInteger INTEGER_COUNTER = new AtomicInteger(0);

public static final String TIMESTAMP = "timeStamp";
private int count;

private String timeStamp;
public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public String getTimeStamp() {
return timeStamp;
public String getCountString() {
return Integer.toString(count);
}

public void setTimeStamp(String timeStamp) {
this.timeStamp = timeStamp;
public void increment() {
this.count = INTEGER_COUNTER.getAndIncrement();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@
import android.widget.ListView;

import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmResults;
import io.realm.examples.adapters.R;
import io.realm.examples.adapters.model.TimeStamp;
import io.realm.examples.adapters.model.Counter;

public class ListViewExampleActivity extends AppCompatActivity {

Expand All @@ -43,19 +42,19 @@ protected void onCreate(Bundle savedInstanceState) {
// RealmResults are "live" views, that are automatically kept up to date, even when changes happen
// on a background thread. The RealmBaseAdapter will automatically keep track of changes and will
// automatically refresh when a change is detected.
RealmResults<TimeStamp> timeStamps = realm.where(TimeStamp.class).findAll();
final MyListAdapter adapter = new MyListAdapter(timeStamps);
RealmResults<Counter> counters = realm.where(Counter.class).findAllSorted(Counter.FIELD_COUNT);
final MyListAdapter adapter = new MyListAdapter(counters);

ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
final String timestamp = adapter.getItem(i).getTimeStamp();
final int id = adapter.getItem(i).getCount();
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.where(TimeStamp.class).equalTo("timeStamp", timestamp).findAll().deleteAllFromRealm();
realm.where(Counter.class).equalTo(Counter.FIELD_COUNT, id).findAll().deleteAllFromRealm();
}
});
return true;
Expand All @@ -79,11 +78,10 @@ public boolean onCreateOptionsMenu(Menu menu) {
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_add) {
final String timestamp = Long.toString(System.currentTimeMillis());
realm.executeTransactionAsync(new Realm.Transaction() {
@Override
public void execute(Realm realm) {
realm.createObject(TimeStamp.class).setTimeStamp(timestamp);
realm.createObject(Counter.class).increment();
}
});
return true;
Expand Down
Loading

0 comments on commit 2f1c060

Please sign in to comment.