AndroidのListPreferenceの内容をenumで設定できるようにしてみた

# はじめに

Androidの設定画面でListPreferenceを使ったアプリを作ってみた時に、コードが見にくいなぁと感じたので列挙型（enum）を使って比較的簡単に項目を指定できるような関数を作ってみました。

# 普通にListPreferenceを使った場合

たとえば、更新間隔を設定するListPreferenceを作成する場合、`res/values/arrays.xml`に`string-array`を使って

```xml:res/values/arrays.xml
<?xml version="1.0" encoding="utf-8"?>
 <resources>
 	<string-array name="update_time">
 		<item>@string/update_time_15</item>
 		<item>@string/update_time_30</item>
 		<item>@string/update_time_45</item>
 		<item>@string/update_time_60</item>
 		<item>@string/update_time_120</item>
 		<item>@string/update_time_180</item>
 		<item>@string/update_time_360</item>
 	</string-array>
 	<string-array name="update_time_value">
 		<item>15m</item>
 		<item>30m</item>
 		<item>45m</item>
 		<item>1h</item>
 		<item>2h</item>
 		<item>3h</item>
 		<item>6h</item>
 	</string-array>
 </resources>    
```
のように、表示される項目（`update_time`）とその値（`update_time_value`）を作成して、設定画面のxmlに

```xml:res/xml/preference.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="Yahoo Weather API">
        <ListPreference
        			android:key="update_time"
        			android:title="@string/preference_update_time_title"
        			android:entries="@array/update_time"
        			android:entryValues="@array/update_time_value"/>
    </PreferenceCategory>
</PreferenceScreen>
```

このように、先ほど作成した`string-array`のnameを`android:entries`と`android:entryValues`に指定してあげる必要があります。

そして実際に設定された後は、設定に応じて処理を行うために

```java
String value = sharedPreferences.getString( "update_Time", "30m" );
if( value.equals( "15m" ) )
{
    // 15mの時の動作
}
else if( value.equals( "30m" ) )
{
    // 30mの時の動作
}
...あと続く...
```

というようなコードを書く必要があります。

私が思っているだけかもしれませんが、`android:entryValues`に`integer-array`が使えなかったり、項目が変わると複数の場所を変えないといけなくなったり、項目の数だけif文書かないといけなかったりと、ちょっとややこしいです。

# てことで作ってみた

## 列挙型(enum)の作成

さっきと同じ更新間隔を設定するListPreferenceの内容だったとして、まず次のようなenumのクラスを作成します。

```java:UpdateTime.java
public enum UpdateTime
{
    MIN_15( R.string.update_time_15, "15m", TimeUnit.MINUTES.toSeconds( 15 ) ),
    MIN_30( R.string.update_time_30, "30m", TimeUnit.MINUTES.toSeconds( 30 ) ),
    MIN_45( R.string.update_time_45, "45m", TimeUnit.MINUTES.toSeconds( 45 ) ),
    HOUR_1( R.string.update_time_60, "1h", TimeUnit.HOURS.toSeconds( 1 ) ),
    HOUR_2( R.string.update_time_120, "2h", TimeUnit.HOURS.toSeconds( 2 ) ),
    HOUR_3( R.string.update_time_180, "3h", TimeUnit.HOURS.toSeconds( 3 ) ),
    HOUR_6( R.string.update_time_360, "6h", TimeUnit.HOURS.toSeconds( 6 ) );

    private final int _resourceId;
    private final String _entryValue;
    private final long _seconds;

    UpdateTime( final int resourceId, final String entryValue, final long seconds )
    {
        _resourceId = resourceId;
        _entryValue = entryValue;
        _seconds = seconds;
    }

    public int getResourceId()
    {
        return _resourceId;
    }

    public String getEntryValue()
    {
        return _entryValue;
    }

    public long getSeconds()
    {
        return _seconds;
    }

    @Nullable
    public static UpdateTime getByEntryValue( final String string )
    {
        for( UpdateTime updateTime : values() )
        {
            if( updateTime.getEntryValue().equals( string ) ) return updateTime;
        }
        return null;
    }
}
```

上のenumにはそれぞれの列挙子に順に
* ListPreferenceに表示したい項目のStringリソースのID
* ListPreferenceの項目に対応する値
* 項目に対応した秒（オプション）

