メインコンテンツまでスキップ

Androidスコープの管理 (Android Scopesの管理)

Androidライフサイクルとの連携

Androidコンポーネントは主にライフサイクルによって管理されます。ActivityやFragmentを直接インスタンス化することはできません。システムがすべての生成と管理を行い、onCreateやonStartなどのメソッドでコールバックを行います。

そのため、KoinモジュールでActivity/Fragment/Serviceを記述することはできません。プロパティに依存性を注入する必要があり、ライフサイクルも尊重する必要があります。UI部分に関連するコンポーネントは、不要になったらすぐに解放する必要があります。

したがって、以下のようなコンポーネントがあります。

  • 長寿命コンポーネント(Service、Data Repositoryなど) - 複数の画面で使用され、破棄されない
  • 中寿命コンポーネント(ユーザーセッションなど) - 複数の画面で使用され、一定時間後に破棄される
  • 短寿命コンポーネント(ビュー) - 1つの画面でのみ使用され、画面終了時に破棄される

長寿命コンポーネントは、single定義として簡単に記述できます。中寿命および短寿命コンポーネントについては、いくつかのアプローチがあります。

MVP(Model-View-Presenter)アーキテクチャスタイルでは、PresenterはUIを支援/サポートする短寿命コンポーネントです。Presenterは画面が表示されるたびに作成し、画面が閉じたら破棄する必要があります。

新しいPresenterは毎回作成されます

class DetailActivity : AppCompatActivity() {

// 注入されたPresenter
override val presenter : Presenter by inject()

モジュールで記述できます。

  • factoryとして - by inject()またはget()が呼び出されるたびに新しいインスタンスを生成します
val androidModule = module {

// PresenterのFactoryインスタンス
factory { Presenter() }
}
  • scopeとして - スコープに関連付けられたインスタンスを生成します
val androidModule = module {

scope<DetailActivity> {
scoped { Presenter() }
}
}
注記

Androidのメモリリークのほとんどは、UI/Androidコンポーネントを非Androidコンポーネントから参照することから発生します。システムがその参照を保持し、ガベージコレクションによって完全に破棄できません。

Androidコンポーネントのスコープ (3.2.1以降)

Androidスコープの宣言

Androidコンポーネントの依存関係をスコープするには、次のようにscopeブロックを使用してスコープセクションを宣言する必要があります。

class MyPresenter()
class MyAdapter(val presenter : MyPresenter)

module {
// MyActivityのスコープを宣言
scope<MyActivity> {
// 現在のスコープからMyPresenterインスタンスを取得
scoped { MyAdapter(get()) }
scoped { MyPresenter() }
}
}

Androidスコープクラス

Koinは、ActivityまたはFragmentに対して宣言されたスコープを直接使用できるように、ScopeActivityRetainedScopeActivity、およびScopeFragmentクラスを提供します。

class MyActivity : ScopeActivity() {

// MyPresenterはMyActivityのスコープから解決されます
val presenter : MyPresenter by inject()
}

内部的には、AndroidスコープはAndroidScopeComponentインターフェースで使用して、次のようにscopeフィールドを実装する必要があります。

abstract class ScopeActivity(
@LayoutRes contentLayoutId: Int = 0,
) : AppCompatActivity(contentLayoutId), AndroidScopeComponent {

override val scope: Scope by activityScope()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

checkNotNull(scope)
}
}

AndroidScopeComponentインターフェースを使用して、scopeプロパティを実装する必要があります。これにより、クラスで使用されるデフォルトのスコープが設定されます。

AndroidスコープAPI

AndroidコンポーネントにバインドされたKoinスコープを作成するには、次の関数を使用します。

  • createActivityScope() - 現在のActivityのスコープを作成します(スコープセクションを宣言する必要があります)
  • createActivityRetainedScope() - 現在のActivityの保持されたスコープ(ViewModelライフサイクルによってサポートされる)を作成します(スコープセクションを宣言する必要があります)
  • createFragmentScope() - 現在のFragmentのスコープを作成し、親Activityスコープにリンクします

これらの関数はデリゲートとして使用でき、さまざまな種類のスコープを実装できます。

  • activityScope() - 現在のActivityのスコープを作成します(スコープセクションを宣言する必要があります)
  • activityRetainedScope() - 現在のActivityの保持されたスコープ(ViewModelライフサイクルによってサポートされる)を作成します(スコープセクションを宣言する必要があります)
  • fragmentScope() - 現在のFragmentのスコープを作成し、親Activityスコープにリンクします
class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent {

override val scope: Scope by activityScope()

}

