android マッシュアップ

android講座 マッシュアップ

android マッシュアップを利用しよう

androidでマッシュアップをやってみましょう。マッシュアップとは他のサービスを呼び出してその結果を利用したアプリを作ることです。

マッシュアップの種類

androidにはGPS機能が付いています。現在地の緯度経度を使って、そのデータをgoogleに送ると、現在地の地名を返してくれます。
同じ方法でHotPepperに問い合わせるとその近辺で登録済みの飲食店の一覧を返してくれます。
地域を指定して天気予報を要求すれば、天気概況を返してくれます。
このように他のサービスを利用して、そのデータと組み合わせてアプリを作ることをマッシュアップといいます。今回はHotPepperを利用してみましょう。

他のサービスを呼び出す

HotPepperは、リクルートの経営する飲食店・理容店などの店の情報のサービスです。Googleで「hotpepper api」で検索してみましょう。ホットペッパー APIリファレンスというページに行きつきます。
このサービスを利用するには新規登録が必要です。無料です。ページの新規登録からAPIキーを取得してください。

hotpepper APIの登録

HotPepper APIなかに、グルメサーチAPIというものがあります。以下のurlにオプションを指定すればHotPepperがデータを検索して返してくれます。返す形はxmlとjsonが指定できますが、今回はjsonを使用します。

https://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー]&format=json&large_area=Z097

[APIキー]のところに、さきほど取得した16桁のキーを入れて、鹿児島のlarge_areaコードであるZ097を指定すると、jsonの形で検索にヒットした店の情報が返ってきます。

