8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Jetpack ComposeでHiltを使ってDIする

Last updated at Posted at 2022-10-17

はじめに

私は業務でServer-side Kotlinの開発に携わっているのですが、Androidアプリ開発に関してはかなり初心者です。
ふと、Jetpack Composeでアプリを作ってみようと思い立ち、その過程で勉強したDIライブラリのHiltの使い方についてまとめました。

開発環境

  • PC: M1 Mac mini / macOS 12.5
  • Android Studio: Dolphin|2021.3.1

サンプルアプリの概要

機能要件

  • Joe SchmoeのAPIをコールして、アバターを生成
  • アバターをローカルDBに保存
  • 保存されているデータの一覧を表示

Joe Schmoe APIを使用したのは無料で利用できるAPIを探していてたまたま見つけたからです。
勉強のためとは言え、無料で利用させていただき、感謝いたします。

ライブラリ

  • DI: Hilt
  • その他: 画像表示にCoil、DatabaseにRoomを使用しています。

build.gradleの設定についてはAndoridの公式ページを参照してください。

設計

基本的にはクリーンアーキテクチャを採用しています。
Android開発ではMVVMとRepositoryパターンがよく用いられると思いますが、複数のレイヤーに対してDIするやり方を学ぶために敢えてViewModelからUseCaseを呼び出すようにしています。

ディレクトリ構成

app
├── di
│   └── modules
├── domains
│   ├── entities
│   └── repositories
├── infrastructures
│   ├── dao
│   └── repositories
├── presentations
│   ├── navigations
│   ├── screens
│   ├── theme
│   ├── viewmodels
│   └── MainActivity.kt
├── usecases
│       ├── inputports
│       └── interactors
└── MainApplication.kt

拡張子が付いてるもの以外はディレクトリを指してます。

Hiltの使い方

準備

MainApplicationクラスの作成

Jetpack Composeプロジェクトを開始しただけではまだ作成されていないので、MainApplicationクラスを追加します。
特に詳細の実装は不要です。


@HiltAndroidApp
class MainApplication: Application()

MainActivityの修正

既存のMainActivityに@AndroidEntryPointを付けるだけです。

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }
}

AndroidManifest.xmlの修正

<application>タグに作成したMainApplicationのパスを設定します。


    <application
        ~
        ~
        android:name=".MainApplication">
        <activity
            android:name=".presentations.MainActivity"
        ~
        ~
    </application>

実装

ViewにViewModelをインジェクト

Jetpack ComposeではViewModelを使用することは推奨されていない1ようですが、HiltはViewModelをライフサイクルに合わせて制御してくれるので安全に使用できると思っています(詳細は公式ページを参照)。

ここでは、ViewModelにUseCaseをインジェクトしています。
ViewModelは@HiltViewModelアノテーションを付けると特に何も記述しないで自動でバインディングしてくれます。

@HiltViewModel
class AvatarCreationScreenViewModel @Inject constructor(
    private val saveAvatarUseCase: SaveAvatarUseCase,
): ViewModel() {
    // View Model Logic
}

一方で、ViewではViewModelをhiltViewModel()から取得します。

@Composable
fun AvatarCreationScreen(
    viewModel: AvatarCreationScreenViewModel = hiltViewModel(),
    navigateToList: () -> Unit
) {
    // View
}

ViewModelは裏でインジェクトされるため、NavigationGraphではViewModelを敢えて渡さなくて良いのが楽です。

@Composable
fun NavigationGraph(navController: NavHostController) {
    NavHost(
        navController = navController,
        startDestination = "avatar_creation"
    ) {
        composable("avatar_creation") {
            AvatarCreationScreen(navigateToList = {
                navController.navigate("avatar_list")
            })
        }
        composable("avatar_list") {
            AvatarListScreen(backToCreation = {
                navController.popBackStack()
            })
        }
    }
}

UseCaseとReppositoryをインジェクト

ViewModel以外をインジェクトする場合は、di/moduleパスにModuleクラスを定義する必要があります(公式ページ)。
ネットを探すと書き方に揺らぎがありますが、公式ではprovideXXXX()に統一されてるのでprovideXXXX()を使えば良いです。

ざっと要点をまとめると

  • di/moduleパスに定義すると自動で読み込まれる
  • @InstallInでコンポーネントのスコープを設定する
    • ここでは、UseCaseやRepositoryはViewのライフサイクルには依存してほしくないので、SingletonComponentにしています
  • @Providesでバインドする対象を定義する
  • コンテキストを渡す場合は@ApplicationContextを利用するとApplicationのコンテキストがインジェクトされます
    • Roomを使うために必要だったからです
    • @ActivityContextなど、コンポーネントのスコープに合わせる必要があります
@Module
@InstallIn(SingletonComponent::class)
object ApplicationModule {

    @Provides
    fun provideAvatarRepository(
        @ApplicationContext context: Context
    ): AvatarRepository = AvatarRepositoryImpl(
        context = context
    )

    @Provides
    fun provideListAvatarUseCase(
        avatarRepository: AvatarRepository
    ): ListAvatarUseCase = ListAvatarUseCaseImpl(
        repository = avatarRepository
    )

    @Provides
    fun provideSaveAvatarUseCase(
        avatarRepository: AvatarRepository
    ): SaveAvatarUseCase = SaveAvatarUseCaseImpl(
        repository = avatarRepository
    )
}

最後に

ソースコードはこちらに置いてます。雑な設計なので参考程度に。

参考文献

  1. https://qiita.com/karamage/items/9b2b5a79c364b72836d4

8
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?