Coder Social home page Coder Social logo

psync's Introduction

Development in this repository is stopped. Future development continues on https://github.com/hzsweers/psync

==========================

PSync

PSync is a gradle plugin for android projects to generate Java representations of xml preferences.

Some applications have a lot of preferences, each their own keys, default values, and more. These tend to be stored in xml files (under res/xml), and don't have any programmatic linking of their values. The result? You have to manually keep these values in sync with your Java code. Yikes!

We got tired of dealing with this at Flipboard. Our preference class ended up with 200+ lines of boilerplate at the top that we manually had to keep in sync, and it was becoming a nuisance. PSync was developed to resolve this, and we hope it helps you too!

Setup

Apply the PSync plugin to your module below your android plugin application

buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "gradle.plugin.com.flipboard:psync:1.1.5"
  }
}

apply plugin: 'com.android.application' // Can be library too
apply plugin: 'com.flipboard.psync'

The PSync plugin will create a generating task for each of your variants, and will generate a file at compilation that will be included in your classpath. This process is purposefully very similar to how the R.java file works. The default name for this class is P.java, but you can configure it to be another name if you wish.

Speaking of configuration, here's how you can configure PSync to work for you.

psync {
    className = "MyClassName"
    includesPattern = "**/xml/<mypatternforfiles>.xml"
    packageName = "com.example.myapp"
    generateRx = true
}

Let's take a look at each:

className is the name of the class. The default is just P.

includesPattern is an ant-style includes pattern for preference xml files in your resources. This is useful if you have a lot of other xml files and want to save the plugin a little work by filtering out non-pref ones. In Flipboard, all our preference files are prefixed with prefs_, so our includes pattern would be **/xml/prefs_*.xml. The default is to parse all xml files in xml resource directories.

packageName is what you want to use for the package name of the generated resource classes. The default behavior of the plugin is to retrieve this from your variant's applicationId value. NOTE: Library projects MUST specify this, since they don't have applicationId values.

generateRx is a flag indicating whether or not you want code generated for usage with Rx-Preferences, which is a great library that adds reactive bindings around SharedPreferences

Usage

Using the generated file is easy, and should feel very familiar to how you would use R.java.

Each preference is represented by a static inner class, with a key field, a defaultResId field if it's a resource, and some or all of following functions:

  • defaultValue() for retrieving the default value
  • get() for retrieving the current stored value
  • put() for storing a new value
    • NOTE: This returns an Editor instance, where you much apply or commit the change(s)
  • rx() for retrieving an appropriate Rx-Preferences instance of Preference, if you enabled generateRx above.

These functions are generated when appropriate. If no default value or resource reference is specified, the plugin will not try to guess the type and generate code for it.

First thing's first: Initialize your P.java file in your Application's onCreate() method.

public class MyApp extends Application {

    @Override
    public void onCreate() {

        // Required
        P.init(this);

        // Optional
        // By default, it will initialize its internal SharedPreferences instance to the system default
        // You can change its used instance at any time
        P.setSharedPreferences(myOtherInstance);
    }

}

Let's take a look at an example. The following xml preference:

<CheckboxPreference
    android:key="show_images"
    android:defaultValue="true"
    />

Becomes this Java code:

public final class P {
    public static final class showImages {
        public static final String key = "show_images";

        public static final boolean defaultValue() {
            return true;
        }

        public static final boolean get() {
            return PREFERENCES.getBoolean(key, defaultValue());
        }

        public static final SharedPreferences.Editor put(final boolean val) {
            return PREFERENCES.edit().putBoolean(key, val);
        }

        public static final Preference<Boolean> rx() {
            return RX_SHARED_PREFERENCES.getBoolean(key);
        }
    }
}

You can now reference this in code like so:

String theKey = P.showImages.key;
boolean theDefault = P.showImages.defaultValue();
boolean current = P.showImages.get();
P.showImages.put(false).apply();

// If you use Rx-Preferences
P.showImages.rx().asObservable().omgDoRxStuff!

Nice and simple right? Note that the entry block's name will be a camelCaseLower conversion of the key.

Let's look at a resource example now. The following preference:

<Preference
    android:key="server_url"
    android:defaultValue="@string/server_url"
    />

Becomes the following Java code:

public final class P {
    public static final class serverUrl {
        public static final String key = "server_url";
        public static final int defaultResId = R.string.server_url;

        public static final String defaultValue() {
            return RESOURCES.getString(defaultResId);
        }

