Merge changes from topic 'baku'
* changes:
Making APIs consistent
Baku - Factoring out sync error handlers
Baku - improving docs and easing composition
Baku Toolkit - Adding ID-list bindings
Baku - Splitting collection adapter components
diff --git a/baku-toolkit/gradle/wrapper/gradle-wrapper.properties b/baku-toolkit/gradle/wrapper/gradle-wrapper.properties
index de00d04..89af0bd 100644
--- a/baku-toolkit/gradle/wrapper/gradle-wrapper.properties
+++ b/baku-toolkit/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip
diff --git a/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/SyncbaseRangeAdapterTest.java b/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/CollectionBindingTest.java
similarity index 69%
rename from baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/SyncbaseRangeAdapterTest.java
rename to baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/CollectionBindingTest.java
index 9b916bd..2b17d53 100644
--- a/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/SyncbaseRangeAdapterTest.java
+++ b/baku-toolkit/lib/src/androidTest/java/io/v/baku/toolkit/bind/CollectionBindingTest.java
@@ -12,7 +12,7 @@
import io.v.rx.syncbase.RxSyncbase;
import io.v.rx.syncbase.RxTable;
-public class SyncbaseRangeAdapterTest extends VAndroidTestCase {
+public class CollectionBindingTest extends VAndroidTestCase {
private RxSyncbase mRxSyncbase;
private RxTable mTable;
@@ -37,34 +37,33 @@
mTable.put("Good morning", "starshine")));
final ListView listView = new ListView(getContext());
- try (final SyncbaseListAdapter<String> adapter = SyncbaseRangeAdapter.builder()
+ try (final SyncbaseListAdapter<RxTable.Row<String>> adapter = CollectionBinding.builder()
.onError(t -> fail(Throwables.getStackTraceAsString(t)))
.viewAdapterContext(getContext())
.rxTable(mTable)
- .prefix("Good")
+ .onPrefix("Good")
.type(String.class)
- .bindTo(listView)
- .getAdapter()) {
+ .bindTo(listView)) {
pause();
assertEquals(2, listView.getCount());
- assertEquals("Goodnight", adapter.getRowAt(0).getRowName());
- assertEquals("moon", adapter.getItem(0));
- assertEquals("Good morning", adapter.getRowAt(1).getRowName());
- assertEquals("starshine", adapter.getItem(1));
+ assertEquals("Goodnight", adapter.getItem(0).getRowName());
+ assertEquals("moon", adapter.getItem(0).getValue());
+ assertEquals("Good morning", adapter.getItem(1).getRowName());
+ assertEquals("starshine", adapter.getItem(1).getValue());
start(mTable.put("Goodbye", "Mr. Bond"));
pause();
- assertEquals("Goodbye", adapter.getRowAt(0).getRowName());
- assertEquals("Mr. Bond", adapter.getItem(0));
+ assertEquals("Goodbye", adapter.getItem(0).getRowName());
+ assertEquals("Mr. Bond", adapter.getItem(0).getValue());
start(mTable.delete("Good morning"));
pause();
assertEquals(1, adapter.getRowIndex("Goodnight"));
- assertEquals("moon", adapter.getItem(1));
+ assertEquals("moon", adapter.getItem(1).getValue());
}
}
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java
index e506464..3e424ea 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivity.java
@@ -12,16 +12,16 @@
import lombok.extern.slf4j.Slf4j;
/**
- * A default application of {@link BakuActivityTrait} extending {@link android.app.Activity}. Most
+ * A default integration with {@link BakuActivityTrait} extending {@link android.app.Activity}. Most
* activities with distributed state should inherit from this.
*/
@Slf4j
public abstract class BakuActivity extends VActivity implements BakuActivityTrait<Activity> {
@Delegate
- private BakuActivityTrait mBakuActivityTrait;
+ private BakuActivityTrait<Activity> mBakuActivityTrait;
- protected BakuActivityTrait createBakuActivityTrait() {
- return new BakuActivityMixin(this);
+ protected BakuActivityTrait<Activity> createBakuActivityTrait() {
+ return new BakuActivityMixin<>(this);
}
@Override
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java
index 8172987..79975d6 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityMixin.java
@@ -5,10 +5,10 @@
package io.v.baku.toolkit;
import android.app.Activity;
+import android.os.Bundle;
-import io.v.baku.toolkit.bind.RangeAdapter;
import io.v.baku.toolkit.bind.SyncbaseBinding;
-import io.v.baku.toolkit.bind.SyncbaseRangeAdapter;
+import io.v.baku.toolkit.bind.CollectionBinding;
import io.v.baku.toolkit.syncbase.BakuDb;
import io.v.baku.toolkit.syncbase.BakuSyncbase;
import io.v.baku.toolkit.syncbase.BakuTable;
@@ -27,6 +27,7 @@
* <li>{@link BakuActivity} (extends {@link Activity})</li>
* <li>{@link BakuAppCompatActivity} (extends {@link android.support.v7.app.AppCompatActivity})</li>
* </ul>
+ * <p>
* Since Java doesn't actually support multiple inheritance, clients requiring custom inheritance
* hierarchies will need to wire in manually, like any of the examples above.
*/
@@ -61,6 +62,33 @@
joinInitialSyncGroup();
}
+ /**
+ * Convenience constructor for compositional integration. Example usage:
+ *
+ * <pre><code>
+ * public class SampleCompositionActivity extends Activity {
+ * private BakuActivityTrait<SampleCompositionActivity> mBaku;
+ *
+ * @Override
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * setContentView(R.layout.activity_hello);
+ *
+ * mBaku = new BakuActivityMixin<>(this, savedInstanceState);
+ * }
+ *
+ * @Override
+ * protected void onDestroy() {
+ * mBaku.close();
+ * super.onDestroy();
+ * }
+ * }
+ * </code></pre>
+ */
+ public BakuActivityMixin(final T context, final Bundle savedInstanceState) {
+ this(VAndroidContextMixin.withDefaults(context, savedInstanceState));
+ }
+
@Override
public void close() {
mSubscriptions.unsubscribe();
@@ -84,16 +112,16 @@
}
public void onSyncError(final Throwable t) {
- mVAndroidContextTrait.getErrorReporter().onError(R.string.err_sync, t);
+ ErrorReporters.getDefaultSyncErrorReporter(mVAndroidContextTrait);
}
public <U> SyncbaseBinding.Builder<U> binder() {
return SyncbaseBinding.<U>builder()
- .bakuActivity(this);
+ .activity(this);
}
- public <U> SyncbaseRangeAdapter.Builder<U, ?> collectionBinder() {
- return SyncbaseRangeAdapter.<U, RangeAdapter>builder()
- .bakuActivity(this);
+ public CollectionBinding.Builder collectionBinder() {
+ return CollectionBinding.builder()
+ .activity(this);
}
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java
index 6ba0d0f..9fbd4ec 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuActivityTrait.java
@@ -7,7 +7,7 @@
import android.app.Activity;
import io.v.baku.toolkit.bind.SyncbaseBinding;
-import io.v.baku.toolkit.bind.SyncbaseRangeAdapter;
+import io.v.baku.toolkit.bind.CollectionBinding;
import io.v.baku.toolkit.syncbase.BakuDb;
import io.v.baku.toolkit.syncbase.BakuSyncbase;
import io.v.baku.toolkit.syncbase.BakuTable;
@@ -22,6 +22,6 @@
String getSyncbaseTableName();
void onSyncError(Throwable t);
<U> SyncbaseBinding.Builder<U> binder();
- <U> SyncbaseRangeAdapter.Builder<U, ?> collectionBinder();
+ CollectionBinding.Builder collectionBinder();
void close();
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java
index e2c5ae0..14220dd 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/BakuAppCompatActivity.java
@@ -12,7 +12,7 @@
import lombok.extern.slf4j.Slf4j;
/**
- * A default application of {@link BakuActivityTrait} extending
+ * A default integration with {@link BakuActivityTrait} extending
* {@link android.support.v7.app.AppCompatActivity}.
*/
@Slf4j
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/ErrorReporters.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/ErrorReporters.java
index b9df48b..2ade39d 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/ErrorReporters.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/ErrorReporters.java
@@ -8,6 +8,7 @@
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
+import rx.functions.Action1;
@Slf4j
@UtilityClass
@@ -23,4 +24,22 @@
}
};
}
+
+ /**
+ * Derives a default sync error reporting function from a {@link VAndroidContextTrait}. The
+ * error message is {@link io.v.baku.toolkit.R.string#err_sync}.
+ *
+ * @see #getDefaultSyncErrorReporter(ErrorReporter)
+ */
+ public static Action1<Throwable> getDefaultSyncErrorReporter(final VAndroidContextTrait<?> v) {
+ return getDefaultSyncErrorReporter(v.getErrorReporter());
+ }
+
+ /**
+ * Derives a default sync error reporting function from an {@link ErrorReporter}. The error
+ * message is {@link io.v.baku.toolkit.R.string#err_sync}.
+ */
+ public static Action1<Throwable> getDefaultSyncErrorReporter(final ErrorReporter r) {
+ return t -> r.onError(R.string.err_sync, t);
+ }
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java
index 97218a6..8ebd442 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VActivity.java
@@ -11,7 +11,7 @@
import lombok.experimental.Delegate;
/**
- * A default application of {@link VAndroidContextTrait} extending {@link Activity}.
+ * A default integration with {@link VAndroidContextTrait} extending {@link Activity}.
*/
public abstract class VActivity extends Activity implements VAndroidContextTrait<Activity> {
@Delegate
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java
index 08dad20..c3ca36c 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/VAppCompatActivity.java
@@ -12,7 +12,7 @@
import lombok.extern.slf4j.Slf4j;
/**
- * A default application of {@link VAndroidContextTrait} extending
+ * A default integration with {@link VAndroidContextTrait} extending
* {@link android.support.v7.app.AppCompatActivity}.
*/
@Slf4j
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/AbstractViewAdapter.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/AbstractViewAdapter.java
index 6fdece9..3a51515 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/AbstractViewAdapter.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/AbstractViewAdapter.java
@@ -10,7 +10,8 @@
import java8.util.function.Function;
public abstract class AbstractViewAdapter<T, VH extends ViewHolder> implements ViewAdapter<T, VH> {
- public <U> AbstractViewAdapter<U, VH> map(final Function<U, T> fn) {
+ @Override
+ public <U> ViewAdapter<U, VH> map(final Function<U, ? extends T> fn) {
return new TransformingViewAdapter<>(this, fn);
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/BaseBuilder.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/BaseBuilder.java
index 36e0be8..64ba08a 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/BaseBuilder.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/BaseBuilder.java
@@ -6,10 +6,10 @@
import android.app.Activity;
-import android.support.annotation.IdRes;
-import android.view.View;
import io.v.baku.toolkit.BakuActivityTrait;
+import io.v.baku.toolkit.ErrorReporters;
+import io.v.baku.toolkit.VAndroidContextTrait;
import io.v.rx.syncbase.RxTable;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@@ -19,15 +19,14 @@
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public abstract class BaseBuilder<T extends BaseBuilder<T>> {
+ @SuppressWarnings("unchecked")
+ protected final T mSelf = (T)this;
+
protected Activity mActivity;
protected RxTable mRxTable;
protected CompositeSubscription mSubscriptionParent;
protected Action1<Throwable> mOnError;
- @SuppressWarnings("unchecked")
- protected final T mSelf = (T)this;
-
-
public T activity(final Activity activity) {
mActivity = activity;
return mSelf;
@@ -38,13 +37,34 @@
return mSelf;
}
- public T bakuActivity(final BakuActivityTrait<?> trait) {
+ /**
+ * Sets the following properties from the given {@link BakuActivityTrait}:
+ * <ul>
+ * <li>{@link #activity(Activity)}</li>
+ * <li>{@link #rxTable(RxTable)}</li>
+ * <li>{@link #subscriptionParent(CompositeSubscription)}</li>
+ * <li>{@link #onError(Action1)}</li>
+ * </ul>
+ */
+ public T activity(final BakuActivityTrait<?> trait) {
return activity(trait.getVAndroidContextTrait().getAndroidContext())
.rxTable(trait.getSyncbaseTable())
.subscriptionParent(trait.getSubscriptions())
.onError(trait::onSyncError);
}
+ /**
+ * Sets the following properties from the given {@link VAndroidContextTrait}:
+ * <ul>
+ * <li>{@link #activity(Activity)}</li>
+ * <li>{@link #onError(Action1)}</li>
+ * </ul>
+ */
+ public T activity(final VAndroidContextTrait<? extends Activity> trait) {
+ return activity(trait.getAndroidContext())
+ .onError(ErrorReporters.getDefaultSyncErrorReporter(trait));
+ }
+
public T subscriptionParent(final CompositeSubscription subscriptionParent) {
mSubscriptionParent = subscriptionParent;
return mSelf;
@@ -66,10 +86,4 @@
mOnError = onError;
return mSelf;
}
-
- public abstract T bindTo(final View view);
-
- public T bindTo(final @IdRes int viewId) {
- return bindTo(mActivity.findViewById(viewId));
- }
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/BaseCollectionBindingBuilder.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/BaseCollectionBindingBuilder.java
new file mode 100644
index 0000000..4433363
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/BaseCollectionBindingBuilder.java
@@ -0,0 +1,29 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import android.content.Context;
+
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Encapsulates the common logic for building the Syncbase side of a collection binding.
+ *
+ * @see CollectionAdapterBuilder
+ */
+@RequiredArgsConstructor
+public abstract class BaseCollectionBindingBuilder<B extends BaseCollectionBindingBuilder<B>>
+ extends BaseBuilder<B> {
+ private Context mViewAdapterContext;
+
+ public B viewAdapterContext(final Context context) {
+ mViewAdapterContext = context;
+ return mSelf;
+ }
+
+ public Context getDefaultViewAdapterContext() {
+ return mViewAdapterContext == null ? mActivity : mViewAdapterContext;
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionAdapterBuilder.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionAdapterBuilder.java
new file mode 100644
index 0000000..bbaf946
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionAdapterBuilder.java
@@ -0,0 +1,90 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.ListView;
+
+import java8.util.function.Function;
+import lombok.RequiredArgsConstructor;
+import rx.Observable;
+
+/**
+ * Encapsulates the common logic for building the widget side of a collection binding.
+ *
+ * @see BaseCollectionBindingBuilder
+ */
+@RequiredArgsConstructor
+public abstract class CollectionAdapterBuilder<B extends CollectionAdapterBuilder<B, T, A>,
+ T, A extends RangeAdapter> {
+ @SuppressWarnings("unchecked")
+ protected final B mSelf = (B)this;
+
+ protected final CollectionBinding.Builder mBase;
+
+ private ViewAdapter<? super T, ?> mViewAdapter;
+
+ public B viewAdapter(
+ final ViewAdapter<? super T, ?> viewAdapter) {
+ mViewAdapter = viewAdapter;
+ return mSelf;
+ }
+
+ public B textViewAdapter() {
+ return viewAdapter(new TextViewAdapter(mBase.getDefaultViewAdapterContext()));
+ }
+
+ public B textViewAdapter(final Function<T, ?> fn) {
+ return viewAdapter(new TextViewAdapter(mBase.getDefaultViewAdapterContext()).map(fn));
+ }
+
+ protected ViewAdapter<? super T, ?> getDefaultViewAdapter() {
+ return new TextViewAdapter(mBase.getDefaultViewAdapterContext());
+ }
+
+ private ViewAdapter<? super T, ?> getViewAdapter() {
+ return mViewAdapter == null ? getDefaultViewAdapter() : mViewAdapter;
+ }
+
+ private <U extends RangeAdapter> U subscribeAdapter(final U adapter) {
+ mBase.subscribe(adapter.getSubscription());
+ return adapter;
+ }
+
+ public abstract Observable<? extends ListAccumulator<T>> buildListAccumulator();
+
+ public SyncbaseListAdapter<T> buildListAdapter() {
+ return subscribeAdapter(new SyncbaseListAdapter<>(
+ buildListAccumulator(), getViewAdapter(), mBase.mOnError));
+ }
+
+ public SyncbaseRecyclerAdapter<T, ?> buildRecyclerAdapter() {
+ return subscribeAdapter(new SyncbaseRecyclerAdapter<>(
+ buildListAccumulator(), getViewAdapter(), mBase.mOnError));
+ }
+
+ public SyncbaseListAdapter<T> bindTo(final ListView listView) {
+ final SyncbaseListAdapter<T> adapter = buildListAdapter();
+ listView.setAdapter(adapter);
+ return adapter;
+ }
+
+ public SyncbaseRecyclerAdapter<T, ?> bindTo(final RecyclerView recyclerView) {
+ final SyncbaseRecyclerAdapter<T, ?> adapter = buildRecyclerAdapter();
+ recyclerView.setAdapter(adapter);
+ return adapter;
+ }
+
+ public RangeAdapter bindTo(final View view) {
+ if (view instanceof ListView) {
+ return bindTo((ListView) view);
+ } else if (view instanceof RecyclerView) {
+ return bindTo((RecyclerView) view);
+ } else {
+ throw new IllegalArgumentException("No default binding for view " + view);
+ }
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionBinding.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionBinding.java
new file mode 100644
index 0000000..ac8abeb
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/CollectionBinding.java
@@ -0,0 +1,28 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import io.v.v23.syncbase.nosql.PrefixRange;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class CollectionBinding {
+ public static class Builder extends BaseCollectionBindingBuilder<Builder> {
+ public <T, A extends RangeAdapter> PrefixBindingBuilder<T, A> onPrefix(final String prefix) {
+ return new PrefixBindingBuilder<T, A>(this).prefix(prefix);
+ }
+ public <T, A extends RangeAdapter> PrefixBindingBuilder<T, A> onPrefix(final PrefixRange prefix) {
+ return new PrefixBindingBuilder<T, A>(this).prefix(prefix);
+ }
+
+ public <A extends RangeAdapter> IdListBindingBuilder<A> onIdList(final String idListRowName) {
+ return new IdListBindingBuilder<A>(this).idListRowName(idListRowName);
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/IdListAccumulator.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/IdListAccumulator.java
new file mode 100644
index 0000000..b75997e
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/IdListAccumulator.java
@@ -0,0 +1,51 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import com.google.common.collect.ImmutableList;
+
+import io.v.rx.syncbase.SingleWatchEvent;
+import lombok.RequiredArgsConstructor;
+import rx.Observable;
+
+@RequiredArgsConstructor
+public class IdListAccumulator implements ListAccumulator<String> {
+ private final ImmutableList<String> mIds;
+
+ public IdListAccumulator() {
+ this(ImmutableList.of());
+ }
+
+ public Observable<IdListAccumulator> scanFrom(
+ final Observable<SingleWatchEvent<ImmutableList<String>>> watch) {
+ return watch.scan(this, (t, w) -> new IdListAccumulator(w.getValue()));
+ }
+
+ @Override
+ public int getCount() {
+ return mIds.size();
+ }
+
+ @Override
+ public String getRowAt(final int position) {
+ return mIds.get(position);
+ }
+
+ @Override
+ public boolean containsRow(String rowName) {
+ // TODO(rosswang): possibly index
+ // Since contains and indexOf are O(n) on a list, these don't scale too well. If we ever
+ // have to deal with large lists and performance becomes an issue, it might be indicated to
+ // index these into a map. On the other hand, that's premature right now and would probably
+ // end up being even slower for most cases, so I'm punting on that until there's evidence.
+ return mIds.contains(rowName);
+ }
+
+ @Override
+ public int getRowIndex(final String rowName) {
+ // TODO(rosswang): possibly index
+ return mIds.indexOf(rowName);
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/IdListBindingBuilder.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/IdListBindingBuilder.java
new file mode 100644
index 0000000..988de7a
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/IdListBindingBuilder.java
@@ -0,0 +1,42 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.reflect.TypeToken;
+
+import java.util.List;
+
+import io.v.rx.syncbase.SingleWatchEvent;
+import rx.Observable;
+
+public class IdListBindingBuilder<A extends RangeAdapter>
+ extends CollectionAdapterBuilder<IdListBindingBuilder<A>, String, A> {
+ private String mIdListRowName;
+
+ public IdListBindingBuilder(final CollectionBinding.Builder base) {
+ super(base);
+ }
+
+ /**
+ * This binding will produce lists of row name strings, which the item {@link ViewAdapter} will
+ * need to bind to Syncbase rows with scalar {@link SyncbaseBinding}s.
+ */
+ public IdListBindingBuilder<A> idListRowName(final String idListRowName) {
+ mIdListRowName = idListRowName;
+ return this;
+ }
+
+ public Observable<SingleWatchEvent<ImmutableList<String>>> buildIdListWatch() {
+ return mBase.mRxTable.watch(mIdListRowName, new TypeToken<List<String>>() {
+ }, ImmutableList.of()).map(w -> w.map(ImmutableList::copyOf));
+ }
+
+ @Override
+ public Observable<IdListAccumulator> buildListAccumulator() {
+ return new IdListAccumulator()
+ .scanFrom(buildIdListWatch());
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ListAccumulator.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ListAccumulator.java
new file mode 100644
index 0000000..b7d2f9c
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ListAccumulator.java
@@ -0,0 +1,17 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+public interface ListAccumulator<T> {
+ boolean containsRow(String rowName);
+ int getCount();
+ T getRowAt(int position);
+
+ /**
+ * @return the row index, or a negative index if not present. The negative value (and whether or
+ * not it has meaning) may vary by implementation.
+ */
+ int getRowIndex(String rowName);
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ListAccumulators.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ListAccumulators.java
new file mode 100644
index 0000000..f6737a4
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ListAccumulators.java
@@ -0,0 +1,42 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import java.util.NoSuchElementException;
+
+import io.v.rx.syncbase.RxTable;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class ListAccumulators {
+
+
+ public static final ListAccumulator<Object> EMPTY = new ListAccumulator<Object>(){
+ @Override
+ public int getCount() {
+ return 0;
+ }
+
+ @Override
+ public boolean containsRow(final String rowName) {
+ return false;
+ }
+
+ @Override
+ public RxTable.Row<Object> getRowAt(final int position) {
+ throw new NoSuchElementException("No elements in empty ListAccumulator.");
+ }
+
+ @Override
+ public int getRowIndex(final String rowName) {
+ return -1;
+ }
+ };
+
+ @SuppressWarnings("unchecked")
+ public static <T> ListAccumulator<T> empty() {
+ return (ListAccumulator<T>)EMPTY;
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixBindingBuilder.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixBindingBuilder.java
new file mode 100644
index 0000000..57e8588
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixBindingBuilder.java
@@ -0,0 +1,105 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import com.google.common.collect.Ordering;
+
+import io.v.rx.syncbase.RangeWatchBatch;
+import io.v.rx.syncbase.RxTable;
+import io.v.v23.syncbase.nosql.PrefixRange;
+import io.v.v23.syncbase.nosql.RowRange;
+import rx.Observable;
+import rx.functions.Func1;
+
+/**
+ * If {@code T} is {@link Comparable}, the default row ordering is natural ordering on row
+ * values. Otherwise, the default is natural ordering on row names.
+ */
+public class PrefixBindingBuilder<T, A extends RangeAdapter>
+ extends CollectionAdapterBuilder<PrefixBindingBuilder<T, A>, RxTable.Row<T>, A> {
+ private Class<T> mType;
+ private PrefixRange mPrefix;
+ private Ordering<? super RxTable.Row<T>> mOrdering;
+ private Func1<String, Boolean> mKeyFilter;
+
+ public PrefixBindingBuilder(final CollectionBinding.Builder base) {
+ super(base);
+ }
+
+ public PrefixBindingBuilder<T, A> prefix(final PrefixRange prefix) {
+ mPrefix = prefix;
+ return this;
+ }
+
+ public PrefixBindingBuilder<T, A> prefix(final String prefix) {
+ return prefix(RowRange.prefix(prefix));
+ }
+
+ /**
+ * This setter is minimally typesafe; after setting the {@code type}, clients should
+ * probably also update {@code ordering} and {@code viewAdapter}. If intending to use a
+ * collection binding that requires a
+ */
+ public <U> PrefixBindingBuilder<U, A> type(final Class<U> type) {
+ @SuppressWarnings("unchecked")
+ final PrefixBindingBuilder<U, A> casted = (PrefixBindingBuilder<U, A>) this;
+ casted.mType = type;
+ return casted;
+ }
+
+ public PrefixBindingBuilder<T, A> ordering(
+ final Ordering<? super RxTable.Row<? extends T>> ordering) {
+ mOrdering = ordering;
+ return this;
+ }
+
+ public PrefixBindingBuilder<T, A> valueOrdering(final Ordering<? super T> ordering) {
+ return ordering(ordering.onResultOf(RxTable.Row::getValue));
+ }
+
+ public PrefixBindingBuilder<T, A> valueAdapter(final ViewAdapter<? super T, ?> viewAdapter) {
+ return viewAdapter(new TransformingViewAdapter<>(viewAdapter, RxTable.Row::getValue));
+ }
+
+ @Override
+ protected ViewAdapter<RxTable.Row<T>, ?> getDefaultViewAdapter() {
+ return new TextViewAdapter(mBase.getDefaultViewAdapterContext()).map(RxTable.Row::getValue);
+ }
+
+ /**
+ * For comparable {@code T}, default to natural ordering on values. Otherwise, default to
+ * natural ordering on row names.
+ */
+ private Ordering<? super RxTable.Row<? extends T>> getDefaultOrdering() {
+ if (mOrdering == null && Comparable.class.isAssignableFrom(mType)) {
+ return Ordering.natural().onResultOf(r -> (Comparable) r.getValue());
+ } else {
+ return Ordering.natural().onResultOf(RxTable.Row::getRowName);
+ }
+ }
+
+ public PrefixBindingBuilder<T, A> keyFilter(final Func1<String, Boolean> keyFilter) {
+ mKeyFilter = keyFilter;
+ return this;
+ }
+
+ public Observable<RangeWatchBatch<T>> buildPrefixWatch() {
+ if (mType == null) {
+ throw new IllegalStateException("Missing required type property");
+ }
+ return mBase.mRxTable.watch(mPrefix == null? RowRange.prefix("") : mPrefix,
+ mKeyFilter, mType);
+ }
+
+ private Ordering<? super RxTable.Row<T>> getOrdering() {
+ return mOrdering == null ? getDefaultOrdering() : mOrdering;
+ }
+
+ @Override
+ public Observable<PrefixListAccumulator<T>> buildListAccumulator() {
+ return new PrefixListAccumulator<>(getOrdering())
+ .scanFrom(buildPrefixWatch());
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListAccumulator.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListAccumulator.java
new file mode 100644
index 0000000..af5bd6c
--- /dev/null
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/PrefixListAccumulator.java
@@ -0,0 +1,133 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.baku.toolkit.bind;
+
+import com.google.common.collect.Ordering;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.v.rx.syncbase.RangeWatchBatch;
+import io.v.rx.syncbase.RangeWatchEvent;
+import io.v.rx.syncbase.RxTable;
+import io.v.v23.syncbase.nosql.ChangeType;
+import rx.Observable;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Func2;
+
+/**
+ * This class accumulates prefix watch streams into observable lists. It is meant to be used in
+ * conjunction with {@link Observable#scan(Object, Func2)}:
+ *
+ * <p>{@code .scan(new PrefixListAccumulator<>(...), PrefixListAccumulator::add)}
+ * @param <T>
+ */
+public class PrefixListAccumulator<T> implements ListAccumulator<RxTable.Row<T>> {
+ private static final String ERR_INCONSISTENT = "Sorted data are inconsistent with map data";
+
+ private final Map<String, T> mRows = new HashMap<>();
+ private final List<RxTable.Row<T>> mSorted = new ArrayList<>();
+ private final Ordering<? super RxTable.Row<T>> mOrdering;
+
+ public PrefixListAccumulator(final Ordering<? super RxTable.Row<T>> ordering) {
+ // ensure deterministic ordering by always applying secondary order on row name
+ mOrdering = ordering.compound(Ordering.natural().onResultOf(RxTable.Row::getRowName));
+ }
+
+ public Observable<PrefixListAccumulator<T>> scanFrom(
+ final Observable<RangeWatchBatch<T>> watch) {
+ return watch
+ .concatMap(RangeWatchBatch::collectChanges)
+ .observeOn(AndroidSchedulers.mainThread()) // required unless we copy
+ .scan(this, PrefixListAccumulator::withUpdates);
+ }
+
+ private int findRowForEdit(final String rowName, final T oldValue) {
+ final int oldIndex = Collections.binarySearch(mSorted,
+ new RxTable.Row<>(rowName, oldValue), mOrdering);
+ if (oldIndex < 0) {
+ throw new ConcurrentModificationException(ERR_INCONSISTENT);
+ } else {
+ return oldIndex;
+ }
+ }
+
+ private PrefixListAccumulator<T> withUpdates(final Collection<RangeWatchEvent<T>> events) {
+ // TODO(rosswang): more efficient updates for larger batches
+ // TODO(rosswang): allow option to copy on add (immutable accumulator)
+ for (final RangeWatchEvent<T> e : events) {
+ if (e.getChangeType() == ChangeType.DELETE_CHANGE) {
+ removeOne(e.getRow());
+ } else {
+ updateOne(e.getRow());
+ }
+ }
+ return this;
+ }
+
+ protected void removeOne(final RxTable.Row<T> entry) {
+ final T old = mRows.remove(entry.getRowName());
+ if (old != null) {
+ mSorted.remove(findRowForEdit(entry.getRowName(), old));
+ }
+ }
+
+ private int insertionIndex(final RxTable.Row<T> entry) {
+ final int bs = Collections.binarySearch(mSorted, entry, mOrdering);
+ return bs < 0 ? ~bs : bs;
+ }
+
+ protected void updateOne(final RxTable.Row<T> entry) {
+ final T old = mRows.put(entry.getRowName(), entry.getValue());
+ if (old == null) {
+ mSorted.add(insertionIndex(entry), entry);
+ } else {
+ final int oldIndex = findRowForEdit(entry.getRowName(), old);
+ int newIndex = insertionIndex(entry);
+
+ if (newIndex >= oldIndex) {
+ newIndex--;
+ for (int i = oldIndex; i < newIndex; i++) {
+ mSorted.set(i, mSorted.get(i + 1));
+ }
+ } else {
+ for (int i = oldIndex; i > newIndex; i--) {
+ mSorted.set(i, mSorted.get(i - 1));
+ }
+ }
+ mSorted.set(newIndex, entry);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mRows.size();
+ }
+
+ @Override
+ public RxTable.Row<T> getRowAt(final int position) {
+ return mSorted.get(position);
+ }
+
+ public T getValue(final String rowName) {
+ return mRows.get(rowName);
+ }
+
+ @Override
+ public int getRowIndex(final String rowName) {
+ return Collections.binarySearch(mSorted, new RxTable.Row<>(rowName, mRows.get(rowName)),
+ mOrdering);
+ }
+
+ @Override
+ public boolean containsRow(final String rowName) {
+ return mRows.containsKey(rowName);
+ }
+}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseBinding.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseBinding.java
index 052e000..48dbca0 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseBinding.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseBinding.java
@@ -4,6 +4,7 @@
package io.v.baku.toolkit.bind;
+import android.support.annotation.IdRes;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
@@ -164,7 +165,6 @@
return bindTwoWay(editText);
}
- @Override
public Builder<T> bindTo(final View view) {
if (view instanceof TextView) {
return bindTo((TextView) view);
@@ -172,6 +172,14 @@
throw new IllegalArgumentException("No default binding for view " + view);
}
}
+
+ /**
+ * Binds to the view identified by {@code viewId}.
+ * @see #bindTo(View)
+ */
+ public Builder<T> bindTo(final @IdRes int viewId) {
+ return bindTo(mActivity.findViewById(viewId));
+ }
}
public static <T> Builder<T> builder() {
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseListAdapter.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseListAdapter.java
index 150e699..7877239 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseListAdapter.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseListAdapter.java
@@ -8,51 +8,53 @@
import android.view.ViewGroup;
import android.widget.BaseAdapter;
-import com.google.common.collect.Ordering;
-
-import java.util.Collection;
-
-import io.v.rx.syncbase.RangeWatchBatch;
-import io.v.rx.syncbase.RangeWatchEvent;
-import io.v.rx.syncbase.RxTable;
+import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.Delegate;
import rx.Observable;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
@Accessors(prefix = "m")
-public class SyncbaseListAdapter<T> extends BaseAdapter implements RangeAdapter {
+public class SyncbaseListAdapter<T> extends BaseAdapter
+ implements RangeAdapter, ListAccumulator<T> {
+ private final ViewAdapter<? super T, ?> mViewAdapter;
@Delegate
- private final SyncbaseRangeAdapter<T> mAdapter;
- private final ViewAdapter<? super RxTable.Row<T>, ?> mViewAdapter;
+ private ListAccumulator<T> mLatestState = ListAccumulators.empty();
+ @Getter
+ private final Subscription mSubscription;
- public SyncbaseListAdapter(final Observable<RangeWatchBatch<T>> watch,
- final Ordering<? super RxTable.Row<T>> ordering,
- final ViewAdapter<? super RxTable.Row<T>, ?> viewAdapter,
+ public SyncbaseListAdapter(final Observable<? extends ListAccumulator<T>> data,
+ final ViewAdapter<? super T, ?> viewAdapter,
final Action1<Throwable> onError) {
- mAdapter = new SyncbaseRangeAdapter<T>(watch, ordering, onError) {
- @Override
- protected void processEvents(Collection<RangeWatchEvent<T>> rangeWatchEvents) {
- super.processEvents(rangeWatchEvents);
- notifyDataSetChanged();
- }
- };
mViewAdapter = viewAdapter;
+ mSubscription = data
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(d -> {
+ mLatestState = d;
+ notifyDataSetChanged();
+ }, onError);
+ }
+
+ @Override
+ public void close() throws Exception {
+ mSubscription.unsubscribe();
}
@Override
public View getView(final int position, View view, final ViewGroup parent) {
- final RxTable.Row<T> entry = mAdapter.getRowAt(position);
+ final T row = mLatestState.getRowAt(position);
if (view == null) {
view = mViewAdapter.createView(parent);
}
- mViewAdapter.bindView(view, position, entry);
+ mViewAdapter.bindView(view, position, row);
return view;
}
@Override
public T getItem(int position) {
- return mAdapter.getRowAt(position).getValue();
+ return getRowAt(position);
}
/**
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseRangeAdapter.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseRangeAdapter.java
deleted file mode 100644
index 8b40617..0000000
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseRangeAdapter.java
+++ /dev/null
@@ -1,308 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package io.v.baku.toolkit.bind;
-
-import android.content.Context;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.widget.ListView;
-
-import com.google.common.collect.Ordering;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.ConcurrentModificationException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import io.v.rx.syncbase.RangeWatchBatch;
-import io.v.rx.syncbase.RangeWatchEvent;
-import io.v.rx.syncbase.RxTable;
-import io.v.v23.syncbase.nosql.ChangeType;
-import io.v.v23.syncbase.nosql.PrefixRange;
-import io.v.v23.syncbase.nosql.RowRange;
-import java8.util.function.Function;
-import lombok.Getter;
-import lombok.experimental.Accessors;
-import rx.Observable;
-import rx.Subscription;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.functions.Action1;
-import rx.functions.Func1;
-
-@Accessors(prefix = "m")
-public class SyncbaseRangeAdapter<T> implements RangeAdapter {
- private static final String ERR_INCONSISTENT = "Sorted data are inconsistent with map data";
-
- /**
- * If {@code T} is {@link Comparable}, the default row ordering is natural ordering on row
- * values. Otherwise, the default is natural ordering on row names.
- */
- @Accessors(prefix = "m")
- public static class Builder<T, A extends RangeAdapter>
- extends BaseBuilder<Builder<T, ? extends RangeAdapter>> {
- private PrefixRange mPrefix = RowRange.prefix("");
- private Class<T> mType;
- private Ordering<? super RxTable.Row<T>> mOrdering;
- private Func1<String, Boolean> mKeyFilter;
- private ViewAdapter<? super RxTable.Row<T>, ?> mViewAdapter;
- private Context mViewAdapterContext;
-
- @Getter
- private A mAdapter;
-
- public Builder<T, A> prefix(final PrefixRange prefix) {
- mPrefix = prefix;
- return this;
- }
-
- public Builder<T, A> prefix(final String prefix) {
- return prefix(RowRange.prefix(prefix));
- }
-
- /**
- * This setter is minimally typesafe; after setting the {@code type}, clients should
- * probably also update {@code ordering} and {@code viewAdapter}.
- */
- public <U> Builder<U, SyncbaseRangeAdapter<U>> type(final Class<U> type) {
- @SuppressWarnings("unchecked")
- final Builder<U, SyncbaseRangeAdapter<U>> casted =
- (Builder<U, SyncbaseRangeAdapter<U>>) this;
- casted.mType = type;
- return casted;
- }
-
- public Builder<T, A> ordering(final Ordering<? super RxTable.Row<? extends T>> ordering) {
- mOrdering = ordering;
- return this;
- }
-
- public Builder<T, A> valueOrdering(final Ordering<? super T> ordering) {
- return ordering(ordering.onResultOf(RxTable.Row::getValue));
- }
-
- /**
- * For comparable {@code T}, default to natural ordering on values. Otherwise, default to
- * natural ordering on row names.
- */
- private Ordering<? super RxTable.Row<? extends T>> getDefaultOrdering() {
- if (mOrdering == null && Comparable.class.isAssignableFrom(mType)) {
- return Ordering.natural().onResultOf(r -> (Comparable) r.getValue());
- } else {
- return Ordering.natural().onResultOf(RxTable.Row::getRowName);
- }
- }
-
- public Builder<T, A> keyFilter(final Func1<String, Boolean> keyFilter) {
- mKeyFilter = keyFilter;
- return this;
- }
-
- public Builder<T, A> viewAdapter(final ViewAdapter<? super RxTable.Row<T>, ?> viewAdapter) {
- mViewAdapter = viewAdapter;
- return this;
- }
-
- public <U> Builder<T, A> textViewAdapter(final Function<RxTable.Row<T>, U> fn) {
- return viewAdapter(getDefaultViewAdapter(fn));
- }
-
- public Builder<T, A> valueAdapter(final ViewAdapter<T, ?> viewAdapter) {
- return viewAdapter(new TransformingViewAdapter<>(viewAdapter, RxTable.Row::getValue));
- }
-
- private <U> ViewAdapter<? super RxTable.Row<T>, ?> getDefaultViewAdapter(
- final Function<RxTable.Row<T>, U> fn) {
- return new TextViewAdapter<U>(getDefaultViewAdapterContext()).map(fn);
- }
-
- /**
- * The default view adapter stringizes values.
- */
- private ViewAdapter<? super RxTable.Row<T>, ?> getDefaultViewAdapter() {
- return getDefaultViewAdapter(RxTable.Row::getValue);
- }
-
- public Builder<T, A> viewAdapterContext(final Context context) {
- mViewAdapterContext = context;
- return this;
- }
-
- public Context getDefaultViewAdapterContext() {
- return mViewAdapterContext == null ? mActivity : mViewAdapterContext;
- }
-
- public Observable<RangeWatchBatch<T>> buildWatch() {
- if (mType == null) {
- throw new IllegalStateException("Missing required type property");
- }
- return mRxTable.watch(mPrefix, mKeyFilter, mType);
- }
-
- private Ordering<? super RxTable.Row<T>> getOrdering() {
- return mOrdering == null ? getDefaultOrdering() : mOrdering;
- }
-
- private ViewAdapter<? super RxTable.Row<T>, ?> getViewAdapter() {
- return mViewAdapter == null ? getDefaultViewAdapter() : mViewAdapter;
- }
-
- private <U extends RangeAdapter> U subscribeAdapter(final U adapter) {
- subscribe(adapter.getSubscription());
- mAdapter = null;
- return adapter;
- }
-
- public SyncbaseListAdapter<T> buildListAdapter() {
- return subscribeAdapter(new SyncbaseListAdapter<>(
- buildWatch(), getOrdering(), getViewAdapter(), mOnError));
- }
-
- public SyncbaseRecyclerAdapter<T, ?> buildRecyclerAdapter() {
- return subscribeAdapter(new SyncbaseRecyclerAdapter<>(
- buildWatch(), getOrdering(), getViewAdapter(), mOnError));
- }
-
- public Builder<T, SyncbaseListAdapter<T>> bindTo(final ListView listView) {
- @SuppressWarnings("unchecked")
- final Builder<T, SyncbaseListAdapter<T>> casted =
- (Builder<T, SyncbaseListAdapter<T>>) this;
- casted.mAdapter = buildListAdapter();
- listView.setAdapter(casted.mAdapter);
- return casted;
- }
-
- public Builder<T, SyncbaseRecyclerAdapter<T, ?>> bindTo(final RecyclerView recyclerView) {
- @SuppressWarnings("unchecked")
- final Builder<T, SyncbaseRecyclerAdapter<T, ?>> casted =
- (Builder<T, SyncbaseRecyclerAdapter<T, ?>>) this;
- casted.mAdapter = buildRecyclerAdapter();
- recyclerView.setAdapter(casted.mAdapter);
- return casted;
- }
-
- @Override
- public Builder<T, ?> bindTo(final View view) {
- if (view instanceof ListView) {
- return bindTo((ListView) view);
- } else if (view instanceof RecyclerView) {
- return bindTo((RecyclerView) view);
- } else {
- throw new IllegalArgumentException("No default binding for view " + view);
- }
- }
- }
-
- public static <T, A extends RangeAdapter> Builder<T, A> builder() {
- return new Builder<>();
- }
-
- private final Map<String, T> mRows = new HashMap<>();
- private List<RxTable.Row<T>> mSorted = new ArrayList<>();
- private final Ordering<? super RxTable.Row<T>> mOrdering;
- private final Action1<Throwable> mOnError;
- @Getter
- private final Subscription mSubscription;
-
- public SyncbaseRangeAdapter(final Observable<RangeWatchBatch<T>> watch,
- final Ordering<? super RxTable.Row<T>> ordering,
- final Action1<Throwable> onError) {
- // ensure deterministic ordering by always applying secondary order on row name
- mOrdering = ordering.compound(Ordering.natural().onResultOf(RxTable.Row::getRowName));
- mOnError = onError;
-
- mSubscription = subscribeTo(watch);
- }
-
- @Override
- public void close() {
- mSubscription.unsubscribe();
- }
-
- private Subscription subscribeTo(final Observable<RangeWatchBatch<T>> watch) {
- return watch
- .concatMap(RangeWatchBatch::collectChanges)
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(this::processEvents, mOnError);
- }
-
- private int findRowForEdit(final String rowName, final T oldValue) {
- final int oldIndex = Collections.binarySearch(mSorted,
- new RxTable.Row<>(rowName, oldValue), mOrdering);
- if (oldIndex < 0) {
- throw new ConcurrentModificationException(ERR_INCONSISTENT);
- } else {
- return oldIndex;
- }
- }
-
- protected void processEvents(final Collection<RangeWatchEvent<T>> events) {
- // TODO(rosswang): more efficient updates for larger batches
- for (final RangeWatchEvent<T> e : events) {
- if (e.getChangeType() == ChangeType.DELETE_CHANGE) {
- removeOne(e.getRow());
- } else {
- updateOne(e.getRow());
- }
- }
- }
-
- protected void removeOne(final RxTable.Row<T> entry) {
- final T old = mRows.remove(entry.getRowName());
- if (old != null) {
- mSorted.remove(findRowForEdit(entry.getRowName(), old));
- }
- }
-
- private int insertionIndex(final RxTable.Row<T> entry) {
- final int bs = Collections.binarySearch(mSorted, entry, mOrdering);
- return bs < 0 ? ~bs : bs;
- }
-
- protected void updateOne(final RxTable.Row<T> entry) {
- final T old = mRows.put(entry.getRowName(), entry.getValue());
- if (old == null) {
- mSorted.add(insertionIndex(entry), entry);
- } else {
- final int oldIndex = findRowForEdit(entry.getRowName(), old);
- int newIndex = insertionIndex(entry);
-
- if (newIndex >= oldIndex) {
- newIndex--;
- for (int i = oldIndex; i < newIndex; i++) {
- mSorted.set(i, mSorted.get(i + 1));
- }
- } else {
- for (int i = oldIndex; i > newIndex; i--) {
- mSorted.set(i, mSorted.get(i - 1));
- }
- }
- mSorted.set(newIndex, entry);
- }
- }
-
- public int getCount() {
- return mRows.size();
- }
-
- public RxTable.Row<T> getRowAt(final int position) {
- return mSorted.get(position);
- }
-
- public T getValue(final String rowName) {
- return mRows.get(rowName);
- }
-
- public int getRowIndex(final String rowName) {
- return Collections.binarySearch(mSorted, new RxTable.Row<>(rowName, mRows.get(rowName)),
- mOrdering);
- }
-
- public boolean containsRow(final String rowName) {
- return mRows.containsKey(rowName);
- }
-}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseRecyclerAdapter.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseRecyclerAdapter.java
index 6260631..1bc4d83 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseRecyclerAdapter.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/SyncbaseRecyclerAdapter.java
@@ -7,22 +7,18 @@
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
-import com.google.common.collect.Ordering;
-
-import java.util.Collection;
-
-import io.v.rx.syncbase.RangeWatchBatch;
-import io.v.rx.syncbase.RangeWatchEvent;
-import io.v.rx.syncbase.RxTable;
+import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.experimental.Delegate;
import rx.Observable;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
@Accessors(prefix = "m")
public class SyncbaseRecyclerAdapter<T, VH extends ViewHolder>
extends RecyclerView.Adapter<SyncbaseRecyclerAdapter.ViewHolderAdapter<VH>>
- implements RangeAdapter {
+ implements RangeAdapter, ListAccumulator<T> {
public static class ViewHolderAdapter<B extends ViewHolder> extends RecyclerView.ViewHolder {
public final B bakuViewHolder;
@@ -32,27 +28,28 @@
}
}
- private interface SimilarButDifferent {
- int getCount();
+ private final ViewAdapter<? super T, VH> mViewAdapter;
+ @Delegate
+ private ListAccumulator<T> mLatestState = ListAccumulators.empty();
+ @Getter
+ private final Subscription mSubscription;
+
+ public SyncbaseRecyclerAdapter(final Observable<? extends ListAccumulator<T>> data,
+ final ViewAdapter<? super T, VH> viewAdapter,
+ final Action1<Throwable> onError) {
+ mViewAdapter = viewAdapter;
+ mSubscription = data
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(d -> {
+ mLatestState = d;
+ notifyDataSetChanged();
+ // TODO(rosswang): Use higher-fidelity update notifications.
+ }, onError);
}
- @Delegate(excludes = SimilarButDifferent.class)
- private final SyncbaseRangeAdapter<T> mAdapter;
- private final ViewAdapter<? super RxTable.Row<T>, VH> mViewAdapter;
-
- public SyncbaseRecyclerAdapter(final Observable<RangeWatchBatch<T>> watch,
- final Ordering<? super RxTable.Row<T>> ordering,
- final ViewAdapter<? super RxTable.Row<T>, VH> viewAdapter,
- final Action1<Throwable> onError) {
- mAdapter = new SyncbaseRangeAdapter<T>(watch, ordering, onError) {
- @Override
- protected void processEvents(Collection<RangeWatchEvent<T>> rangeWatchEvents) {
- super.processEvents(rangeWatchEvents);
- notifyDataSetChanged();
- // TODO(rosswang): Use higher-fidelity update notifications.
- }
- };
- mViewAdapter = viewAdapter;
+ @Override
+ public void close() throws Exception {
+ mSubscription.unsubscribe();
}
@Override
@@ -63,7 +60,13 @@
@Override
public void onBindViewHolder(final ViewHolderAdapter<VH> holder, final int position) {
- mViewAdapter.bindViewHolder(holder.bakuViewHolder, position, mAdapter.getRowAt(position));
+ mViewAdapter.bindViewHolder(
+ holder.bakuViewHolder, position, mLatestState.getRowAt(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return getCount();
}
/**
@@ -73,9 +76,4 @@
public long getItemId(int i) {
return RecyclerView.NO_ID;
}
-
- @Override
- public int getItemCount() {
- return mAdapter.getCount();
- }
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TextViewAdapter.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TextViewAdapter.java
index 9482adc..a233704 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TextViewAdapter.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TextViewAdapter.java
@@ -21,7 +21,7 @@
@RequiredArgsConstructor
@Slf4j
-public class TextViewAdapter<T> extends AbstractViewAdapter<T, TextViewAdapter.ViewHolder> {
+public class TextViewAdapter extends AbstractViewAdapter<Object, TextViewAdapter.ViewHolder> {
@Accessors(prefix = "m")
@Getter
@RequiredArgsConstructor
@@ -87,7 +87,8 @@
}
@Override
- public void bindViewHolder(final ViewHolder viewHolder, final int position, final T value) {
+ public void bindViewHolder(final ViewHolder viewHolder, final int position,
+ final Object value) {
viewHolder.setText(format(position, value));
}
@@ -96,7 +97,7 @@
* or stringizes otherwise. We avoid agnostic stringization to preserve any Android formatting
* that might be present, like with {@link android.text.SpannableString}.
*/
- protected CharSequence format(final int position, final T value) {
+ protected CharSequence format(final int position, final Object value) {
return value instanceof CharSequence ? (CharSequence) value : Objects.toString(value);
}
}
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TransformingViewAdapter.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TransformingViewAdapter.java
index 334b2bd..71325b6 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TransformingViewAdapter.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/TransformingViewAdapter.java
@@ -14,7 +14,7 @@
public class TransformingViewAdapter<T, U, VH extends ViewHolder>
extends AbstractViewAdapter<T, VH> {
private final ViewAdapter<U, VH> mBase;
- private final Function<T, U> mFn;
+ private final Function<T, ? extends U> mFn;
@Override
public View createView(final ViewGroup parent) {
diff --git a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ViewAdapter.java b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ViewAdapter.java
index 6a0de8f..7883265 100644
--- a/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ViewAdapter.java
+++ b/baku-toolkit/lib/src/main/java/io/v/baku/toolkit/bind/ViewAdapter.java
@@ -8,12 +8,12 @@
import android.view.View;
import android.view.ViewGroup;
-import java8.lang.FunctionalInterface;
+import java8.util.function.Function;
-@FunctionalInterface
public interface ViewAdapter<T, VH extends ViewHolder> {
View createView(ViewGroup parent);
VH createViewHolder(View view);
void bindViewHolder(VH viewHolder, int position, T value);
void bindView(View view, int position, T value);
+ <U> ViewAdapter<U, VH> map(final Function<U, ? extends T> fn);
}
\ No newline at end of file
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/GlobalUserSyncgroup.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/GlobalUserSyncgroup.java
index 17e594a..7bafee7 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/GlobalUserSyncgroup.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/GlobalUserSyncgroup.java
@@ -52,12 +52,6 @@
return builder().activity(t).build();
}
- /*
- As of Lombok IntelliJ 0.9.6, @Builder exhibits a few bugs interacting with @Accessors (Gradle
- build is fine).
-
- https://github.com/mplushnikov/lombok-intellij-plugin/issues/151
- */
public static class Builder {
private String sgSuffix = DEFAULT_SYNCGROUP_SUFFIX;
private Function<String, String> descriptionForUsername = u -> "User syncgroup for " + u;
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RangeWatchEvent.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RangeWatchEvent.java
index 6810f50..5de69b4 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RangeWatchEvent.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RangeWatchEvent.java
@@ -4,6 +4,8 @@
package io.v.rx.syncbase;
+import com.google.common.reflect.TypeToken;
+
import java.util.Map;
import io.v.v23.syncbase.nosql.ChangeType;
@@ -21,19 +23,25 @@
boolean mFromSync;
@SuppressWarnings("unchecked")
- private static <T> T getWatchValue(final WatchChange change, final Class<T> type)
+ private static <T> T getWatchValue(final WatchChange change, final TypeToken<T> tt)
throws VException {
if (change.getChangeType() == ChangeType.DELETE_CHANGE) {
return null;
} else {
- return (T) VomUtil.decode(change.getVomValue(), type);
+ return (T) VomUtil.decode(change.getVomValue(),
+ tt == null? Object.class : tt.getType());
}
}
+ public static <T> RangeWatchEvent<T> fromWatchChange(final WatchChange c, final TypeToken<T> tt)
+ throws VException {
+ return new RangeWatchEvent<>(new RxTable.Row<>(c.getRowName(), getWatchValue(c, tt)),
+ c.getChangeType(), c.isFromSync());
+ }
+
public static <T> RangeWatchEvent<T> fromWatchChange(final WatchChange c, final Class<T> type)
throws VException {
- return new RangeWatchEvent<>(new RxTable.Row<>(c.getRowName(), getWatchValue(c, type)),
- c.getChangeType(), c.isFromSync());
+ return fromWatchChange(c, TypeToken.of(type));
}
public void applyTo(final Map<String, T> accumulator) {
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java
index 15ba1c0..ead7b5f 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/RxTable.java
@@ -7,6 +7,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -92,11 +93,11 @@
}
private <T> Observable<T> getInitial(
- final BatchDatabase db, final String tableName, final String key, final Class<T> type,
+ final BatchDatabase db, final String tableName, final String key, final TypeToken<T> tt,
final T defaultValue) {
@SuppressWarnings("unchecked")
final ListenableFuture<T> fromGet = (ListenableFuture<T>) db.getTable(tableName).get(
- mVContext, key, type);
+ mVContext, key, tt == null ? Object.class : tt.getType());
return toObservable(Futures.withFallback(fromGet, t -> t instanceof NoExistException ?
Futures.immediateFuture(defaultValue) : Futures.immediateFailedFuture(t)));
}
@@ -104,14 +105,14 @@
@SuppressWarnings("unchecked")
private <T> Observable<Row<T>> getInitial(
final BatchDatabase db, final String tableName, final RowRange keys,
- @Nullable final Func1<String, Boolean> keyFilter, final Class<T> type) {
+ @Nullable final Func1<String, Boolean> keyFilter, final TypeToken<T> tt) {
Observable<KeyValue> untyped = RxInputChannel.wrap(
db.getTable(tableName).scan(mVContext, keys)).autoConnect();
if (keyFilter != null) {
untyped = untyped.filter(kv -> keyFilter.call(kv.getKey()));
}
return untyped.concatMap(VFn.wrap(kv -> new Row<>(kv.getKey(),
- (T) VomUtil.decode(kv.getValue(), type))));
+ (T) VomUtil.decode(kv.getValue(), tt == null ? Object.class : tt.getType()))));
}
/**
@@ -126,14 +127,15 @@
* once, as we can only iterate over the underlying stream once.
*/
private static <T> Observable<SingleWatchEvent<T>> observeWatchStream(
- final InputChannel<WatchChange> s, final String key, final T defaultValue) {
+ final InputChannel<WatchChange> s, final String key, final TypeToken<T> tt,
+ final T defaultValue) {
return RxInputChannel.wrap(s)
.autoConnect()
.filter(c -> c.getRowName().equals(key))
// About the Vfn.wrap, on error, the wrapping replay will disconnect,
// calling cancellation (see cancelOnDisconnect). The possible source of
// VException here is VOM decoding.
- .concatMap(VFn.wrap(c -> SingleWatchEvent.fromWatchChange(c, defaultValue)))
+ .concatMap(VFn.wrap(c -> SingleWatchEvent.fromWatchChange(c, tt, defaultValue)))
.distinctUntilChanged();
}
@@ -181,7 +183,7 @@
*/
private static <T> Observable<RangeWatchBatch<T>> observeWatchStream(
final InputChannel<WatchChange> s, @Nullable final Func1<String, Boolean> prefixFilter,
- final Class<T> type) {
+ final TypeToken<T> tt) {
// TODO(rosswang): support other RowRange types
final Observable<WatchChange> raw = RxInputChannel.wrap(s).autoConnect();
@@ -193,7 +195,7 @@
if (prefixFilter == null || prefixFilter.call(c.getRowName())) {
try {
windower.onNext(c.getResumeMarker(),
- RangeWatchEvent.fromWatchChange(c, type));
+ RangeWatchEvent.fromWatchChange(c, tt));
} catch (final VException e) {
windower.onError(c.getResumeMarker(), e);
}
@@ -246,11 +248,11 @@
}
private <T> void subscribeWatch(final Subscriber<? super SingleWatchEvent<T>> subscriber,
- final Database db, final String key, final Class<T> type,
+ final Database db, final String key, final TypeToken<T> tt,
final T defaultValue) {
subscribeWatch(subscriber, db, key,
- b -> getInitial(b, mName, key, type, defaultValue),
- s -> observeWatchStream(s, key, defaultValue),
+ b -> getInitial(b, mName, key, tt, defaultValue),
+ s -> observeWatchStream(s, key, tt, defaultValue),
(i, s) -> s.startWith(i.initial.map(iv ->
new SingleWatchEvent<>(iv, i.resumeMarker, false))));
}
@@ -258,10 +260,10 @@
private <T> void subscribeWatch(
final Subscriber<? super RangeWatchBatch<T>> subscriber, final Database db,
final PrefixRange prefix, @Nullable final Func1<String, Boolean> keyFilter,
- final Class<T> type) {
+ final TypeToken<T> tt) {
subscribeWatch(subscriber, db, prefix.getPrefix(),
- b -> getInitial(b, mName, prefix, keyFilter, type),
- s -> RxTable.observeWatchStream(s, keyFilter, type),
+ b -> getInitial(b, mName, prefix, keyFilter, tt),
+ s -> RxTable.observeWatchStream(s, keyFilter, tt),
(i, s) -> s.startWith(new RangeWatchBatch<>(i.resumeMarker, i.initial.map(r ->
new RangeWatchEvent<>(r, ChangeType.PUT_CHANGE, false)))));
}
@@ -277,10 +279,10 @@
/**
* Watches a specific Syncbase row for changes.
*/
- public <T> Observable<SingleWatchEvent<T>> watch(final String key, final Class<T> type,
+ public <T> Observable<SingleWatchEvent<T>> watch(final String key, final TypeToken<T> tt,
final T defaultValue) {
return this.<SingleWatchEvent<T>>watch((db, s) ->
- subscribeWatch(s, db, key, type, defaultValue))
+ subscribeWatch(s, db, key, tt, defaultValue))
// Don't create new watch streams for subsequent subscribers, but do cancel the
// stream if no subscribers are listening (and restart if new subscriptions happen).
.replay(1)
@@ -288,12 +290,29 @@
}
/**
+ * Watches a specific Syncbase row for changes.
+ */
+ public <T> Observable<SingleWatchEvent<T>> watch(final String key, final Class<T> type,
+ final T defaultValue) {
+ return watch(key, TypeToken.of(type), defaultValue);
+ }
+
+ /**
+ * Watches a Syncbase prefix for changes.
+ */
+ public <T> Observable<RangeWatchBatch<T>> watch(
+ final PrefixRange prefix, @Nullable final Func1<String, Boolean> keyFilter,
+ final TypeToken<T> tt) {
+ return watch((db, s) -> subscribeWatch(s, db, prefix, keyFilter, tt));
+ }
+
+ /**
* Watches a Syncbase prefix for changes.
*/
public <T> Observable<RangeWatchBatch<T>> watch(
final PrefixRange prefix, @Nullable final Func1<String, Boolean> keyFilter,
final Class<T> type) {
- return watch((db, s) -> subscribeWatch(s, db, prefix, keyFilter, type));
+ return watch(prefix, keyFilter, TypeToken.of(type));
}
/**
diff --git a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SingleWatchEvent.java b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SingleWatchEvent.java
index 2f76ae8..69f1741 100644
--- a/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SingleWatchEvent.java
+++ b/baku-toolkit/lib/src/main/java/io/v/rx/syncbase/SingleWatchEvent.java
@@ -4,11 +4,14 @@
package io.v.rx.syncbase;
+import com.google.common.reflect.TypeToken;
+
import io.v.v23.services.watch.ResumeMarker;
import io.v.v23.syncbase.nosql.ChangeType;
import io.v.v23.syncbase.nosql.WatchChange;
import io.v.v23.verror.VException;
import io.v.v23.vom.VomUtil;
+import java8.util.function.Function;
import lombok.Value;
import lombok.experimental.Accessors;
@@ -20,18 +23,28 @@
boolean mFromSync;
@SuppressWarnings("unchecked")
- private static <T> T getWatchValue(final WatchChange change, final T defaultValue)
- throws VException {
+ private static <T> T getWatchValue(final WatchChange change, final TypeToken<T> tt,
+ final T defaultValue) throws VException {
if (change.getChangeType() == ChangeType.DELETE_CHANGE) {
return defaultValue;
} else {
- return (T) VomUtil.decode(change.getVomValue());
+ return (T) VomUtil.decode(change.getVomValue(),
+ tt == null? Object.class : tt.getType());
}
}
- public static <T> SingleWatchEvent<T> fromWatchChange(final WatchChange c, final T defaultValue)
- throws VException {
- return new SingleWatchEvent<>(getWatchValue(c, defaultValue),
+ public static <T> SingleWatchEvent<T> fromWatchChange(
+ final WatchChange c, final TypeToken<T> tt, final T defaultValue) throws VException {
+ return new SingleWatchEvent<>(getWatchValue(c, tt, defaultValue),
c.getResumeMarker(), c.isFromSync());
}
+
+ public static <T> SingleWatchEvent<T> fromWatchChange(
+ final WatchChange c, final Class<T> type, final T defaultValue) throws VException {
+ return fromWatchChange(c, TypeToken.of(type), defaultValue);
+ }
+
+ public <U> SingleWatchEvent<U> map(final Function<T, U> fn) {
+ return new SingleWatchEvent<>(fn.apply(mValue), mResumeMarker, mFromSync);
+ }
}