もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

AndroidでAPIを使ったListViewを表示してみる その1

最近、Android始めたので色々お試しした内容を作業メモ的に日記に残していきます。AndroidJavaもそれほど造詣が深い訳でもないのでコード見てここはおかしいとかあったらコメントで指摘してもられると嬉しいです(゚∀゚)

※ 続編書きました

Androidのデザインパターンを考えてみた - もやもやエンジニア

前提

開発環境はAndroid Studio + Genymotion。SDKのターゲットは4.0以上とします。

今回のテーマ

何かAPIを呼んで取得した結果をリストに表示するという画面を作ってみようと思います。アプリ開発する上ではポピュラーなスタイルですね。今回はATNDのAPIを叩いてみます。

https://api.atnd.org

作ってみよう

プロジェクト作成時に作られる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を動かしてみます。

f:id:Rei19:20140725224542p:plain

デフォルトで用意されている項目の下にAtndのAPIから取得したイベントのタイトルが表示されていますね。次回は追加分の読み込みやリストの項目に画像を表示する部分などを作ってみようと思います。

リポジトリはこちら
https://github.com/rei-m/androidSample