web-dev-qa-db-ja.com

Android @ Singletonアノテーション付きクラスが注入されていない上の短剣2

現在、Dagger2をAndroidアプリケーションに統合しようとしています。プロジェクトのセットアップは次のとおりです。

  • 図書館
  • アプリ(ライブラリによって異なります)

私のライブラリプロジェクトでは、後でそれを必要とする他のクラス(アクティビティと通常のクラス)にライブラリとアプリプロジェクトに挿入するクラスを定義しました。

@Singleton
public class MyManager{
  @Inject
  public MyManager(){
    //Do some initializing
  }
}

さて、たとえば私のフラグメントやアクティビティ、または通常のクラスでは、上記のシングルトンを次のように注入します。

public class SomeClass{

  @Inject
  MyManager myManager;
}

実際にはmyManagerは常にnullであるため、私は考えました。そして、どうやらコンストラクターも呼び出されないようですので、構成的に何かが欠けているに違いないと思いますか?または、ドキュメントを誤解していて、このように機能することを意図していないのでしょうか。 MyManagerクラスの目的は、アプリケーション全体でアクセス可能なコンポーネント蓄積エンティティになることです。そのため、@ Singletonを選択しました。

[〜#〜]更新[〜#〜]

混乱を避けるために:コメントのどこかにコンポーネントがあると述べました。これは「コンポーネントベースの設計」という意味でのコンポーネントを指し、短剣とは関係ありません。私が持っている短剣ベースのコードはすべて上記にリストされています-私のコードには短剣に関連するものは他にありません。

@Componentを追加し始めたとき、dagger2が正しくセットアップされていなかったため、コンパイラの問題が発生しました-dagger2を正しくセットアップする方法についてこの非常に役立つスレッドを確認してください: https://stackoverflow.com/a/29943394/104153

UPDATE 2

これは、G。Lombardの提案に基づいて更新されたコードです-コードを次のように変更しました-元のシングルトンはライブラリプロジェクトにあります:

@Singleton
public class MyManager{
  @Inject
  public MyManager(){
    //Do some initializing
  }
}

また、ライブラリプロジェクトにはbootstrap class:

@Singleton
@Component
public interface Bootstrap {
    void initialize(Activity activity);
}

次に、上記のBootstrapクラスをアクティビティで使用します(具体的なアプリでは、[〜#〜] not [〜#〜]ライブラリプロジェクトにあります!ただし、ライブラリには、アクセスするクラス/アクティビティもありますBootstrap MyManagerを注入する):

public class MyActivity extends Activity{

    @Inject
    MyManager manager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //DONT DO THIS !!! AS EXPLAINED BY EpicPandaForce
        DaggerBootstrap.create().initialize(this);
    }
}

しかし、この行の後でも:

        DaggerBootstrap.create().initialize(this);

マネージャインスタンスはまだnullです。つまり、注入されていません。

私はちょうどこれを見つけました: https://stackoverflow.com/a/29326023/10415

これは、読み間違えない限り、@ Injectを使用してデータを注入するBootstrapクラスのすべてのクラスを指定する必要があることを意味します。残念ながら、これはオプションではありません。私がそれをしなければならない40以上のクラスと活動。

つまり、私のBootstrapインターフェイスは、どうやら次のようになっている必要があります。

@Singleton
@Component
public interface Bootstrap {
    void initialize(ActivityA activity);
    void initialize(ActivityB activity);
    void initialize(ActivityC activity);
    void initialize(ActivityD activity);
    void initialize(ActivityE activity);
    void initialize(ActivityF activity);
    //and so on and so forth...
}

上記が当てはまる場合、それは私のユースケースには価値がありません。プラス:ここで40以上のクラスのいずれかを指定するのを忘れた場合、コンパイル時のチェックがないようです。それは機能しません-つまり、実行時にアプリをクラッシュさせます。

12
AgentKnopf

使用しているという点で間違いを犯しています

DaggerBootstrap.create().initialize(this);

スコープは複数のコンポーネントインスタンス間で共有されないため、アクティビティで。私がお勧めするのは、カスタムアプリケーションクラスを使用することです

public class CustomApplication extends Application {
    @Override
    public void onCreate() {
         super.onCreate();
         Bootstrap.INSTANCE.setup();
    }
}

@Component
@Singleton
public interface _Bootstrap {
    void initialize(ActivityA activityA);
    //void initiali...
}

public enum Bootstrap {
    INSTANCE;

    private _Bootstrap bootstrap;

    void setup() {
        bootstrap = Dagger_Bootstrap.create();
    }

    public _Bootstrap getBootstrap() {
        return bootstrap;
    }
}

それからあなたはそれを次のように呼ぶことができます

Bootstrap.INSTANCE.getBootstrap().initialize(this);

このようにして、クラス間でコンポーネントを共有します。私は個人的にBootstrapinjector、および_Bootstrap as ApplicationComponentなので、次のようになります。

Injector.INSTANCE.getApplicationComponent().inject(this);

しかし、それは私の典型的な設定です。名前は本当に重要ではありません。

編集:最後の質問まで、サブスコープとコンポーネントの依存関係によってこれを解決できます。

あなたの図書館プロジェクトは図書館のクラスしか見ることができないはずですよね?その場合、あなたがするのは

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface LibraryScope {
}

@Component(modules={LibraryModule.class})
@LibraryScope
public interface LibraryComponent {
    LibraryClass libraryClass(); //provision method for `MyManager`
}

@Module
public class LibraryModule {
    @LibraryScope
    @Provides
    public LibraryClass libraryClass() { //in your example, LibraryClass is `MyManager`
        return new LibraryClass(); //this is instantiation of `MyManager`
    }
}

public enum LibraryBootstrap {
    INSTANCE;

    private LibraryComponent libraryComponent;

    static {
        INSTANCE.libraryComponent = DaggerLibraryComponent.create();
    }

    public LibraryComponent getLibraryComponent() {
        return libraryComponent;
    }
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

@Component(dependencies={LibraryComponent.class}, modules={AdditionalAppModule.class})
@ApplicationScope
public interface ApplicationComponent extends LibraryComponent {
    AdditionalAppClass additionalAppClass();

    void inject(InjectableAppClass1 injectableAppClass1);
    void inject(InjectableAppClass2 injectableAppClass2);
    void inject(InjectableAppClass3 injectableAppClass3);
}

@Module
public class AdditionalAppModule {
    @ApplicationScope
    @Provides
    public AdditionalAppClass additionalAppClass() { //something your app shares as a dependency, and not the library
        return new AdditionalAppClass();
    }
}

public enum ApplicationBootstrap {
    INSTANCE;

    private ApplicationComponent applicationComponent;

    void setup() {
        this.applicationComponent = DaggerApplicationComponent.builder()
                                        .libraryComponent(LibraryBootstrap.INSTANCE.getLibraryComponent())
                                        .build();
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }
}

次に

@Inject
LibraryClass libraryClass; //MyManager myManager;

...
    ApplicationBootstrap.INSTANCE.getApplicationComponent().inject(this);
10
EpicPandaForce

Componentがどのように見えるか、複数のコンポーネントがあるかどうかなどを示さなかったため、問題が何であったかを言うのは難しいです。

この論理構造を想定すると:

/app
   MainComponent
   SomeClass    // where MyManager is to be injected
   MainActivity // where SomeClass is to be injected
/library
   LibraryComponent
   MyManager    // Singleton

次に、リストされているクラスは、次の構成で正しく注入されます。

@Singleton
@Component
public interface LibraryComponent {
    MyManager getMyManager();
}

依存関係をアクティビティに注入するアプリレベルのコンポーネント:

@ActivityScope
@Component(dependencies = LibraryComponent.class)
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

MainComponentLibraryComponentに依存しますが、後者はシングルトンスコープを持っているため、他のスコープも定義する必要があります。ここでは「アクティビティスコープ」を使用しました。 (または、MainComponentをシングルトンにして、必要に応じてLibraryComponentを完全に削除することもできます。)

最後に、すべて次のようなアクティビティに注入されます。

@Inject
SomeClass someClass;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    DaggerMainComponent.builder()
            .libraryComponent(DaggerLibraryComponent.create())
            .build()
            .inject(this);

    someClass.doSomething();
}

実用的なサンプルを入れました ここではGitHubにあります

更新1:

セットアップを正しく理解していれば、これまでにリストされている2つのクラス(MyManagerSomeClass)で@Singleton@Injectアノテーションのみを使用しており、他のDagger関連のコードはありません。あなたのプロジェクト。

その場合、MyManagerが注入されない理由は、Daggerが依存関係を提供/インスタンス化する方法を知らないためです。これが、前述の「コンポーネント」の出番です。 Dagger 2コンポーネント(@Componentでアノテーションが付けられたインターフェースまたは抽象クラス)がないと、依存関係は自動的に挿入されません。

依存性注入の概念の経験があるかどうかはわかりませんが、経験がないと仮定して、MyManagerSomeClass

まず、DIを使用するときは、「newables」と「injectables」の違いを理解する必要があります。 このブログ投稿 MiskoHeveryによる詳細を説明しています。

つまり、newSomeClassアップすることはできません。これは機能しません:

mSomeClass = new SomeClass();

これを行った場合(アクティビティやフラグメントなど)、Daggerは、依存関係がSomeClassに注入されることを期待していたことを認識せず、何も注入する機会がないためです。

依存関係を注入するには、Daggerを介してSomeClass自体をインスタンス化(または注入)する必要があります。

つまり、SomeClassが使用されているアクティビティで、次のものが必要になります。

@Inject
SomeClass mSomeClass;

次に、実際の注入を実行するためにDaggerコンポーネントが必要です。コンポーネントを作成するには、ルートオブジェクト(たとえばMainActivity)を引数として取るメソッドを使用してインターフェースを作成します。例:

@Singleton
@Component
public interface Bootstrap {
    void initialize(MainActivity activity);
}

プロジェクトをビルドすると、Dagger2はこのインターフェイスを実装するDaggerBootstrapというクラスを生成します。この生成されたクラスを使用して、アクティビティのonCreateでインジェクションを実行します。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    DaggerBootstrap.create().initialize(this);

    mSomeClass.doSomething();
}

この生成されたコンポーネントは、あなたが見逃している重要な部分だと思います。上記の完全なコード ここ

いくつかの便利なDagger2リソース:

更新2:

パズルの最後の欠けている部分は、コンポーネントがActivity基本クラスに注入メソッドを提供したが、実際の具体的なアクティビティには提供しなかったようです。

残念ながら、Dagger 2には、eachアクティビティまたは注入する他のクラスの注入メソッドが必要です。

あなたが言ったように、あなたがあなたのアプリで多くの異なる活動をしているとき、これは迷惑になるでしょう。これにはいくつかの可能な回避策があります。「dagger2inject base class」を検索してください。たとえば、@ EpicPandaForceによるこの提案: Dagger 2基本クラスの注入

また、コメントで@EpicPandaForceが指摘しているように、私の単純な例では、毎回DaggerLibraryComponent.create()を呼び出しましたが、そのコンポーネントはシングルトンを提供することになっているため、おそらく必要なものではありません。アプリケーションインスタンスなど、別の場所から既存のインスタンスを取得することをお勧めします。

7
G. Lombard