次のように、保持されたスコープ(ViewModelライフサイクルによってサポートされる)を設定することもできます。

class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent {

override val scope: Scope by activityRetainedScope()
}
注記

Androidスコープクラスを使用しない場合は、独自のクラスを使用し、AndroidScopeComponentをスコープ作成APIとともに使用できます

AndroidScopeComponentとスコープのクローズ処理

AndroidScopeComponentonCloseScope関数をオーバーライドすることにより、Koinスコープが破棄される前にいくつかのコードを実行できます。

class MyActivity() : AppCompatActivity(contentLayoutId), AndroidScopeComponent {

override val scope: Scope by activityScope()

override fun onCloseScope() {
// スコープを閉じる前に呼び出されます
}
}
注記

onDestroy()関数からスコープにアクセスしようとすると、スコープはすでに閉じられています。

ViewModelスコープ (3.5.4以降)

ViewModelは、リーク(ActivityやFragmentのリークなど)を回避するために、ルートスコープに対してのみ作成されます。これは、ViewModelが互換性のないスコープにアクセスする可能性がある可視性の問題を保護します。

ViewModelはActivityまたはFragmentスコープにアクセスできません。なぜでしょうか?ViewModelはActivityやFragmentよりも長く存続するため、適切なスコープ外の依存関係をリークさせる可能性があるためです。

注記

ViewModelスコープ外から依存関係をブリッジする必要がある場合は、「注入されたパラメータ」を使用して、オブジェクトをViewModelに渡すことができます:viewModel { p -> }

ScopeViewModelは、ViewModelスコープでの作業を支援する新しいクラスです。これはViewModelのスコープの作成を処理し、scopeプロパティを提供してby scope.inject()で注入できるようにします。

module {
viewModelOf(::MyScopeViewModel)
scope<MyScopeViewModel> {
scopedOf(::Session)
}
}

class MyScopeViewModel : ScopeViewModel() {

// onClearedで、スコープが閉じられます

// 現在のMyScopeViewModelのスコープから注入されます
val session by scope.inject<Session>()

}

ScopeViewModelを使用すると、onCloseScope()関数をオーバーライドして、スコープが閉じられる前にコードを実行することもできます。

注記

ViewModelスコープ内のすべてのインスタンスは同じ可視性を持ち、ViewModelインスタンスのライフタイムの間、ViewModelのonCleared関数が呼び出されるまで存続します

たとえば、ActivityまたはFragmentがViewModelを作成すると、関連するスコープが作成されます。

class MyActivity : AppCompatActivity() {

// ViewModelとそのスコープを作成
val myViewModel by viewModel<MyScopeViewModel>()

}

ViewModelが作成されると、このスコープ内の関連するすべての依存関係を作成して注入できます。

ScopeViewModelクラスを使用せずにViewModelスコープを手動で実装するには、次の手順に従います。

class MyScopeViewModel : ViewModel(), KoinScopeComponent {

override val scope: Scope = createScope(this)

// 依存関係を注入
val session by scope.inject<Session>()

// スコープをクリア
override fun onCleared() {
super.onCleared()
scope.close()
}
}

スコープリンク

スコープリンクを使用すると、カスタムスコープを持つコンポーネント間でインスタンスを共有できます。

より拡張された使用法では、コンポーネント間でScopeインスタンスを使用できます。たとえば、UserSessionインスタンスを共有する必要がある場合です。

最初にスコープ定義を宣言します。

module {
// 共有ユーザーセッションデータ
scope(named("session")) {
scoped { UserSession() }
}
}

UserSessionインスタンスの使用を開始する必要がある場合は、そのスコープを作成します。

val ourSession = getKoin().createScope("ourSession",named("session"))

// ScopeActivityまたはScopeFragmentから、ourSessionスコープを現在の`scope`にリンクします
scope.linkTo(ourSession)

次に、必要な場所で使用します。

class MyActivity1 : ScopeActivity() {

fun reuseSession(){
val ourSession = getKoin().createScope("ourSession",named("session"))

// ScopeActivityまたはScopeFragmentから、ourSessionスコープを現在の`scope`にリンクします
scope.linkTo(ourSession)

// MyActivity1のスコープ+ ourSessionスコープを調べて解決します
val userSession = get<UserSession>()
}
}
class MyActivity2 : ScopeActivity() {

fun reuseSession(){
val ourSession = getKoin().createScope("ourSession",named("session"))

// ScopeActivityまたはScopeFragmentから、ourSessionスコープを現在の`scope`にリンクします
scope.linkTo(ourSession)

// MyActivity2のスコープ+ ourSessionスコープを調べて解決します
val userSession = get<UserSession>()
}
}