アプリ起動中にテーマ変更しちゃって…全然音楽が再生できなくてェ…

## はじめに
まずはこちらの動画を見ていただきたいです。
<iframe width="560" height="315" src="https://www.youtube.com/embed/yQG-vPO0P5w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
アプリ起動時は音が鳴るのに、ダークテーマに変更すると、音が鳴らなくなってしまいました。
これはどのような現象なのでしょうか？？

## デバイス構成の変更
まず、「デバイスのテーマを変更する」ことは、**デバイス構成を変更する**といいます。
デバイス構成を変更すると、既存のActivityは破棄され、新しいActivityが再生成されます。
Activityが処理するデバイス構成の変更には、以下のものがあります。
([Android Developers : android:configChanges](https://developer.android.com/guide/topics/manifest/activity-element?hl=ja#config))

<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2780537/bb2e9bec-7907-4b8f-fc22-3b7e9a5f30a7.png>
<br>
<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2780537/4824a705-7a0a-4bd4-9562-9d504b94f28a.png>

画面回転すると`onDestroy()`→`onCreate()`が実行されることは知っていたのですが、テーマ変更やフォントサイズの変更なども同じ**構成変更**の一つなのですね！
(本課題に直面したときに作成していたアプリでは画面回転をできないように設定していたので、この課題に気づくのが遅れました…)


## どういう実装をしていたのか
音楽の再生にはExoPlayer Media3を使用しています。
実装コードは次のとおりです。
### SoundPlayerModule.kt
```kotlin
@Module
@InstallIn(SingletonComponent::class)
class SoundPlayerModule {

    @Singleton
    @Provides
    fun provideMediaPlayer(@ApplicationContext context: Context) = ExoPlayer.Builder(context).build()
}

@Module
@InstallIn(SingletonComponent::class)
abstract class BindSoundPlayerModule {
    @Singleton
    @Binds
    abstract fun soundPlayer(soundPlayerImpl: SoundPlayerImpl): SoundPlayer
}
```
### SoundPlayer.kt
```kotlin
interface SoundPlayer {
    fun play()
    fun release()
}

@UnstableApi
class SoundPlayerImpl @Inject constructor(private val exoPlayer: ExoPlayer) : SoundPlayer {

    override fun play() {
        val media = MediaItem.fromUri(RawResourceDataSource.buildRawResourceUri(R.raw.level_up))
        exoPlayer.setMediaItem(media)
        exoPlayer.prepare()
        exoPlayer.play()
    }

    override fun release() {
        exoPlayer.release()
    }
}
```

### MainActivity.kt
```kotlin
@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    @Inject
    lateinit var soundPlayer: SoundPlayer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            SampleMediaPlayerTheme {
                Box(
                    modifier = Modifier.fillMaxSize(),
                    contentAlignment = Alignment.Center
                ){
                    Button(onClick = { soundPlayer.play() }) {
                        Text(text = "Play Sound")
                    }
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        soundPlayer.release()
    }
}
```

### なぜ構成変更後に音楽が再生できないのか:thinking_face:
ログを確認したところ、(もちろんですが)`onDestroy()` → `onCreate()`が実行されていたので、`soundPlayer.release()`した後に、ExoPlayerインスタンスの再生成?が上手くいっていなさそうだと考えました。
ExoPlayerインスタンスの生成はHiltを使用して行っていたので、Injectのタイミングが良くない？？上手くできていない？？
じゃあExoPlayerインスタンスはいつ生成されているのでしょうか？？

## Hiltで生成したインスタンスのライフサイクル
Hiltで生成するコンポーネントごとに、作成・破棄のタイミングが決まっています。
([Android Developers:Hilt を使用した依存関係の注入-コンポーネントのライフタイム-](https://developer.android.com/training/dependency-injection/hilt-android?hl=ja#component-lifetimes))
<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2780537/96b9e970-e898-6988-11b7-494e4aa84395.png>
私の実装では、`SingletonComponent`を使用していました。したがって、ExoPlayerインスタンスはApplication#onCreate()のタイミングで作成され、Applicationが破棄されるタイミングで一緒に破棄されます。
つまり、冒頭の動画の状態は、「Activityは破棄されているが、Applicationは生きている」→「releaseされたExoPlayerインスタンスが生きているため、音楽再生ができない」状態だと考えられます。

私の実現したいことは、「ExoPlayerインスタンスの生成・破棄のタイミングを、Activityのライフサイクルと合わせる」ことです。
ちょうど良さそうな！`ActivityComponent`というものが！ありますね！

## ActivityComponentを使用して書き直してみる
`ActivityComponent`を使用して、`SoundPlayerModule.kt`を書き直してみます。
```kotlin
@Module
@InstallIn(ActivityComponent::class)
class SoundPlayerModule {

    @ActivityScoped
    @Provides
    fun provideMediaPlayer(@ActivityContext context: Context) = ExoPlayer.Builder(context).build()
}

@Module
@InstallIn(ActivityComponent::class)
abstract class BindSoundPlayerModule {
    @ActivityScoped
    @Binds
    abstract fun soundPlayer(soundPlayerImpl: SoundPlayerImpl): SoundPlayer
}
```

できたものがこちらになります。
<iframe width="560" height="315" src="https://www.youtube.com/embed/Va2iRO5LimQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
テッテレー！！！！！！テーマ変更後も音楽再生できました！！！！！！！！

これで適切にExoPlayerインスタンスを処理することができたのではないでしょうか。

## まとめ
今まで深く考えず`SingletonComponent`を使用していたのですが、**Hiltにより生成したComponentのライフサイクル**を意識し、適切なComponentを選択することの大切さを学びました。
また、**システム構成の変更**には画面回転だけでなく、テーマ変更など、さまざまな種類があることを知りました。

## 最後に
公式に構成変更に関するベストプラクティスが乗っているので、そちらを参照することをオススメします。
([Android Developers: 構成の変更に対処する](https://developer.android.com/guide/topics/resources/runtime-changes?hl=ja#configuration-changes))

## おまけ
GitHubリポジトリを見ていると、`release()`を実行していないコードもいくつか見られました。
そこで、ChatGPTに質問してみた結果がこちらです。
<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2780537/2d03b535-7dbc-16ef-5827-519c7fa07e0a.png>
たしかに、Hiltによってコンポーネントを生成する際に、指定したライフサイクルに応じてよしなに処理してくれる旨の記載が公式にありました。
なんとなく理論はわかるけれど、果たして本当にいいのか…？の気持ちです。もしご存知の方がいらっしゃったら、教えてくださると嬉しいです。