Git Product home page Git Product logo

rx-preferences's Introduction

DEPRECATED

rx-preferences is deprecated. Please consider using a replacement, such as this. Thank you for using the library!

Rx Preferences

Reactive SharedPreferences for Android.

Usage

Create an RxSharedPreferences instance which wraps a SharedPreferences:

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
RxSharedPreferences rxPreferences = RxSharedPreferences.create(preferences);

Hint: Keep a strong reference on your RxSharedPreferences instance for as long as you want to observe them to prevent listeners from being GCed.

Create individual Preference objects:

Preference<String> username = rxPreferences.getString("username");
Preference<Boolean> showWhatsNew = rxPreferences.getBoolean("show-whats-new", true);

Observe changes to individual preferences:

username.asObservable().subscribe(new Action1<String>() {
  @Override public void call(String username) {
    Log.d(TAG, "Username: " + username);
  }
}

Subscribe preferences to streams to store values:

RxCompoundButton.checks(showWhatsNewView)
    .subscribe(showWhatsNew.asConsumer());

(Note: RxCompoundButton is from RxBinding)

Download

implementation 'com.f2prateek.rx.preferences2:rx-preferences:2.0.1'

License

Copyright 2014 Prateek Srivastava

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.

rx-preferences's People

Contributors

andrewreitz avatar banasiak avatar bemusementpark avatar braisgabin avatar egor-n avatar f2prateek avatar freszu avatar jakewharton avatar m4xp1 avatar michaelevans avatar nekdenis avatar nightlynexus avatar paulwoitaschek avatar r-gurzkowski avatar rjrjr avatar ubuntudroid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rx-preferences's Issues

Feature/3.0 request: Hide Adapter from implementers

Users can't do anything special with the SharedPreferences or Editor except store strings.
Instead of an Adapter, how about making the Adapter internal and exposing a Converter:

public interface Converter<T> {
  @NonNull T deserialize(@NonNull String serialized);
  @NonNull String serialize(@NonNull T value);
}

Internally, the adapter delegates to the supplied Converter.

final class CustomTypeStringAdapter<T> implements RealPreference.Adapter<T> {
  private final Converter<T> converter;
  
  CustomTypeStringAdapter(Converter<T> converter) {
    this.converter = converter;
  }
  
  @Override public T get(@NonNull String key,
      @NonNull SharedPreferences preferences) {
    String string = preferences.getString(key, null);
    if (string == null) {
      throw new AssertionError(); // Not called unless key is present.
    }
    return converter.deserialize(string);
  }
  
  @Override public void set(@NonNull String key, @NonNull T value,
      @NonNull SharedPreferences.Editor editor) {
    String serialized = converter.serialize(value);
    checkNotNull(serialized,
        "Serialized string must not be null from value: " + value);
    editor.putString(key, serialized);
  }
}

Emissions from RxSharedPreferences.keyChanges

When migrating from SharedPreferences to RxSharedPreferences, it's possible to have existing updates to SharedPreferences on the main thread. This ends up triggering onSharedPreferenceChanged() within RxSharedPreferences on the main thread, which eventually makes its way to RealPreference.get(). This can become an issue when using ConverterAdapter, since it allows deserialization of any Object.

Is there any way to observe the emissions from onSharedPreferenceChanged()/emitter.onNext(key) on a different Scheduler? Or does it require an update to the library? For example:

public static RxSharedPreferences create(@NonNull SharedPreferences preferences) {
    checkNotNull(preferences, "preferences == null");
    return new RxSharedPreferences(preferences, scheduler);
}

public static RxSharedPreferences createWithScheduler(@NonNull SharedPreferences preferences, @NonNull Scheduler scheduler) {
    checkNotNull(preferences, "preferences == null");
    checkNotNull(scheduler, "scheduler == null");
    return new RxSharedPreferences(preferences, scheduler);
}

private RxSharedPreferences(final SharedPreferences preferences, final Scheduler scheduler) {
    this.preferences = preferences;
    this.scheduler = scheduler;
    this.keyChanges = Observable.create(new ObservableOnSubscribe<String>() {
      @Override public void subscribe(final ObservableEmitter<String> emitter) throws Exception {
        final OnSharedPreferenceChangeListener listener = new OnSharedPreferenceChangeListener() {
          @Override
          public void onSharedPreferenceChanged(SharedPreferences preferences, String key) {
            emitter.onNext(key);
          }
        };

        emitter.setCancellable(new Cancellable() {
          @Override public void cancel() throws Exception {
            preferences.unregisterOnSharedPreferenceChangeListener(listener);
          }
        });

        preferences.registerOnSharedPreferenceChangeListener(listener);
      }
    }).share();
    if (scheduler != null) {
      this.keyChanges = keyChanges.observeOn(scheduler);
    }
  }

Add MemoryPreference artifact?

I have rewritten MemoryPreference a few times in various projects for tests and mock mode.

public final class MemoryPreference<T> implements Preference<T> {
  private final String key;
  private final T defaultValue;
  @Nullable private volatile T value;
  ...
}

Maybe there should be an artifact here for it?

Is the default value useful?

On the projects I work on, I never use the default value, and I always check isSet() first. If my data isn't there, I need to get it from some source (or throw an ISE, as the case may be).
Especially with disallowing null default values, I need to take care that that check exists everywhere.

Is it useful to have Preference.get() return a default value when the value is not set?

I may be missing good use cases.

Provide read-only super-interface for Preference

When using injection to inject preferences it is a good idea to differentiate between read-only and read-write access to the preference. I would like to see a super-interface for Preference that only exposes the read methods, Ideally, this would be the methods get and asObservable. You might want this interface to extend javax.inject.Provider which is an interface for just the get method.

NullPointerException for empty preferences.

With the change on Rx2 that null cannot be emitted, the simples code implementation like below crashes with NPE (stack below).

As far as I could find, it should be easy to fix.
The call that triggers a initial load on RealPreferences@31 (.startWith("<init>")) should only happen if(preferences.containsKey(key)

        RxSharedPreferences rxPref = RxSharedPreferences.create(getSharedPreferences("myprefs", 0));
        Preference<String> rxString = rxPref.getString("string");
        rxString.asObservable().subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {

            }
        });
