rejasupotaro/kvs-schema

Create initialize methods in a compile time

hotchemi opened this issue ยท 10 comments

Proposal

My proposal is creating initialize methods in a compile time automatically.

public final class ExamplePrefs extends PrefsSchema {

  private static ExamplePrefs prefs;

  // guess double check pattern like Picasso.with is better for performance...
  public static synchronized ExamplePrefs get(Context context) {
    if (prefs == null) {
         prefs = new ExamplePrefs(context);
     }
     return prefs;
  }
}

Why

Now, we have to define like create method in a schema class and maintain the state of generated class instance.

I can't figure out the appropriate reason for that. Any thoughts?

Thank you for your proposal!

Generating initialize method

Good point! Ideally, it should be like you said.

However, sometimes we want to change creation mode of SharedPreferences or use special SharedPreferences like below.

// CredentialPrefsSchema.java
public static synchronized CredentialPrefs get(Context context) {
    if (prefs == null) {
        EncryptedSharedPreferences encryptedSharedPreferences = EncryptedSharedPreferencesProvider.create(
                context.getSharedPreferences(TABLE_NAME, Context.MODE_PRIVATE),
                context);
        prefs = new CredentialPrefs(encryptedSharedPreferences);
    }
    return prefs;
}

I can't think of a good interface for these cases for now ๐Ÿ˜• Any thoughts?

Synchronization

IMHO, the synchronization cost wouldn't be a problem in most cases because *Schema.get isn't called so many times in a event loop unlike Picasso. I like to reduce handwritten code as much as possible. So I'm not using double checked locking in my project. I don't force this way. It's up to each developer because they have different use cases.

However if we generate a initialize method, that's another story. Double checked locking should be generated by processor ๐Ÿ˜

Umm, might be I miss the point but how about preparing below two methods? I think you already create two constructors.

public final class ExamplePrefs extends PrefsSchema {

  private static ExamplePrefs prefs;

  public static synchronized ExamplePrefs get(Context context) {
    if (prefs == null) {
         prefs = new ExamplePrefs(context);
     }
     return prefs;
  }

  public static synchronized ExamplePrefs get(SharedPreferences prefs) {
    if (prefs == null) {
         prefs = new ExamplePrefs(prefs);
     }
     return prefs;
  }
}

And when it comes to the synchronization, I agree with you.
But yes, we have JavaPoet so we can generate whatever java code, even double checked locking!

how about preparing below two methods?

@hotchemi Does it mean that we have to give a SharedPreferences every time we get a Prefs instance?

CustomSharedPreferences customSharedPreferences = ...;
...
ExamplePrefs.get(customSharedPreferences).putValue(...)
...
ExamplePrefs.get(customSharedPreferences).getValue(...)

In this case, we have to keep custom SharedPreferences as a singleton (singleton for singleton ๐Ÿ˜•).
Hmm... I have 2 ideas.

1. Create Builder class

We may be able to build custom SharedPreferences by creating builder class or something like below.

// ExamplePrefsSchema.class
@Table(name = "example", builder = ExamplePrefsBuilder.class)
public abstract class ExamplePrefsSchema {
    @Key("key") int value;
}

// ExamplePrefs.class
public final class ExamplePrefs extends PrefsSchema {

    private static ExamplePrefs prefs;

    public static synchronized ExamplePrefs get(Context context) {
        if (prefs == null) {
            prefs = new ExamplePrefsBuilder().build(context);
        }
        return prefs;
    }
}

// ExamplePrefsBuilder.java
public class ExamplePrefsBuilder implements PrefsBuilder {
    @Override
    public ExamplePrefs build(Context context) {
        ...
        return new ExamplePrefs(customSharedPreferences);
    }
}

If builder is not specified in the Schema class, this library just generate get method.

// ExamplePrefsSchema.class
@Table(name = "example")
public abstract class ExamplePrefsSchema {
    @Key("key") int value;
}

// ExamplePrefs.class
public final class ExamplePrefs extends PrefsSchema {

    private static ExamplePrefs prefs;

    public static synchronized ExamplePrefs get(Context context) {
        if (prefs == null) {
            prefs = new ExamplePrefs(context);
        }
        return prefs;
    }
}

2. Create Initializer class

It's like below.

// MyApplication.java
@Override
private void onCreate() {
    ....
    PrefsSchemaInitializer
            .initExamplePrefs(context)
            .initCredentialsPrefs(customSharedPreferences);
}

I would like to avoid creating initializer class from the viewpoint of performance and maintainability. However, this way has an advantage that we don't have to give context when getting Prefs class.

ExamplePrefs.get().getValue(); // Already initialized!

What do you think?

Umm, IMO Builder idea is better! I definitely don't want to invite user to "initializing party".
We should provide it as an option, and it would be pretty nice ๐Ÿ˜„

Did you already kick off the development?

That's how I feel also.

I already started the implementation. My plan is

  • Generate initialize methods: #11
  • Bump version to 2.1.0
  • Enable to specify builder
  • Bump version to 3.0.0
    • Due to breaking change @Table("name") => @Table(name = "name")

Gotcha!

@hotchemi 2 PRs were merged. kvs-schema became far better! Thank you ๐Ÿป โœจ

๐Ÿ‘ We're trying to adopt it in production!

Great ๐Ÿ‘