Android Tips #44 Spring for Android で REST クライアントを超楽に実装する

2013.03.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Spring for Android とは

Spring for Android は Spring フレームワークの Android 用ライブラリです。REST クライアントを超シンプルに実装することができたので、その手順を載せたいと思います。

概要については以下も参照してください。

Spring for Android の導入

まずは以下より Spring for Android のライブラリをダウンロードします。

http://www.springsource.org/spring-android

zip ファイルを解凍し、その中の以下のファイルをプロジェクトの libs フォルダに入れます。

  • libs/spring-android-auth-1.0.1.RELEASE.jar
  • libs/spring-android-core-1.0.1.RELEASE.jar
  • libs/spring-android-rest-template-1.0.1.RELEASE.jar

また、さらにレスポンスデータをマッピングするためのライブラリも別途必要になります。 レスポンスが XML の場合は SimpleFramework というライブラリが必要で、JSONの場合は Jackson というライブラリが必要です。 今回は両方試してみたいのでどちらもダウンロードしました。

SimpleFramework のライブラリからは以下のファイルをコピーして libs フォルダに入れます。

  • jar/simple-xml-2.7.jar

Jackson のライブラリは 3 つに分かれているのですべてダウンロードして libs フォルダに入れます。

  • jackson-core-2.1.4.jar
  • jackson-annotations-2.1.4.jar
  • jackson-databind-2.1.4.jar

レスポンスボディが XML の場合の REST 処理を実装する

まずは XML の場合です。仮に下記の XML データの Get 処理を実装して、レスポンスボディの XML データをオブジェクトにマッピングしてみましょう。

<?xml version="1.0" encoding="utf-8"?>
<book>
  <author>Classmethod</author>
  <title>XmlBook</title>
  <price>1000</price>
</book>

まずはオブジェクトクラスを作ります。SimpleFramework の記法で要素になるプロパティにアノテーションを付けていきます。

package jp.classmethod.android.sample.springforandroid;

import org.simpleframework.xml.Element;
import org.simpleframework.xml.Root;

@Root(name = "book")
public class BookXml {

    @Element
    public String author;

    @Element
    public String title;

    @Element
    public String price;

    @Override
    public String toString() {
        return "author:" + author + "\ntitle:" + title + "\nprice:" + price + "\n";
    }

}

あとは REST 処理を実装します。 RestTemplate というクラスの RestTemplate#getMessageConverters() でレスポンスボディのデータをマッピングするインスタンスを追加します。XML の場合は SimpleXmlHttpMessageConverter を使います。あとは RestTemplate#exchange() を呼ぶと GET 処理を実行しつつレスポンスを返します。

private String getXml() {
    RestTemplate template = new RestTemplate();
    template.getMessageConverters().add(new SimpleXmlHttpMessageConverter());
    String url = "http://http://192.168.x.x/book.xml";
    try {
        ResponseEntity<BookXml> responseEntity = template.exchange(url, HttpMethod.GET, null, BookXml.class);
        BookXml res = responseEntity.getBody();
        return res.toString();
    } catch (Exception e) {
        Log.d("Error", e.toString());
        return null;
    }
}

レスポンスボディが JSON の場合の REST 処理を実装する

次に JSON の場合です。仮に下記の JSON データの Get 処理を実装して、レスポンスボディの JSON データをオブジェクトにマッピングしてみましょう。

{
  "author":"Classmethod",
  "title":"JsonBook",
  "price":1000
}

こちらも XML とほぼ同様の手順です。まずはオブジェクトのクラスを作ります。JSON の場合はアノテーションなどは特に必要ありません。

package jp.classmethod.android.sample.springforandroid;

public class BookJson {
    
    public String author;
    
    public String title;
    
    public String price;
    
    @Override
    public String toString() {
        return "author:" + author + "\ntitle:" + title + "\nprice:" + price + "\n";
    }

}

