If you have read the documentation of the SharedPreferences
you might have seen this:
Note: currently this class does not support use across multiple processes. This will be added later.
Sometimes later becomes never!
Tray solves this problem with a ContentProvider
based storage. Tray also provides an advanced API which makes it super easy to access and maintain your data with upgrade and migrate mechanisms. Welcome to SharedPreferences 2.0 aka Tray.
- works multiprocess
- stores simple data types as key value pairs
- automatically saves metadata for each entry (created, last updated, ...)
- manage your Preferences in modules TrayModulePreference
- Delete single modules, all modules, or all modules except some very important ones
- update and migrate your data from one app version to next one with versioned Preferences and a
onUpgrade()
method - Migrate your current data stored in the SharedPreferences to Tray with
SharedPreferencesImport
- tray is 100% unit tested!
- 0 lint warnings/errors
Simple tutorial how to use Tray in your project instead of the SharedPreferences
// create a preference accessor. This is for global app preferences.
final TrayAppPreferences appPreferences = new TrayAppPreferences(getContext()); //this Preference comes for free from the library
// save a key value pair
appPreferences.put("key", "lorem ipsum");
// read the value for your key. the second parameter is a fallback
final String value = appPreferences.getString("key", "default");
Log.v(TAG, "value: " value); // value: lorem ipsum
// read a key that isn't saved. returns the default
final String defaultValue = appPreferences.getString("key2", "default");
Log.v(TAG, "value: " defaultValue); // value: default
No Editor
, no commit()
or apply()
😉
It's recommended to bundle preferences in groups, so called modules instead of putting everyting in one global module. If you were using SharedPreferences
before, you might have used different files to group your preferences. Extending the TrayModulePreferences
and put all Keys inside this class is a recommended way to keep your code clean.
// create a preference accessor for a module
public class MyModulePreference extends TrayModulePreferences {
public static String KEY_IS_FIRST_LAUNCH = "first_launch";
public MyModulePreference(final Context context) {
super(context, "myModule", 1);
}
@Override
protected void onCreate(final int initialVersion) {
}
@Override
protected void onUpgrade(final int oldVersion, final int newVersion) {
}
}
// accessing the preferences for my own module
final MyModulePreference myModulePreference = new MyModulePreference(getContext());
myModulePreference.put(MyModulePreference.KEY_IS_FIRST_LAUNCH, false);
See the sample project for more a demo
// TOOD add clear sample
// TODO
Tray is available via jcenter
dependencies {
compile 'net.grandcentrix.tray:tray:0.9'
}
To set the authority you need to override the string resource of the library with resValue
in your build.gradle
android {
...
defaultConfig {
applicationId "your.app.id" // this is your unique applicationId
resValue "string", "tray__authority", "${applicationId}.tray" // add this to set a unique tray authority based on your applicationId
}
}
Clean your project afterwards to generate the /build/generated/res/generated/BUILDTYPE/values/generated.xml
which should then contain the following value:
<!-- Values from default config. -->
<item name="tray__authority" type="string">your.app.id.tray</item>
Tray is based on a ContentProvider. A ContentProvider needs a unique authority. When you use the same authority for multiple apps you will be unable to install the app due to a authority conflict with the error message:
Failure [INSTALL_FAILED_CONFLICTING_PROVIDER]
Changing the authority from one version to another app version is no problem! Tray always uses the same database.
If you are using different applicationIds for different buildTypes of flavors read this article.
Tray is currently in active development by grandcentrix. We decided to go open source after reaching 100% test coverage. grandcentrix uses Tray in production in two apps without problems.
Before version 1.0 we'd like to have some feedback.
You can follow the development in the develop
branch.
Tray has 100% test coverage and we'll try to keep it at that level for stable releases.
You can run the coverage report with ./gradlew createDebugCoverageReport
. You'll find the output in library/build/outputs/coverage/debug/index.html
which looks like this:
Those ~120 tests will help us indicate bugs in the future before we publish them. Don't think the code is 100% bug free based on the test coverage.
Branch | Status |
---|---|
master |
|
develop |
At first, it was the simpst way to use IPC with Binder
to solve the multiprocess problem. Using the ContentProvider
with a database turned out to be very handy when it comes to save metadata. We thought about replacing the database with the real SharedPreferences
to boost the performance (the SharedPreferences do not access the disk for every read/write action which causes the multiprocess problem btw) but the metadata seemed to be more valuable to us.
If you have found a better solution implement the ModularizedStorage
and contribute to this project! We would appreciate it.
That said, yes the performance isn't as good as the SharedPreferences. But the performance is good enough to save/access single key value pairs synchron. If you want to save more you should think about a simple database.
Tray is ready to use without showblockers! But here are some nice to have features for the future:
- saving
null
doesn't work - Reactive wrapper to observe values
- no support to save
Set<String>
. Is someone using this? - more metadata fields: (i.e. app version code/name)
- initial public release
- Refactoring
- 100% Testing
- Bugfixing
- first working prototype
- Pascal Welsch - https://github.com/passsy
- Jannis Veerkamp - https://github.com/jannisveerkamp
Copyright 2015 grandcentrix GmbH
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.