第 1 章 駄目コードとその対策 6
1.9 Preference への書き込みに個々で edit メソッドを呼んでいる
設定値や小さな値を保存するのに
SharedPreference
を使います。SharedPreference
はString
型以外のデータもKey-Value
で値を保持できるのでちょっとした値を保存するのに便利です。しかし便利ゆえにいくつかの弱点があります。
1. String
型でキーを指定しなければならず、しばしばキーを間違えてバグの原因になる2.
誤って違うデータ型の値を入れると、取り出し時にClassCastException
がスローさ れるこれらの弱点は非常に厄介です。どのように厄介かというと、間違えてもコンパイルエラー にならず、実行時の例外やおかしな挙動を引き起こすからです。
リスト
1.26
を例に見てみましょう。このコードではボタンが2個あり、押されるとそのボ タン名をSharedPreferences
に保存します。そしてActivity#onCreate
の中でeditText1
に そのボタン名を設定する処理になっています。動作には問題はありませんが前述の問題があ り、開発を進めていく上での不安となる点が残っています。リスト1.26: 悪い例 SharedPreferencesDameActivity.java
public class SharedPreferencesDameActivity extends Activity implements View.OnClickListener {
private SharedPreferences mPref;
private EditText editText1;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_many_handlers);
findViewById(R.id.button1).setOnClickListener(this);
findViewById(R.id.button2).setOnClickListener(this);
editText1 = (EditText) findViewById(R.id.editText1);
mPref = PreferenceManager.getDefaultSharedPreferences(this);
String lastButton = mPref.getString("lastButton", "");
editText1.setText(lastButton);
}
@Override
public void onClick(View v) { if (v.getId() == R.id.button1) {
mPref.edit()
.putString("lastButton", "button1") .apply();
} else if (v.getId() == R.id.button2) { mPref.edit()
.putString("lastButton", "button2") .apply();
} } }
SharedPreferences
に保存する際のキーは"lastButton"
となっていますので、これを定数 化してみましょう。定数化したものはリスト1.27
となります。リスト1.27: 良い例 SharedPreferencesRefine1Activity.java
public class SharedPreferencesRefine1Activity extends Activity implements View.OnClickListener {
private SharedPreferences mPref;
private EditText editText1;
private String KEY_LAST_BUTTON = "lastButton";
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_many_handlers);
findViewById(R.id.button1).setOnClickListener(this);
findViewById(R.id.button2).setOnClickListener(this);
editText1 = (EditText) findViewById(R.id.editText1);
mPref = PreferenceManager.getDefaultSharedPreferences(this);
String lastButton = mPref.getString(KEY_LAST_BUTTON, "");
editText1.setText(lastButton);
}
@Override
public void onClick(View v) { if (v.getId() == R.id.button1) {
mPref.edit()
.putString(KEY_LAST_BUTTON, "button1") .apply();
} else if (v.getId() == R.id.button2) { mPref.edit()
.putString(KEY_LAST_BUTTON, "button2") .apply();
} }
}
見やすくなったでしょうか。そして前述の問題点は解決できているでしょうか。キー の
"lastButton"
が定数化されていますが、実は解決できていません。なぜならキーが複数ある 場合は、別のキーと取り違える可能性がまだ残っているからです。こ の よ う な 場 合 、こ れ ら の 問 題 を 回 避 す る に は リ ス ト
1.28
の よ う なSharedPref-erences
の ラ ッ パ ー ク ラ ス を 作 る と 回 避 で き ま す 。こ の ラ ッ パ ー ク ラ ス で は キ ー をKEY_LAST_BUTTON
の定数にしていますがprivate
になっているので、メソッドの呼び元はキーを意識しなくてよくなっており、キーを取り違える心配もありません。またアクセ スする処理を
getLastButton
メソッドとputLastButton
メソッドにまとめているので、誤っ て別のデータ型を入れることはありません。すなわちラッパークラスに前述の問題点を閉じ 込めているのです。リスト1.28: MySharedPreferences.java
public class MySharedPreferences {
private static final String KEY_LAST_BUTTON = "lastButton";
private SharedPreferences mPref;
private SharedPreferences.Editor mEditor;
public MySharedPreferences(SharedPreferences mPref) { this.mPref = mPref;
}
public MySharedPreferences edit() { mEditor = mPref.edit();
return this;
}
public void apply() { mEditor.apply();
mEditor = null;
}
public String getLastButton() {
return mPref.getString(KEY_LAST_BUTTON, "");
}
public MySharedPreferences putLastButton(String value) { mEditor.putString(KEY_LAST_BUTTON, value);
return this;
} }
このラッパークラスを扱うように直したものがリスト
1.29
となります。MySharedPrefer-ences
を使うようにしたことで、前述の問題がいずれも解決していることがわかります。リスト1.29: 良い例 SharedPreferencesRefine2Activity.java
public class SharedPreferencesRefine2Activity extends Activity implements View.OnClickListener {
private MySharedPreferences mPref;
private EditText editText1;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_many_handlers);
findViewById(R.id.button1).setOnClickListener(this);
findViewById(R.id.button2).setOnClickListener(this);
editText1 = (EditText) findViewById(R.id.editText1);
mPref = new MySharedPreferences(
PreferenceManager.getDefaultSharedPreferences(this));
String lastButton = mPref.getLastButton();
editText1.setText(lastButton);
}
@Override
public void onClick(View v) { if (v.getId() == R.id.button1) {
mPref.edit()
.putLastButton("button1") .apply();
} else if (v.getId() == R.id.button2) { mPref.edit()
.putLastButton("button2") .apply();
} } }
動けばいいという意味ではラッパークラス作らず、そのまま書いてしまっても違いはありま せん。しかしコード後から見たときや修正するときに、キーやデータ型のように手で合わせな いといけないものがそのままになっていると、誤って記述した際に不具合の原因となってしま います。特にこれらのキーやデータ型は誤って記述してしまうとコンパイルエラーにもなら ないため非常に厄介です。これら避けるため、ラッパークラスを作成して問題点の責務をラッ パークラスに持たせ、扱う側が無駄に悩まなくていいようにしましょう。ラッパークラスは単 にラッピングしているのではなく、そういった責務も封じ込めているのです。