Skip to content

Androidのスコープ

このガイドでは、Android固有のスコープ実装について説明します。

INFO

コアスコープの概念については、スコープを参照してください。

概要

Koinのスコープを使用すると、Androidコンポーネントのライフサイクルに合わせて依存関係のライフサイクルを管理できます。これにより、メモリリークを防ぎ、適切なリソース管理を確実に行うことができます。

スコープの階層

スコープの種類生存期間画面回転後も保持されるかDSLアノテーション
Applicationアプリ全体✅ はいsingle { }@Singleton
ActivityActivityのライフサイクル❌ いいえactivityScope { }@ActivityScope
Activity Retainedfinish()まで✅ はいactivityRetainedScope { }@ActivityRetainedScope
FragmentFragmentのライフサイクル❌ いいえfragmentScope { }@FragmentScope
ViewModelViewModelのライフサイクル✅ はいviewModelScope { }@ViewModelScope

スコープの関係性

Application Scope (single { })
    └── Activity Retained Scope (画面回転後も保持)
            └── Activity Scope
                    ├── Fragment Scope 1
                    └── Fragment Scope 2
            └── ViewModel Scope (Activity/Fragmentスコープにはアクセス不可)

INFO

重要な原則: 子スコープは親スコープの定義にアクセスできますが、その逆はできません。

スコープされた依存関係の宣言

コンパイラプラグインDSL

kotlin
val appModule = module {
    // Activityスコープ
    activityScope {
        scoped<ActivityPresenter>()
        scoped<ActivityNavigator>()
    }

    // Fragmentスコープ
    fragmentScope {
        scoped<FragmentPresenter>()
    }

    // ViewModelスコープ
    viewModelScope {
        scoped<UserCache>()
        viewModel<UserViewModel>()
    }
}

アノテーション

kotlin
// Activityスコープ
@ActivityScope
class ActivityPresenter(private val repository: UserRepository)

@ActivityScope
class ActivityNavigator

// Activityリテインスコープ (画面回転後も保持)
@ActivityRetainedScope
class RetainedPresenter

// Fragmentスコープ
@FragmentScope
class FragmentPresenter

// ViewModelスコープ
@ViewModelScope
class UserCache

@KoinViewModel
@ViewModelScope
class UserViewModel(private val cache: UserCache) : ViewModel()

従来のDSL

kotlin
val appModule = module {
    activityScope {
        scoped { ActivityPresenter(get()) }
        scoped { ActivityNavigator() }
    }

    fragmentScope {
        scoped { FragmentPresenter(get()) }
    }

    viewModelScope {
        scoped { UserCache() }
        viewModel { UserViewModel(get()) }
    }
}

Androidコンポーネントでのスコープの使用

Activityスコープ

kotlin
class MyActivity : AppCompatActivity(), AndroidScopeComponent {

    // Activityのライフサイクルに紐付いたスコープを作成
    override val scope: Scope by activityScope()

    // スコープから注入
    private val presenter: ActivityPresenter by inject()
}

または、便利なベースクラスを使用します:

kotlin
class MyActivity : ScopeActivity() {

    // スコープはすでにセットアップされています
    private val presenter: ActivityPresenter by inject()
}

Activityリテインスコープ

設定変更(回転、テーマの変更など)後も保持されます:

kotlin
class MyActivity : AppCompatActivity(), AndroidScopeComponent {

    // ViewModelのライフサイクルに支えられており、画面回転後も保持されます
    override val scope: Scope by activityRetainedScope()

    private val presenter: RetainedPresenter by inject()
}

または、便利なベースクラスを使用します:

kotlin
class MyActivity : RetainedScopeActivity() {

    private val presenter: RetainedPresenter by inject()
}

Fragmentスコープ

Fragmentスコープは自動的に親Activityのスコープにリンクされます:

kotlin
class MyFragment : Fragment(), AndroidScopeComponent {

    override val scope: Scope by fragmentScope()

