android.preference ライブラリのサポート終了対応

#android.preference ライブラリのサポートが終了
Android は Preference ライブラリ（フレームワーク）を使用することで設定画面を簡単に実装することができていた。 
公式サイトのAndroid 10 動作変更点の項目に「android.preference ライブラリのサポート終了」の記載がある。
[公式サイト：Android10 動作の変更点: すべてのアプリ](https://developer.android.com/about/versions/10/behavior-changes-all#preferences)

この為、今後の為に既存で Preference ライブラリ（フレームワーク）を使用していた場合は対応した方が良いと考えられる。

#API も deprecated（非推奨）に 

サポート終了に伴い公式サイトの PreferenceActivity 等でも deprecated（非推奨）の記載がヘッターに出てきた。
[公式サイト：PreferenceActivity](https://developer.android.com/reference/android/preference/PreferenceActivity)
[公式サイト：PreferenceCategory](https://developer.android.com/reference/android/preference/PreferenceCategory)

![スクリーンショット 2020-07-12 14.30.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264726/c6e3c1d8-e3c4-04a2-874b-c9b4357e9378.png)

#これまでの Preference ライブラリ（フレームワーク）を使用した実装
PreferenceActivity と PreferenceFragment を組み合わせて作成しているのが一般的。

targetSDK は 29 とする

```kotlin:MyPreferenceActivity.kt
import android.R
import android.os.Bundle
import android.preference.PreferenceActivity

class MyPreferenceActivity : PreferenceActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        fragmentManager.beginTransaction()
            .replace(R.id.content, MyPreferenceFragment()).commit()
    }
}
```

```kotlin:MyPreferenceFragment.kt
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle
import android.preference.PreferenceFragment

class MyPreferenceFragment : PreferenceFragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        addPreferencesFromResource(R.xml.preferences)
    }

    override fun onResume() {
        super.onResume()
        preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
    }

    override fun onPause() {
        super.onPause()
        preferenceScreen.sharedPreferences
            .unregisterOnSharedPreferenceChangeListener(listener)
    }

    private val listener =
        OnSharedPreferenceChangeListener { sharedPreferences, key ->
            if (key == "KEY") {
                // setSummary等行う
            }
        }

    companion object {
        private const val PREF_KEY = "KEY"
    }
}
```

読み込むxmlファイルは以下とする

```xml:preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- シークバーPreference -->
    <SeekBarPreference
        android:id="@+id/seekbar"
        android:key="seek_bar"
        android:summary=""
        android:title="SeekBarテキスト" />

    <!-- チェックボックスPreference -->
    <CheckBoxPreference
        android:defaultValue="@string/default_text"
        android:key="check_box"
        android:summary="@string/default_text"
        android:title="チェックボックステキスト" />

    <!-- テキスト入力を行うPreference -->
    <EditTextPreference
        android:defaultValue="@string/default_text"
        android:key="text"
        android:summary="@string/default_text"
        android:title="表示するテキスト" />

    <!-- リストから選択を行うPreference -->
    <ListPreference
        android:defaultValue="white"
        android:key="screen_color"
        android:title="画面の色" />

    <!-- スイッチでON/OFF切り替えを行うPreference -->
    <SwitchPreference
        android:defaultValue="false"
        android:key="enable_other_settings"
        android:title="その他の設定を有効にする" />

    <!-- 複数のPreferenceをカテゴライズしたグループ -->
    <PreferenceCategory
        android:enabled="false"
        android:key="other_settings"
        android:title="その他の設定">

        <!-- 複数選択可能なListPreference -->
        <MultiSelectListPreference
            android:key="display_images"
            android:title="画像を表示する" />

    </PreferenceCategory>

</PreferenceScreen>
```

マニフェストファイルとテーマ設定は以下とした

```xml:AndroidManifest.xml
  <activity
    android:name=".MyPreferenceActivity"
    android:theme="@style/MyPreferenceTheme" />
```

```xml:styles.xml
<resources>
    <style name="MyPreferenceTheme" parent="android:Theme.Holo.Light">
        <item name="android:textColor">@color/colorPrimary</item>
    </style>
</resources>
```

この実装を端末で表示すると↓のようになった。
![スクリーンショット 2020-07-12 15.24.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264726/cf36fb8c-e6b5-94e7-a886-0a8118515bb2.png)

#androidx.preference への対応方法

Jetpack の androidx.preference に置き換えることで対応できる。
[公式サイト：androidx.preference](https://developer.android.com/jetpack/androidx)

gradle に以下を記載する。

```gradle:app/build.gradle
dependencies {
    // 最新のバージョンを使ってください
    // java
    implementation 'androidx.preference:preference:1.1.1'
    // Kotlin
    implementation 'androidx.preference:preference-ktx:1.1.1'
}
```

公式サイトを参考に Activity と Fragment を修正する。
[公式サイト：JetPack Settings](https://developer.android.com/guide/topics/ui/settings)

```kotlin:MyPreferenceActivity.kt
import android.R
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

// androidx.appcompat に変更
class MyPreferenceActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // fragmentManager → supportFragmentManager に変更
        supportFragmentManager.beginTransaction()
            .replace(R.id.content, MyPreferenceFragment()).commit()
    }
}
```

```kotlin:MyPreferenceFragment.kt
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat

// android.preference → androidx.preference に変更
class MyPreferenceFragment : PreferenceFragmentCompat() {
    // interface が変わる
    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
        addPreferencesFromResource(R.xml.preferences)
    }

    override fun onResume() {
        super.onResume()
        preferenceScreen.sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
    }

    override fun onPause() {
        super.onPause()
        preferenceScreen.sharedPreferences
            .unregisterOnSharedPreferenceChangeListener(listener)
    }

    private val listener =
        OnSharedPreferenceChangeListener { sharedPreferences, key ->
            if (key == "KEY") {
                // setSummary等行う
            }
        }

    companion object {
        private const val PREF_KEY = "KEY"
    }
}
```

この実装を実行すると以下の Exception でクラッシュする。
java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
スタイルのテーマ設定を AppCompat に変更する必要がある。

```xml:styles.xml
<!-- ThemeをAppCompatに変更 -->
<resources>
    <style name="MyPreferenceTheme" parent="Theme.AppCompat.Light">
        <item name="android:textColor">@color/colorPrimary</item>
    </style>
</resources>
```

この実装を端末で表示すると↓のようになった。
![スクリーンショット 2020-07-12 16.41.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264726/acc1cb65-f682-93c3-45d5-19049d410e33.png)

文字が小さくなって、テキストの左にマージンが出現したが Jetpack には対応できた。
対応前後の表示の差異はマテリアルデザインの影響かと思われる。
左のマージンはアイコンを表示する部分で、アイコンの表示が必要なくてもデフォルトで表示されるようになる為、そのアイコン表示エリアが表示されていると考えられる。
このアイコンエリアは iconSpaceReserved の設定値を false にすることで無くすことができる。
さらに preferences.xml の PreferenceScreen も実は deprecated（非推奨）なので修正する必要がある。android: の定義を app: に変えないと上手く動いてくれないのでこちらも変更する必要がある。

```xml:preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- androidx.preference に変更 -->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- シークバーPreference -->
    <SeekBarPreference
        android:id="@+id/seekbar"
        app:iconSpaceReserved="false"
        app:key="seek_bar"
        app:summary=""
        app:title="SeekBarテキスト" />

    <!-- チェックボックスPreference -->
    <CheckBoxPreference
        app:defaultValue="@string/default_text"
        app:iconSpaceReserved="false"
        app:key="check_box"
        app:summary="@string/default_text"
        app:title="チェックボックステキスト" />

    <!-- テキスト入力を行うPreference -->
    <EditTextPreference
        app:defaultValue="@string/default_text"
        app:iconSpaceReserved="false"
        app:key="text"
        app:summary="@string/default_text"
        app:title="表示するテキスト" />

    <!-- リストから選択を行うPreference -->
    <ListPreference
        app:defaultValue="white"
        app:iconSpaceReserved="false"
        app:key="screen_color"
        app:title="画面の色" />

    <!-- スイッチでON/OFF切り替えを行うPreference -->
    <SwitchPreference
        app:defaultValue="false"
        app:iconSpaceReserved="false"
        app:key="enable_other_settings"
        app:title="その他の設定を有効にする" />

    <!-- 複数のPreferenceをカテゴライズしたグループ -->
    <PreferenceCategory
        app:enabled="false"
        app:iconSpaceReserved="false"
        app:key="other_settings"
        app:title="その他の設定">

        <!-- 複数選択可能なListPreference -->
        <MultiSelectListPreference
            app:iconSpaceReserved="false"
            app:key="display_images"
            app:title="画像を表示する" />

    </PreferenceCategory>

</androidx.preference.PreferenceScreen>
```
これで左の隙間が消える。
![スクリーンショット 2020-07-12 17.07.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264726/79a88faf-7244-ed39-f459-c795a797de73.png)

文字サイズとデザインの変更はマテリアルデザインの影響なのでこのままで良いと思う。
[参考サイト：Android マテリアルデザイン](https://learnui.design/blog/android-material-design-font-size-guidelines.html)

ちなみにアイコンも表示可能

```xml:styles.xml
<resources>
    <style name="MyPreferenceTheme" parent="Theme.AppCompat.Light">
        <!-- ベースとなるテキストカラー -->
        <item name="android:textColor">@color/colorPrimary</item>
        <!-- ナビゲーションのアイコン ※別途リソースサイズ調整必要 -->
        <item name="navigationIcon">@mipmap/ic_launcher_round</item>
    </style>
</resources>
```

![スクリーンショット 2020-07-19 13.49.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264726/7c85549d-3cb4-f076-3c98-8ae394112fe7.png)

#カスタマイズ実装例
ここからさらに画面をカスタマイズすることも可能

```xml:styles.xml
<style name="MyPreferenceTheme" parent="Theme.AppCompat.Light">
        <!-- ベースとなるテキストカラー -->
        <item name="android:textColor">@color/colorPrimary</item>
        <!-- ベースとなる背景色 -->
        <item name="android:windowBackground">#ffdd01</item>
        <!-- 各設定の背景色 -->
        <item name="android:selectableItemBackground">@drawable/background_red</item>
        <!-- ナビケーションバックグラウンドカラー -->
        <item name="colorPrimary">#ef8201</item>
        <!-- アクションバーバックグラウンドカラー -->
        <item name="android:actionBarItemBackground">@drawable/background_red</item>
        <!-- ナビゲーションのアイコン ※別途リソースサイズ調整必要 -->
        <item name="navigationIcon">@mipmap/ic_launcher_round</item>
        <!-- チェックボックス未チェック時の色 -->
        <item name="colorControlNormal">#b52872</item>
        <!-- チェックボックスチェック時の色 -->
        <item name="colorSwitchThumbNormal">#444444</item>
        <!-- リスト背景色 -->
        <item name="colorBackgroundFloating">#ffffff</item>
        <!-- リストダイアログの文字色 -->
        <item name="textColorAlertDialogListItem">#121212</item>
        <!-- ダイアログテーマ -->
        <item name="alertDialogTheme">@style/AlertDialogTheme</item>
        <!-- リップル効果色 -->
        <item name="colorControlHighlight">@color/colorPrimary</item>
        <item name="colorControlActivated">@color/colorPrimary</item>
        <!-- popup menuの(チェックボックスを指定してない場合はチェックボックスが含まれる)色 -->
        <item name="colorAccent">#000000</item>
        <!-- Title -->
        <item name="android:textColorPrimary">#940017</item>
        <!-- Summary -->
        <item name="android:textColorSecondary">#940017</item>
        <!-- アプリ外の最上部の色 -->
        <item name="colorPrimaryDark">#059b6d</item>
    </style>

    <style name="AlertDialogTheme" parent="ThemeOverlay.AppCompat.Dialog.Alert">
        <!-- ダイアログ選択肢の文字色 -->
        <item name="android:textColor">#555555</item>
        <!-- ダイアログ選択肢の文字サイズ -->
        <item name="android:textSize">15sp</item>
        <!-- ダイアログタイトルのテーマ -->
        <item name="android:windowTitleStyle">@style/AlertDialogTitleTheme</item>
    </style>

    <style name="AlertDialogTitleTheme" parent="RtlOverlay.DialogWindowTitle.AppCompat">
        <!-- ダイアログタイトルの文字色 -->
        <item name="android:textColor">#876543</item>
        <!-- ダイアログタイトルの文字サイズ -->
        <item name="android:textSize">20sp</item>
    </style>
```

```xml:background_red.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 赤っぽい色を指定 -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#cb270d" />
</shape>
```

![スクリーンショット 2020-07-19 14.35.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264726/2cd0a08c-c8ee-6d9e-ef64-70e58d9c480f.png)

#最後に
今回のサポート終了は、おそらくダークテーマの対応できるようにする意図ではなかろうかと思っている。
Android 11 のリリースが近い。アプリを最新の状態にアップデートしておきたい。