Androidアプリ開発の覚書き

Androidアプリ開発を、Kotlin, Jetpack Composeで行っています。気になったことをChatGPTに聞いて記事にしています。

ボタン押下後の長い処理とか、二度押し禁止とか

本職で何年かプログラマーをしていましたが、その頃に比べても、非同期処理を普通に用いるAndroidプログラミングはすごく難しく感じます。

ChatGPTがなかったら、ずっと悩んだままだったかもしれません。

ボタンを押した後、時間がかかる処理をするときは、coroutineや別スレッドを用いて、バックグラウンドで処理させるのが通常だと思いますが、具体的にはどのようなコードを書けば良いのですか?

User
Androidアプリ開発をKotlinとJetpackで行っています。

ボタンを押した後、時間がかかる処理をするときは、coroutineや別スレッドを用いて、バックグラウンドで処理させるのが通常だと思いますが、具体的にはどのようなコードを書けば良いのですか?

ChatGPT
KotlinとJetpackを使用したAndroidアプリ開発では、時間がかかる処理をバックグラウンドで実行するためにコルーチンをよく使います。コルーチンは、非同期タスクをシンプルかつ効率的に扱えるように設計されています。

以下は、ボタンが押された後に時間がかかる処理をバックグラウンドで実行する基本的な例です。

まず、build.gradle (Module: app)ファイルにコルーチンの依存関係を追加します:

dependencies {
  // コルーチンを使用するためのKotlinの依存関係
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
}

そして、Kotlinでのコルーチンの使用例を以下に示します:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {
  // UIスレッドとバインドされたコルーチンスコープを作成
  private val uiScope = CoroutineScope(Dispatchers.Main)

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // ボタンがクリックされたときのリスナーを設定
    findViewById<View>(R.id.yourButtonId).setOnClickListener {
      // UIスレッドからバックグラウンド処理を開始
      uiScope.launch {
        // 重い処理をバックグラウンドスレッドで実行
        val result = withContext(Dispatchers.IO) {
          // ここに時間がかかる処理を書く
          doLongRunningTask()
        }
        // バックグラウンド処理の結果をUIに反映
        updateUI(result)
      }
    }
  }

  // 時間がかかる処理の例
  private fun doLongRunningTask(): String {
    // 仮の時間がかかる処理
    Thread.sleep(2000) // 2秒間スリープ
    return "Task Completed"
  }

  // UIを更新するメソッド
  private fun updateUI(result: String) {
    // 例:TextViewのテキストを更新
    findViewById<TextView>(R.id.yourTextViewId).text = result
  }
}

この例では、Dispatchers.IOを使用して時間がかかる処理(doLongRunningTask関数内の処理)をバックグラウンドスレッドで実行し、その結果をDispatchers.Mainを使ってメインスレッドに戻してUIを更新しています。これにより、アプリの応答性が保たれ、UIスレッドがブロックされることがありません。

あと、例えば、時間のかかる処理の進行状況を表示したい場合も知りたいです。

User
ありがとうございます。すごく分かりやすいです。

あと、例えば、時間のかかる処理の進行状況を表示したい場合も知りたいです。

ChatGPT
時間のかかる処理の進行状況を表示するには、バックグラウンド処理からUIスレッドに定期的に情報を送信し、それを基にUIを更新する必要があります。コルーチンを使用してこのような処理を実装する一般的な方法は、LiveDataやStateFlowを使うことです。ここではStateFlowを使用した例を紹介します。

StateFlowを使用した進行状況の更新 まず、StateFlowを使うために必要な依存関係をbuild.gradleファイルに追加します。

dependencies {
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
}

次に、進行状況を表すStateFlowを定義し、バックグラウンド処理中にその値を更新します。そして、UI側でこのStateFlowを監視し、更新があった場合にUIを更新します。

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

class MainActivity : AppCompatActivity() {
  private val uiScope = CoroutineScope(Dispatchers.Main)

  // 進行状況を表すStateFlowを定義
  private val _progress = MutableStateFlow(0) // 私的なMutableStateFlow
  val progress = _progress.asStateFlow() // 外部公開用の読み取り専用StateFlow

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // 進行状況バーを取得
    val progressBar = findViewById<ProgressBar>(R.id.progressBar)
    // StateFlowを監視してUIを更新
    uiScope.launch {
      progress.collect { value ->
        progressBar.progress = value
      }
    }

    findViewById<View>(R.id.yourButtonId).setOnClickListener {
      uiScope.launch {
        doLongRunningTaskWithProgress()
      }
    }
  }

  private suspend fun doLongRunningTaskWithProgress() = withContext(Dispatchers.IO) {
    // 仮の進行状況の更新
    for (i in 1..100) {
      delay(100) // 時間がかかる処理を模倣
      _progress.value = i // 進行状況の更新
    }
  }
}

この例では、progressはMutableStateFlowで進行状況を保持し、その値をprogressを通じて公開しています。バックグラウンド処理中にprogress.valueを更新することで進行状況を変更し、progress.collectを使ってその変更をUIスレッドで監視し、プログレスバーの進行状況を更新しています。

この方法を使用すると、バックグラウンド処理の進行状況をリアルタイムでUIに反映させることができます。

処理中にボタンの二度押しの禁止についてはどうすれば良いですか?

User
ありがとうございます! StateFlowを使うのですね!

どうしてもうまくできなくて諦めていました。

処理中にボタンの二度押しの禁止についてはどうすれば良いですか?

ChatGPT
処理中にボタンの二度押しを禁止するには、フラグを使用して処理中かどうかを追跡するか、ボタンの有効/無効状態を切り替える方法があります。ここでは、ボタンの有効/無効状態を切り替える方法を紹介します。これは直感的で、ユーザーインターフェイスにも明確に反映されます。

以下の例では、ボタンをクリックした時にそのボタンを無効化し、処理が完了したら再び有効化する方法を示しています。これにより、処理が完了するまでの間、ユーザーがボタンを再度クリックすることを防ぎます。

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {
  private val uiScope = CoroutineScope(Dispatchers.Main)

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val button = findViewById<View>(R.id.yourButtonId)

    button.setOnClickListener {
      // ボタンを無効化して二度押しを防ぐ
      it.isEnabled = false

      // UIスレッドからバックグラウンド処理を開始
      uiScope.launch {
        doLongRunningTask()
         
        // バックグラウンド処理が完了したらボタンを再び有効化
        it.isEnabled = true
      }
    }
  }

  private suspend fun doLongRunningTask() = withContext(Dispatchers.IO) {
    // ここに時間がかかる処理を実行
    delay(2000) // 2秒間の遅延を模倣
    // 処理が完了したら結果をUIに反映するなど
  }
}

このコードでは、ボタンがクリックされたときに最初にボタンを無効化(it.isEnabled = false)しています。これにより、処理中はユーザーがボタンをクリックできなくなります。その後、バックグラウンドで時間がかかる処理(doLongRunningTask)を実行し、処理が完了したらボタンを再び有効化(it.isEnabled = true)しています。

この方法を使用すると、ユーザーが処理中に誤ってボタンを二度押ししてしまうことを防ぎつつ、処理が完了したことがユーザーに視覚的にわかりやすくなります。

User
シンプルに二度押し禁止が実現出来そうですね! これもすごく困ってました。

ありがとうございました!

ChatGPT
喜んでお手伝いできてうれしいです!シンプルな解決策が見つかってよかったですね。もし他にAndroid開発やプログラミングに関して質問があれば、いつでもお聞かせください。開発の進行において、さらにサポートが必要な場合は遠慮なくご相談を。成功をお祈りしています!