Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fine grained notification for RecyclerViewAdapter #83

Merged
merged 6 commits into from
Feb 28, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
## 2.0.0 (YYYY-MM-DD)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should state supporting Realm-Java version at the beginning of each version entry in Changelog

now 3.x?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a good idea.


### Breaking changes

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that removing RealmBaseAdapter#inflater, RealmBaseAdapter#context, RealmRecyclerViewAdapter#inflater and RealmRecyclerViewAdapter#context are also breaking changes.

### Enhancements

* Added fine grained notification support to `RealmRecyclerViewAdapter` (requires Realm Java version >= v3.0.0).

## 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<Object> 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<Object>() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be RealmChangeListener<OrderedRealmCollection<T>> since addListener can only accept RealmResults or RealmList

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I think it's OK to use raw type (RealmChangeListener).
RealmRecyclerViewAdapter also is using raw type.

@Override
public void onChange(BaseRealm results) {
public void onChange(Object results) {
notifyDataSetChanged();
}
};
Expand All @@ -86,23 +54,27 @@ public void onChange(BaseRealm results) {

private void addListener(@NonNull OrderedRealmCollection<T> data) {
if (data instanceof RealmResults) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check for instance of RealmCollectionObservable instead? and collapse the two if sections?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore this after removing this interface

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a space after cast

} 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
92 changes: 41 additions & 51 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 @@ -85,12 +79,7 @@ public RealmRecyclerViewAdapter(@Nullable OrderedRealmCollection<T> data, boolea
// Right now don't use generics, since we need maintain two different
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to

// types of listeners until RealmList is properly supported.
// See https://github.com/realm/realm-java/issues/989
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the above assumption still valid? realm/realm-java#989 is closed now?

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 +166,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remember to remove when releasing

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 integerCounter = new AtomicInteger(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why static? can't we use a member counter with @Ignore ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is in order to have a global counter while the app is running so the list will have ever increasing numbers.


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 setAndIncrease() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think increment is probably a more reflective name, as we're not setting any value (method doesn't take args)

this.count = integerCounter.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).setAndIncrease();
}
});
return true;
Expand Down
Loading