Kotlin + Androidで『nifty cloud mobile backend』と『Firebase』のAuthenticationを使ってみた

# はじめに
Google I/O 2017で発表されたように、KotlinがAndroidの公式言語に加わったということで、mBaaS(mobile Backend as a Service)のncmb(nifty cloud mobile backend)とFirebaseを使用し、早速kotlinでAndroidプログラミングしてみました。
mBaaSはアプリ開発によく利用される汎用的な機能(ユーザー管理、データストア、プッシュ通知とか)をクラウドから提供されるサービスですが、今回は両者のユーザー認証機能について紹介していきます。

#開発環境
* AndroidStudio2.3.3
* Kotlin 1.1.3
* ncmb
* firebase

※AndroidStudioにKotlinを導入する手順は[こちら](http://crossbridge-lab.hatenablog.com/entry/2015/12/03/220000)の記事をご参照ください。


#ncmb(nifty cloud mobile backend)
## アプリ新規作成
まずはダッシュボードからアプリを新規作成します。
![図1.png](https://qiita-image-store.s3.amazonaws.com/0/119288/6bf03ae2-b4ff-a744-c586-bfa2e82bfa6b.png)

![スクリーンショット 2017-07-16 13.03.38.png](https://qiita-image-store.s3.amazonaws.com/0/119288/f78d2863-a313-eabf-6005-d228e4728d06.png)
アプリを新規作成するとアプリケーションキーとクライアントキーが発行されます。
![スクリーンショット 2017-07-16 12.52.52.png](https://qiita-image-store.s3.amazonaws.com/0/119288/fee17ef5-876f-091d-7693-5db142b83514.png)

## メールアドレス認証
ncmbのメールアドレス認証の流れは以下のようになっています。

1.ユーザーがメールアドレスを入力して認証をリクエストする
2.入力したメールアドレスに対して会員登録画面へのURLを含むメールが送信される
3.会員登録画面でパスワードを登録し、会員登録を行う
4.メールアドレスとパスワードでのログインが可能になる




## アプリの実装


```MainActivity.kt

class MainActivity : AppCompatActivity(), View.OnClickListener{

    // 定数
    companion object Factory {
        // ApplicationKey
        val APP_KEY = "YOUR APP KEY" // アプリ新規作成時発行されたアプリケーションキー設定
        // ClientKey
        val CLIENT_KEY = "YOUR CLIENT KEY" // アプリ新規作成時発行されたクライアントキー設定
    }

    private var mEmailField: EditText? = null
    private var mPasswordField: EditText? = null
    private var loginMailAddress: EditText? = null
    private var loginPassword: EditText? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //********** APIキーの設定とSDKの初期化 **********
        NCMB.initialize(this.baseContext, APP_KEY, CLIENT_KEY)

        // 会員登録
        mEmailField = findViewById(R.id.MailAddress) as EditText
        mPasswordField = findViewById(R.id.Password) as EditText
        // ログイン
        loginMailAddress = findViewById(R.id.login_MailAddress) as EditText
        loginPassword = findViewById(R.id.login_Password) as EditText

        // SIGNUPボタン
        findViewById(R.id.Button)?.setOnClickListener(this)

        // LOGINボタン
        findViewById(R.id.login_Button)?.setOnClickListener(this)

        // LOGOUTボタン
        findViewById(R.id.logout_Button)?.setOnClickListener(this)
        }
    }

    /**
     * ボタンクリック時
     */
    override fun onClick(v : View) {
        val id = v.id
        if(id == (R.id.Button)) {
            // 会員登録
            createAccount(mEmailField?.text.toString())
        } else if(id == (R.id.login_Button)) {
            // ログイン
            signIn(loginMailAddress?.text.toString(), loginPassword?.text.toString())
        } else if(id == (R.id.logout_Button)) {
            // ログアウト
            signOut()
        }

    }

```
### 会員登録処理
会員登録処理はSIGN UPボタン押下時に呼ばれるString型のemailを引数に持つcreateAccountメソッドで行います。

```MainActivity.kt
private fun createAccount(email: String) {
        NCMBUser.requestAuthenticationMailInBackground(email) { e ->
                if(e == null) {
                    println("ユーザーメール認証成功")
                } else {
                    println("ユーザーメール認証失敗")
                }
        }
    }
```
javaで書くとこんな感じになります。kotlinより冗長的な記述になっています。
NCMBUserのrequestAuthenticationMailInBackgroundメソッドは、引数にユーザーが入力したメールアドレスと[DoneCallback](http://www.programcreek.com/java-api-examples/index.php?source_dir=ncmb_android-master/ncmb-core/src/main/java/com/nifty/cloud/mb/core/DoneCallback.java)というインターフェースを引数にとります。ここでは[DoneCallback](http://www.programcreek.com/java-api-examples/index.php?source_dir=ncmb_android-master/ncmb-core/src/main/java/com/nifty/cloud/mb/core/DoneCallback.java)インターフェースを実装した匿名クラスを生成して、引数に渡しています。

```MainActivity.java
private void createAccount(String email) {
  NCMBUser.requestAuthenticationMailInBackground(email, 
   new DoneCallback() {
    @Override
    public void done(NCMBException e) {
        if (e == null) {
            System.out.println("ユーザーメール認証成功");
        } else if {
            System.out.println("ユーザーメール認証失敗");
        }
      }
   });
 }
```
kotlinで記述した方にはDoneCallbackインターフェースやdone(NCMBException e)というメソッドが省略されていますが、これはSAMインターフェースによるSAM変換が行われています。
SAM(Single Abstract Method)は一つだけ抽象メソッドを持つインターフェースです。
[DoneCallback](http://www.programcreek.com/java-api-examples/index.php?source_dir=ncmb_android-master/ncmb-core/src/main/java/com/nifty/cloud/mb/core/DoneCallback.java)インターフェースの抽象メソッドdone(NCMBException e)は、戻り値にvoid型、引数にNCMBException型を持ちます。そのため関数リテラルの型を省略し型推論により、kotlinのようにスッキリ記述することが出来ます。
関数リテラルについては[こちら](http://kotlin.hatenablog.jp/entry/2012/12/10/093018)の記事を参考にさせて頂きました。

正常に会員登録が出来たら、ncmbの会員管理画面にユーザー情報が登録されます。
![スクリーンショット 2017-07-17 0.52.31.png](https://qiita-image-store.s3.amazonaws.com/0/119288/d9ecd05b-70ae-e9a1-0e51-99f9669557bc.png)


### ログイン処理
ログイン処理はLOGINボタン押下時に呼ばれるString型のemailとString型のpasswordを引数に持つsignInメソッドで行います。signInメソッドの中でNCMBUserのloginWithMailAddressInBackgroundメソッドを呼び出せばログインします。

```MainActivity.kt

private fun signIn(email: String, password: String) {
        NCMBUser.loginWithMailAddressInBackground(email, password) {user, e ->
                if(e == null) {
                    println("ログイン成功")
                } else {
                    println("ログイン失敗 :" + e.message.toString() )
                }
         }
}
```
会員登録用メールからパスワードを登録し会員登録を行った後であれば、メールアドレスとパスワードでのログインが可能になります。

### ログアウト処理
ログアウト処理はログアウトボタン押下時に呼ばれる引数なしのsignOutメソッドを呼び出します。
sighOutメソッドの中でNCMBUserのlogoutInBackgroundメソッド呼び出せばログアウトします。

```MainActivity.kt
    private fun signOut() {
        NCMBUser.logoutInBackground{ e ->
            if (e == null) {
                println("ログアウト成功")
            } else {
                println("ログアウト失敗")
            }
        }
    }
```

## ダッシュボードの設定
メールアドレスパスワード認証を許可する場合は、ダッシュボードのアプリ設定→会員認証設定からメールアドレス/パスワード認証を許可するにチェックし保存してください。
![スクリーンショット 2017-07-16 21.25.06.png](https://qiita-image-store.s3.amazonaws.com/0/119288/1c346726-65f0-40fd-ceba-61e5b9089bec.png)

#Firebase
## プロジェクト新規作成
[Firebase](https://firebase.google.com/?hl=ja)にGoogleアカウントでログインし、プロジェクトを新規作成します。
![スクリーンショット 2017-07-17 0.35.53.png](https://qiita-image-store.s3.amazonaws.com/0/119288/858752f9-6cd6-3f51-4262-73f9ce3f9002.png)

## メール/パスワードの認証を許可する
FirebaseのAuthentication管理画面からメール/パスワードのステータスを有効にします。
![スクリーンショット 2017-07-17 0.46.26.png](https://qiita-image-store.s3.amazonaws.com/0/119288/89bb168f-111a-4150-9774-77d9ecfe0044.png)

##アプリの実装

```MainActivity.kt

class MainActivity : AppCompatActivity(), View.OnClickListener {

    private var mEmailField: EditText? = null
    private var mPasswordField: EditText? = null
    private var loginEmail: EditText? = null
    private var loginPassword: EditText? = null

    private var mAuth: FirebaseAuth? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ユーザー新規作成項目
        mEmailField = findViewById(R.id.MailAddress) as EditText
        mPasswordField = findViewById(R.id.Password) as EditText
        // ログイン時項目
        loginEmail = findViewById(R.id.login_MailAddress) as EditText
        loginPassword = findViewById(R.id.login_Password) as EditText

        // SIGN UPボタン押下時
        findViewById(R.id.Button)?.setOnClickListener(this)
        // LOGINボタン押下時
        findViewById(R.id.login_Button)?.setOnClickListener(this)
        // LOGOUTボタン押下時
        findViewById(R.id.logout_Button)?.setOnClickListener(this)

        // FirebaseAuthインスタンス取得
        mAuth = FirebaseAuth.getInstance()
    }

    /**
     * ボタンクリック時処理
     */
    override fun onClick(v: View) {
        val i = v.id
        if (i == R.id.Button) {
            //新規作成ボタン押下時
            createAccount(mEmailField?.text.toString(), mPasswordField?.text.toString())
        } else if (i == R.id.login_Button) {
            // ログインボタン押下時
            signIn(loginEmail?.text.toString(), loginPassword?.text.toString())
        } else if (i == R.id.logout_Button) {
            // ログアウトボタン押下時
            signOut()
        }
    }
```
アプリレベルのbuild.gradleにFirebase Authenticationの依存関係を追加

```build.gradle
dependencies {
 
     (省略　）
     
    // Firebase Authentication
    compile 'com.google.firebase:firebase-auth:10.0.1'　追加
}
```

###会員登録処理
SIGN UPボタン押下時に呼ばれるcreateAccountメソッドの中で、FirebaseインスタンスのcreateUserWithEmailAndPasswordメソッドにユーザーが入力したメールアドレス、パスワードを渡して、新しいアカウントを作成します。

```MainActivity.kt
private fun createAccount(email: String, password: String) {
        mAuth?.createUserWithEmailAndPassword(email, password)
                ?.addOnCompleteListener(this) { task ->
                    if (task.isSuccessful) {
                        println("ユーザー作成成功")
                        sendEmailVerrification()// 確認メール送信
                    } else {
                        println("ユーザー作成失敗 :" + task.exception.toString())
                    }
                }
    }
```
**注意点**：ncmbでは会員登録時に確認用のメールが送信され、メール内のリンクをクリックしてパスワードを設定し会員登録完了になりましたが、Firebaseでは会員登録時確認メールが送信されません。
そのため、新規ユーザー作成が成功したら確認メールを送信するメソッドを作り呼び出すようにしています。

正常にユーザーが作成出来たらFirebaseの管理画面にユーザー情報が保存されます。
![スクリーンショット 2017-07-17 1.59.22.png](https://qiita-image-store.s3.amazonaws.com/0/119288/caed97f8-c46d-be1f-42fd-ae896faf119e.png)


###確認メール送信処理
新規ユーザー作成に成功したらこのメソッドを呼び出して、ユーザーに確認メールを送信します。

```MainActivity.kt
private fun sendEmailVerrification() {
        val user = FirebaseAuth.getInstance().currentUser
        user?.sendEmailVerification()
                ?.addOnCompleteListener(this) { task ->
                    if(task.isSuccessful) {
                        println("メール送信成功")
                    } else {
                        println("メール送信失敗 :" + task.exception.toString())
                    }
                }
    }

```

###ログイン処理
LOGINボタン押下時に呼ばれるdignInメソッドの中で、FirebaseインスタンスのsignInWithEmailAndPasswordメソッドにユーザーが入力したメールアドレス、パスワードを引数に渡します。

```MainActivity.kt
private fun signIn(email: String, password: String) {
        mAuth?.signInWithEmailAndPassword(email, password)
                ?.addOnCompleteListener(this) { task ->
                    if(task.isSuccessful) {
                        println("ログイン成功 ユーザー:" + mAuth?.currentUser.toString())
                    } else {
                        println("ログイン失敗")
                    }
                }
    }
```

###ログウト処理
LOGOUTボタン押下時に呼ばれるsignOutメソッドで、FirebaseインスタンスのsignOutメソッドを呼んでログアウトします。

```MainActivity.kt
private fun signOut() {
        mAuth?.signOut()
 }
```

## Firebase Analytics
Firebaseにはモバイルアプリに特化したGoogleアナリティクスの位置づけのFirebase Analyticsというツールもあり、あまり詳しく調べてませんがアクセス解析にも便利そうです。
導入手順については[こちら](http://dev.classmethod.jp/smartphone/firebase-analytics-getting-started/)の記事が分かり易かったです。

![スクリーンショット 2017-07-17 2.12.58.png](https://qiita-image-store.s3.amazonaws.com/0/119288/8b38dc7a-c1b5-ce97-19db-a04bbbf6138c.png)


#参考資料
[NCMB公式ドキュメント](http://mb.cloud.nifty.com/doc/current/user/authorize_email_android.html#アプリでの実装)
[Firebase公式ドキュメント](https://firebase.google.com/docs/auth/android/password-auth?hl=ja)
http://qiita.com/RyotaMurohoshi/items/01b370f34a4bf96f5c39
http://kotlin.hatenablog.jp/entry/2012/12/10/093018