Androidアプリ開発者のためのFirebase入門(Auth,Realtime DB,Storage,Rule) #firebase #Android #GCP

普段Androidアプリを開発していて、ちょっとFirebaseに興味がある人向けに、Firebaseでよく使うであろう機能Auth,Realtime DB,Storage,Ruleについてまとめてみました。この記事を読んで少しでもFirebaseに興味を持って頂けたら幸いです。

#Firebase SDKの特徴
- リアルタイム処理が優れている
- オフライン対応が優れている

#Firebaseの実用例

- [アプリケーション](https://firebase.google.com/customers/)

#導入編
1. 新規プロジェクトの作成
2. google-services.json.jsonをダウンロード

##新規プロジェクトの作成
[Firebase](https://firebase.google.com)にログイン
コンソールに行くと「新規プロジェクトの作成」と「Googleプロジェクトをインポート」がある。

既にGoogle Maps APIやGAEなどを使用しており、同じプロジェクトにFirebaseを入れたい人はインポートを選ぶ。

新規プロジェクトを作成した後は、[AndroidアプリにFirebaseを追加]を選択

<img width="551" alt="Screen Shot 0028-11-24 at 11.16.25.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/7305aa0a-ad6a-7afc-3322-b7c6c69887f2.png">

<br>
すると以下のような画面がでてきます。

<img width="689" alt="Screen Shot 0028-11-24 at 11.23.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/114922/de639f4f-5743-a4ab-395d-fdf67647fbec.png">

必要な情報を入力しましょう。
デバック用のkeystoreは以下のようなコードで取得可能です。

```text:Mac/Linux
keytool -exportcert -list -v \
-alias androiddebugkey -keystore ~/.android/debug.keystore
```

```text:Windows
keytool -exportcert -list -v \
-alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore
```

##google-services.json のインポート
アプリを作成すると、google-services.jsonが生成され自動的にダウンロードされます。
こちらのファイルをAndroidプロジェクトのappの配下に入れましょう。

#Auth認証を触ってみる
Firebase Authではユーザーの管理を行います。
一般的なユーザー作成、ログイン、ログアウトの処理を簡単に実装することができます。Google,Facebook,Twitter,Github認証がサポートされており、メールアドレス＆パスワードによるユーザー登録も可能となっています。匿名によるログインも実装可能であり、一時的なユーザー登録やゲストユーザーなどを作成することが出来ます。また複数の認証も対応しており、GoogleとFacebookの２つの認証を作るということも可能です。これにより片方の認証でログインできなかったとしてももう一つの認証でログインすることが可能です。

※メールアドレス＆パスワードの認証では、会員登録時の確認用メールを自分でカスタマイズすることが可能です。


##ライブラリの追加
２つのライブラリを追加する
今回はGoogle認証を試したいので、Play Servicesのライブラリが必要となります。
※Google Play ServicesとFirebaseのライブラリのバージョンコードは基本統一されています。

```text:build.gradle
 compile 'com.google.android.gms:play-services-auth:9.8.0'
 compile 'com.google.firebase:firebase-auth:9.8.0'
```

##Google認証を許可する
Google認証を実装するには、予めFirebaseのAPIコンソール上でGoogle認証を許可しておきます。方法はとても簡単です。
1. コンソールのタブからAuthenticationを選択
2. ログイン方法を選択
3. Google認証を許可する
4. 保存を選択
5. Google認証のステータスが有効となっていることを確認


##Googleでログイン
初回の場合は自動で新規作成となります。

```java:SignInActivity.java
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken(getString(R.string.default_web_client_id))
                .requestEmail()
                .build();

GoogleApiClient gac = new GoogleApiClient.Builder(this)
                .enableAutoManage(this /* FragmentActivity */, this /* OnConnectionFailedListener */)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

//以下のIntentでログインActivityを呼びます
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
```

Google認証の結果は以下のコードで取得できます。

```java:SignInActivity.java

 @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);       
        if (requestCode == RC_SIGN_IN) {
            GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
            if (result.isSuccess()) {
                // Google Sign In was successful, authenticate with Firebase
                GoogleSignInAccount account = result.getSignInAccount();
                firebaseAuthWithGoogle(account);
            } else {
                // Google Sign In failed
            }
        }
    }
```


得られたGoogle認証の結果をFirebaseに送信しユーザー登録を完了させます。

```java:SignInActivity.java
private void firebaseAuthWithGoogle(GoogleSignInAccount acct) {
        AuthCredential credential = GoogleAuthProvider.getCredential(acct.getIdToken(), null);
        mFirebaseAuth.signInWithCredential(credential)
                .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull Task<AuthResult> task) {
                        if (!task.isSuccessful()) {
                            //ログイン失敗
                        } else {
                            //ログイン成功
                        }
                    }
                });
}
```

実際にアプリケーションを作成する時はメイン画面でユーザーがログイン済みかを確認し、ログイン情報がnullの場合はサインイン画面に遷移するのが一般的です。

```java:MainaActivity.java
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

　if (user == null) {
      // ログインされていません。
      // ログイン画面に遷移
 }else{
      //ログイン済み
 }
```


##ログアウト
ログアウトは以下の１行を呼ぶだけです。

```java:MainActivity.java
FirebaseAuth.getInstance().signOut();
```



#Realtime DBを触ってみる
Firebaseの目玉機能であるRealtime DBに触れていきます。
主な特徴

- データはリアルタイムで同期される
- アプリがオフラインになっても利用可能な状態を保つ
    - オンラインになると自動的に同期されます
- データはJSONとして保存される
- コンソールからはJSONで保存されたデータをいつでも確認することができる
- Android,iOS,Webからアクセスでき同じインスタンスにアクセスできる


対応している型

- String
- Long
- Double
- Boolean
- Map<String, Object>
- List<"Object">


##ライブラリの追加
```text:build.gradle
compile 'com.google.firebase:firebase-database:9.8.0'
```


##データの保存
最もシンプルなコードはこちらです。
'message'の中に'Hello, World!'という文字列を保存した事になります。

```java:MainActivity.java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("message");

myRef.setValue("Hello, World!");
```


もう少し凝った保存の仕方をしてみましょう。
Userのエンティティクラスを生成します。
※データを取得する際に空のコンストラクタが必要なので必ず空のコンストラクタを宣言してください。

```java:MainActivity.java
@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        
    }

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

}
```


こちらのUserクラスをRealtimeDBに保存してみます。
この時FirebaseのUser情報を取得し、uidをnestの名前にしておきましょう。
後に幸せになります。

```java:MainActivity.java
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();

String uid =  user.getUid();

User user = new User(name, email);

mDatabase.child("users").child(uid).setValue(user);
```

##データの取得
上で作成したUserインスタンスを取得してみます。
onDataChageはデータに変更がある度に呼ばれるので、ここにRecyclerviewなどの通知をセットします。

```java:MainActivity.java
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String uid =  user.getUid();

FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference userRef = database.getReference("users").(uid);

userRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
　　　　　//Userインスタンスの取得
        User user = dataSnapshot.getValue(User.class);
        Log.d(TAG, "Username is: " + user.username);
        Log.d(TAG, "Email address is: " + user.email);
    }

    @Override
    public void onCancelled(DatabaseError error) {
        // データの取得に失敗
        Log.w(TAG, "Failed to read value.", error.toException());
    }
});

```

##データの削除
データの削除はremoveValue()を呼びます。
もしくはsetValueにnullをセットします。

```java:MainActivity.java
mDatabase.child("users").child(uid).removeValue();
```


#Storageを触ってみる
Firebase Storageは画像、動画などのファイルを保存することができます。
そしてFirebase StorageをGoogle cloud Storageを使用しており、アップロードした後Google AppEngineや外部からもアクセスが可能です。



##ライブラリの追加


```text:build.gradle
compile 'com.google.firebase:firebase-storage:9.8.0'
compile 'com.google.firebase:firebase-auth:9.8.0'
```



##バケット名の取得
はじめにRealtime DBのDatabaseReferenceのようにStorageReferenceを取得します。
StorageReferenceを取得するにはGCSのバケット名を取得する必要があります。
バケット名はFirebaseコンソールのStorageタブから取得可能です。
こんな感じの名前です。

```text:バケット名
gs://angler-camera.appspot.com
```



##画像のアップロード
画像をFirebaseStorageにアップロードします。この時もFirebaseのuidを入れましょう。
putByte()で画像をアップロードします。

```java:MainActivity.java
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String uid =  user.getUid();

StorageReference storageRef = storage.getReferenceFromUrl("gs://<your-bucket-name>");
StorageReference images = storageRef.child("users").child(uid).child("images");

byte[] data = //画像のデータ

UploadTask uploadTask = images.putBytes(data);
uploadTask.addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // アップロード失敗
    }
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        // 画像のアップロード成功
        // 画像のダウンロードURLの取得
        Uri downloadUrl = taskSnapshot.getDownloadUrl();
    }
});

```


画像のダウンロードURLが取得できたら、アップロード成功です。
ダウンロードURLをDBに保存しておくと後に便利です。

```java:MainActivity.java
String downloadURL = //画像のダウンロードURL

FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
String uid =  user.getUid();
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference userRef = database.getReference("users").(uid).("images");
userRef.putValue(downloadURL);
```


##画像のダウンロード
画像のダウンロードさえあれば画像のダウンロードはすごく簡単です。

```java:MainActivity.java
String downloadURL = //画像のダウンロードURL
StorageReference httpsReference = storage.getReferenceFromUrl(downloadURL);
//ダウンロードの最大のサイズを指定する
final long ONE_MEGABYTE = 1024 * 1024;
islandRef.getBytes(ONE_MEGABYTE).addOnSuccessListener(new OnSuccessListener<byte[]>() {
    @Override
    public void onSuccess(byte[] bytes) {
        // 画像のダウンロード成功
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // 画像のダウンロード失敗
    }
});
```


##画像の削除
削除もダウンロードURLがあれば簡単です。
削除したStorageReferenceのインスタンスに対してdelete()を実行します。

```java:MainActivity.java
// Create a reference to the file to delete
StorageReference httpsReference = storage.getReferenceFromUrl(downloadURL);

// Delete the file
httpsReference.delete().addOnSuccessListener(new OnSuccessListener() {
    @Override
    public void onSuccess(Void aVoid) {
        // 削除成功
    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception exception) {
        // 削除失敗
    }
});

```


#Firebase Rules でデータを保護する
最後にFirebaseRulesについて触れていきたいと思います。今回は自分のデータが他人から読み書きできないようにしてみます。
FirebaseのRulesはWebのコンソール上で編集を行います。FirebaseのRealtime DBやStorageタブを選択し、ルールタブを選択することで編集が可能です。



##初期設定はこうなってる！
Realtime Databaseのルールの初期設定はこんな感じです。認証をしているユーザーは全てのデータにアクセスが可能です。匿名でも認証さえ終えていれば、全てのデータにアクセスできることになります。

```json:RealtimeDatabaseRules
{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}
```

続いてStorageのルールの初期設定です。こちらは誰も読み書きできない設定になっています。逆にfalseの部分をtrueにしてあげれば外部を含め誰でもアクセス可能となります。

```text:StorageRules
service firebase.storage {
  match /b/hogehoge.appspot.com/o {
    match /{allPaths=**} {
      allow read, write: if false;
    }
  }
}
```


##自分のデータが他人からアクセスできないようにする
さきほどRealtime DBとStorageに設定した/users/uidを思い出してください。これを利用し他人からのアクセスを防ぐ事ができます。FirebaseルールではAuth認証が済んでいるユーザーからユーザーのuidを取得することが出来るのでそれを利用してみます。

```json:RealtimeDatabaseRules
{
  "rules": {    
    "users": {
      "$user_id": {
    		".write": "auth != null && auth.uid == $user_id",
    		".read": "auth != null && auth.uid == $user_id"
      }
    }
  }
}
```


ユーザーのuidを取得して他人からのアクセスを防ぎます。 ```auth != null```でAuth認証をしていないユーザーはその時点でアクセスが不可能になります。そして ```auth.uid == $user_id```で自分がアクセスしようとしてるディレクトリが自分のものか判定します。自分のものであればアクセスが可能となります。

同じ要領でStorageのルールも設定してみましょう。

```text:StorageRules
service firebase.storage {
  match /b/hogehoge.appspot.com/o {
  
  	match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  
    match /users/{userId}/images/{allPaths=**} {
      allow read: if request.auth.uid == userId;
      allow write: if request.auth.uid == userId;
    } 
  }
}
```
こちらではAuth認証を一番上で宣言してみました。これにより全てディレクトリにAuth認証が必要となります。


###おまけ
Storageのルールではではどんなファイルを？どれくらいのサイズまで？の制限を設けることができます。
/images/のディレクトリに対してアップロードを行う時、5MB（1024 * 1024）以下の画像データのみ、アップロードを許可しています。

```text:StorageRules
match /images/{imageId} {
        allow write: if request.resource.size < 5 * 1024 * 1024
                     && request.resource.metadata.contentType.matches('image/.*');
}  
```