LoginSignup
7
8

More than 3 years have passed since last update.

【Android】ライフサイクル(Lifecycle)アンチパターン 〜2020年版〜 その2

Last updated at Posted at 2020-08-01

はじめに

以前、Lifecycle のアンチパターンについて最新のベターな実装を紹介しました。
【Android】ライフサイクル(Lifecycle)アンチパターン 〜2020年版〜 その1

今回は第2弾となります。
Activity(Fragment)の Lifecycle のアンチパターンについてと最新のベターな実装について紹介します。
第1弾で紹介した実装と組み合わせて使用するとよりよいと思います。

アンチパターンの実装例

onResume() で通信処理等をしたいケースがあったとします。
しかし、onResume() は、onCreate(画面生成)時以外に画面回転、バックグラウンド復帰、別の画面から戻ってくる onActivityResult() 後のケースも通知されてしまいます。その場合にありがちな実装は以下です。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    var isAfterOnCreate: Boolean = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // フラグを立てる
        isAfterOnCreate = true
    }

    override fun onResume() {
        super.onResume()
        if (isAfterOnCreate) {
            isAfterOnCreate = false

            // onCreate() 後の onResume() で一度だけしたい処理
        }
    }
}

なぜアンチパターンなのか

上記の実装で仕様は満たせます。しかし今回の例は1つの Activity で処理も少なかったので問題になりませんが、機能や画面が多くなると以下の問題が出てきます。

・画面が多い場合も同じようにフラグ管理しないといけない
・onResume() に多くの処理が集まりがちで、複雑度も上がり可読性が悪くなる

ベターな実装

LifecycleCoroutineScope
を使用して管理します。
※今回の実装例は Kotlin のみ使用可能で Java では使えませんので注意してください

(Google公式サイト)LifecycleCoroutineScope

こちらは、名前の通り Coroutine の利用を前提とした API ですが、Coroutine を使わないケースでも十分に使えます。
Coroutine の処理で利用すればより便利です。

LifecycleCoroutineScope の実装方法

MainActivity.kt
// インポートが必要
import androidx.lifecycle.lifecycleScope

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launchWhenResumed {
            // onCreate() 後の onResume() で一度だけしたい処理
        }
    }
}

これだけで OK です!
実際使う場合は lifecycleScope.launchWhenResumed の部分をメソッド化した方が良いです。

LifecycleCoroutineScope の通知

上記では launchWhenResumed を使用した例を紹介しましたが、他に数種類通知が用意されています。
仕様に合ったものを使いましょう。

・lifecycleScope.launch
・lifecycleScope.launchWhenCreated
・lifecycleScope.launchWhenStarted
・lifecycleScope.launchWhenResumed

応用方法やログでの動作確認

ログを確認すると動作が分かりやすいと思います。
また、通知するケースを増やしてみます。

MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("MainActivity", "_onCreate")
        lifecycleScope.launchWhenResumed {
            Log.d("MainActivity", "_launchWhenResumed onCreate")
            // onCreate() 後の onResume() で一度だけしたい処理
        }

        // ここに別の Activity への遷移する処理を追加
        val button: Button = root.findViewById(R.id.button)
        button.setOnClickListener {
            startActivityForResult(Intent(this, OtherActivity::class.java), 0)
        }
    }

    override fun onResume() {
        super.onResume()
        Log.d("MainActivity", "_onResume")
    }

    override fun onStart() {
        super.onStart()
        Log.e("MainActivity", "_onStart")
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        Log.e("MainActivity", "_onActivityResult")
        //outputLog()
        lifecycleScope.launchWhenStarted {
            Log.e("MainActivity", "_launchWhenStarted _onActivityResult")
        }
        // onActivityResult() 後の onStart() で一度だけしたい処理
    }
}

ログを確認すると以下になると思います。
・画面生成時
_onCreate → onStart → _onResume → _launchWhenResumed onCreate
・バックグラウンド復帰時
_onCreate → onStart → _onResume
・別Activityから戻ってくる場合
_onActivityResult → onStart → _launchWhenStarted _onActivityResult → _onResume

Fragment での利用

当然ですが Fragment でも同じように使えます。

MainFragment.kt
class MainFragment : Fragment() {

    override fun onAttach(context: Context) {
        super.onAttach(context)
        lifecycleScope.launchWhenResumed {
            // 実装したい処理
        }
    }
}

lifecycleScope.launch を使ってみる

lifecycle.currentState.isAtLeast と組み合わせて使います。

MainFragment.kt
class MainFragment : Fragment() {

    init {
        // init内でも使えます
        lifecycleScope.launch {
            when {
                lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED) -> {
                }
                lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED) -> {
                }
                lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) -> {
                }
                lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED) -> {
                }
            }
        }
    }
}

最後に

Android も進化していっているので便利な機能を使ってバグが少ないアプリを作りましょう。

7
8
0

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
7
8