の値を格納できるようにコンストラクターを作成して、getterで取得できるようにしています。
ここで必要なのは、項目のStringリソースのIDを得る`getResourceId`関数と、その項目に対応する値を得る`getEntryValue`関数です。
また、`entityValue`に対応する`UpdateTime`の列挙子を得られるように`getByEntryValue`関数も定義しています。

## 設定画面のxmlの作成
次に簡単な設定画面を作成します。

```xml:xml/preference.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="Yahoo Weather API">
        <ListPreference
        			android:key="update_time"
        			android:title="@string/preference_update_time_title"/>
    </PreferenceCategory>
</PreferenceScreen>
```

ここでは、`android:key`と`android:title`だけを定義して`android:entries`と`android:entryValues`は定義しません。

## 設定画面の作成を行うクラスへ関数を追加
次に設定画面用のクラス（`PreferenceFragmentCompat`など）を継承したクラスの中に次の関数を追加します。

```java
    private void setListEntities( String key, Class<? extends Enum<?>> enumClass )
    {
        // enumを元に、ListPreferenceの設定を行う関数
        Preference preference = findPreference( key );
        if( !( preference instanceof ListPreference ) )
        {
            // 指定されたkeyのPreferenceがListPreferenceじゃ無いのでエラー
            return;
        }
        ListPreference listPreference = (ListPreference)preference;

        List<String> entityList = new ArrayList<>();
        List<String> entityValueList = new ArrayList<>();

        for( Enum<?> enumerate : enumClass.getEnumConstants() )
        {
            Class clazz = enumerate.getClass();

            try
            {
                Method getResourceIdMethod = clazz.getDeclaredMethod( "getResourceId" );
                Method getEntryValueMethod = clazz.getDeclaredMethod( "getEntryValue" );
                int resourceId = (int)getResourceIdMethod.invoke( enumerate );
                String entryValue = (String)getEntryValueMethod.invoke( enumerate );
                entityList.add( getResources().getString( resourceId ) );
                entityValueList.add( entryValue );
            }
            catch( Exception e )
            {
                // ここで必要な「getResourceId」と「getEntryValue」の関数がenumに定義されていないのでエラー
                return;
            }
        }

        String[] entities = entityList.toArray( new String[ entityList.size() ] );
        String[] entityValues = entityValueList.toArray( new String[ entityValueList.size() ] );

        listPreference.setEntries( entities );
        listPreference.setEntryValues( entityValues );
    }
```

リフレクションとか使わなくても、もっと簡単な方法とかありそうですが・・・こんな感じです。
例外処理とか手抜きですので、もし使っていただける場合は適宜変更してください。

# 使い方

## ListPreferenceの項目の設定

実際に、ListPreferenceへの項目の設定ですが、例えばPreferenceFragmentCompatを継承したクラスの中のonCreatePreferencesの中などの初期化を行う部分に

```java
setListEntities( "update_time", UpdateTime.class );
```

を追加する事で、"update_time"をキーに持つListPreferenceの項目と項目に対応する値がUpdateTimeクラスのenumの識別子に格納された値を元に設定されます。

## 設定完了後の処理

設定画面が終わった後の処理ですが、たとえば次のようなコードを書くと設定に対しての値を簡単に得る事ができます。

```java
UpdateTime updateTime = UpdateTime.getByEntryValue( preferences.getString( "update_time", UpdateTime.MIN_30.getEntryValue() ) );
// updateSecondに対応する設定の秒数が入る
int updateSecond = updateTime.getSeconds();
```

また、enumはswitch文で使うことができるので
次のように書くこともできます。

```java
UpdateTime updateTime = UpdateTime.getByEntryValue( preferences.getString( "update_time", UpdateTime.MIN_30.getEntryValue() ) );
switch( updateTime )
{
    case MIN_15:
        // １５分の時の処理
        break;
        
    case MIN_30:
        // 30分の時の処理
        break;
        
        ....
}
```

# 最後に

いかがでしょうか？私的には見やすくなって気に入っています。
項目の追加や削除があっても、enumのクラスを調整すれば対応できるし、処理についてもわざわざswitchで分岐させなくてもenumの列挙子に独自の関数を実装すれば対応できちゃったりするので、便利なんじゃないかなぁと思っています。
というか、javaのenumが便利すぎですね。