【Android】Twitter4jのTwitterの通信に関する部分を全部AsyncTaskでラップする
色々思うことあって自作のAndroid用Twitterクライアントを全面的に書き直すことにしました。具体的な理由としては、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のほとんどのメソッドをこれでラップしてるんですが、死にそうです。