Androidアプリを書いている人は既に既知の問題だとは思いますが、API26にてProgressDialogがDeprecatedになりました。
This class was deprecated in API level 26.
ProgressDialog is a modal dialog, which prevents the user from interacting with the app. Instead of using this class, you should use a progress indicator like ProgressBar, which can be embedded in your app’s UI. Alternatively, you can use a notification to inform the user of the task’s progress.
雑に和訳すると「ProgressDialogはユーザーのインタラクションを阻害するので、代わりにProgressBarなどのインジケーターで進捗をユーザーに伝えるようにしましょう」とのこと。
代わりのProgressBarがこちら
ってもうね、2年くらい前の話ですが、いまさら、自作TwitterクライアントのTwitMorseではTargetApiを28としていながらもProgressDialogを使っていました。
これの類ですね。「通信中」とかそんなことをユーザーさんに知らせる役割を持っていました。
ところがこいつがAndroid Oreo(API26~27)でGoogle先生が使わないように!!と言ったからさあ大変。世の中のAndroidエンジニアはさぞその対応に追われたことでしょう。
中にはAPI26以上でもProgressDialogを使おうというQiita記事やブログ記事が散見されました。
私は与えられたものは素直に使う主義なので、(というかそんなに熱意がないので)、さくっとProgressDialog達を消してProgressBarに以降しようとしました。ところが意外にもAsyncTask(Androidの非同期処理)の部分で思わぬ罠にハマったのでそれをどう回避したかだけ。
前提
- Twitter4jを利用している
- AsyncTaskを利用しているが、推奨された使い方ではありません(Warningが出ます)
今回はホーム・タイムライン画面のFragmentにおける説明をします。
まずはレイアウトファイルにProgressBarを追加
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="jp.sub.takelab.twitmorus.activity.MainActivity">
<ProgressBar
android:id="@+id/progress_bar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="5dp"
android:indeterminate="false" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_refresh_widget"
android:layout_width="fill_parent"
android:layout_height="match_parent">
<ListView
android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</RelativeLayout>
このようにSwipeRefreshLayout上にProgressBarを追記します。
widthはmatch_parentでheightは好きなだけ。個人的に5dpぐらいでいいやと思ったのでそうしたまで。
indeterminateは進捗が不定期かどうか、ということのようです。
今回はAPIとの進捗は割と通信環境に左右されるものだったのでfalse、つまり、不定期ですよ、と明示しておきます。
なぜRelativeLayout使っているのかは放置しましょう。いつか直します。
参考資料
kotlinコードの方ではprivateメソッドを作っておく
とりあえず下記のようにProgressBarを取得し、maxを初期値として200に設定するようなprivateメソッドを
private fun setupProgressBar() {
mProgressBar = activity!!.findViewById(R.id.progress_bar)
mProgressBar.visibility = ProgressBar.VISIBLE
mProgressBar.max = GET_TWEET_NUM
mProgressBar.progressTintList = ColorStateList.valueOf(Color.CYAN)
}
プログレスバーの色はなんとなくシアンが好きなのでmProgressBar.progressTintList = ColorStateList.valueOf(Color.CYAN)
をする
こやつをFragmentのonActivityCreated付近で呼んでおく。
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mTwitter = TwitterUtils.getTwitterInstance(activity!!)
setupProgressBar()
...
...
}
タイムラインの更新処理(ここがポイント)
下記のような匿名のAsyncTaskを用いるのはメモリリークの可能性を秘めているため、非推奨です。Android Studioから警告が出ます。
正しい実装方法は下記を参照してください。
(追記 : 2019年10月4日)
/**
* @return void
* タイムラインを更新する
*/
fun reloadTimeLine() {
val paging = Paging()
paging.count = GET_TWEET_NUM
val task = object : AsyncTask<Void, Void, List<Status>>() {
override fun onPreExecute() {
}
override fun doInBackground(vararg params: Void): List<twitter4j.Status>? {
try {
var homeTimeLine: List<twitter4j.Status> = mTwitter.getHomeTimeline(paging)
mProgressBar.max = homeTimeLine.size
for (status in homeTimeLine) {
mProgressBar.progress++
}
return homeTimeLine
} catch (e: TwitterException) {
e.printStackTrace()
}
return null
}
override fun onPostExecute(result: List<twitter4j.Status>?) {
if (result != null) {
mAdapter.clear()
for (status in result) {
mAdapter.add(status)
}
} else {
showToast(getString(R.string.failed_to_load_timeline))
}
mProgressBar.visibility = ProgressBar.INVISIBLE
//くるくる消す
mSwipeRefreshLayout.isRefreshing = false
}
}
task.execute()
}
ここでポイントとなるのはTwitter4jを使ってユーザーのフォローしている人々のタイムラインを取得するのですが
今回はAPIとの通信状況次第ですが、処理上、一応進捗が読めるのですね。先にcompanion objectで読み込む件数を200と指定しているからです。
companion object {
/** Tweetの取得件数 200が限界のようだ */
private val GET_TWEET_NUM = 200
}
companion objectについては下記を参照。
なのでAsyncTaskのdoInBackgroundメソッドで下記のようなことをする必要がありました。
override fun doInBackground(vararg params: Void): List<twitter4j.Status>? {
try {
var homeTimeLine: List<twitter4j.Status> = mTwitter.getHomeTimeline(paging)
mProgressBar.max = homeTimeLine.size
for (status in homeTimeLine) {
mProgressBar.progress++
}
return homeTimeLine
} catch (e: TwitterException) {
e.printStackTrace()
}
return null
}
try文の中で一旦mTwitter.getHomeTimeLine(paging)の中身を変数として取っておき、
そのhometimeline変数(リスト)からサイズ(.sizeメソッド)を使ってProgressBarのmax値をセット
その後
for (status in homeTimeLine) {
mProgressBar.progress++
}
としてprogressbarの進捗を足していきます。
そして最後にProgressBarを消す
AsycTask#onPostExecuteの適当な場所でmProgressBar.visibility = ProgressBar.INVISIBLE
としてプログレスバーを消してあげます。
override fun onPostExecute(result: List<twitter4j.Status>?) {
if (result != null) {
mAdapter.clear()
for (status in result) {
mAdapter.add(status)
}
} else {
showToast(getString(R.string.failed_to_load_timeline))
}
mProgressBar.visibility = ProgressBar.INVISIBLE
//くるくる消す
mSwipeRefreshLayout.isRefreshing = false
}
ProgressBarを実装した結果こうなった
ツイートの読み込み中はシアンのバーが進捗を示してくれます。
ちなみにコレ、すぐに読み込み終わってスクショがなかなか取れないのでハードコーディングの上、デバッガで止めています。
問題点
- 4G回線が安定しているとProgressBarが一瞬で消える・・・
まあこれは通信環境次第だと思うのですが、Twitter4jで200個のツイートを取得する程度だと一瞬です。
本当はもっとなめらかにしたかったのですが、デバッグするとたまに通信に手間取っている様子も伺えます。
ProgressBarのスタイルにはいろいろある
今回はプログレスバーのスタイル(style)を
style="@android:style/Widget.ProgressBar.Horizontal"
のように平行にしましたが他にもいくつか種類があります。
- Widget.ProgressBar.Horizontal
- Widget.ProgressBar.Small
- Widget.ProgressBar.Large
- Widget.ProgressBar.Inverse
- Widget.ProgressBar.Small.Inverse
- Widget.ProgressBar.Large.Inverse
それぞれどのように挙動するかは調べてみてください!!
久々にandroid×kotlin記事でした。
コメント