        public static final String get() {
            return PREFERENCES.getString(key, defaultValue());
        }

        public static final SharedPreferences.Editor put(final String val) {
            return PREFERENCES.edit().putString(key, val);
        }

        public static final Preference<String> rx() {
            return RX_SHARED_PREFERENCES.getString(key);
        }
    }
}

Notice that resources are handled a little differently. This is intentional, and for convenience. Using this code now looks like this:

String theKey = P.serverUrl.key;
int theResId = P.serverUrl.defaultResId;
String theDefault = P.serverUrl.defaultValue();
String currentValue = P.serverUrl.get();
P.serverUrl.put("https://example.com").apply();

// If you use Rx-Preferences
P.serverUrl.rx().asObservable().omgDoRxStuff!

Easy peasy. Enjoy!

Contributing

We welcome pull requests for bug fixes, new features, and improvements to PSync. Contributors to PSync repository must accept Flipboard's Apache-style Individual Contributor License Agreement (CLA) before any changes can be merged.

psync's People

Contributors

zacsweers 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

psync's Issues

long support

Unless I'm missing something, preferences default to being integers when the default value is an integer, and trying to specify something like 1L won't really make it (probably intended I'm guessing?). It would be nice if there was a way to state that the preference is desired to be a long instead, or if methods for setting and retrieval of the preference as both long and int were generated.

Crash When Using EditTextPreference with Numerical Default

When using a numerical value as the defaultValue attribute in an EditTextPreference, PSync seems to determine that the preference value associated with the key should be an int rather than a String (as it would be if the defaultValue attribute was omitted).

The problem is that EditTextPreference saves the value into the SharedPreferences file as a String, so it causes a crash when trying to read the value using PSync.

The following example will cause the crash:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <EditTextPreference
        android:key="a_number"
        android:title="Test Number"
        android:inputType="number"
        android:defaultValue="0"
        />

</PreferenceScreen>
public class SettingsActivity extends PreferenceActivity {

    @Override protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preferences);


        // This crashes, because EditTextPreference always saves the value as a String, not an int
        P.aNumber.get();
    }

}

I think the most ideal solution here would be to detect the desired type from the inputType xml attribute and have PSync load the String value and parse it to the appropriate type. Though that may be difficult to get right in every case. If that proves unfeasible, PSync should just use a String type for EditTextPreference values.

Custom attributes

Adding support for custom attributes would make it easier for users to coerce the resulting classes just the way they want, while extending functionality by being able to specify non-standard types.

Initial ideas:

  • valueType - Specify a custom type to cast the value to.
    • Useful for things like String -> CharSequence.
    • EditTextPreference is considered a String by shared prefs but can be a numerical value. Current behavior reads it as an int (#5)
    • This would also be useful for specifying non-xml-friendly types that SharedPreferences supports, such as StringSet or Long.
  • entryName - Custom name for the entry in xml. Would take precedence over generating one from the key.
  • skip - Don't generate a block for this preference

Support Annotations in generated code

Hacked at this for awhile and can't for the life of me get it to work.

Adding a dependency with a local maven URL (as per the docs' suggestion) doesn't work because it still can't find it.

Adding the jar works, but doesn't get included in the packaging for some reason.

If we do add this though, we would want the following:

  • All put() methods would have @CheckResult and @NonNull annotations
  • All rx() methods would have @CheckResult and @NonNull annotations
  • All put(String) methods would have @Nullable annotations on the parameter
    • This applies to future object types too

Batch editing API

Discussed this with someone on reddit. This should be safe, because commits to memory are done in a synchronized block and then clears the "mModified" map. Only question for me is that if it's safe for reuse, why doesn't SharedPreferencesImpl do it? Currently it returns a new EditorImpl every time you call edit().

@markrietveld @ghawk1ns @petershu thoughts?

Allow multiple preference classes for a single build variant

I use separate SharedPreferences for each appwidget the user creates. I would like to be able to be able to configure Psync to generate two preference classes: P.java for my global application settings, and W.java for my widget specific settings which I can set up using W.setSharedPreferences(widgetPrefs).

I imagine it could work something like this:

psync {
    varient{
        className = "P"
        includesPattern = "**/xml/app_prefs.xml"
        packageName = "com.example.myapp"
    }
    varient{
        className = "W"
        includesPattern = "**/xml/widget_*.xml"
        packageName = "com.example.myapp"
    }
}

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.