java.lang.NullPointerException: The mapper function returned a null value.
at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:58)
at io.reactivex.internal.operators.observable.ObservableConcatMap$ConcatMapDelayErrorObserver.drain(ObservableConcatMap.java:462)
at io.reactivex.internal.operators.observable.ObservableConcatMap$ConcatMapDelayErrorObserver.onSubscribe(ObservableConcatMap.java:325)
at io.reactivex.internal.operators.observable.ObservableFromArray.subscribeActual(ObservableFromArray.java:29)
at io.reactivex.Observable.subscribe(Observable.java:10514)
at io.reactivex.internal.operators.observable.ObservableConcatMap.subscribeActual(ObservableConcatMap.java:54)
at io.reactivex.Observable.subscribe(Observable.java:10514)
at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:32)
at io.reactivex.Observable.subscribe(Observable.java:10514)
at io.reactivex.Observable.subscribe(Observable.java:10500)
at io.reactivex.Observable.subscribe(Observable.java:10403)

Backpressure is missing

The preference observables do not handle backpressure which will lead to an exception when it's applied aggressively.

Handling backpressure in a system like this is interesting because it should be applied after the filter operator rather than inside of the OnSubscribe. This ensures the pressure is applied to potentially-emitted stream values rather than simply any change to any key. When backpressure is applied and exceeded, values should be dropped and not emitted downstream (like onBackpressureDrop()). When backpressure is let off (i.e., more values are requested) and values were dropped, the operator should immediately emit a single value (the value of which doesn't matter because it's unused) so that the latest preference value is loaded and sent out.

Use case for commit...

In a developer feature, I want to kill the process immediately after setting a preference value. So, I need to ensure the value is written to the disk (using commit instead of apply).

This is a pretty rare use case, and I can't yet think of an API to propose.

Reconcile null handling

There's currently possibility of NPEs to be thrown on implicit unboxing. I have a branch that fixes this, but wanted to file the issue to make sure it was tracked by others.

Javadoc

  • toObservable
  • toAction
  • get/set/delete/isSet

2.0 Release

It seems like the last real change was for RC3 in August 2017. What about releasing the 2.0?

Evaluate moving to base class

Hadn't done this in the original (non-reactive version to avoid unnecessary auto boxing, but that might not be a concern with RxJava that creates so many short lived objects itself.

abstract class TypedPreference<T, R> {
  private final SharedPreferences sharedPreferences;
  private final String key;
  private final T defaultValue;

