読者です 読者をやめる 読者になる 読者になる

visible true

技術的なメモを書く

kvs-schemaとdagger2を同時につかう

Android dagger2

kvs-schemaというSharedPreferenceをいい感じにクラス化出来るライブラリがあるんですが、このライブラリはJSR 269(Pluggable Annotation Processing API)を使ってコンパイル時にコード生成をしています。dagger2も同様にJSR269によってコード生成をしています。なんとなく「変な衝突の仕方しないかな?」と思ったので試してみました。案の定問題がありました。本エントリではJSR 269を使ったライブラリの衝突の問題とその回避方法について解説します。

kvs-schemaの使い方については以下のライブラリ作者が書いた解説記事を参照してください。

qiita.com

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");
  }
}

GuidePreferenceSchemaGuidePreferenceを返却するメソッドを生やしておきます。これは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()){
      //..
    }
    //...

コード

コード例はこちらです。

github.com

おわりに

JSR 269(Pluggable Annotation Processing API)で生成したクラスをDagger2等であつかおうとすると罠がある事がわかりました。Dagger2で触らない場合には問題にはなりません。なかなかめんどくさいですが現状は別のクラスを経由する方法をとるしかなさそうです。何か回避方法がちゃんとある気はするので知っているかた教えてください。

*1:これらのクラス名に特に意味はありません。アプリケーション全体で1つである事を表す為に用いています