Android勉強会 vol.4 GoogleSpreadsheetにセンサーデータをアップロードする

この記事では Android Studio 2.1.2 を使用しています。
大学の研究室で開催している勉強会の資料共有とアーカイブのためにQiitaに投稿しています。

AndroidアプリからGoogleのAPIを用いて様々なGoogleのサービスを利用します。
最終的にはセンサーデータをGoogleのSpreadsheetにアップロードします。

# APIとは
APIはざっくりと説明すると他の人が用意してくれた便利なライブラリのことです。
プログラミングはすべて自分で作るのには限界があるためAPIを組み合わせてアプリを作っていくことが多いと思われます。
今回はそんなAPIについて学んでいきたいと思います

# Google スプレッドシート とは
Excelのような表計算ソフトをWebブラウザでも使えるようにしたものです。
インターネット上で動くためプログラミングで通信し利用することができます。
Excelと同様計算したり表にまとめたりすることが得意なのでセンサーデータを保存したりまとめたりすることを目的に進めていきたいと思います。

# APIでできること
Googleのスプレッドシートを作成、閲覧、編集、削除など基本的なことはできます。
今回は作業を簡単にするために予め自分で用意したスプレッドシートに対しひたすら書き込んでいくプログラムを作成していきます。

# GoogleのAPIを利用するために
GoogleのAPIを利用するためには開発者としての登録とどの機能を使うか選択する必要があります。
さらに実際に利用する際には認証が必要になるためアプリ開発に用いるにはすこし難しいと感じています。

## 開発者として登録する
まずは[Google API Console](https://console.developers.google.com)にアクセスしプロジェクトを作成します。
大学や企業のアカウントなどでは以下のように利用できない場合があります。この表示が出た時は通常のGoogleアカウントにします。
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/2ba292cf-9b4b-dbc1-e3aa-e10440f071fd.jpeg)

## プロジェクトを作成する
勉強会で使用するプロジェクトを作成します。
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/a986fb24-0e61-01f0-5c01-7a128d28239e.jpeg)
画面右上周辺の「プロジェクトを選択」をクリックし「プロジェクトの作成」をクリックしてください。
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/d8c8729d-e21f-5134-b38d-ba9eac7db84f.jpeg)
プロジェクト名は好きなもので構いません。入力したら「作成」をクリックします。
プロジェクトの作成には1分ほどかかりますので待ちます。

## 利用するAPIを選択する
今回はSheetsAPIを用いるためGoogle Apps APIの中にあるSheetsAPIを選択します。
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/c2fc5f55-4138-0211-f1f3-c258f0d066ee.jpeg)
「有効にする」をクリックします。
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/fc4ecd5a-b3f5-1136-2a4b-00c0a4d1c6b5.jpeg)
警告が出てきますので「認証情報に進む」をクリックします。
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/b11b850a-1964-e9b2-7218-8651292d01bd.jpeg)
今回はSheetsAPIを選択し、呼び出す場所をAndroidとします。
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/6532f72e-dea1-82f7-963c-cf2222d3d905.jpeg)
次にこのような画面になります
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/31e618c5-fef8-1d2e-66b7-e7ccc95ae20c.jpeg)
ここでAPIを使用するAndroidアプリの「パッケージ名」と「フィンガープリント」が必要になるため調べていきます

## パッケージ名を調べる
まずは空のAndroidアプリを作成します
EmptyActivityを選択しアプリを作成しました
はじめに生成されたbuild.gradle(Module: app)を開きます。

```rb:build.gradle
apply plugin: 'com.android.application'

android {
    ...
    defaultConfig {
        applicationId "パッケージ名"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    ...
}

...
```

android -> defaultConfig -> applicationId がパッケージ名になります

## フィンガープリントを調べる
これが少し面倒です

まずはJavaのインストールされたフォルダのbinフォルダを開きます
大抵の場合`C:\Program Files\Java\jre7\bin`となっていると思われますが適宜調べてください
そしてコマンドプロンプトを開きます(Shift+右クリックで簡単に開けます)

次にAndroidStudioが使用するkeystoreファイルを探します
こちらは基本的に`C:\Users\ユーザー名\.android`となります
このフォルダの中にdebug.keystoreというファイルがあるはずです
先ほど開いたコマンドプロンプトに
`keytool -list -v -keystore `
と入力しdebug.keystoreファイルをコマンドプロンプトにドラッグします
するとコマンドプロンプト上には
`C:\Program Files\Java\jre7\bin>keytool -list -v -keystore C:\Users\ユーザ名\.android\debug.keystore`
のように表示されますのでEnterキーを押して実行します

パスワードの入力を求められるので`android`と入力します
この時に入力しても何も表示されませんがそのままEnterキーを押します