{"results":{"results_start":1,"results_returned":"10","api_version":"1.26",
"shop":[
{"name_kana":"ゆゆ かごしまてんもんかん","other_memo":"【全席完全個室】2名様~最大75名様まで個室にご案内致します!★","photo":{"mobile":{"l":"https://imgfp.hotp.jp/IMGH/80/60/P026258060/P026258060_168.jpg","s":"https://imgfp.hotp.jp/IMGH/80/60/P026258060/P026258060_100.jpg"},"pc":{"l":"https://imgfp.hotp.jp/IMGH/80/60/P026258060/P026258060_238.jpg","m":"https://imgfp.hotp.jp/IMGH/80/60/P026258060/P026258060_168.jpg","s":"https://imgfp.hotp.jp/IMGH/80/60/P026258060/P026258060_58_s.jpg"}},"large_area":{"name":"鹿児島","code":"Z097"},"sommelier":"いない","party_capacity":"100","large_service_area":{"name":"九州・沖縄","code":"SS90"},"mobile_access":"天文館通から徒歩2分!いわさきパーキングの横ビル3F","id":"J001161648","address":"鹿児島県鹿児島市東千石町1-26 フォリス観光ビル天文館 【3階】 ※1階がローソンのビル★","lng":"130.5536980111","course":"あり","show":"なし","parking":"なし :お近くのコインパーキングをご利用ください★","non_smoking":"禁煙席なし :当店では食べ放題プランはご用意しておりません。","horigotatsu":"あり :【全席完全個室】2名様~最大100名様まで個室にご案内致します!★","name":"柚柚 yuyu 鹿児島天文館店 離れ","genre":{"name":"居酒屋","catch":"[天文館個室居酒屋]飲み放題充実100名まで!","code":"G001"},"open":"月~日、祝日、祝前日: 17:00~翌0:00 (料理L.O. 23:30 ドリンクL.O. 23:30)","card":"利用可","tatami":"なし :【全席完全個室】2名様~最大100名様まで個室にご案内致します!★","charter":"貸切可 :要相談★","wifi":"あり","equipment":"なし","sub_genre":{"name":"和食","code":"G004"},"shop_detail_memo":"【全席完全個室】2名様~最大100名様まで個室にご案内致します!★","band":"不可","middle_area":{"name":"鹿児島市 天文館・中央駅・ふ頭","code":"Y780"},"lat":"31.5903208827","karaoke":"なし","logo_image":"https://imgfp.hotp.jp/IMGH/86/17/P024578617/P024578617_69.jpg","midnight":"営業している","budget":{"average":"3000円(通常平均)/3500円(宴会平均)/(ドリンク飲放は200種超!)","name":"2001~3000円","code":"B002"},"urls":{"qr":"http://webservice.recruit.co.jp/common/qr?url=http%3A%2F%2Fhpr.jp%2FstrJ001161648%2F%3Fuid%3DNULLGWDOCOMO%26vos%3Dhpp337","mobile":"http://hpr.jp/strJ001161648/?uid=NULLGWDOCOMO&vos=hpp336","pc":"https://www.hotpepper.jp/strJ001161648/?vos=nhppalsa000016"},"english":"なし","lunch":"なし","service_area":{"name":"鹿児島","code":"SA97"},"close":"★お問い合わせ時間も24時間受付中です(不在時の場合折り返しお電話をさせて頂きます)天文館の居酒屋探しは当店で★","budget_memo":"サービス料5%/お通し代:有 (コース利用時は頂きません)","tv":"なし","private_room":"あり :2名から最大100名様まで一体感のある個室でのご宴席をご用意いたします。","coupon_urls":{"sp":"https://www.hotpepper.jp/strJ001161648/scoupon/?vos=nhppalsa000016","qr":"http://webservice.recruit.co.jp/common/qr?url=http%3A%2F%2Fhpr.jp%2FS%2FS511.jsp%3FSP%3DJ001161648%26uid%3DNULLGWDOCOMO%26vos%3Dhpp337","mobile":"http://hpr.jp/S/S511.jsp?SP=J001161648&uid=NULLGWDOCOMO&vos=hpp336","pc":"https://www.hotpepper.jp/strJ001161648/map/?vos=nhppalsa000016"},"barrier_free":"なし :お手伝いが必要な際は、お教え願います★","sub_food":{"name":"和風創作料理","code":"R021"},"small_area":{"name":"天文館","code":"X780"},"wedding":"【全席完全個室】2名様~最大100名様まで個室にご案内致します!★","access":"[天文館通電停]から[徒歩2分]★[いわさきパーキング]さんの横のビル[3F]にあります★迷った際はお気軽に電話下さいませ!","ktai":"つながる :全機種良好★","child":"お子様連れ歓迎 :お子様連れも大歓迎★","capacity":"230","open_air":"なし","pet":"不可","free_food":"なし :お気軽に店舗へお問合せ下さい★","food":{"name":"和食全般","code":"R001"},"ktai_coupon":"0","station_name":"天文館通","catch":"個室×飲み放題付コース★ 天文館通完全個室居酒屋","free_drink":"あり :クーポンで当日OK単品飲み放題1780円→1500円もあり!"}

今回はjsonデータで受信します。このデータは階層になっているので、後程この階層をたどって情報を取得します。階層の構造に関しては同じページのレスポンスデータの表に書いてあります。

APIデータの受信用ライブラリー Volley

APIを呼び出すにはurlを出力してその結果を待つことになります。普通のandroidの処理であれば瞬時に済む処理もNET経由ではどれだけ掛かるか分かりません。そのような処理の終了を待つのは特別な方法で行います。

以前はAsyncTaskやAsyncTaskLoaderを使っていましたが、androidのバージョンによって非推奨(deprecated)になるので、今回はvolleyというライブラリーを使用します。

volleyはgitでソースコードが公開されています。gitを使ってvolleyのソースコードをandroidに取り込んで、これから作成するアプリから呼び出せるように保存します。
まずは git for windowsをインストールしてgitからソースを受信する準備をします。

https://git-for-windows.github.io

自分のパソコンに合致するプログラムをDownloadして起動し、途中「Use Git the Windows Command Prompt」を選択するところ以外は、next>で設定してください。
インストールが終わったら、いったんandroid Studioの「Welcome to Android Studio」のメニュー画面まで戻り、「Check out project from Version Control」をクリックして「Git」を選択します。
Clone Repository画面で以下のGit repository urlを入力します。

https://android.googlesource.com/platform/frameworks/volley

Parent Directoryには、c:\AndroidStudioProjects等のいつもprojectを作成している親フォルダーを指定します。そして Directory Nameに volleyを入れます。
Checkout From Version Control画面では Yes ではなく No を選択してください。

これでvolleyがインストールできるのですが、android API 25でコンパイルしたところ、エラーが表示されてコンパイルできませんでした。一か所修正をお願いします。
ご利用のソースエディタでAndroidStudioProjectsのvolleyのbintray.gradleを開きます
64行目を以下のようにhas をhasPropertyを変更してください。

    publish = project.hasProperty("release")
HotList 新規作成

では、HotPepperを緯度経度指定で読み込む試験的アプリ HotListを作りましょう。
Welcome to Android Studioのメニューで Start a new Android Studio projectを選び、HotListプロジェクトを作成します。
画面は、Empty Activityでかまいません。

メイン画面を作る前に、さきほどinstallしたvolleyと接続します。「File」->「Project Structure」を選択して「Project Structure」画面で画面左上の「+」ボタンをクリックして新しいモジュールを追加します。
「New Module」で「Import Gradle Project」を選択して「Next」をクリックして「Create New Module」画面の「Source directory」にさっき入れたvolleyを指定して、finishします。

volley自体は以前は22.0.1でコンパイルされていたが、最低25.0.0が必要になったとメッセージが表示されるので、その青い文字を押して25.0.0に切り替えてください。25.0.0が入っていない場合はdownloadしてください。
その後、Sync projectが自動で行われるので、Projectのツリーの中にvolleyが取り込まれます。

続いて、「File」->「Project Structure」の左の列のappをクリックして、右のタグの「Dependencies」を開きます。画面右側の「+」を押し、「3 Module dependency」をクリックすると、Choose Modulesに:volleyがあるのでそれを選択してOK,OKでvolleyの導入が完成します。

volley用のクラスを追加する

volleyはプロセス管理やスレッド管理を裏で行って、urlへのリクエストやリクエストキュー、レスポンスを一つ一つ受けるスレッドなどを行ってくれます。そのためこれを使う人はその高度な処理を知らなくてもリクエストとレスポンスの通信を行うことができます。

インターネットの通信を行ってくれるリクエストキューは複数同時に実行されるとリクエストとレスポンスが合わなくなってしまいますので、シングルトンといってインスタンスが複数できない形での使用が推奨されています。そのためvolley用のクラスはシングルトンで作成します。

MainActivityの兄弟としてクラスを追加しますので、MainActivityの上の段のproject名をクリックして右クリック「Singleton」を選択して「MySingleton」と名前を付けます。

singletonの追加

すると自動的にひな形のsingletonが作成されますので、それを書き換えていきます。

package jp.sample.hotlist;

import android.content.Context;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

public class MySingleton {
    private static MySingleton ourInstance;
    private RequestQueue mRequestQueue;
    private static Context mCtx;

    public static synchronized MySingleton getInstance(Context context) {
        if (ourInstance == null) {
            ourInstance = new MySingleton(context);
        }
        return ourInstance;
    }

    private MySingleton(Context context) {
        mCtx = context;
        mRequestQueue = getRequestQueue();
    }

    public RequestQueue getRequestQueue() {
        if (mRequestQueue==null) {
            mRequestQueue =
                    Volley.newRequestQueue(mCtx.getApplicationContext());
        }
        return mRequestQueue;
    }

    public  void addToRequestQueue(Request req) {
        getRequestQueue().add(req);
    }
}
画面作成

jsonで受信したデータは、listViewで一覧表示しようと思います。まずはListLayoutをverticalで作成し、その中にlistViewを挿入します。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context="jp.sample.hotlist.MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:drawSelectorOnTop="false"></ListView>

</LinearLayout>

listViewの中に表示したい項目をLinearLayout中にtextViewを縦にならべたlayoutを追加したいので、activity_main.xmlの兄弟としてlayoutを追加します。

item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@android:color/white"
        android:background="@android:color/black"
        android:paddingEnd="5dp"
        android:paddingLeft="5dp"
        android:paddingTop="5dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large" />
    <TextView
        android:id="@+id/genre_catch"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/genre_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/open"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
</LinearLayout>
MainActivity

MainActivityのソースコードを組んで行きます。まず表示する一覧のArrayList関係を宣言します。

public class MainActivity extends AppCompatActivity {

    ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String, String>>();
    SimpleAdapter adapter;
    ListView listView;

    private static final String  TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState)  {

onCreate内でlistView関係のプログラムを書いていきます。jsonで返ってくる値から指定項目を抜き出して表示するための領域を作ります。

    protected void onCreate(Bundle savedInstanceState)  {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String URLTEXT =
                "http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?lat=31.583892&lng=130.542555&range=1&format=json";
        String APIKEY =
                "&key=your API key";

        String url = URLTEXT + APIKEY;
        listView= (ListView)findViewById(R.id.listView);
        adapter = new SimpleAdapter(this, list,  R.layout.item,
                new String[]{"name","genre_catch","genre_name","address","open"},
                new int[] {R.id.name,R.id.genre_catch,R.id.genre_name,R.id.address,R.id.open}
        );

API KEYのところには HotPepperから取得した自分のAPI key16桁を入れてください。今回は鹿児島中央駅周辺300mの店の情報を取得します。
item.xmlで宣言したlistViewにどの項目を対応させるかをadapterに作成します。

urlを送出して、そのレスポンスを受け取り終わった時にjsonの各項目をlistViewにaddしていく部分を宣言しておきます。

        JsonObjectRequest jsObjRequest = new JsonObjectRequest(
                Request.Method.GET,
                url,
                null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject response) {
                        try {
                            Log.d(TAG, response.toString(2));
                            int lcnt = Integer.parseInt(response
                                    .getJSONObject("results")
                                    .getString("results_returned"));
                            for(int i=0; i<lcnt; i++) {
                                HashMap<String, String> temp = new HashMap<String, String>();
                                temp.put("name", response
                                        .getJSONObject("results")
                                        .getJSONArray("shop")
                                        .getJSONObject(i)
                                        .getString("name"));
                                temp.put("genre_catch",response
                                        .getJSONObject("results")
                                        .getJSONArray("shop")
                                        .getJSONObject(i)
                                        .getJSONObject("genre")
                                        .getString("catch"));
                                temp.put("genre_name",response
                                        .getJSONObject("results")
                                        .getJSONArray("shop")
                                        .getJSONObject(i)
                                        .getJSONObject("genre")
                                        .getString("name"));
                                temp.put("address",response
                                        .getJSONObject("results")
                                        .getJSONArray("shop")
                                        .getJSONObject(i)
                                        .getString("address"));
                                temp.put("open",response
                                        .getJSONObject("results")
                                        .getJSONArray("shop")
                                        .getJSONObject(i)
                                        .getString("open"));
                                list.add(temp);
                            }
                            adapter.notifyDataSetChanged();
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                 },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d(TAG,error.toString());
                    }
                }
        );

このオブジェクトをMySingletonに渡して、唯一のインスタンスを作り、リクエストを送ります。先に作ったadapterをlistViewに追加します。

        MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);
        listView.setAdapter(adapter);
    }

プログラムは以上ですが、実行時の権限としてInternetの使用権を宣言しておく必要があります。AndroidManifest.xmlにpermissionを追加します。

    <uses-permission android:name="android.permission.INTERNET"/>
    <application

これをコンパイルして実行してみましょう。このサンプルでは緯度経度を固定していますので、同じ値しか返ってきません。これをkeywordを指定する、androidのgps情報を取得して現在地周りを取得するなど改造してみましょう。