【Android/Kotlin】SharedPreferencesで 保存、呼び出し、削除 ができるようになる DataBinding も使ってみる

# SharedPreferences とは
Androidアプリで簡単な永続データを保存するための仕組み。

Androidアプリは通常、起動するたびに初期化されるため、データを保存するには、起動中に取得したデータを保存する必要がある。
そこでSharedPreferencesの仕組みを使えば、アプリケーションが再起動されたときにも保存されたデータにアクセスができる。

SharedPreferencesは、名前と値のペアのセットを格納する。
名前は文字列で、値には整数、ブール値、浮動小数点数、文字列などのデータ型が使用できる。

## SharedPreferencesの使用手順
- SharedPreferencesオブジェクトをインスタンス化する
- SharedPreferences.Editorオブジェクトを使用して、値を保存する
- SharedPreferencesオブジェクトを使用して、保存された値を読み取る

## 実際にSharedPreferencesを使ってみる
#### 手順
- build.gradleに依存関係を追加
- xmlでレイアウト作成
- ViewModel追加
- ActivityでVIewModelを呼び出せるようにする
- ActivityでDataBindingの定義
- ViewModelでクリックイベントを検知する
- SharedPreferencesでデータの保存、呼び出し、削除の処理を追加する

### build.gradleに依存関係を追加
```gradle
android {
	dataBinding{
		enabled = true // 追加
	}
}


dependencies {
		androidTestImplementation 'androidx.test.ext:junit:1.1.5' // 追加
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // 追加
}
```


### xmlでレイアウト作成
```html
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
    <data>
        <--これから作成するViewModelを指定する->
        <variable
            name="vm"
            type="com.example.study.MainViewModel" />
    </data>
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/save_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="保存"
        android:onClick="@{() -> vm.saveBtn()}"
        app:layout_constraintBottom_toTopOf="@+id/read_btn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/read_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="呼び出し"
        android:onClick="@{() -> vm.readBtn()}"
        app:layout_constraintBottom_toTopOf="@+id/delete_btn"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/save_btn" />

    <Button
        android:id="@+id/delete_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="削除"
        android:onClick="@{() -> vm.deleteBtn()}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/read_btn" />

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

**こんな感じの画面ができる**
![pref.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2917260/24c0ad79-4d76-0b04-b795-f3cb8a5632c5.png)




### ViewModel追加 ActivityでVIewModelを呼び出せるようにする
```kotlin
class MainViewModel(application: Application): AndroidViewModel(application) {

    class Factory(private val application: Application) : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            when (modelClass) {
                MainViewModel::class.java-> return MainViewModel(application) as T
                else -> throw RuntimeException()
            }
        }
    }
}

```

### ActivityでDataBindingの定義
```kotlin
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val mainViewModel: MainViewModel by lazy {
        ViewModelProvider(this, MainViewModel.Factory(application))[MainViewModel::class.java]
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater).also {
            it.lifecycleOwner = this
            it.vm = mainViewModel
        }
        setContentView(binding.root)
    }
}
```

### ViewModelでクリックイベントを検知する
```kotlin
class MainViewModel(application: Application): AndroidViewModel(application) {

    class Factory(private val application: Application) : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            when (modelClass) {
                MainViewModel::class.java-> return MainViewModel(application) as T
                else -> throw RuntimeException()
            }
        }
    }

   // 以下を追加
    private var _isSaveBtnClicked = MutableLiveData(false)
    val isSaveBtnClicked: LiveData<Boolean> get() = _isSaveBtnClicked

    private var _isReadBtnClicked = MutableLiveData(false)
    val isReadBtnClicked: LiveData<Boolean> get() = _isReadBtnClicked

    private var _isDeleteBtnClicked = MutableLiveData(false)
    val isDeleteBtnClicked: LiveData<Boolean> get() = _isDeleteBtnClicked

    fun saveBtn() {
        Log.d("###ボタンクリック", "保存")
        _isSaveBtnClicked.value= true
    }

    fun readBtn() {
        Log.d("###ボタンクリック", "呼び出し")
        _isReadBtnClicked.value= true
    }

    fun deleteBtn() {
        Log.d("###ボタンクリック", "削除")
        _isDeleteBtnClicked.value= true
    }

}
```

### SharedPreferencesでデータの保存、呼び出し、削除の処理を追加する
```kotlin
class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val mainViewModel: MainViewModel by lazy {
        ViewModelProvider(this, MainViewModel.Factory(application))[MainViewModel::class.java]
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater).also {
            it.lifecycleOwner = this
            it.vm = mainViewModel
        }
        setContentView(binding.root)

        mainViewModel.isSaveBtnClicked.observe(this){
            // 保存ボタンがクリックされた場合
            if(it){
                saveSharedPref()
            }
        }

        mainViewModel.isReadBtnClicked.observe(this){
            // 呼び出しボタンがクリックされた場合
            if(it){
                readSharedPref()
            }
        }

        mainViewModel.isDeleteBtnClicked.observe(this){
            // 削除ボタンがクリックされた場合
            if(it){
                deleteSharedPref()
            }
        }
    }

    private fun saveSharedPref() {
        val sharedPref = setSharedPref()

        val editor = sharedPref.edit()
        editor.putString("DataString", "文字列")
        editor.putInt("DataInt", 123)
        editor.putBoolean("DataBoolean", true)

        editor.apply() // 状態を更新
    }

    private fun readSharedPref() {
        val sharedPref = setSharedPref()

        // 読み出し
        val dataString = sharedPref.getString("DataString", null)
        Log.d("###DataString", dataString.toString())

        val dataInt = sharedPref.getInt("DataInt", 0)
        Log.d("###DataInt", dataInt.toString())

        val dataBoolean = sharedPref.getBoolean("DataBoolean", false)
        Log.d("###DataInt", dataBoolean.toString())
    }

    private fun deleteSharedPref() {
        val sharedPref = setSharedPref()

        val editor = sharedPref.edit()
        editor.remove("DataString")
        editor.remove("DataInt")
        editor.remove("DataBoolean")

        editor.apply() // 状態を更新
    }

    private fun setSharedPref(): SharedPreferences {
        return getSharedPreferences(
            getString(R.string.preference_file_key), Context.MODE_PRIVATE
        )
    }

}

```
## ログで動作確認
- **呼び出しボタンクリック**
デフォルト値が入っている
```
D/###ボタンクリック: 呼び出し
D/###DataString: null
D/###DataInt: 0
D/###DataInt: false
```

- **保存ボタンクリック + 呼び出しボタンクリック**
`saveSharedPref` メソッドで追加した値が入っている
```
D/###ボタンクリック: 保存
D/###ボタンクリック: 呼び出し
D/###DataString: 文字列
D/###DataInt: 123
D/###DataInt: true
```

- **削除ボタンクリック+呼び出しボタンクリック**
データが削除されてデフォルト値が入っている
```
D/###ボタンクリック: 削除
D/###ボタンクリック: 呼び出し
D/###DataString: null
D/###DataInt: 0
D/###DataInt: false
```


## おわりに
ログイン情報を保持するのに使いました。おさらいで記事にしてみました。
がんばるぞい！