すると様々な情報が表示されますがフィンガープリントはSHA1の部分になります
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/cb0642c8-af89-47b4-566c-348167e5b581.jpeg)
※一部隠してあります

## パッケージ名とフィンガープリントを登録する
再びGoogleの登録画面にもどり2つを登録します
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/8b2edb9e-a679-e639-8197-aa222f61ecac.jpeg)
入力したら「APIキーを生成する」をクリックし「完了」をクリックします
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/dcb4d15c-25a7-d664-9aff-250f313c70e6.jpeg)
このような画面になります

ここまででAPIを利用する準備は整いました

# アプリ側の準備をする
APIを使用するために必要なライブラリを読み込みます
まずは(Module: app)と表記されているbuild.gradleを編集します

```rb:build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "パッケージ名"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'
    // ここから下を追加
    compile 'com.google.android.gms:play-services-auth:9.0.2'
    compile 'pub.devrel:easypermissions:0.1.5'
    compile('com.google.api-client:google-api-client-android:1.22.0') {
        exclude group: 'org.apache.httpcomponents'
    }
    compile('com.google.apis:google-api-services-sheets:v4-rev10-1.22.0') {
        exclude group: 'org.apache.httpcomponents'
    }
}
```

基本的には生成されたままですがdependenciesに幾つか追記しています
次にAndroidManifest.xmlを編集します

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="daiki.example.com.androidsheetapi">


    <!--ネットワーク接続の許可を追加-->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!--meta情報を追加-->
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    </application>

</manifest>
```

2か所追加しました

## 編集するためのスプレッドシートを用意する
[Google スプレッドシート](https://docs.google.com/spreadsheets)にアクセスして右下の+ボタンを押して新しくスプレッドシートを作成します
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/b3b65194-db4f-988c-75da-f425745a775f.jpeg)
新しく作成されると自動的にそのスプレッドシートが開かれます
アクセスがうまくできているかを確認するために4つだけ追加します
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/e78d0fa6-c7d2-0ce1-05ea-08ed924d7b74.jpeg)

## サンプルコードをコピーする
[公式のサンプルコード](https://developers.google.com/sheets/quickstart/android?hl=ja#step_5_setup_the_sample)をそのまま一度写します
1行目はそれぞれ異なるはずので1行目以外をすべてコピペします

## サンプルコードを自分用にアレンジする
さきほど作成したスプレッドシートにアクセスするようにプログラムを変更していきます

```java:MainActivity.java
package パッケージ名;

import ...;

public class MainActivity extends Activity
    ...

    /**
     * An asynchronous task that handles the Google Sheets API call.
     * Placing the API calls in their own task ensures the UI stays responsive.
     */
    private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
        ...

        /**
         * Fetch a list of names and majors of students in a sample spreadsheet:
         * https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
         * @return List of names and majors
         * @throws IOException
         */
        private List<String> getDataFromApi() throws IOException {
            String spreadsheetId = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms";
            String range = "Class Data!A2:E";
            List<String> results = new ArrayList<String>();
            ValueRange response = this.mService.spreadsheets().values()
                .get(spreadsheetId, range)
                .execute();
            List<List<Object>> values = response.getValues();
            if (values != null) {
              results.add("Name, Major");
              for (List row : values) {
                results.add(row.get(0) + ", " + row.get(4));
              }
            }
            return results;
        }

        ...
    }
}
```

このgetDataFromApiという関数を変更します
まずspreadsheetIdを先ほど作成したスプレッドシートのIDにします
IDはURLの以下の部分です
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/3608fe85-d141-4af0-a27d-2698ee74029c.jpeg)
次にrangeを先ほど追加したデータの範囲にしたいので`シート1!A1:D1`にします
そしてデータをresultに追加していく部分も変更します

```java
for (List row : values) {
    results.add(
            row.get(0) + ", " +
                    row.get(1) + ", " +
                    row.get(2) + ", " +
                    row.get(3)
    );
}
```

それでは実行してみます
アプリが起動したらCAlL GOOGLE SHEETS APIを押します

![device-2016-07-20-105720.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/be6053ac-4ea2-907f-873b-a4ccb89ba417.jpeg)

先ほど入力したデータがとれました

# 書き込みをしてみる
次にデータを書き込むようなコードを追記します

```java:MainAcitivty.java
package sheetsquickstart.example.com.sheetsquickstart;

import ...;

