🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

2回答

4045閲覧

プログレスバーの表示→コルーチンの処理→完了したらプログレスバーの非表示という流れのプログラムを作りたい。

j.f15

総合スコア23

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Android Studio

Android Studioは、 Google社によって開発された、 Androidのネイティブアプリケーション開発に特化した統合開発ツールです。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2021/02/16 14:20

前提・実現したいこと

プログレスバーの表示→コルーチンの処理→完了したらプログレスバーの非表示という流れのプログラムを作りたい。
※プログレスバーの表示非表示の処理自体は出来ています。

発生している問題・エラーメッセージ

プログレスバーの表示が完了する前にコルーチンの処理が始まってしまう。

・実現したいことの例でいうと
プログレスバーの表示(処理途中)→コルーチンの処理→
完了したらプログレスバーの表示→表示直後にプログレスバーの非表示
というプログラムの流れになってしまっている(恐らくですが)

該当のソースコード

・LoginViewModel

kotlin

1class LoginViewModel(application: Application) : AndroidViewModel(application) { 2 3 private val scope = CoroutineScope(Dispatchers.Default) 4 5 var input_password: MutableLiveData<String> = MutableLiveData("") 6 var input_email: MutableLiveData<String> = MutableLiveData("") 7 8 fun sendJsonData() { 9 10 val apiUrl = getApplication<Application>().getString(R.string.api_url) 11 var returnParam: AccountSignin 12 13 val service: LoginApi = Repository().getLoginRetrofit(apiUrl) 14 15 val login = Login(input_password.value!!, input_email.value!!) 16 scope.launch { 17 try { 18 returnParam = service.postSignin(login) 19 Log.d("入力内容確認!", input_password.value + "/" + input_email.value) 20 Log.d("レスポンス内容!", returnParam.accessToken + "/" + returnParam.userId) 21 22 //DBのアクセストークン登録or更新 23 val loginEntity = 24 LoginEntity.login_data(returnParam.userId, returnParam.accessToken) 25 26 val user_data = GolfLocalDatabase.getInstance(getApplication()).loginDao() 27 .dataSearch(returnParam.userId) 28 if (user_data == null) { 29 GolfLocalDatabase.getInstance(getApplication()).loginDao() 30 .insert(loginEntity) 31 } else { 32 GolfLocalDatabase.getInstance(getApplication()).loginDao() 33 .updateToken(returnParam.userId, returnParam.accessToken) 34 } 35 36 } catch (e: HttpException) { 37 Log.d("Httpエラー", e.response()!!.errorBody()!!.string()) 38 39 } catch (e: Exception) { 40 Log.d("コルーチンエラー", e.toString()) 41 } 42 } 43 } 44}

試したこと

そもそも scope.launch ではメインスレッドは止まらないのでプログレスバーの表示完了後、すぐにプログレスバーの非表示処理が走ることは理解できています。

しかし、runBlockingでコルーチンが終わるのを待つ処理に変更すると

//DBのアクセストークン登録or更新 val loginEntity = LoginEntity.login_data(returnParam.userId, returnParam.accessToken) val user_data = GolfLocalDatabase.getInstance(getApplication()).loginDao() .dataSearch(returnParam.userId) if (user_data == null) { GolfLocalDatabase.getInstance(getApplication()).loginDao() .insert(loginEntity) } else { GolfLocalDatabase.getInstance(getApplication()).loginDao() .updateToken(returnParam.userId, returnParam.accessToken) }

ここでDBに接続する処理を実装しているので

Cannot access database on the main thread since it may potentially lock the UI for a long period of time

というエラーが発生してしまい runBlocking での実装が上手く出来ません。

整理すると
1、プログレスバーの表示が完了してからコルーチンの処理を走らせる
2、runBlockingを使わずにコルーチンの処理完了後にプログレスバーの非表示(runBlockingを用いた処理が可能でしたら runBlocking を用いた処理を行いたいです)
以上の2点を実現したいです。

他に確認したいファイル等ありましたら教えてください。
1点だけの解決、また scope.launch ではなく runBlocking を用いた処理でないといけないといった説明も大歓迎ですのでご教示をお願いしたいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

AndroidのViewModelは触ったことがないので、その辺りのコードはさっぱりわからないのですが、コルーチンのところだけ抜き出すと、

kotlin

1 fun sendJsonData() { 2 // launchを呼び出す前にプログレスバーを表示 3 scope.launch { 4 // 非同期のいろいろな処理 5 6 // 処理が終わったら、メインスレッドに切り替える。 7 withContext(Dispatchers.Main) { 8 // プログレスバーを非表示 9 } 10 } 11 }

でお望みの処理ができると思いますが、いかがでしょう。
(自分もkotlinのコルーチンはよく理解していないので、自信がありませんが)

投稿2021/02/16 15:27

katsuko

総合スコア3536

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

j.f15

2021/02/16 16:30 編集

回答ありがとうございます。 一点伺いたいんですけど、 現在LoginFragmentで (activity as LoginActivity?)!!.progressbarVisible() このような記述でLoginActivityにあるプログレスバーの表示非表示を切り替える処理をしているんですけど、 これをLoginViewModel(コルーチン処理しているファイル)で同じように記述すると (activity as LoginActivity?)の activity で「未解決の参照: アクティビティー」といったエラーが出てしまうのですが、LoginViewModelからLoginActivityのメソッドを走らせる記述を知っていますでしょうか?もしも知っていたら教えて頂きたいです。 またwithContextメソッドというのは知らなかったので勉強になります。
j.f15

2021/02/16 16:39 編集

自分の方でもしかしたらと思い ・LoginFragment fun progressVisible() { (activity as LoginActivity?)!!.progressbarVisible() } fun progresInvisible() { (activity as LoginActivity?)!!.progressbarInvisible() } LoginViewModelの回答で示していただいた表示非表示処理を走らせる箇所で ・LoginFragment LoginFragment().progressVisible() このようにLoginFragmentを一回挟む処理方法を試してみたのですがnullPointerExceptionが発生してしまいました。
hoshi-takanori

2021/02/16 22:59

横から失礼します。前回見落としてましたが、ViewModel を使ってたんですね。 ViewModel から直接 activity や fragment のメソッドは呼び出せませんので、activity で何か処理を行いたいなら activity から ViewModel の状態を監視して、状態が変わったら処理を行えば良いのでは。
katsuko

2021/02/16 23:13

AndroidViewModelはActivityやFragmentのライフサイクルに関係なくデータを保持するものなので、AndroidViewModelでそれらの処理を行うのはよろしくないようですね。 https://kcpoipoi.hatenablog.com/entry/2018/12/01/220327 先も言ったとおり、AndroidViewModelについてはさっぱりわからないので、わかる方からコメントをもらえたのはありがたいです。hoshi-takanoriさん、感謝です。
j.f15

2021/02/17 01:08

->hoshi-takanoriさん、前回に引き続き回答ありがとうございます。 なるほど、activity から ViewModel を監視して、状態が変更された時に処理をするという形で実装すれば良かったのですね。 activity から ViewModel を監視する方法がよくわかっていないため少し調べて見たいと思います。 -> katsuko さん、参考になるサイトの提示ありがとうございます。 確かに提示していただいたサイトを見てみるとメモリリークする可能性があるなど怖い文言がありますし、今の自分の行おうとしていた実装方法は違うみたいですね。 それを教えて頂いただけでもとてもありがたいです。
guest

0

自己解決

アドバイスして頂きなんとか自己解決することが出来ました。

・LoginActivity

class LoginActivity : AppCompatActivity() { private lateinit var binding : ActivityLoginBinding lateinit var viewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityLoginBinding.inflate(layoutInflater) setContentView(binding.root) viewModel = ViewModelProvider(this).get(LoginViewModel::class.java) viewModel.progressbarFrag.observe(this, Observer { progressbarFrag -> Log.d("ViewModelの値の変更検知", "111111111111111") if (progressbarFrag == true) { progressbarVisible() } else { progressbarInvisible() } }) // val nameObserver = Observer<Boolean> { // Log.d("ViewModelの値の変更検知", "111111111111111") // if (it == true) { // progressbarVisible() // } else { // progressbarInvisible() // } // } // viewModel.progressbarFrag.observe(this, nameObserver) val loginFragment = LoginFragment() val fragmentTransaction = supportFragmentManager.beginTransaction() fragmentTransaction.add(R.id.login_fragment_container, loginFragment) fragmentTransaction.commit() } //プログレスバーの表示 fun progressbarVisible() { binding.loginProgressbarLayout.visibility = View.VISIBLE } //プログレスバーの非表示 fun progressbarInvisible() { binding.loginProgressbarLayout.visibility = View.INVISIBLE } }

アドバイスで頂いたように最初は上記のように LoginActivity で LoginViewModel の値変更を検知するような実装を試したのですが、どうしても値変更時に検知する処理を走らせることが出来ませんでした。(原因がわかりませんでした…)
そのため検知は LoginFragment で行い、その後 activity の処理を走らせる形にしたところ上手く値の変更を検知し、望んだ実装をすることができました。

アドバイスして下さったお二方、本当にありがとうございました!

・以下、最終的なソースコード

・LoginActivity

class LoginActivity : AppCompatActivity() { private lateinit var binding : ActivityLoginBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityLoginBinding.inflate(layoutInflater) setContentView(binding.root) val loginFragment = LoginFragment() val fragmentTransaction = supportFragmentManager.beginTransaction() fragmentTransaction.add(R.id.login_fragment_container, loginFragment) fragmentTransaction.commit() } //プログレスバーの表示 fun progressbarVisible() { binding.loginProgressbarLayout.visibility = View.VISIBLE } //プログレスバーの非表示 fun progressbarInvisible() { binding.loginProgressbarLayout.visibility = View.INVISIBLE } }

・LoginFragment

class LoginFragment : Fragment() { private lateinit var login_binding: FragmentLoginBinding private val viewModel: LoginViewModel by lazy { ViewModelProviders.of(this).get(LoginViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { (activity as LoginActivity?)!!.getSupportActionBar()?.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); (activity as LoginActivity?)!!.getSupportActionBar()?.setCustomView(R.layout.login_header); login_binding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container, false) login_binding.lifecycleOwner = viewLifecycleOwner login_binding.viewModel = viewModel return login_binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.progressbarFrag.observe(viewLifecycleOwner, { Log.d("ViewModelの値の変更検知", "111111111111111") if (it == true) { (activity as LoginActivity?)!!.progressbarVisible() } else { (activity as LoginActivity?)!!.progressbarInvisible() } }) //ログイン処理 login_binding.loginBtn.setOnClickListener { viewModel.sendJsonData() } } }

・LoginViewModel

class LoginViewModel(application: Application) : AndroidViewModel(application) { val progressbarFrag: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() } private val scope = CoroutineScope(Dispatchers.Default) var input_password: MutableLiveData<String> = MutableLiveData("") var input_email: MutableLiveData<String> = MutableLiveData("") fun sendJsonData() { progressbarFrag.setValue(true) Log.d("プログレスフラグ表示", ""+progressbarFrag.value) val apiUrl = getApplication<Application>().getString(R.string.api_url) var returnParam: AccountSignin val service: LoginApi = Repository().getLoginRetrofit(apiUrl) val login = Login(input_password.value!!, input_email.value!!) scope.launch { try { returnParam = service.postSignin(login) Log.d("入力内容確認!", input_password.value + "/" + input_email.value) Log.d("レスポンス内容!", returnParam.accessToken + "/" + returnParam.userId) //DBのアクセストークン登録or更新 val loginEntity = LoginEntity.login_data(returnParam.userId, returnParam.accessToken) val user_data = GolfLocalDatabase.getInstance(getApplication()).loginDao() .dataSearch(returnParam.userId) if (user_data == null) { GolfLocalDatabase.getInstance(getApplication()).loginDao().insert(loginEntity) } else { GolfLocalDatabase.getInstance(getApplication()).loginDao().updateToken(returnParam.userId, returnParam.accessToken) } } catch (e: HttpException) { Log.d("Httpエラー", e.response()!!.errorBody()!!.string()) } catch (e: Exception) { Log.d("コルーチンエラー", e.toString()) } withContext(Dispatchers.Main) { progressbarFrag.setValue(false) Log.d("プログレスフラグ非表示", ""+progressbarFrag.value) } } } fun getToken():String { var token = "aaa" scope.launch { try { val user_data = GolfLocalDatabase.getInstance(getApplication()).loginDao().dataSearch("995767") if(user_data != null) { token = user_data Log.d("DB内のユーザーデータ", token) } } catch (e: Exception) { Log.d("コルーチンエラー", e.toString()) } } Thread.sleep(1000L) Log.d("DB内のユーザーデータ",token) return token } }

※コルーチン内でpostValueで値代入をすると何故か true の値が入ってしまっていたため
withContextを使用して setValue する形で実装しました。

投稿2021/02/17 05:02

j.f15

総合スコア23

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問