【Android】便利なPreferenceFragmentCompatで設定画面を作る

## Jetpack Preference

[公式ページ](https://developer.android.com/guide/topics/ui/settings/)

JetpackのPreferenceを使う事で、設定画面を簡単に作成する事ができる。

###  :computer:環境構築
****

`app/build.gradle` に以下を追加

```gradle
dependencies {
    implementation 'androidx.preference:preference:1.0.0'
}
```

### :pencil: 実装
****

#### 簡単なサンプル

1. まずは設定の項目を定義した、`res/xml/preferences.xml` を以下の内容で作成する

    ```xml
    <androidx.preference.PreferenceScreen
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <SwitchPreferenceCompat
            app:key="notifications"
            app:title="Enable message notifications"/>
    
        <Preference
            app:key="feedback"
            app:title="Send feedback"
            app:summary="Report technical issues or suggest new features"/>
    
    </androidx.preference.PreferenceScreen>
    ```
    ※ xmlのルートが `PreferenceScreen` になっている事に注意

2. 次に **[PreferenceFragmentCompat](https://developer.android.com/reference/android/support/v7/preference/PreferenceFragmentCompat)** を継承したFragmentを作成

    ```kotlin:SettingsFragment.kt
    class SettingsFragment : PreferenceFragmentCompat() {
    
        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            setPreferencesFromResource(R.xml.preferences, rootKey)
        }
    }
    ```
    `setPreferencesFromResource` で定義した `res/xml/preferences.xml` を設定

3. 土台となるActivityで作成した `SettingsFragment` を表示する

    ```kotlin:SettingsActivity.kt
    class SettingsActivity : RxAppCompatActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_settings)
    
            supportFragmentManager
                .beginTransaction()
                .replace(R.id.settingsContainer, SettingsFragment())
                .commit()
        }
    }
    ```

    レイアウトファイルにはFragmentが表示される `FrameLayout` を定義

    ```xml:res/layout/activity_settings.xml
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    xmlns:app="http://schemas.android.com/apk/res-auto"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">
    
        <androidx.appcompat.widget.Toolbar
                android:id="@+id/settingsToolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:minHeight="?attr/actionBarSize"
                android:background="?attr/colorPrimaryDark"
                app:titleTextColor="@android:color/white"/>
    
        <FrameLayout
                android:id="@+id/settingsContainer"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_below="@id/settingsToolbar"/>
    
    </RelativeLayout>
    ```

上記まで実装したら、一度実行してみる。
![image.png (40.2 kB)](https://img.esa.io/uploads/production/attachments/5529/2018/12/31/18452/93265d82-4812-4b7d-a523-d0aaa4b7313b.png)

`res/xml/preferences.xml` で定義した内容が表示されていればOK

#### SharedPreferenceファイル名
作成されるSharedPreferenceファイル名は

```kotlin
"${BuildConfig.APPLICATION_ID}_preferences"
```
となる。

#### Preferenceの種類

一覧は[こちら](https://developer.android.com/guide/topics/ui/settings/components-and-attributes#individual_preferences)

- Preference
    - 基本的な構成要素で対応するキーと値のペアを持つ
- EditTextPreference
    - String値を保持するPreference、入力欄がテキストフィールドで表示される
- ListPreference
    - String値を保持するPreference、入力欄がラジオボタンのリストで表示される
- MultiSelectListPreference
    - 一連の文字列を永続化する設定、入力欄がラベル付きチェックボックスのリストで表示される
- SeekBarPreference
    - 整数値を保持する設定、入力欄がシークバーで表示される
- SwitchPreferenceCompat
    - ブール値を保持する設定、入力欄がスイッチで表示される
- CheckBoxPreference
    -  ブール値を保持する設定、入力欄がチェックボックスで表示される


###  :raised_hands: SharedPreferencesに保存された時に何かしたい場合 
****

[SharedPreferences.OnSharedPreferenceChangeListener](https://developer.android.com/reference/android/content/SharedPreferences.OnSharedPreferenceChangeListener)を使う

`SharedPreferences#registerOnSharedPreferenceChangeListener` でlistenerを登録。
※ 上記を無名関数でやっちゃうと上手く動かないので注意 [こちら参照](https://stackoverflow.com/questions/2542938/sharedpreferences-onsharedpreferencechangelistener-not-being-called-consistently)

PreferenceFragmentCompatが終了するタイミングで登録解除する
`SharedPreferences#unregisterOnSharedPreferenceChangeListener`

```kotlin
    var sharedPreferences: SharedPreferences? = null

    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        setPreferencesFromResource(R.xml.preferences, rootKey)

        context?.also {
            // SharedPreferencesを取得しOnSharedPreferenceChangeListenerを設定
            sharedPreferences = it.getSharedPreferences("xxxxxxxxx", Context.MODE_PRIVATE).apply {
                registerOnSharedPreferenceChangeListener(changeListener)
            }
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        context?.also {
            // OnSharedPreferenceChangeListenerの登録解除
     sharedPreferences?.unregisterOnSharedPreferenceChangeListener(changeListener)
        }
    }

    private val changeListener = SharedPreferences.OnSharedPreferenceChangeListener { pref, key ->
        // do something
    }
```

### :link: 関連リンク
****

- [[Android] PreferenceFragmentCompatで設定画面を作る](https://mnoqlo.hatenablog.com/entry/2018/10/12/214802)