    // Fragmentスコープから取得
    private val presenter: FragmentPresenter by inject()

    // Activityスコープの依存関係にもアクセス可能
    private val activityPresenter: ActivityPresenter by inject()
}

または、便利なベースクラスを使用します:

kotlin
class MyFragment : ScopeFragment() {

    private val presenter: FragmentPresenter by inject()
}

型ベース vs アーキタイプスコープ

アーキタイプスコープ (推奨)

任意のActivity/Fragmentで動作する汎用的なスコープです:

kotlin
module {
    activityScope {
        scoped<MyPresenter>()
    }
}

// 任意のActivityで使用可能
class ActivityA : ScopeActivity() {
    private val presenter: MyPresenter by inject()
}

class ActivityB : ScopeActivity() {
    private val presenter: MyPresenter by inject()
}

型ベースのスコープ

特定のクラスに紐付いたスコープです:

kotlin
module {
    scope<MyActivity> {
        scoped<MyPresenter>()
    }
}

// MyActivityでのみ動作します
class MyActivity : AppCompatActivity(), AndroidScopeComponent {
    override val scope: Scope by activityScope()
    private val presenter: MyPresenter by inject()
}

ViewModelスコープ

ViewModelは(メモリリークを防ぐために)ActivityやFragmentのスコープにアクセスできません。スコープされた依存関係にはViewModelスコープを使用してください:

kotlin
module {
    viewModelScope {
        scoped<UserCache>()
        scoped<UserRepository>()
        viewModel<UserViewModel>()
    }
}
kotlin
@ViewModelScope
class UserCache

@ViewModelScope
class UserRepository(private val cache: UserCache)

@KoinViewModel
@ViewModelScope
class UserViewModel(
    private val repository: UserRepository
) : ViewModel()

ViewModelスコープの詳細な使用方法については、スコープ - ViewModelスコープを参照してください。

スコープのライフサイクル

スコープ終了の処理

スコープが破棄される前にクリーンアップを実行するには、onCloseScope() をオーバーライドします:

kotlin
class MyActivity : AppCompatActivity(), AndroidScopeComponent {

    override val scope: Scope by activityScope()

    override fun onCloseScope() {
        // scope.close() の直前に呼び出されます
        // ここではまだスコープにアクセス可能です
    }
}

WARNING

onDestroy() 内でスコープにアクセスしないでください。その時点ですでにスコープは閉じられています。

スコープのリンク

カスタムスコープを使用して、コンポーネント間でインスタンスを共有します:

kotlin
module {
    scope(named("session")) {
        scoped<UserSession>()
    }
}
kotlin
class MyActivity : ScopeActivity() {

    fun startSession() {
        val sessionScope = getKoin().createScope("session", named("session"))

        // 現在のスコープにリンク
        scope.linkTo(sessionScope)

        // これでUserSessionにアクセス可能になります
        val session: UserSession = get()
    }
}

クイックリファレンス

コンポーネントデリゲートベースクラス
Activityby activityScope()ScopeActivity
Activity (retained)by activityRetainedScope()RetainedScopeActivity
Fragmentby fragmentScope()ScopeFragment
スコープ画面回転後も保持されるかユースケース
activityScope❌ いいえUI状態、Presenter
activityRetainedScope✅ はいフォームの状態、保留中のリクエスト
fragmentScope❌ いいえFragment固有のPresenter
viewModelScope✅ はいViewModelの依存関係

ベストプラクティス

  1. アーキタイプを使用する - 再利用性のために、scope<MyActivity> { } よりも activityScope { } を優先してください。
  2. 回転時はリテインを使用する - 画面回転後も保持すべき状態には activityRetainedScope を使用してください。
  3. リークさせない - SingletonにActivityやFragmentを注入しないでください。
  4. カスタムスコープを閉じる - 手動で作成したスコープは必ず閉じてください。
  5. onCloseScopeを使用する - スコープ破棄前のクリーンアップに使用してください。

次のステップ