public class MainActivity extends Activity
        implements EasyPermissions.PermissionCallbacks {
    ...

    /**
     * An asynchronous task that handles the Google Sheets API call.
     * Placing the API calls in their own task ensures the UI stays responsive.
     */
    private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
        ...

        /**
         * Background task to call Google Sheets API.
         *
         * @param params no parameters needed for this task.
         */
        @Override
        protected List<String> doInBackground(Void... params) {
            try {
                putDataFromApi(); //ボタンを押した時に反応させるために追記
                return getDataFromApi();
            } catch (Exception e) {
                mLastError = e;
                cancel(true);
                return null;
            }
        }

        /**
         * データを書き込むメソッド
         * @throws IOException
         */
        private void putDataFromApi() throws IOException {
            String spreadsheetId = "スプレッドシートのID";
            String range = "シート1!A2:D2";
            ValueRange valueRange = new ValueRange();
            List row = new ArrayList<>();
            List col = Arrays.asList("This", "is", "test", "test");
            row.add(col);
            valueRange.setValues(row);
            valueRange.setRange(range);
            this.mService.spreadsheets().values()
                    .update(spreadsheetId, range, valueRange)
                    .setValueInputOption("USER_ENTERED")
                    .execute();
        }

        ...
    }
}
```

これでアプリのボタンを押すとスプレッドシートが以下のように更新されます
![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/8f03a2f6-9937-7fda-9e96-e1c3981ce701.jpeg)

# センサーデータをアップする
## センサーデータを取得する
まずはSensorEventListenerを実装しSensorManagerなどの初期化を行います

```java:MainActivity.java
package sheetsquickstart.example.com.sheetsquickstart;

import ...;

public class MainActivity extends Activity
        implements EasyPermissions.PermissionCallbacks, SensorEventListener {
    SensorManager manager;
    Sensor sensor;
    private List data = new ArrayList<>(); // 加速度センサーのデータを保持するための変数

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...

        manager = (SensorManager) getSystemService(SENSOR_SERVICE);
        sensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); // 今回は加速度を使用します
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        data.add(Arrays.asList(
                event.timestamp,
                event.values[0],
                event.values[1],
                event.values[2]
        ));
        while (data.size() > 50) {
            data.remove(data.size() - 1); // 送信するデータ数を50個に制限しています
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // 使用しません
    }

    @Override
    protected void onResume() {
        super.onResume();
        manager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_UI);
    }

    @Override
    protected void onPause() {
        super.onPause();
        manager.unregisterListener(this);
    }

```

## センサーデータを送信する
送信する部分のプログラムを変更しセンサーデータを送信します

```java:MainActivity.java
package sheetsquickstart.example.com.sheetsquickstart;

import ...;

public class MainActivity extends Activity
        implements EasyPermissions.PermissionCallbacks {
    ...

    private class MakeRequestTask extends AsyncTask<Void, Void, List<String>> {
        ...

        /**
         * データを書き込むメソッド
         *
         * @throws IOException
         */
        private void putDataFromApi() throws IOException {
            List values = data;
            String spreadsheetId = "スプレッドシートのID";
            String range = "シート1!A2:D" + (values.size() + 1);
            ValueRange valueRange = new ValueRange();
            valueRange.setValues(values);
            valueRange.setRange(range);
            this.mService.spreadsheets().values()
                    .update(spreadsheetId, range, valueRange)
                    .setValueInputOption("USER_ENTERED")
                    .execute();
        }

        ...
    }
}
```

これでボタンを押した時に直近50個のセンサーデータが送信されます

![無題.jpg](https://qiita-image-store.s3.amazonaws.com/0/113969/9e6fe443-cb75-9542-9170-7778c19b602a.jpeg)


# せめて操作だけでもしてみる！
## サンプルコードを取る
`git clone https://github.com/googlecodelabs/sheets-api.git`
Gitコマンドでサンプルプログラムを取得します
cloneされたフォルダ内のstep10が完成なのでそちらのフォルダをコマンドプロンプトで開きます
`npm install`
を実行してライブラリを取得します
## APIキーを追加する
現在Android用の認証情報だけなのでテスト用にWeb用のものも作成します
Google API Consoleにアクセスし
先ほど作成したプロジェクトを選択し「認証情報」をクリック
認証情報の作成からOAuthクライアントIDを選択します
ウェブアプリケーションを選択し生成元にはhttp://localhost:3000
と指定します
作成を行うと client IDが出てきます

Client IDをコピーし
step10/views/layout.handlebarsの43行目の{YOUR CLIENT ID}を先ほどの client IDにします

## 実行してみる
step10のフォルダを開いているコマンドプロンプトで`npm start`を実行しhttp://localhost:3000にアクセスしてみます
右のCreateボタンによりスプレッドシートがGoogle Driveに追加されデータも編集できることが確認できると思います

今回の勉強会はここまでしかできていません
いそいで続きつくります…