I have 3 preference settings:
- An EditTextPreference to enter a web address
- An EditTextPreference to enter a port
- A ListPreference to show some elements from the web page (e.g. http://www.igs-ip.net:2101) defined by the previous 2 settings.
To make this viable, I was thinking to validate the web connection on the click of the 3rd setting. So far, I was able to catch the click to dynamically fill the ListPreference:
ListPreferenceDynamic dlp = (ListPreferenceDynamic)findPreference(strKey);
dlp.setOnClickListner(new ListPreferenceDynamicOnClickListener()
{
public void onClick(ListPreferenceDynamic preference)
{
String[] astr = astrOpenWebPageAndGetInfo(strAddress, strPort);
if (astr != null)
{
preference.setEntries(astr);
preference.setEntryValues(astr);
}
}
});
My problem now is to find a way to prevent to show the ListPreference dialog when there is a problem, let say, with the internet address. I would like only to show a Toast to explain the problem without showing an empty ListPreference dialog.
A bit late but maybe still useful to some of you:
I figured that the OnPreferenceChangeListener can deal with that. Just register one and return true if you want accept the new value or return false if you want to cancel the click:
yourPreference.setOnPreferenceChangeListener((preference, newValue) -> {
if(condition){
showToast();
return false; //cancel the click
}
else return true; //accept the click
});
The parameter newValue determines the new would-be value of your preference.
I was actually after this.
But the ChangeListener triggers after a new value is picked, right?
So it's not really canceling the list which is what I'm after.
I did found a solution which was making a custom ListPreference class and conditionally handle the onClick.
That way if it's set as not active throught the setActive method the list will not show after clicking the option.
public class CustomListPreference extends ListPreference {
private boolean isActive;
public CustomListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomListPreference(Context context) {
super(context);
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
public boolean getActive() {
return this.isActive;
}
#Override
public void onClick() {
if (isActive) super.onClick();
}
}
Related
We are currently migrating to Androidx namespace with our Android app project. However I noticed that not only the namespace seems to have changed. For DialogPreference also some interfaces which were using before are now missing
new interfaces: https://developer.android.com/reference/androidx/preference/DialogPreference
old interfaces: https://developer.android.com/reference/kotlin/android/preference/DialogPreference
For example the following methods seem to be missing: onBindDialogView, showDialog, onDialogClosed.
Since we use some of these methods to influence the default behavior of the dialog, it is unclear to me how I should realize this functionality now. For example we are validating the input before closing the dialog, we are saving the value in a database instead of the sharedpreferences and adding some dynamic elements to the dialog.
Has anyone else already encountered this problem and found a solution? Did I miss anything in the documentation? Is there another concept that we can / should use?
It would be possible to use Fragments instead of DialogPreference but for small amounts of content (e.g. a list of tree items, where the user can choose from) this seems to be a lot of overhead for me...
Starting from androidx source files, I've migrated custom classes based on old DialogPreference to new androidx.preference.DialogPreference with the following procedure:
Step 1
The old custom dialog class (e.g. CustomDialogPreference) based on legacy DialogPreference should be split into two separate classes:
One class (e.g. CustomPreference) should extend androidx.preference.DialogPreference and will contain only the code related to preference handling (data management).
Another class (e.g. CustomDialog) should extend androidx.preference.PreferenceDialogFragmentCompat and will contain only the code related to dialog handling (user interface), including onDialogClosed. This class should expose a static method newInstance to return an instance of this class.
Step 2
In the main fragment handling preferences based on PreferenceFragmentCompat the onDisplayPreferenceDialog method should be overridden to show the custom dialog, e.g.:
private static final String DIALOG_FRAGMENT_TAG = "CustomPreference";
#Override
public void onDisplayPreferenceDialog(Preference preference) {
if (getParentFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
return;
}
if (preference instanceof CustomPreference) {
final DialogFragment f = CustomDialog.newInstance(preference.getKey());
f.setTargetFragment(this, 0);
f.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
} else {
super.onDisplayPreferenceDialog(preference);
}
}
Instead of using DialogPreference, you can write your own custom Preference with an AlertDialog.
This may be a workaround for those who don't want to deal with the DialogPreference and PreferenceDialogFragmentCompat.
import android.content.Context;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
public class CustomDialogPreference extends Preference {
private final Context context;
public CustomDialogPreference(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
#Override
protected void onClick() { //what happens when clicked on the preference
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("TITLE")
.setMessage("message")
.setPositiveButton("OK", (dialog, which) -> {
String preferenceKey = getKey(); //You can update the SharedPreference with the key
//....
})
.setNegativeButton("CANCEL", (dialog, which) -> {
//....
})
.create().show();
}
}
onClick() and getKey() methods belong to the Preference class. The context object comes with the constructor and so on..
The key can be defined, as other preferences, in xml file or programmatically in the PreferenceFragment.
<com.myApp.CustomDialogPreference
android:key="my_preference_key"
android:summary="..."
android:title="..." />
A little hack for everyone who (like me) do not completely understand how we should combine androidx.preference.DialogPreference and androidx.preference.PreferenceDialogFragmentCompat:
Step 1:
In your DialogFragment's onAttach() method get the value of your desired SharedPreference (get the key either from newInstance() method or just hardcore it inside) and save it as a variable. On the other hand, save your new value in SharedPreference before closing your DialogFragment. By doing so, you have created your "custom Preference".
Step 2:
Create empty androidx.preference.DialogPreference and use it inside your PreferenceScreen. Then combine it with your DialogFragment as described in 2nd step by #Livio:
private static final String DIALOG_FRAGMENT_TAG = "CustomPreference";
#Override
public void onDisplayPreferenceDialog(Preference preference) {
if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
return;
}
if (preference instanceof CustomPreference) {
final DialogFragment f = CustomDialog.newInstance(preference.getKey());
f.setTargetFragment(this, 0);
f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
} else {
super.onDisplayPreferenceDialog(preference);
}
}
By doing so, you will get the same result with only difference that you need to deal with SharedPreference yourself inside your DialogFragment.
In Android, how do I take an action whenever a variable changes?
So I want to implement a listener for an object I created. What I want it to do is execute a block of code when its value changes from false to true.
As I am following this thread, I can't understand where the person wants us to implement the last block of code containing the logic for the listener.
Could someone, hopefully, guide me in the right direction?
(This question is being asked here as I don't have enough rep. points)
That last bit of example code triggers the listener, so it basically needs to be run whenever the "event" occurs. In this case the "event" is whenever (wherever in the code) the value of the variable changes.
If you have a setter and that is the only place the value changes, that is where you'd put it. If you are changing the value in multiple places throughout your code, I would make a new private method (call it signalChanged), put your code there, and then call it immediately after the variable assignment in the cases you want the listener to fire.
Here's an example (some code borrowed from linked answer, haven't checked that it compiles).
public class MyObj
{
public MyObj(int value)
{
setValue(value);
}
private int myValue;
public int getValue() { return myValue; }
public void setValue( int value )
{
if (value != myValue)
{
myValue = value;
signalChanged();
}
}
public interface VariableChangeListener
{
public void onVariableChanged(Object... variableThatHasChanged);
}
private VariableChangeListener variableChangeListener;
public void setVariableChangeListener(VariableChangeListener variableChangeListener)
{
this.variableChangeListener = variableChangeListener;
}
private void signalChanged()
{
if (variableChangeListener != null)
variableChangeListener.onVariableChanged(myValue);
}
}
you have to create a callback interface
here is a good about custom listener tutorial
here is a sample
public class MyObj {
VariableChanger onVariableChanged ;
public void setOnVariableChanged(VariableChanger onVariableChanged) {
this.onVariableChanged = onVariableChanged;
}
void log(){
boolean changed = false;
onVariableChanged.onVariableChanged();
//this will call it
}
interface VariableChanger{
void onVariableChanged();
}
}
class logic {
MyObj mo = new MyObj();
void main(){
mo.setOnVariableChanged(new MyObj.VariableChanger() {
#Override
public void onVariableChanged() {
//do your action
}
});
}
}
In Android, like any language, most developper uses logic comparisons to check values (if, else, switch, =, !=, >, <, etc) or Event (signal)
What kind of listener do you want to implement?
Current behavior of PreferenceFragment: Upon first display on screen PreferenceFragment writes to associated SharedPreferences all default values defined in PreferenceScreen XML resource. I tested this couple times and PreferenceFragment as well as PreferenceActivity writes all preferences defaults to SharedPreferences when user opens Settings activity, even if he immediately close it without touching anything.
Problem: When in the next version of my app I decide to change some default user preferences, they will not apply to the devices where user at least once opened app preferences, because PreferenceFragment wrote all default values to SharedPreferences. I know that I can reapply new default values by overwriting all values in SharedPreferences, not only default, but user chosen too. But resetting user preferences in app update is completely unacceptable. So the problem is that we cant distinguish when some particular preference was set explicity by user or its just default preference written by PreferenceFragment upon first display on screen.
What I want: If the user explicity set some preference, whatever he has chosen, I should not touch this with my updated app defaults, even if user choice is coincides with my old default. But if user was not explicity chose preference I want that my new default preferences start working for him with app update.
So: How to prevent write of default preferences values by PreferenceFragment to associated SharedPreferences?
After studying sources I found a way to achieve requested behavior.
The only place where real write to SharedPreferences occurs it's bunch of persist[Type] methods in Preference class. And subclasses of Preference usually call persist[Type] method only in single internal method, that has similar structure across all subclasses. For example, method from TwoStatePreference, that is superclass of ChekBoxPreference and SwitchPreference:
public void setChecked(boolean checked) {
boolean changed = this.mChecked != checked;
if(changed || !this.mCheckedSet) {
this.mChecked = checked;
this.mCheckedSet = true;
this.persistBoolean(checked);
if(changed) {
this.notifyDependencyChange(this.shouldDisableDependents());
this.notifyChanged();
}
}
}
Next, setChecked method of TwoStatePreference is called in about five other methods, and two of these calls can produce default value to be committed to SharedPreferences. Here is first:
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setChecked(restoreValue ? getPersistedBoolean(mChecked)
: (Boolean) defaultValue);
}
And second:
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());
setChecked(myState.checked);
}
And here is the solution, custom class, that subclasses SwitchPreferenceCompat and preventing commit in two above calls:
public class MySwitchPref extends SwitchPreferenceCompat
{
private boolean mAllowPersist;
#Override
protected boolean persistBoolean(boolean value) {
if (mAllowPersist) {
return super.persistBoolean(value);
}
return false;
}
#Override
protected void onSetInitialValue(boolean restoreValue,
Object defaultValue) {
mAllowPersist = false;
super.onSetInitialValue(restoreValue, defaultValue);
mAllowPersist = true;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
mAllowPersist = false;
super.onRestoreInstanceState(state);
mAllowPersist = true;
}
public MySwitchPref(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public MySwitchPref(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MySwitchPref(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MySwitchPref(Context context) {
super(context);
}
}
You should replace your SwitchPreferenceCompat declaration in xml PreferenceScreen to this subclass and all should work, I have tested it. And if you use other then SwitchPreference types of preferences, you as well should similarly subclass and override their behavior.
Beware: This solution relies upon internal realization of current, com.android.support:preference-v7:23.4.0 library. It might change with future releases, so if you use other library version, or use non support implementation you should look in to source, and ensure there is no other calls to persist values in SharedPreferences except that two, that I overriden. As well if you use other subclasses of Preference, not only SwitchPreference, you should check for other calls to persist values in SharedPreferences.
I'm working on an app using Chromecast and I want to be able to filter the available devices or routes based on device name or description. So when a user clicks the MediaRouteButton only some of the available devices would show. The use case for my app happens in a place where many cast devices are available and I want to make sure the user doesn't accidentally select a device in another room. The user information in the app stores the room information that the user is based in and the chromecasts are being named in an intelligent way so that, ideally, only the chromecast device for a specific user's room would show up as available to them.
I have tried grabbing the MediaRouteDialogFactory and filtering devices at that level but have had no luck. There doesn't seem to be any mechanism that I can find to hide or remove routes.
To filter Chromecast devices from chooser dialog you can use onFilterRoute:
public boolean onFilterRoute (MediaRouter.RouteInfo route)
Returns true if the route should be included in the list.
The default implementation returns true for enabled non-default routes
that match the selector. Subclasses can override this method to filter
routes differently.
You need to create a CustomMediaRouteChooserDialog:
public class CustomMediaRouteChooserDialog extends MediaRouteChooserDialog {
public CustomMediaRouteChooserDialog(Context context) {
super(context);
}
public CustomMediaRouteChooserDialog(Context context, int theme) {
super(context, theme);
}
#Override
public boolean onFilterRoute(MediaRouter.RouteInfo route) {
// Apply your logic here.
// Return false to hide the device, true otherwise
if (TextUtils.equals(route.getName(), "Chromecast-hidden"))
return false;
else
return true;
}
}
Then create a CustomMediaRouteChooserDialogFragment:
public class CustomMediaRouteChooserDialogFragment extends MediaRouteChooserDialogFragment {
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
CustomMediaRouteChooserDialog dialog = new CustomMediaRouteChooserDialog(getActivity());
dialog.setRouteSelector(getRouteSelector());
return dialog;
}
}
Then create a CustomMediaRouteDialogFactory:
public class CustomMediaRouteDialogFactory extends MediaRouteDialogFactory {
#Override
public MediaRouteChooserDialogFragment onCreateChooserDialogFragment() {
return new CustomMediaRouteChooserDialogFragment();
}
}
Then after create your MediaRouteActionProvider call setDialogFactory:
mediaRouteActionProvider.setDialogFactory(new CustomMediaRouteDialogFactory());
One approach would be the following:
Extend MediaRouteDialogFactory and override onCreateChooserDialogFragment() to return your own chooser dialog fragment, say 'MyChooserDialogFragment'; this should extend MediaRouteChooserDialogFactory.
In MyChooserDialogFragment, override onCreateChooserDialog() to return, say, MyChooserDialog which extends MediaRouteChooserDialog
In MyChooserDialog, override onFilterRoute(MediaRouter.RouteInfo route)). In this override, you are passed in a route and you can return true to accept or false to reject that route; so a naive implementation would be return route.getName().startsWith('room10') (in reality, it should be a bit smarter like:
String validPrefix = 'room10';
boolean validRoute = route.getName().startsWith(validPrefix);
return !route.isDefault()
&& route.matchesSelector(getRouteSelector())
&& validRoute;
I have a SyncAdapter running on its own process separately from the main app process.
I'm using a static wrapper class around my SharedPreferences that creates a static object on process load (Application's onCreate) like so:
myPrefs = context.getSharedPreferences(MY_FILE_NAME, Context.MODE_MULTI_PROCESS | Context.MODE_PRIVATE);
The wrapper has get and set methods, like so:
public static String getSomeString() {
return myPrefs.getString(SOME_KEY, null);
}
public static void setSomeString(String str) {
myPrefs.edit().putString(SOME_KEY, str).commit();
}
Both SyncAdapter and app uses this wrapper class to edit and get from the prefs, this works sometimes but a lot of times I see the SyncAdapter getting old/missing prefs on accesses to the prefs, while the main app sees the recent changes properly.
According to the docs I think the MODE_MULTI_PROCESS flag should work as I expect it to, allowing both processes to see latest changes, but it doesn't work.
Update:
Per x90's suggestion, I've tried refraining from using a static SharedPreferences object and instead calling getSharedPreferences on each get/set method.
This caused a new issue, where the prefs file gets deleted (!!!) on multi-process simultaneous access.
i.e. I see in the logcat:
(process 1): getName => "Name"
(process 2): getName => null
(process 1): getName => null
and from that point all the prefs saved on the SharedPreferences object were deleted.
This is probably a result of another warning I see in the log:
W/FileUtils(21552): Failed to chmod(/data/data/com.my_company/shared_prefs/prefs_filename.xml): libcore.io.ErrnoException: chmod failed: ENOENT (No such file or directory)
P.S this is not a deterministic issue, I saw the above logs after a crash happened, but couldn't recreate yet on the same device, and until now it didn't seem to happen on other devices.
ANOTHER UPDATE:
I've filed a bug report on this, after writing a small testing method to confirm this is indeed an Android issue, star it at https://code.google.com/p/android/issues/detail?id=66625
I gave a very quick look at Google's code and apparently Context.MODE_MULTI_PROCESS is not an actual way to ensure process-safety of SharedPreferences.
SharedPreferences itself is not process-safe. (That's probably why SharedPreferences documentation says "currently this class does not support use across multiple processes. This will be added later.")
MODE_MULTI_PROCESS just works in conjunction with every Context.getSharedPreferences(String name, int mode) call: when you retrieve an instance of SharedPreferences specifying the MODE_MULTI_PROCESS flag android will reload the preferences file to be up to date with any (eventual) concurrent modification that occurred to it. If you then keep that instance as a class (static or not) member, the preference file won't be reloaded again.
Using Context.getSharedPreferences(...) every time you want to write or read into preferences is not process-safe either, but I guess it's probably the closest that you can get to it at the moment.
If you don't actually need to read the same preference from the different processes, then a workaround could be to use different preferences files for the different processes.
Had exactly the same problem and my solution was to write a ContentProvider based replacement for the SharedPreferences. It works 100% multiprocess.
I made it a library for all of us. Here is the result:
https://github.com/grandcentrix/tray
I just ran into the same problem. I switched my app to run the service in a separate process and realized sharedPreferences was all broken.
Two things:
1) Are you using Editor.apply() or .commit()? I was using .apply(). I started checking my preference file either after the activity or the service made changes to it and realized whenever one would make a change, it would create a new file with only the newly changed value. I.E., a value written from the activity would be erased when a new value was written/changed from the service and vice versa. I switched to .commit() everywhere and this is no longer the case! From the documentation: "Note that when two editors are modifying preferences at the same time, the last one to call apply wins.
2) SharedPreferencesListener doesn't appear to work across processes even after switching to .commit(). You'll have to use Messenger Handlers or Broadcast Intents to notify of a change. When you look at the documentation for the SharedPreferences class it even says "Note: currently this class does not support use across multiple processes. This will be added later." http://developer.android.com/reference/android/content/SharedPreferences.html
In that respect we're lucky we even have the MODE_MULTI_PROCESS flag working to read/write from the same SharedPreferences across different processes.
MODE_MULTI_PROCESS for SharedPreferences is depreciated now (android M -API level 23-onward).It was not process safe.
MODE_MULTI_PROCESS was deprecated in API level 23. You can solve this problem with ContentProvider. DPreference uses a ContentProvider wrappering sharepreference. It has a better performance than using sqlite implmented.
https://github.com/DozenWang/DPreference
Because MODE_MULTI_PROCESS is not currently supported, I haven't found any way to work with Shared Preferences between processes other than working around it.
I know people are sharing the libraries they wrote to address this, but I actually used a third-party library I found on another thread that implements SQLLite in lieu of the Shared Preferences:
https://github.com/hamsterready/dbpreferences
However, what was important to me that I haven't found addressed in other solutions was maintaining the automatic UI generation already built into Preference Fragment - better to be able to specify your elements in XML and call addPreferencesFromResource(R.xml.preferences) than have to build your UI from scratch.
So, to make this work, I subclassed each of the Preference elements I needed (in my case just Preference, SwitchPreference, and EditTextPreference), and overrode a few methods from the base classes to include saving to an instance of DatabaseSharedPreferences taken from the above library.
For example, below I subclass EditTextPreference and get the preference key from the base class. I then override the persist and getPersisted methods in Preference base class. I then override onSetInitialValue, setText, and getText in the EditText base class.
public class EditTextDBPreference extends EditTextPreference {
private DatabaseBasedSharedPreferences mDBPrefs;
private String mKey;
private String mText;
public EditTextDBPreference(Context context) {
super(context);
init(context);
}
public EditTextDBPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public EditTextDBPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public EditTextDBPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context);
}
private void init(Context context)
{
mDBPrefs = new DatabaseBasedSharedPreferences(context);
mKey = super.getKey();
}
public DatabaseBasedSharedPreferences getSharedDBPreferences()
{
if (mDBPrefs == null) {
return null;
}
return mDBPrefs;
}
#Override
protected boolean persistBoolean(boolean value) {
if (mKey != null)
mDBPrefs.putBoolean(mKey,value);
return super.persistBoolean(value);
}
#Override
protected boolean persistFloat(float value) {
if (mKey != null)
mDBPrefs.putFloat(mKey, value);
return super.persistFloat(value);
}
#Override
protected boolean persistInt(int value) {
if (mKey != null)
mDBPrefs.putInt(mKey, value);
return super.persistInt(value);
}
#Override
protected boolean persistLong(long value) {
if (mKey != null)
mDBPrefs.putLong(mKey, value);
return super.persistLong(value);
}
#Override
protected boolean persistString(String value) {
if (mKey != null)
mDBPrefs.putString(mKey, value);
return super.persistString(value);
}
#Override
protected boolean getPersistedBoolean(boolean defaultReturnValue) {
if (mKey == null)
return false;
return mDBPrefs.getBoolean(mKey, defaultReturnValue);
}
#Override
protected float getPersistedFloat(float defaultReturnValue) {
if (mKey == null)
return -1f;
return mDBPrefs.getFloat(mKey, defaultReturnValue);
}
#Override
protected int getPersistedInt(int defaultReturnValue) {
if (mKey == null)
return -1;
return mDBPrefs.getInt(mKey, defaultReturnValue);
}
#Override
protected long getPersistedLong(long defaultReturnValue) {
if (mKey == null)
return (long)-1.0;
return mDBPrefs.getLong(mKey, defaultReturnValue);
}
#Override
protected String getPersistedString(String defaultReturnValue) {
if (mKey == null)
return null;
return mDBPrefs.getString(mKey, defaultReturnValue);
}
#Override
public void setKey(String key) {
super.setKey(key);
mKey = key;
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setText(restoreValue ? getPersistedString(mText) : (String) defaultValue);
}
#Override
public void setText(String text) {
final boolean wasBlocking = shouldDisableDependents();
boolean textChanged = false;
if (mText != null && !mText.equals(text))
textChanged = true;
mText = text;
persistString(text);
if (textChanged) {
// NOTE: This is a an external class in my app that I use to send a broadcast to other processes that preference settings have changed
BASettingsActivity.SendSettingsUpdate(getContext());
}
final boolean isBlocking = shouldDisableDependents();
if (isBlocking != wasBlocking) {
notifyDependencyChange(isBlocking);
}
}
#Override
public String getText() {
return mText;
}
Then you simply specify the new element in your preferences.xml file, and voila!
You now get the process interoperability of SQLLite and the UI auto-generation of PreferenceFragment!
<com.sampleproject.EditTextDBPreference
android:key="#string/pref_key_build_number"
android:title="#string/build_number"
android:enabled="false"
android:selectable="false"
android:persistent="false"
android:shouldDisableView="false"/>
I know this is an old post, but still a problem that many folks have tried to resolve with a ContentProvider, but that is a problematic approach, as it can cause ANRs, crashes, and general UI performance problems if read/write calls are not done on a background thread.
I don't mean this to be a plug, however, I wrote a library that implements the SharedPreference interface that is process-safe, but doesn't utilize ContentProvider (it uses FileObserver to sync data between processes). https://github.com/pablobaxter/Harmony
My hope is that this solves the issue many folks are having with trying to use SharedPreferences in multiple processes, and still keep their apps performant.