  public BooleanPreference(SharedPreferences preferences, String key) {
    this(preferences, key, null);
  }

  public BooleanPreference(SharedPreferences sharedPreferences, String key, T defaultValue) {
    this.sharedPreferences = assertNotNull(sharedPreferences);
    this.key = assertNotNullOrEmpty(key);
    this.defaultValue = defaultValue;
  }

  public abstract T get();

  public boolean isSet() {
    return sharedPreferences.contains(key);
  }

  public abstract void set(T value);

  public void delete() {
    sharedPreferences.edit().remove(key).commit();
  }

  public abstract Observable<R> asObservable();
}

Usecase for getAll()

Hey folks, does rx-preferences intentionally not include a way to observe all items? If not, I'd like to contribute by sending a PR.

transforming to single

Hi and thank you for your great effort on this library.
there is only one thing I'd like to ask:
when i use .asObservable() to get the result as a stream so I can transform it later in the chain (using concatMap or singleOrError or even toList) the chain won't work and .subscribe() won't get called.
as far as I could understand the reason for this problem is that you are using OnSharedPreferenceChangeListener to get changes not the value therefore by not calling .complete() in the onCreate of your observable. we won't be able to transform the chain to any form of Single (since it needs the sourceObservable to call onComplete to carry on)

is there any way we can do the transformation without breaking the chain?

Cache values in RealPreference

I imagine this has been considered long ago, but I didn't find any discussions in GH, at least. I'm considering doing this in my fork, so that's mostly why I'm filing ha.

It should be easy enough to be thread-safe, but I don't know how much speed there is to be gained from skipping the SharedPreference disk read.

Bulk Set/Observe

Is it possible to bulk set/observe several values?

For example I have one string and two enums, which I set at the same time.
Currently this causes as to be expected three emissions (I use combineLatest) .

Is there a way to delay the apply so all three values are set together, and observe this set as a whole, while still using different datatypes?

Expose preference key to Converter

