【Android】DeprecatedのProgressDialogを倒してProgressBarを実装した話【kotlin】

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を使っていました。

非推奨になった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については下記を参照。

オブジェクト式と宣言 | Kotlin公式

なので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を実装した結果こうなった

ツイートの読み込み中はシアンのバーが進捗を示してくれます。

Androidで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記事でした。


コメント

タイトルとURLをコピーしました