kvs-schemaとdagger2を同時につかう
kvs-schemaというSharedPreferenceをいい感じにクラス化出来るライブラリがあるんですが、このライブラリはJSR 269(Pluggable Annotation Processing API)を使ってコンパイル時にコード生成をしています。dagger2も同様にJSR269によってコード生成をしています。なんとなく「変な衝突の仕方しないかな?」と思ったので試してみました。案の定問題がありました。本エントリではJSR 269を使ったライブラリの衝突の問題とその回避方法について解説します。
kvs-schemaの使い方については以下のライブラリ作者が書いた解説記事を参照してください。
KVS Schemaを定義する
まずKVS Schemaを定義します。例として初回起動時にガイドを出すフラグをひとつだけもつSchema
を作ります。
@Table("guide") public abstract class GuidePreferenceSchema extends PrefSchema { @Key("should_show_guide") boolean shouldShowGuide; }
これによりGuidePreference
が生成されます。
public final class GuidePreference extends GuidePreferenceSchema { public final String TABLE_NAME = "guide"; GuidePreference(Context context) { init(context, TABLE_NAME); } GuidePreference(SharedPreferences prefs) { init(prefs); } public boolean getShouldShowGuide() { return getBoolean("should_show_guide", shouldShowGuide); } public void putShouldShowGuide(boolean shouldShowGuide) { putBoolean("should_show_guide", shouldShowGuide); } public boolean hasShouldShowGuide() { return has("should_show_guide"); } public void removeShouldShowGuide() { remove("should_show_guide"); } }
GuidePreferenceSchema
にGuidePreference
を返却するメソッドを生やしておきます。これはGuidePreference
のコンストラクタがpackage privateなためです。
@Table("guide") public abstract class GuidePreferenceSchema extends PrefSchema { @Key("should_show_guide") boolean shouldShowGuide; public static GuidePreference create(Context context) { return new GuidePreference(context); } }
Dagger2のComponentとModuleを定義する
Dagger2のComponentを定義します。SharedPreferenceはApplicationのスコープで管理すれば良いと思うのでAppComponent
およびAppModule
*1で提供する事にします。
@Singleton @Component(modules = {AppModule.class}) public interface AppComponent { void inject(MainActivity target); }
GuidePreference
はContextが必要なのでAppModule
にContextを持たせておきます。
@Module public class AppModule { Context context; public AppModule(Context context) { this.context = context; } @Singleton @Provides public GuidePreference provideGuidePreference() { return GuidePreferenceSchema.create(context); } }
あとは使う準備です。Application
クラスを定義してAppComponentをもたせます。
public class Application extends android.app.Application { static AppComponent appComponent; public static AppComponent getAppComponent() { return appComponent; } @Override public void onCreate() { super.onCreate(); appComponent = DaggerAppComponent.builder() .appModule(new AppModule(this)) .build(); } }
injectの対象となるMainActivity
でinjectionの処理を書きます。
public class MainActivity extends AppCompatActivity { @Inject static GuidePreference guidePreference; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Application.getAppComponent().inject(this); //..
これで準備万端です。
コンパイルできない
ところがこの状態でコンパイルすると以下のエラーでコケます。
Execution failed for task ':app:compileDebugJavaWithJavac'. > java.lang.IllegalArgumentException: GuidePreference cannot be represented as a Class<?>.
Dagger2がComponentやModuleを処理する時、kvs-schemaが生成したGuidePreference
が見えないのが原因っぽいです。apt
のオプション等色々見ましたが設定で回避はできなさそうでした。
生成されたクラスを直接参照しない
仕方ないのでkvs-schemaが生成するクラスを直接参照しない形にすることにしました。
public class GuidePreferenceProvider { GuidePreference guidePreference; public GuidePreferenceProvider(Context context) { this.guidePreference = GuidePreferenceSchema.create(context); } public GuidePreference get() { return guidePreference; } }
AppModule
ではGuidePreferenceProvider
をprovidesする事になります。
@Module public class AppModule { Context context; public AppModule(Context context) { this.context = context; } @Singleton @Provides public GuidePreferenceProvider provideGuidePreference() { return new GuidePreferenceProvider(context); } }
若干もやっとしますがこういう風に使います。これでkvs-schemaとdagger2を共存させられます。
public class MainActivity extends AppCompatActivity { @Inject static GuidePreferenceProvider guidePreference; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Application.getAppComponent().inject(this); if(guidePreference.get().getShouldShowGuide()){ //.. } //...
コード
コード例はこちらです。
おわりに
JSR 269(Pluggable Annotation Processing API)で生成したクラスをDagger2等であつかおうとすると罠がある事がわかりました。Dagger2で触らない場合には問題にはなりません。なかなかめんどくさいですが現状は別のクラスを経由する方法をとるしかなさそうです。何か回避方法がちゃんとある気はするので知っているかた教えてください。
*1:これらのクラス名に特に意味はありません。アプリケーション全体で1つである事を表す為に用いています