Kotlin + Firebaseで作るチャットアプリ入門1

#1. 初めに
Android開発の勉強を始めようと思いスタートしました
Javaではなく、Kotlinでやります。

長くなりそうなため、何回かに分けて投稿します

元ネタ　
・[【保存版】言語別Youtubeおすすめチャンネル](https://qiita.com/gjfcgmi/items/a8b55a0a6d4578abac66)
・[Kotlin Firebase Messanger](https://www.youtube.com/watch?v=ihJGxFu2u9Q&list=PL0dzCUj1L5JE-jiBHjxlmXEkQkum_M3R-)

開発言語、環境
・Kotlin
・Android Studio(Target Android9.0 ,API 28)
・Firebase(Authentication,storage,Database)

#2. 事前準備
・Firebaseでプロジェクトを作成すること、Googleアカウントがあれば使えるのでさくっと済ませる
　プランなどが数種類あるが無料のsparkでOK
・AndroidStudio側でもプロジェクトを作成する
・[FirebaseのプロジェクトをAndroidStudioのプロジェクトと接続する](https://firebase.google.com/docs/android/setup?hl=ja)
→最初は少し難しく感じた
→Google認証を使うならAuthenticationに設定するデバッグ証明書(sha-1)を先に取得しておいたほうが良い、取得は以下のコマンドで、初期パスワードはandroidらしい

```
keytool -exportcert -list -v -alias androiddebugkey -keystore %USERPROFILE%\.android\debug.keystore
```
*コマンドが認識されない場合はJavaSDKのパスを確認する、JREではない

・ライブラリの追加、githubや公式のReadmeを参考に導入、JavaとKotlinでは手順が微妙に違うものあり、気を付けて
　・[groupie](https://github.com/lisawray/groupie)
　・[picasso](http://square.github.io/picasso/)

・データベースの作成
→Realtime Databaseを使用する、データベースは2種類あるので要注意
ユーザーの情報を格納するテーブルとしてusersを作っておく
![1.png](https://qiita-image-store.s3.amazonaws.com/0/309466/78110551-b894-c5b4-9193-df6d224b7a86.png)

→参照ルールは暫定で以下のようにする、読み取りは自由、書き込みは認証通った人のみのイメージ
![4.png](https://qiita-image-store.s3.amazonaws.com/0/309466/b5977ede-453c-bc45-29e1-568c93e4d055.png)

・ストレージの作成
→よくあるユーザーのイメージ画像を格納する場所を作っておく
![2.png](https://qiita-image-store.s3.amazonaws.com/0/309466/f7c242e5-5efc-5405-c211-376f48e4ae14.png)

#3. サインアップの画面を作ってみる
外観はこんな感じ
![3.png](https://qiita-image-store.s3.amazonaws.com/0/309466/1da5158b-b3b2-3c38-a0fc-3a058c59f1c5.png)

フォームを丸みを持たせるのはdrawableフォルダに

```
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/holo_green_dark"/>
    <corners android:radius="75dp"/>
</shape>
```
こんな感じのレイアウトを定義したリソースを作っておき、適用したいフォームに下記のように書いてやる

```
android:background="@drawable/{作ったリソース名}"
```

続いてロジック

```RegisterActivity.kt

package com.example.p3.kotlinmessenger.registerLogin

import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.widget.Toast
import com.example.p3.kotlinmessenger.R
import com.example.p3.kotlinmessenger.messages.LatestMessagesActivity
import com.example.p3.kotlinmessenger.model.User
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.FirebaseDatabase
import com.google.firebase.storage.FirebaseStorage
import kotlinx.android.synthetic.main.activity_register.*
import java.util.*

class RegisterActivity : AppCompatActivity() {
    //companion：static変数、メソッドの代わり
    companion object {
        val TAG = "Register Activity"
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_register)

        register_button_register.setOnClickListener {
            performRegister()
        }

        already_have_account_test_view.setOnClickListener {
            Log.d(TAG, "Try to show Login Activity")

            //::class.javaでクラスオブジェクトの参照
            val intent = Intent(this, LoginActivity::class.java)
            startActivity(intent)
        }

        selectphoto_button_register.setOnClickListener {
            Log.d(TAG, "Try to show photo selector")
            //ユーザーが連絡先を選択し、アプリが連絡先情報にアクセスできるようにする
            val intent = Intent(Intent.ACTION_PICK)
            intent.type = "image/*"
            startActivityForResult(intent, 0)
        }

    }

    //varの場合、変更が可能
    var selectedPhotoUri: Uri? = null

    //画像取得の終了後に来る処理
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 0 && resultCode == Activity.RESULT_OK && data != null) {
            Log.d(TAG, "Photo was selected")

            //Bitmapで画像を取得、フォームの周りに黒線もひいとく
            selectedPhotoUri = data.data
            val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, selectedPhotoUri)

            selectphoto_imageview_register.setImageBitmap(bitmap)
            selectphoto_button_register.alpha = 0f

        }
    }

    //Registerボタン押下！
    private fun performRegister() {
        val email = email_edittext_register.text.toString()
        val password = password_edittext_register.text.toString()

        //emailとpwの入力チェック
        if (email.isEmpty() || password.isEmpty()) {
            Toast.makeText(this, "Please enter text in emai/pw", Toast.LENGTH_SHORT).show()
            return
        }

        Log.d(TAG, "Email is: " + email)
        Log.d(TAG, "Password: $password")

        //サインアップ
        FirebaseAuth.getInstance().createUserWithEmailAndPassword(email, password)
            .addOnCompleteListener {
                if (!it.isSuccessful) return@addOnCompleteListener

                //else if Successful
                Log.d(TAG, "Successfully created user with uid: ${it.result?.user?.uid}")
                uploadImageToFireBaseStorage()
            }
            .addOnFailureListener {
                Log.d(TAG, "Failed to create User: ${it.message}")
                Toast.makeText(this, "Failed to create User: ${it.message}", Toast.LENGTH_SHORT).show()
            }
    }

    //ストレージへのプロフィール写真の保存、ユーザー情報をDBへ保存
    //images配下に採番されたファイルIDで登録を行う
    private fun uploadImageToFireBaseStorage() {
        if (selectedPhotoUri == null) return

        val filename = UUID.randomUUID().toString()
        val ref = FirebaseStorage.getInstance().getReference("/images/$filename")

        ref.putFile(selectedPhotoUri!!)
            .addOnSuccessListener {
                Log.d(TAG, "Successfuly uploaded image:${it.metadata?.path}")

                ref.downloadUrl.addOnSuccessListener {
                    Log.d(TAG, "File Location :$it")

                    //画像の保存が完了したタイミングでDBへの登録も行う
                    saveUserToFirebaseDatabase(it.toString())
                }
            }
            .addOnFailureListener {
                //しくじった時、考えないようにする
            }
    }

    //Realtime Databaseにデータを格納
    //users配下にユーザーID単位で登録する
    private fun saveUserToFirebaseDatabase(profileImageUri: String) {
        val uid = FirebaseAuth.getInstance().uid ?: ""
        val ref = FirebaseDatabase.getInstance().getReference("/users/$uid")
        val user = User(uid, username_edittext_register.text.toString(), profileImageUri)

        //Intent.FLAG_ACTIVITY_CLEAR_TASK:Activityのスタックを削除して起動する
        //Intent.FLAG_ACTIVITY_NEW_TASK:タスクがスタックに存在しても新しいタスクとして起動

        ref.setValue(user)
            .addOnSuccessListener {
                Log.d(TAG, "Finally we saved the user to Firebase Database")
                val intent = Intent(this, LatestMessagesActivity::class.java)
                intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK.or(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
            }
            .addOnFailureListener {
                Log.d(TAG, "Failed to set value to Database")
            }
    }
}


```

#4. 以降
少しずつ続編を投稿する予定です。
一旦このアプリを完成させた後、これをベースに色々作っていこうかと妄想中です。