ニフクラ mobile backend のAndroid SDKでGoogle認証を試す - 開発編

[ニフクラ mobile backend (NCMB)](https://mbaas.nifcloud.com/) エバンジェリストの小山([koyhoge](http://twitter.com/koyhoge))です。

前回の「[準備編](http://qiita.com/nifcloud_mb/items/bbbae64875b4021fd304)」を受けて、実際に NCMB と連携して Google 認証を行うアプリを作っていきます。

今回の内容は、基本的に以下のガイドにしたがって進めていきます。

[SDKガイド (Android): SNS連携(Google連携)](https://mbaas.nifcloud.com/doc/current/sns/google_android.html)

## Android Studio でプロジェクトを作成

Android Studio で新しくプロジェクトを作ります。
[準備編](http://qiita.com/nifcloud_mb/items/bbbae64875b4021fd304)

テンプレートプロジェクト画面で`Empty Activity` を選んだら`Next`を押します。

![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596649/61bf3463-d69a-73ad-a8fc-05930c50129d.png)

プロジェクト名を「NcmbGauth」にして `Finish`で完了します。

![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596649/891fe544-b410-6bf3-dcd6-b58b2356c8a3.png)

## プロジェクトの設定

### NCMB Android SDK のインストール

まず NCMB Android SDK である NCMB.jar を取得します。NCMB のクイックスタートのページの中ごろにある「Android SDKダウンロードはこちら」というボタンから、NCMB_latest.zip をダウンロードして下さい。

[クイックスタート (Android)](https://mbaas.nifcloud.com/doc/current/introduction/quickstart_android.html)

NCMB_latest.zip を展開すると NCMB.jar ファイルが現れますので、それを Android Studio で作成したプロジェクトディレクトリの app/libs にコピーします。

### Google Play Service をアプリに追加

以下の説明に従い、Google Play Service 用のライブラリをプロジェクトに追加します。

[Setting Up Google Play Services](https://developers.google.com/android/guides/setup#add_google_play_services_to_your_project)

プロジェクトの build.gradle の dependencies セクションに以下を加えます。

```build.gradle
dependencies {
// (略)
    implementation 'com.google.android.gms:play-services-base:17.3.0'
    implementation 'com.google.android.gms:play-services-auth:19.0.0'
// (略)
}
```

### アプリのネットワークアクセスを許可

プロジェクトの AndroidManifest.xml の <application> の直前に以下を追加して、アプリにネットへのアクセスを許可します。

```AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
```

## ボタンとメッセージ表示枠を配置

次にUI部品を配置します。認証を開始する Button と結果を表示するTextView を1つずつ配置しましょう。

activity_main.xml を RelativeLayout から LinearLayout に変更して、その中に Button と TextView を作ります。最終的には以下のようにになります。

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.google.android.gms.common.SignInButton
        android:id="@+id/sign_in_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

## NCMB SDKの初期化。

ここから先は MainActivity.java にある MainActivity クラスを編集していきます。まずは onCreate() メソッドの中で、SDK の初期化をしましょう。NCMB.initialize() メソッドで SDK 初期化します。第2,3 引数の文字列は NCMB のアプリに紐付いたアプリケーションキーとクライアントキーです。

またそのあとで NCMBGoogleUtils.initialize(this) で Google 認証関係のライブラリの初期化を行います。

```MainAcivity.java
        NCMB.initialize(this.getApplicationContext(), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
```

## イベントハンドラをオーバーライド


以下の2つのイベントハンドラをオーバーライドします。
Google 認証ダイアログから復帰した処理を記述します。

```java
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_SIGN_IN && resultCode == RESULT_OK) {
            getGoogleToken();
        }
    }
```

## Google認証の処理本体を記述

Button が押されたら Google 認証を実行して、その結果得られたユーザ名を TextView に表示してみましょう。該当部分は以下のようになります。

```java
        SignInButton signInButton = findViewById(R.id.sign_in_button);
        signInButton.setSize(SignInButton.SIZE_STANDARD);
        findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(getApplicationContext(),
                        Manifest.permission.GET_ACCOUNTS)
                        == PackageManager.PERMISSION_GRANTED) {
                    Intent intent = AccountManager.newChooseAccountIntent(null
                            , null
                            , new String[] {"com.google"}
                            , null
                            , null
                            , null
                            , null);
                    startActivityForResult(intent, REQUEST_SIGN_IN);
                }
            }
        });
```

最終的な MainActivity.java は以下となります。

```MainActivity.java
package org.koyhoge.ncmbgauth;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.common.SignInButton;
import com.nifcloud.mbaas.core.NCMB;
import com.nifcloud.mbaas.core.NCMBException;
import com.nifcloud.mbaas.core.NCMBGoogleParameters;
import com.nifcloud.mbaas.core.NCMBUser;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {
    private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
    private static final String AUTH_SCOPE = "oauth2:profile email";
    private static final int REQUEST_SIGN_IN = 10000;
    private static final String TAG = "NCMBLOG";

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

        //**************** APIキーの設定とSDKの初期化 **********************
        NCMB.initialize(this.getApplicationContext(), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");

        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.GET_ACCOUNTS)
                != PackageManager.PERMISSION_GRANTED) {

            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    android.Manifest.permission.GET_ACCOUNTS)) {

            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{android.Manifest.permission.GET_ACCOUNTS},
                        1);
            }
        }

        SignInButton signInButton = findViewById(R.id.sign_in_button);
        signInButton.setSize(SignInButton.SIZE_STANDARD);
        findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(getApplicationContext(),
                        Manifest.permission.GET_ACCOUNTS)
                        == PackageManager.PERMISSION_GRANTED) {
                    Intent intent = AccountManager.newChooseAccountIntent(null
                            , null
                            , new String[] {"com.google"}
                            , null
                            , null
                            , null
                            , null);
                    startActivityForResult(intent, REQUEST_SIGN_IN);
                }
            }
        });
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1: {
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "ACCESS_GET_ACCOUNTS is granted!");
                } else {
                    Log.d(TAG, "ACCESS_GET_ACCOUNTS is denied!");
                }
            }
        }
    }

    private void getGoogleToken() {

        AsyncTask<String, Void, String> task = new AsyncTask<String, Void, String> () {
            @Override
            protected String doInBackground(String... accounts) {

                String scopes = AUTH_SCOPE;
                String token = null;
                String id = null;
                try {
                    // Get google account
                    AccountManager accountManager = (AccountManager) getSystemService(ACCOUNT_SERVICE);
                    Account[] getAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE_GOOGLE);
                    if (getAccounts.length>0) {
                        String accountName = getAccounts[0].name;

                        id = GoogleAuthUtil.getAccountId(getApplicationContext(), accountName);
                        token = GoogleAuthUtil.getToken(getApplicationContext(), accountName, scopes);
                        Log.d(TAG, "id: " + id);
                        Log.d(TAG, "token: " + token);

                        NCMBGoogleParameters parameters = new NCMBGoogleParameters(
                                id,
                                token
                        );

                        NCMBUser.loginWith(parameters);

                        Log.d(TAG, "Login success!");
                    }


                } catch (UserRecoverableAuthException e) {
                    startActivityForResult(e.getIntent(), REQUEST_SIGN_IN);
                } catch (IOException | GoogleAuthException | ArrayIndexOutOfBoundsException e) {
                    Log.e(TAG, e.getMessage());
                } catch (NCMBException e) {
                    e.printStackTrace();
                    Log.e(TAG, e.getMessage());
                }
                return token;
            }

            @Override
            protected void onPostExecute(String s) {
                super.onPostExecute(s);
            }
        };

        task.execute();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_SIGN_IN && resultCode == RESULT_OK) {
            getGoogleToken();
        }
    }
}
```

## 実際に実行してみる

それでは早速、実際にビルドして実行してみましょう。アプリを実行すると以下の画面になります。

![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596649/a4554b09-c1cc-df59-d795-8bdde51c3891.png)

「LOGIN WITH GOOGLE」ボタンを押すと、初回はGoogleアカウントに連携したNCMBのユーザアカウントが存在しないので、どの Google アカウントを使用するかというダイアログが表示されます。

![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/596649/a035eab2-e999-5056-dc08-527f28bf8405.png)
## 最後に

いかがでしたでしょうか。特に前準備の部分がちょっと面倒だったかもしれません。Google 認証に限らず、Twitter や Facebook を用いた SNS 認証は、うまく使うことでユーザ登録の精神的な負担を減らしてくれます。コードの記述自体は、今回の例で見たようにかなり簡単に機能を使うことができますので、NCMB をすでにお使いの方や使用を検討している方は、SNS 認証機能をアプリに追加することもご検討いただいく参考になれば幸いです。