読者です 読者をやめる 読者になる 読者になる

【Android】Twitter4jのTwitterの通信に関する部分を全部AsyncTaskでラップする

色々思うことあって自作のAndroidTwitterクライアントを全面的に書き直すことにしました。具体的な理由としては、Android 3.0以降のAPIを使ってみたいと言うのと、せっかくクロージャが使えるようになったんだから全部直してしまえ、の二つです。主に後者が原因で全部書き直しです。

で、Twitterと通信する部分、挙げればキリがないんですが、UIスレッドで実行すると多分怒られるんだろうなぁ、どうせなら全部AsyncTaskでやれればいいのになぁ、と思ったので、やります。

考えなきゃいけないこと

AsyncTaskはParams(処理実行時に渡すパラメータ)、Progress(処理実行中にdoInBackgroundから受け取ることが出来る値)、Result(doInBackgroundの返り値)の三つの型を事前に既定しなくてはなりません。必要ない要素はVoidにでもしとけ、とのことです。

Progressはまぁどうでもよくて、Paramsは出来ればメソッドそのものを受け取って欲しいです。つまり、クロージャを渡す。これは特に問題ないです。ただ、Resultが色々と厄介で、Twitter4jのメソッドから返ってくるものは結構色々あります。この辺も上手く考えないといけないですね。

AsyncTaskに設定する型を考える

Paramsにする型は先述した通り、R1<Hoge, Void>です。HogeがそのままResultの型になります。引数はVoidか、なんならTwitterでもいいかもしれません。

public class TwitterAsyncTask extends AsyncTask<R1<Hoge, Void>, Void, Hoge>

ただ、これも先述した通り、Hogeの部分は何になるのか正直なんとも言えません。List<Status>かもしれないしUserかもしれないしStatusかもしれないし…。型がなんとも言えない場合はどうするのか?そりゃジェネリックでしょう。

public class TwitterAsyncTask<T> extends AsyncTask<R1<T, Void>, Void, T>

後はdoInBackgroundとonPostExecuteを実装してあげればOKです。ただ、返ってきた値で何をするかもケースバイケースでしょう。これもクロージャでいいや。

public class TwitterAsyncTask<T> extends AsyncTask<R1<T, Void>, Void, T> {

    private V1<T> _callBack;
    
    public TwitterAsyncTaskV1<T> callBack) {
        _callBack = callBack;
    }

    @Override
    protected T doInBackground(R1<T, Void>... arg0) {
        return arg0[0].call(null);
    }

    @Override
    protected void onPostExecute(T result) {
        _callBack.call(result);
    }
}

とりあえずTwitter4jのメソッドを投げよう

public R1<List<Status>, Void> getHomeTimeline(final Twitter twitter, final Paging page) {
    return new R1<List<Status>, Void>() {
        @Override
        public List<Status> call(Void arg0) {
            return twitter.getHomeTimeline(page);
        }
    };
}

物凄くシンプルに書くとこんな感じですかね。このメソッドで返ってきたR1<List<Status>, Void>をAsyncTaskのParamsに渡してやり、doInBackgroundでcall(null)してやるのが理想です。

が、これではコンパイルが通りません。Twitter#getHomeTimelineで発生しうるTwitterExceptionが検査例外だからです。かといってthrowsを設定することは出来ません。設定するとなるとpublic List<Status> call(Void arg0)に設定しなくてはならないんですが、ここにthrowsを設定するとOverrideとは言えなくなってしまいます。

そんなわけで、絶対にtry-catchはしないといけません。だからと言って、下記のようなコードにしてしまうのもかなり問題でしょう。

public R1<List<Status>, Void> getHomeTimeline(final Twitter twitter, final Paging page) {
    return new R1<List<Status>, Void>() {
        @Override
        public List<Status> call(Void arg0) {
            try {
                return twitter.getHomeTimeline(page);
            } catch(TwitterException e) {
                e.printStackTrace();
                //とりあえず空のリストを返しておこう
                return new ArrayList<Status>();
            }
        }
    };
}

結果もしくはエラーを格納できるクラスを作る

じゃあどうするのかって言うと、こんなクラスを作ってしまえばいいんじゃないでしょうか。Maybeモナドっぽい何かです。

public class TwitterResult<T> {
    private T result;
    private TwitterException error;

    public T getResult() {
        return result;
    }
    public void setResult(T result) {
        this.result = result;
    }
    public TwitterException getError() {
        return error;
    }
    public void setError(TwitterException error) {
        this.error = error;
    }
    public boolean hasError() {
        return error != null;
    }
}

先ほどのクロージャで返す型をTwitterResult<T>にしてしまいます。

public R1<TwitterResult<List<Status>>, Void> getHomeTimeline(final Twitter twitter, final Paging page) {
    return new R1<TwitterResult<List<Status>>, Void>() {
        @Override
        public TwitterResult<List<Status>> call(Void arg0) {
            TwitterResult<List<Status>> r = new TwitterResult<List<Status>>();
            
            try {
                r.setResult(twitter.getHomeTimeline(page));
            } catch(TwitterException e) {
                e.printStackTrace();
                //エラーが起きたらr.setErrorして返す
                r.setError(e);
            }
            
            return r;
        }
    };
}

AsyncTaskの方も書き換えます。

public class TwitterAsyncTask<T> extends AsyncTask<R1<TwitterResult<T>, Void>, Void, TwitterResult<T>> {

    private V1<TwitterResult<T>> _callBack;
    
    public TwitterAsyncTaskV1<TwitterResult>T>> callBack) {
        _callBack = callBack;
    }

    @Override
    protected T doInBackground(R1<TwitterResult<T>, Void>... arg0) {
        return arg0[0].call(null);
    }

    @Override
    protected void onPostExecute(TwitterResult<T> result) {
        _callBack.call(result);
    }
}

後は_callBackでTwitterResult#hasErrorを呼び出して判定してもいいし、エラー用のコールバックをコンストラクタで渡してあげて、onPostExecuteで判定してあげてもいいと思います。

public class TwitterAsyncTask<T> extends AsyncTask<R1<TwitterResult<T>, Void>, Void, TwitterResult<T>> {

    private V1<TwitterResult<T>> _callBack;
    private V1<TwitterException> _errorCallBack;
    
    public TwitterAsyncTaskV1<TwitterResult<T>> callBack, V1<TwitterException> errorCallBack) {
        _callBack = callBack;
        __errorCallBack = errorCallBack;
    }

    @Override
    protected T doInBackground(R1<TwitterResult<T>, Void>... arg0) {
        return arg0[0].call(null);
    }

    @Override
    protected void onPostExecute(TwitterResult<T> result) {
        if(!result.hasError()){
            _callBack.call(result);
        } else {
            _errorCallBack.call(result.getError());
        }
    }
}

これで怖いものなしですね。

まとめ

で、Twitter4jのほとんどのメソッドをこれでラップしてるんですが、死にそうです。