From 631cf157ddbc7751593413fce425152b2c793151 Mon Sep 17 00:00:00 2001 From: Gavriil Sitnikov Date: Thu, 9 Mar 2017 01:55:03 +0300 Subject: [PATCH] storable bug fix + static fixes --- build.gradle | 6 +- .../core/log/ConsoleLogProcessor.java | 7 +- .../java/ru/touchin/roboswag/core/log/Lc.java | 4 +- .../OnSubscribeRefCountWithCacheTime.java | 212 ++++++++++++++++++ .../roboswag/core/observables/RxUtils.java | 41 ---- .../core/observables/storable/Storable.java | 148 +++++++----- .../builders/NonNullStorableBuilder.java | 43 ++++ .../storable/concrete/NonNullStorable.java | 2 +- 8 files changed, 361 insertions(+), 102 deletions(-) create mode 100644 src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java delete mode 100644 src/main/java/ru/touchin/roboswag/core/observables/RxUtils.java diff --git a/build.gradle b/build.gradle index a014e9a..4f12a5a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' apply plugin: 'me.tatarka.retrolambda' android { - compileSdkVersion 24 + compileSdkVersion 25 buildToolsVersion '25.0.2' compileOptions { @@ -12,11 +12,11 @@ android { defaultConfig { minSdkVersion 9 - targetSdkVersion 24 + targetSdkVersion 25 } } dependencies { - provided 'com.android.support:support-annotations:24.2.1' + provided 'com.android.support:support-annotations:25.2.0' provided 'io.reactivex:rxandroid:1.2.1' } diff --git a/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java b/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java index 04af630..fd7303d 100644 --- a/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java +++ b/src/main/java/ru/touchin/roboswag/core/log/ConsoleLogProcessor.java @@ -41,6 +41,8 @@ public class ConsoleLogProcessor extends LogProcessor { } @Override + @SuppressWarnings("WrongConstant") + //WrongConstant: level.getPriority() is not wrong constant! public void processLogMessage(@NonNull final LcGroup group, @NonNull final LcLevel level, @NonNull final String tag, @NonNull final String message, @Nullable final Throwable throwable) { final String messageToLog = normalize(message + (throwable != null ? '\n' + Log.getStackTraceString(throwable) : "")); @@ -50,8 +52,9 @@ public class ConsoleLogProcessor extends LogProcessor { newline = newline != -1 ? newline : length; do { final int end = Math.min(newline, i + MAX_LOG_LENGTH); - //noinspection WrongConstant - Log.println(level.getPriority(), tag, messageToLog.substring(i, end)); + if (Log.isLoggable(tag, level.getPriority())) { + Log.println(level.getPriority(), tag, messageToLog.substring(i, end)); + } i = end; } while (i < newline); diff --git a/src/main/java/ru/touchin/roboswag/core/log/Lc.java b/src/main/java/ru/touchin/roboswag/core/log/Lc.java index 8ac2c6c..08f554b 100644 --- a/src/main/java/ru/touchin/roboswag/core/log/Lc.java +++ b/src/main/java/ru/touchin/roboswag/core/log/Lc.java @@ -264,7 +264,9 @@ public final class Lc { public static void printStackTrace(@NonNull final String tag) { final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); - Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length))); + if (Log.isLoggable(tag, Log.DEBUG)) { + Log.d(tag, TextUtils.join("\n", Arrays.copyOfRange(stackTrace, STACK_TRACE_CODE_DEPTH, stackTrace.length))); + } } private Lc() { diff --git a/src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java b/src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java new file mode 100644 index 0000000..721bb99 --- /dev/null +++ b/src/main/java/ru/touchin/roboswag/core/observables/OnSubscribeRefCountWithCacheTime.java @@ -0,0 +1,212 @@ +/** + * Copyright 2014 Netflix, Inc. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package ru.touchin.roboswag.core.observables; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReentrantLock; + +import rx.Observable.OnSubscribe; +import rx.Scheduler; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.observables.ConnectableObservable; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; + +/** + * Returns an observable sequence that stays connected to the source as long as + * there is at least one subscription to the observable sequence and also it stays connected + * for cache time after everyone unsubscribe. + * + * @param the value type + */ +@SuppressWarnings({"PMD.AvoidUsingVolatile", "PMD.CompareObjectsWithEquals"}) +//AvoidUsingVolatile,CompareObjectsWithEquals: from OnSubscribeRefCount code +public final class OnSubscribeRefCountWithCacheTime implements OnSubscribe { + + @NonNull + private final ConnectableObservable source; + @NonNull + private volatile CompositeSubscription baseSubscription = new CompositeSubscription(); + @NonNull + private final AtomicInteger subscriptionCount = new AtomicInteger(0); + + @NonNull + private final Scheduler scheduler = Schedulers.computation(); + private final long cacheTime; + @NonNull + private final TimeUnit cacheTimeUnit; + @Nullable + private Scheduler.Worker worker; + + /** + * Use this lock for every subscription and disconnect action. + */ + @NonNull + private final ReentrantLock lock = new ReentrantLock(); + + public OnSubscribeRefCountWithCacheTime(@NonNull final ConnectableObservable source, + final long cacheTime, @NonNull final TimeUnit cacheTimeUnit) { + this.source = source; + this.cacheTime = cacheTime; + this.cacheTimeUnit = cacheTimeUnit; + } + + @Override + public void call(@NonNull final Subscriber subscriber) { + + lock.lock(); + if (subscriptionCount.incrementAndGet() == 1) { + if (worker != null) { + worker.unsubscribe(); + worker = null; + } + final AtomicBoolean writeLocked = new AtomicBoolean(true); + + try { + // need to use this overload of connect to ensure that + // baseSubscription is set in the case that source is a + // synchronous Observable + source.connect(onSubscribe(subscriber, writeLocked)); + } finally { + // need to cover the case where the source is subscribed to + // outside of this class thus preventing the Action1 passed + // to source.connect above being called + if (writeLocked.get()) { + // Action1 passed to source.connect was not called + lock.unlock(); + } + } + } else { + try { + // ready to subscribe to source so do it + doSubscribe(subscriber, baseSubscription); + } finally { + // release the read lock + lock.unlock(); + } + } + + } + + @NonNull + private Action1 onSubscribe(@NonNull final Subscriber subscriber, + @NonNull final AtomicBoolean writeLocked) { + return new Action1() { + @Override + public void call(@NonNull final Subscription subscription) { + + try { + baseSubscription.add(subscription); + // ready to subscribe to source so do it + doSubscribe(subscriber, baseSubscription); + } finally { + // release the write lock + lock.unlock(); + writeLocked.set(false); + } + } + }; + } + + private void doSubscribe(@NonNull final Subscriber subscriber, @NonNull final CompositeSubscription currentBase) { + // handle unsubscribing from the base subscription + subscriber.add(disconnect(currentBase)); + + source.unsafeSubscribe(new Subscriber(subscriber) { + @Override + public void onError(@NonNull final Throwable throwable) { + cleanup(); + subscriber.onError(throwable); + } + + @Override + public void onNext(@Nullable final T item) { + subscriber.onNext(item); + } + + @Override + public void onCompleted() { + cleanup(); + subscriber.onCompleted(); + } + + private void cleanup() { + // on error or completion we need to unsubscribe the base subscription + // and set the subscriptionCount to 0 + lock.lock(); + try { + if (baseSubscription == currentBase) { + baseSubscription.unsubscribe(); + baseSubscription = new CompositeSubscription(); + subscriptionCount.set(0); + } + } finally { + lock.unlock(); + } + } + }); + } + + @NonNull + private Subscription disconnect(@NonNull final CompositeSubscription current) { + return Subscriptions.create(new Action0() { + + @Override + public void call() { + lock.lock(); + try { + if (baseSubscription == current && subscriptionCount.decrementAndGet() == 0) { + if (worker != null) { + worker.unsubscribe(); + } else { + worker = scheduler.createWorker(); + } + worker.schedule(new Action0() { + @Override + public void call() { + lock.lock(); + try { + if (subscriptionCount.get() == 0) { + baseSubscription.unsubscribe(); + // need a new baseSubscription because once + // unsubscribed stays that way + worker.unsubscribe(); + worker = null; + baseSubscription = new CompositeSubscription(); + } + } finally { + lock.unlock(); + } + } + }, cacheTime, cacheTimeUnit); + } + } finally { + lock.unlock(); + } + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/RxUtils.java b/src/main/java/ru/touchin/roboswag/core/observables/RxUtils.java deleted file mode 100644 index 5da00f1..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/RxUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.touchin.roboswag.core.observables; - -import android.support.annotation.NonNull; - -import java.util.concurrent.CountDownLatch; - -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; -import rx.Observable; - -/** - * Created by Gavriil Sitnikov on 21/05/2016. - * Some helper methods to work with JavaRx. - */ -public final class RxUtils { - - /** - * Subscribes to specific {@link Observable} and waits for it's onCompleted event - * and then returns {@link ObservableResult} with all collected items and errors during subscription. - * You should NOT use such method normally. It is safer than {@link Observable#toBlocking()} but it is also like a hack. - * - * @param observable {@link Observable} to be executed; - * @param Type of {@link Observable}'s items; - * @return {@link ObservableResult} which contains all items and errors collected during execution. - */ - @NonNull - public static ObservableResult executeSync(@NonNull final Observable observable) { - final ObservableResult result = new ObservableResult<>(); - final CountDownLatch waiter = new CountDownLatch(1); - observable.subscribe(result::onNext, result::onError, waiter::countDown); - try { - waiter.await(); - } catch (final InterruptedException exception) { - throw new ShouldNotHappenException(exception); - } - return result; - } - - private RxUtils() { - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java index 7841f50..e6529c2 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/Storable.java @@ -24,13 +24,12 @@ import android.support.annotation.Nullable; import java.lang.reflect.Type; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import ru.touchin.roboswag.core.log.LcGroup; -import ru.touchin.roboswag.core.observables.ObservableResult; -import ru.touchin.roboswag.core.observables.RxUtils; +import ru.touchin.roboswag.core.observables.OnSubscribeRefCountWithCacheTime; import ru.touchin.roboswag.core.observables.storable.builders.NonNullStorableBuilder; import ru.touchin.roboswag.core.utils.ObjectUtils; -import ru.touchin.roboswag.core.utils.ShouldNotHappenException; import rx.Completable; import rx.Observable; import rx.Scheduler; @@ -55,6 +54,8 @@ public class Storable { public static final LcGroup STORABLE_LC_GROUP = new LcGroup("STORABLE"); + private static final long DEFAULT_CACHE_TIME_MILLIS = TimeUnit.SECONDS.toMillis(5); + @NonNull private static ObserveStrategy getDefaultObserveStrategyFor(@NonNull final Type objectType, @NonNull final Type storeObjectType) { if (objectType instanceof Class && ObjectUtils.isSimpleClass((Class) objectType)) { @@ -88,18 +89,21 @@ public class Storable { public Storable(@NonNull final BuilderCore builderCore) { this(builderCore.key, builderCore.objectType, builderCore.storeObjectType, builderCore.store, builderCore.converter, builderCore.observeStrategy, - builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler); + builderCore.migration, builderCore.defaultValue, builderCore.storeScheduler, builderCore.cacheTimeMillis); } - public Storable(@NonNull final TKey key, - @NonNull final Type objectType, - @NonNull final Type storeObjectType, - @NonNull final Store store, - @NonNull final Converter converter, - @Nullable final ObserveStrategy observeStrategy, - @Nullable final Migration migration, - @Nullable final TObject defaultValue, - @Nullable final Scheduler storeScheduler) { + @SuppressWarnings("PMD.ExcessiveParameterList") + //ExcessiveParameterList: that's why we are using builder to create it + private Storable(@NonNull final TKey key, + @NonNull final Type objectType, + @NonNull final Type storeObjectType, + @NonNull final Store store, + @NonNull final Converter converter, + @Nullable final ObserveStrategy observeStrategy, + @Nullable final Migration migration, + @Nullable final TObject defaultValue, + @Nullable final Scheduler storeScheduler, + final long cacheTimeMillis) { this.key = key; this.objectType = objectType; this.storeObjectType = storeObjectType; @@ -109,8 +113,8 @@ public class Storable { = observeStrategy != null ? observeStrategy : getDefaultObserveStrategyFor(objectType, storeObjectType); scheduler = storeScheduler != null ? storeScheduler : Schedulers.from(Executors.newSingleThreadExecutor()); storeValueObservable - = createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue); - valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy); + = createStoreValueObservable(nonNullObserveStrategy, migration, defaultValue, cacheTimeMillis); + valueObservable = createValueObservable(storeValueObservable, nonNullObserveStrategy, cacheTimeMillis); } @Nullable @@ -134,7 +138,8 @@ public class Storable { @NonNull private Observable createStoreValueObservable(@NonNull final ObserveStrategy observeStrategy, @Nullable final Migration migration, - @Nullable final TObject defaultValue) { + @Nullable final TObject defaultValue, + final long cacheTimeMillis) { final Observable result = (migration != null ? migration.migrateToLatestVersion(key).subscribeOn(scheduler) : Completable.complete()) @@ -149,13 +154,14 @@ public class Storable { .concatWith(newStoreValueEvent) .map(storeObject -> returnDefaultValueIfNull(storeObject, defaultValue)); return observeStrategy == ObserveStrategy.CACHE_STORE_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE - ? result.replay(1).refCount() + ? Observable.create(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) : result; } @NonNull private Observable createValueObservable(@NonNull final Observable storeValueObservable, - @NonNull final ObserveStrategy observeStrategy) { + @NonNull final ObserveStrategy observeStrategy, + final long cacheTimeMillis) { final Observable result = storeValueObservable .switchMap(storeObject -> Observable .fromCallable(() -> converter.toObject(objectType, storeObjectType, storeObject)) @@ -169,7 +175,7 @@ public class Storable { } })); return observeStrategy == ObserveStrategy.CACHE_ACTUAL_VALUE || observeStrategy == ObserveStrategy.CACHE_STORE_AND_ACTUAL_VALUE - ? result.replay(1).refCount() + ? Observable.create(new OnSubscribeRefCountWithCacheTime<>(result.replay(1), cacheTimeMillis, TimeUnit.MILLISECONDS)) : result; } @@ -223,28 +229,24 @@ public class Storable { return converter; } - /** - * Creates observable which is async setting value to store. - * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! //TODO: it's Completable :( - * - * @param newValue Value to set; - * @return Observable of setting process. - */ @NonNull - public Observable set(@Nullable final TObject newValue) { - return storeValueObservable - .first() + private Completable internalSet(@Nullable final TObject newValue, final boolean checkForEqualityBeforeSet) { + return (checkForEqualityBeforeSet ? storeValueObservable.first() : Observable.just(null)) .switchMap(oldStoreValue -> Observable .fromCallable(() -> converter.toStoreObject(objectType, storeObjectType, newValue)) .subscribeOn(scheduler) .switchMap(newStoreValue -> { - if (ObjectUtils.equals(newStoreValue, oldStoreValue)) { + if (checkForEqualityBeforeSet && ObjectUtils.equals(newStoreValue, oldStoreValue)) { return Observable.empty(); } return store.storeObject(storeObjectType, key, newStoreValue) .doOnCompleted(() -> { newStoreValueEvent.onNext(newStoreValue); - STORABLE_LC_GROUP.i("Value of '%s' changed from '%s' to '%s'", key, oldStoreValue, newStoreValue); + if (checkForEqualityBeforeSet) { + STORABLE_LC_GROUP.i("Value of '%s' changed from '%s' to '%s'", key, oldStoreValue, newStoreValue); + } else { + STORABLE_LC_GROUP.i("Value of '%s' force changed to '%s'", key, newStoreValue); + } }) .toObservable(); })) @@ -255,23 +257,48 @@ public class Storable { STORABLE_LC_GROUP.w(throwable, "Exception while trying to store value of '%s' from store %s by %s", key, newValue, store, converter); } - }); + }) + .toCompletable(); + } + + /** + * Creates observable which is async setting value to store. + * It is not checking if stored value equals new value. + * In result it will be faster to not get value from store and compare but it will emit item to {@link #observe()} subscribers. + * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! //TODO: it's Completable :( + * + * @param newValue Value to set; + * @return Observable of setting process. + */ + @NonNull + public Observable forceSet(@Nullable final TObject newValue) { + return internalSet(newValue, false).toObservable(); + } + + /** + * Creates observable which is async setting value to store. + * It is checking if stored value equals new value. + * In result it will take time to get value from store and compare + * but it won't emit item to {@link #observe()} subscribers if stored value equals new value. + * NOTE: It could emit ONLY completed and errors events. It is not providing onNext event! //TODO: it's Completable :( + * + * @param newValue Value to set; + * @return Observable of setting process. + */ + @NonNull + public Observable set(@Nullable final TObject newValue) { + return internalSet(newValue, true).toObservable(); } /** * Sets value synchronously. You should NOT use this method normally. Use {@link #set(Object)} asynchronously instead. * * @param newValue Value to set; - * @throws Converter.ConversionException Throws if {@link Converter} threw exception during conversion; - * @throws Migration.MigrationException Throws if {@link Migration} threw exception during migration. */ @Deprecated //deprecation: it should be used for debug only and in very rare cases. - public void setSync(@Nullable final TObject newValue) throws Throwable { - final ObservableResult setResult = RxUtils.executeSync(set(newValue)); - if (setResult.getError() != null) { - throw setResult.getError(); - } + public void setSync(@Nullable final TObject newValue) { + set(newValue).toBlocking().subscribe(); } /** @@ -300,21 +327,12 @@ public class Storable { * Gets value synchronously. You should NOT use this method normally. Use {@link #get()} or {@link #observe()} asynchronously instead. * * @return Returns value; - * @throws Converter.ConversionException Throws if {@link Converter} threw exception during conversion; - * @throws Migration.MigrationException Throws if {@link Migration} threw exception during migration. */ @Deprecated //deprecation: it should be used for debug only and in very rare cases. @Nullable - public TObject getSync() throws Throwable { - final ObservableResult getResult = RxUtils.executeSync(get()); - if (getResult.getError() != null) { - throw getResult.getError(); - } - if (getResult.getItems().size() != 1) { - throw new ShouldNotHappenException(); - } - return getResult.getItems().get(0); + public TObject getSync() { + return get().toBlocking().first(); } /** @@ -376,23 +394,25 @@ public class Storable { private TObject defaultValue; @Nullable private Scheduler storeScheduler; + private long cacheTimeMillis; protected BuilderCore(@NonNull final TKey key, @NonNull final Type objectType, @NonNull final Type storeObjectType, @NonNull final Store store, @NonNull final Converter converter) { - this(key, objectType, storeObjectType, store, converter, null, null, null, null); + this(key, objectType, storeObjectType, store, converter, null, null, null, null, DEFAULT_CACHE_TIME_MILLIS); } protected BuilderCore(@NonNull final BuilderCore sourceBuilder) { this(sourceBuilder.key, sourceBuilder.objectType, sourceBuilder.storeObjectType, sourceBuilder.store, sourceBuilder.converter, sourceBuilder.observeStrategy, - sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler); + sourceBuilder.migration, sourceBuilder.defaultValue, sourceBuilder.storeScheduler, sourceBuilder.cacheTimeMillis); } - @SuppressWarnings("CPD-START") + @SuppressWarnings({"PMD.ExcessiveParameterList", "CPD-START"}) //CPD: it is same code as constructor of Storable + //ExcessiveParameterList: that's why we are using builder to create it private BuilderCore(@NonNull final TKey key, @NonNull final Type objectType, @NonNull final Type storeObjectType, @@ -401,7 +421,8 @@ public class Storable { @Nullable final ObserveStrategy observeStrategy, @Nullable final Migration migration, @Nullable final TObject defaultValue, - @Nullable final Scheduler storeScheduler) { + @Nullable final Scheduler storeScheduler, + final long cacheTimeMillis) { this.key = key; this.objectType = objectType; this.storeObjectType = storeObjectType; @@ -411,6 +432,7 @@ public class Storable { this.migration = migration; this.defaultValue = defaultValue; this.storeScheduler = storeScheduler; + this.cacheTimeMillis = cacheTimeMillis; } @SuppressWarnings("CPD-END") @@ -426,6 +448,10 @@ public class Storable { this.migration = migration; } + protected void setCacheTimeInternal(final long cacheTime, @NonNull final TimeUnit timeUnit) { + this.cacheTimeMillis = timeUnit.toMillis(cacheTime); + } + @Nullable protected TObject getDefaultValue() { return defaultValue; @@ -478,6 +504,20 @@ public class Storable { return this; } + /** + * Sets cache time for while value that cached by {@link #setObserveStrategy(ObserveStrategy)} will be in memory after everyone unsubscribe. + * It is important for example for cases when user switches between screens and hide/open app very fast. + * + * @param cacheTime Cache time value; + * @param timeUnit Cache time units. + * @return Builder that allows to specify other fields. + */ + @NonNull + public Builder setCacheTime(final long cacheTime, @NonNull final TimeUnit timeUnit) { + setCacheTimeInternal(cacheTime, timeUnit); + return this; + } + /** * Sets specific {@link Migration} to migrate values from specific version to latest version. * diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java index 65c4716..ab5cfba 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/builders/NonNullStorableBuilder.java @@ -20,11 +20,15 @@ package ru.touchin.roboswag.core.observables.storable.builders; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import java.util.concurrent.TimeUnit; import ru.touchin.roboswag.core.observables.storable.Migration; import ru.touchin.roboswag.core.observables.storable.Storable; import ru.touchin.roboswag.core.observables.storable.concrete.NonNullStorable; import ru.touchin.roboswag.core.utils.ShouldNotHappenException; +import rx.Scheduler; /** * Created by Gavriil Sitnikov on 15/05/2016. @@ -42,6 +46,45 @@ public class NonNullStorableBuilder extends Storabl setDefaultValueInternal(defaultValue); } + /** + * Sets specific {@link Scheduler} to store/load/convert values on it. + * + * @param storeScheduler Scheduler; + * @return Builder that allows to specify other fields. + */ + @NonNull + public NonNullStorableBuilder setStoreScheduler(@Nullable final Scheduler storeScheduler) { + setStoreSchedulerInternal(storeScheduler); + return this; + } + + /** + * Sets specific {@link Storable.ObserveStrategy} to cache value in memory in specific way. + * + * @param observeStrategy ObserveStrategy; + * @return Builder that allows to specify other fields. + */ + @NonNull + public NonNullStorableBuilder setObserveStrategy(@Nullable final Storable.ObserveStrategy observeStrategy) { + setObserveStrategyInternal(observeStrategy); + return this; + } + + /** + * Sets cache time for while value that cached by {@link #setObserveStrategy(Storable.ObserveStrategy)} + * will be in memory after everyone unsubscribe. + * It is important for example for cases when user switches between screens and hide/open app very fast. + * + * @param cacheTime Cache time value; + * @param timeUnit Cache time units. + * @return Builder that allows to specify other fields. + */ + @NonNull + public NonNullStorableBuilder setCacheTime(final long cacheTime, @NonNull final TimeUnit timeUnit) { + setCacheTimeInternal(cacheTime, timeUnit); + return this; + } + /** * Sets specific {@link Migration} to migrate values from specific version to latest version. * diff --git a/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java b/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java index 95dc8ec..16840a9 100644 --- a/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java +++ b/src/main/java/ru/touchin/roboswag/core/observables/storable/concrete/NonNullStorable.java @@ -42,7 +42,7 @@ public class NonNullStorable extends Storable