AndroidでAPIを使ったListViewを表示してみる その1
最近、Android始めたので色々お試しした内容を作業メモ的に日記に残していきます。AndroidもJavaもそれほど造詣が深い訳でもないのでコード見てここはおかしいとかあったらコメントで指摘してもられると嬉しいです(゚∀゚)
※ 続編書きました
Androidのデザインパターンを考えてみた - もやもやエンジニア
前提
開発環境はAndroid Studio + Genymotion。SDKのターゲットは4.0以上とします。
作ってみよう
プロジェクト作成時に作られるActivityに色々なサンプルをリンクする構成にしたいのでListView表示用の画面を新しく作ります。ルートにactivitiesパッケージを作って、そこにactivity(fragment付き)を追加します。今回はListViewSampleActivityという名前にしました。次にfragmentも追加するので同じくfragmentsパッケージを作って、Fragment(List)をListViewSampleFragmentという名前で追加します。これでガワはOKです。
準備
APIを叩くので外部の通信を許可する設定を有効にしておきます。AndroidManifest.xmlに以下の1行を追加しておきます。
<uses-permission android:name="android.permission.INTERNET" />
activity
ではactivityからいきます。ListViewSampleActivity.javaを開いて初期表示時に作られるPlaceholderFragmentクラスを削除してonCreateメソッド内のFragmentをnewしているところを作成したFragmentのファクトリを呼ぶように書き換えます。ファクトリ側で引数を使っていますが今回は特に何か渡す想定はないので削っておきます。
ListViewSampleActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_list_view_sample); if (savedInstanceState == null) { // ここをListViewSampleFragmentを返すように getFragmentManager().beginTransaction() .add(R.id.container, ListViewSampleFragment.newInstance()) .commit(); } }
Loaderの作成
Androidのいまどきな非同期読み込みはAsyncTaskLoaderとやらを使うらしいので、そいつを拡張したAPI読み込み用のクラスを作ります。utilsパッケージを作り、そこにHttpAsyncLoaderという名前で作成します。でloadInBackgroundメソッドをオーバーライドして別スレッドで非同期で実行される処理を書きます。今回はAPIをたたくのでhttpClientで指定したURLをつついてレスポンスを返すというような処理になります。エラーハンドリングは適当です。
HttpAsyncLoader.java
package me.rei_m.androidsample.utils; import android.content.AsyncTaskLoader; import android.content.Context; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import java.io.IOException; /** * Created by rei_m on 2014/07/24. */ public class HttpAsyncLoader extends AsyncTaskLoader<String> { private String url; public HttpAsyncLoader(Context context, String url) { super(context); this.url = url; } @Override public String loadInBackground() { HttpClient httpClient = new DefaultHttpClient(); try { String responseBody = httpClient.execute(new HttpGet(this.url), new ResponseHandler<String>() { @Override public String handleResponse(HttpResponse httpResponse) throws ClientProtocolException, IOException { if(HttpStatus.SC_OK == httpResponse.getStatusLine().getStatusCode()){ return EntityUtils.toString(httpResponse.getEntity(), "UTF-8"); }else{ return null; } } }); return responseBody; } catch (IOException e) { e.printStackTrace(); } finally { httpClient.getConnectionManager().shutdown(); } return null; } }
fragment
では肝となるListViewSampleFragment.javaをいじります。生成時に自動で挿入されるメンバ変数(ARG_PARAM1とARG_PARAM2)は邪魔なので削っときます。非同期通信を使うためにはLoaderManager.LoaderCallbacksインターフェースを使うので、ListViewSampleFragmentクラスを実装します。
public class ListViewSampleFragment extends Fragment implements AbsListView.OnItemClickListener, LoaderManager.LoaderCallbacks<String> {
次にLoaderの初期化を行います。アクティビティ作成時にローダーを初期化するようにします。
// 非同期用ローダー private Loader mLoader; // Activity作成時にローダーの初期化を行う // LoaderManagerはActivityやFragmentごとに1つだけ存在している。 // Loaderは複数持てる。initLoaderの第一引数がLoaderをユニークにするIDとなる @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // LoaderManagerの初期化 Bundle args = new Bundle(); mLoader = getLoaderManager().initLoader(0, args, this); }
これでActivity開始時にローダーが起動するようになりました。最後にLoaderManager.LoaderCallbacksのメソッドを実装していきます。メソッドはonCreateLoader、onLoadFinished、onLoaderResetの3つのメソッドをオーバライドする必要があります。意味はそれぞれLoaderが作成された時、ロードが終わった時、ローダーがリセットされた時に実行される処理となります。実装の内容としてはinitLoaderされた時にHttpAsyncLoaderのインスタンスを作成し、通信を開始します。そしてロードが完了した時にレスポンスを解析してListViewにセットするという処理になります。今回はデフォルトで用意されているダミーコンテンツのクラスを使ってATNDのイベントのタイトルを表示するインスタンスを作成しています。
@Override public Loader<String> onCreateLoader(int id, Bundle args) { // HttpAsyncLoaderを作成して指定したURLを非同期で読み込む HttpAsyncLoader loader = new HttpAsyncLoader(getActivity(), "https://api.atnd.org/events/?keyword_or=google,cloud&format=json"); loader.forceLoad(); return loader; } @Override public void onLoadFinished(Loader<String> loader, String data) { // ローダーの通信完了後の処理を実装 if(loader.getId() == 0){ try { // 受け取ったイベント情報を解析 JSONObject json = new JSONObject(data); int evCnt = json.getInt("results_returned"); if(evCnt > 0){ JSONArray events = json.getJSONArray("events"); for(int i=0;i<evCnt;i++){ JSONObject ev = events.getJSONObject(i).getJSONObject("event"); // イベント情報をセットしたダミークラスのオブジェクトを作り、アダプターに追加する。 DummyContent.DummyItem item = new DummyContent.DummyItem(ev.getString("event_id"), ev.getString("title")); mAdapter.add(item); } // 追加された情報をアダプターに通知する mAdapter.notifyDataSetChanged(); } } catch (JSONException e) { e.printStackTrace(); } } } @Override public void onLoaderReset(Loader<String> loader) { // 今回は何もしない }
動かしてみよう
これで完成です!ビルドしてListViewSampleActivityを動かしてみます。
デフォルトで用意されている項目の下にAtndのAPIから取得したイベントのタイトルが表示されていますね。次回は追加分の読み込みやリストの項目に画像を表示する部分などを作ってみようと思います。