あとは REST 処理の実装です。 XML の場合と異なるところは MassageConverter が MappingJackson2HttpMessageConverter になっているところくらいです。

private String getJson() {
    RestTemplate template = new RestTemplate();
    template.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
    String url = "http://192.168.x.x/book.json";
    try {
        ResponseEntity<BookJson> responseEntity = template.exchange(url, HttpMethod.GET, null, BookJson.class);
        BookJson res = responseEntity.getBody();
        return res.toString();
    } catch (Exception e) {
        Log.d("Error", e.toString());
        return null;
    }
}

動作させてみる

REST 処理を Activity に実装したのが以下のコードになります。「Get XML」ボタンで XML を取得し、「Get JSON」ボタンで JSON を取得します。

MainActivity.java

package jp.classmethod.android.sample.springforandroid;

import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.SimpleXmlHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;

public class MainActivity extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        findViewById(R.id.get_xml_button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                bundle.putString("format", "xml");
                getSupportLoaderManager().initLoader(0, bundle, callbacks);
            }
        });
        
        findViewById(R.id.get_json_button).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                bundle.putString("format", "json");
                getSupportLoaderManager().initLoader(0, bundle, callbacks);
            }
        });
    }
    
    private LoaderCallbacks<String> callbacks = new LoaderCallbacks<String>() {
        
        @Override
        public void onLoaderReset(Loader<String> loader) {
        }
        
        @Override
        public void onLoadFinished(Loader<String> loader, String value) {
            getSupportLoaderManager().destroyLoader(loader.getId());
            TextView v = (TextView) findViewById(R.id.result_text_view);
            v.setText(v.getText() + value);
        }
        
        @Override
        public Loader<String> onCreateLoader(int id, Bundle bundle) {
            CustomLoader loader = new CustomLoader(getApplicationContext(), bundle);
            loader.forceLoad();
            return loader;
        }
    };
    
    private static class CustomLoader extends AsyncTaskLoader<String> {

        private String mFormat;
        
        public CustomLoader(Context context, Bundle bundle) {
            super(context);
            mFormat = bundle.getString("format");
        }

        @Override
        public String loadInBackground() {
            if (TextUtils.equals("xml", mFormat)) {
                return getXml();
            } else {
                return getJson();
            }
        }
        
        private String getXml() {
            RestTemplate template = new RestTemplate();
            template.getMessageConverters().add(new SimpleXmlHttpMessageConverter());
            String url = "http://192.168.x.x/book.xml";
            try {
                ResponseEntity<BookXml> responseEntity = template.exchange(url, HttpMethod.GET, null, BookXml.class);
                BookXml res = responseEntity.getBody();
                return res.toString();
            } catch (Exception e) {
                Log.d("Error", e.toString());
                return null;
            }
        }
        
        private String getJson() {
            RestTemplate template = new RestTemplate();
            template.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
            String url = "http://192.168.x.x/book.json";
            try {
                ResponseEntity<BookJson> responseEntity = template.exchange(url, HttpMethod.GET, null, BookJson.class);
                BookJson res = responseEntity.getBody();
                return res.toString();
            } catch (Exception e) {
                Log.d("Error", e.toString());
                return null;
            }
        }
        
    }
}

activity_main.xml

<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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/get_xml_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Get XML" />
    
    <Button
        android:id="@+id/get_json_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/get_xml_button"
        android:text="Get JSON" />
    
    <TextView 
        android:id="@+id/result_text_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/get_json_button"
        />

</RelativeLayout>

動作させると下図のような感じになります!

spring_for_android

サンプルソース

サンプルソースを GitHub に公開しました!ぜひ参考にしてください。

suwa-yuki/SpringForAndroidSample

まとめ

HttpURLConnection などをそのまま使うよりかなり簡単に REST のクライアントを実装できました。SimpleFramework と Jackson の紹介のようにもなってしまいましたが…これらもあわせて使うとレスポンスボディのマッピングもすごく簡単になるのでオススメです。