The change from Adapter to Converter interface (#75) breaks some functionality use cases where serializing/deserializing from shared preferences depends on the preference key.
For example, I have an EncryptedPreferenceAdapter that worked as follows with the previous Adapter interface:

    @Override
    public Optional<T> get(@NonNull String key, @NonNull SharedPreferences preferences) {
            final String serialized = preferences.getString(key, null);
            if (serialized == null)
                return Optional.empty();
            final String decrypted = Obfuscator.decryptFromStorage(key, serialized);
            return Optional.ofNullable(decrypted);
    }

    @Override
    public void set(@NonNull String key, @NonNull Optional<T> optionalValue, @NonNull SharedPreferences.Editor editor) {
            if (optionalValue.isPresent()) {
                editor.putString(key, Obfuscator.encryptForStorage(key, optionalValue.get()));
            }
            else {
                editor.putString(key, null);
            }
    }

Can Converters be updated to provide the preference key? Or can adapters be resurrected since there may be apps out in the wild that have adapters serializing to non-string types?

Make Preference into an interface

Preference itself is a final class that can only be created through static methods in another class. You have created a unit test nightmare. You should change Preference into an interface and extract the implementation to another class. When using dependency injection the classes needing access to the Preference would inject the interface instead of a concrete class. That makes creating mock preferences for unit testing a breeze.

Without this, unit testing of classes that depend on a preference is much more difficult. It would require mocking SharedPreferences but having to capture the listener that is registered internally for preference changes.

RxJava 2

I'm looking forward for adopting RxJava 2. Therefore this library needs to be updated (and partly rewritten).
As RxJava does not accept nulls any more, it might make sense to split the preference class in two types:
One that emits a java.util.Optional and one that emits the raw type. Here the preference creation must differentiate and check the initial value that will be used as a default.

Another solution could be to just make everything an optional, but that wouldn't result in a too fluent api usage.

If a decision on this would be made by the library owner I could do a pull request.

GC removes preference listeners

In the constructor of RXSharedPreferences a preference listener created and registered using registerOnSharedPreferenceChangeListener(). The documentation of this method states:

The preference manager does not currently store a strong reference to the listener. You must store a strong reference to the listener, or it will be susceptible to garbage collection. We recommend you keep a reference to the listener in the instance data of an object that will exist as long as you need the listener.

That's why it is very important for developers to keep a reference to the RxSharedPreferences instance as long as one needs to listen to changes. I just stumbled upon this issue myself - kept me busy for around an hour before I found the culprit. Thought I was using the library wrong. We should update the README so it states clearly that it is necessary to keep a RxSharedPreferences reference. I'll propose an according change later today.

2.0: Make set() @NonNull

To be consistent with the recent changes about nullity I want to suggest to make set explicity @NonNull as null itself is not supported.

For deletion there is the explicit method delete

Build on PreferenceDataStore

This is in O, but there's a version in the support lib. Allows swapping of plain/encrypted stores or building your own using whatever you want.

asObservable() method do not emit values previously stored in SharedPreferences

mSharedPreferences = SharedPreferenceUtils.getSharedPreferences();
        RxSharedPreferences rxSharedPreferences = RxSharedPreferences.create(mSharedPreferences);
        Preference<Long> measureDeviceOnDB = rxSharedPreferences.getLong(PreferenceConstants.MEASURE_DEVICE_ON_DB.name());

                measureDeviceOnDB.asObservable().subscribe(
                new Observer<Long>() {
                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(Long l) {
                        Log.d("RXPREFERENCES", "onNext -> " + l);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.d("RXPREFERENCES", "onError -> " + e);
                    }

                    @Override
                    public void onComplete() {
                        Log.d("RXPREFERENCES", "onComplete");
                    }
                }
        );

------------------ LOG --------------------

/RXPREFERENCES: onNext -> 1
/RXPREFERENCES: onNext -> 2
/RXPREFERENCES: onNext -> 3
/RXPREFERENCES: onNext -> 4
/RXPREFERENCES: onNext -> 5
/RXPREFERENCES: onNext -> 6
/RXPREFERENCES: onNext -> 7
/RXPREFERENCES: onNext -> 8
/RXPREFERENCES: onNext -> 9
/RXPREFERENCES: onNext -> 10
/RXPREFERENCES: onNext -> 11
/RXPREFERENCES: onNext -> 12...

If I store "5" again, for example, the onNext method is never called.
Is there a way to emit the value, even it already stored

Better unsubscription

Might need to add better handling when unsubscribing from preferences, specifically when multiple subscribers are involved.

Proper null handling

I need to be able to observe a preference's value (or lack of value or if it has been cleared). It's very important to be able to keep the stream alive.

Observable<Optional<Boolean>> //note Optional is my own custom class

Would it be possible to pass in some function to handle a custom mapping when the preference is null?

Cut a release

It appears that the old backpressure solution misbehaves when a project uses the latest version of RxJava (1.1.5).

If you have a new empty SharedPreference instance and try to observe a String in it, null will be emitted non stop when subscribed to. This behavior occurs in 1.0.1, but is fixed if I build my project against the current master which has #39 in it.

Could you cut a release with the updated backpressure solution?

apply() is lossy

Using apply() to save preferences is dangerous as it can and will silently fail.
Instead, all sets should use commit() and return a completable, throwing an error if commit() returns false.

Setting nulls to SharedPreferences breaks the flow for pre-lollipop devices

Thanks for the library! I'm looking into using it for production in my app, however, there seems to be a weird issue on pre-lollipop devices with null values in SharedPreferences. Might be related to the fact that I'm using RxJava2, however onError is not emitted.

    return  getName()
                    .subscribeOn(mSchedulerProvider.io())
                    .observeOn(mSchedulerProvider.ui())
                    .subscribe((name) -> {
                        mLogger.d(TAG,".onNext() - entered");
                    }, (e) ->  {
                        mLogger.e(TAG,".onError() - entered");
                    }, () -> {
                        mLogger.d(TAG," onComplete() - entered");
            });

    public Observable<String> getName() {
         return mName.asObservable().onErrorResumeNext(Observable.just(EMPTY_STRING));
    }

In the example above, onNext will be called when I'm setting a new name, i.e. saving the name to the shared preferences. However, if I save a null, instead of handling that, the getName chain will stop reacting without any events (onError and onComplete are not getting called). Setting any name after attempting to set null will not cause an event to trigger.

This behavior is only seen on pre-lollipop devices - the rest are working fine. I'm using rx-preferences:2.0.0-RC1

Also, if I handle nulls before saving to the SharedPreferences, the issue gets resolved. Therefore, it is something to do with handling nulls. As a workaround:

public void setUserName(String userName) {
    savePref(PREF_USER_NAME, userName != null ? userName : EMPTY_STRING);
}

AssertionError on some devices in non-primitive Adapter.

I've got about 20 distinct-device reports from Crashlytics that show an AssertionError in my StringAdapter.
The only way that could happen is if the device has a broken implementation of SharedPreferences.contains() or Editor.delete().

Crashlytics shows about a third of the devices as rooted (honestly not sure if Crashlytics just does a Runtime.getRuntime().exec("su") or what), so I'm guessing 100% of them are using broken firmware.

I'm closing these crash reports with prejudice, but has anybody observed this behavior?
I'm just hoping there isn't some Samsung phone that legitimately shipped with broken firmware that merits a workaround.

Why default values can't be null?

Standard preference allows you to set default values to null. Why did you decide to give it up? For me, this point is critical when using the "getObject" method. What can you advise me in this case?

Default handling for Enums

Currently an Enum Preference will throw if the string value stored in preference is not part of the enum. This can happen if a user installs a newer version of an app, changes the preference, then downgrades to a version before that enum option existed. Or can easily happen during development when introducing/removing/renaming enums. It's not straightforward for the app to handle in another way.
For my use, I'd like to see the default value returned for invalid enum values.

This isn't too straightforward to implement as the Preference.Adapter doesn't know what the default value is. One approach would be for Preference.get() returning the default value if adapter.get() returns null (or throwing something like an InvalidPreferenceException). I can work on this but wanted to post as an issue first as it might not be desirable functionality.

Convert to Gradle

  • Build against API 22, min of 9 (or whatever), no target (creates manifest merge conflicts)
  • Dependency on support-annotations
    • @CheckResult on toObservable and toAction
    • @NonNull on key param
    • @Nullable on set param and get return value.

Add a way to store a Preference value synchronously

I need to store a value synchronously, something like this:

public boolean setSync(@Nullable T value) {
    SharedPreferences.Editor editor = preferences.edit();
    if (value == null) {
      editor.remove(key);
    } else {
      adapter.set(key, value, editor);
    }
    return editor.commit(); // instead of editor::apply
  }

See: http://stackoverflow.com/questions/5960678/whats-the-difference-between-commit-and-apply-in-shared-preference

By the way, maybe Preference::asAction should be updated to execute the storing process synchronously as well.

Thanks for this great library.

API design

Currently creating an observable on a preference requires that an individual change listener be created and registered. This is wasteful in terms of allocations and the strain it puts on the preference manager (churning its list of listeners and the required iteration over them on each change).

Rather than being able to create preference objects directly from a SharedPreferences instance, I think we should create a type which wraps SharedPreferences and then becomes a factory for the Preference instances. In NotRxAndroid this was called RxSharedPreferences.

Make an emmit for any setting

Why do I only see changes in the value? Is it possible to make an emmit for any setting? Example:

Preference<Boolean> preference = preferences.getBoolean(KEY_FOO, true);
preference.set(true);
preference.set(true);
preference.set(true);

Emmit only onse (( It would be more convenient every time! Thanks!

  /**
   * Observe changes to this preference. The current value or {@link #defaultValue()} will be
   * emitted on first subscribe.
   */
  @CheckResult @NonNull Observable<T> asObservable();

Loose final?

Awesome concept, first of all, thank you!

Now, I got it, it's a library, public API, we have to be dogmatic and stuff, but is it really that important to have Preference final? It makes it impossible to mock, leads to boilerplate code just to get an instance of it and what's more annoying: makes @InjectMocks useless in tests.

Thank you.

2.0.0-RC1 gradle package does not exist

I cannot find the 2.0.0-RC1 package on maven central or jcenter. Yes, I tried changing the package name to add a "2" to the end, as shown in the gradle line example in the README.

Adapter thread safe?

class FooAdapter implements Preference.Adapter<Foo> {
  private Foo cache = null;
  final Gson gson = new Gson();
  @Override
  public synchronized get(@NonNull String key, @NonNull SharedPreferences preferences) {
    if (cache != null) return cache;
    return gson.fromJson(preferences.get(key), Foo.class);
  }
  public synchronized set(@NonNull String key, @NonNull Foo value, @NonNull SharedPreferences.Editor editor) {
    cache = value;
    preferences.set(key, gson.toJson(value));
  }
}

and seems that the get method still returns an old cache, why?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.