앱을 개발하다 보면 빈번하게 설정값을 만들고, 읽고, 변경하는 작업을 많이 하게 되는데, 설정 값을 쉽게 추가하고 자동으로 관리될 수 있도록 SettingsDao를 구현해 보았다.
SettigsDao은 boolean, String, int 등의 타입을 지원하고, 설정값을 메모리에 캐시하고, SparseArray를 이용하기 때문에 cache의 조회가 빠르다.
SQLite db를 오랫동안 사용해왔지만 Realm(램) db를 시범적으로 적용해 보았다. db에서 읽고, 쓰는 method만 교체하면 SQLite로 변경도 간단하게 될 것이다.
SettingRO.java : Setting을 저장하는 Realm 객체
package com.mdiwebma.base.settings;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
/**
* @author djkim
*/
public class SettingRO extends RealmObject {
@PrimaryKey
private String keyName;
private String value;
public String getKeyName() {
return keyName;
}
public void setKeyName(String keyName) {
this.keyName = keyName;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Settings.java : 세팅 설정값을 추가하는 Enum class
package com.mdiwebma.base.settings;
import android.support.annotation.NonNull;
import com.mdiwebma.base.helper.StringBuilderPool;
/**
* @author djkim
*/
public enum Settings {
test_bool("test_bool", Settings.BooleanType, Boolean.TRUE),
test_string("test_str", Settings.StringType, "test_str"),
test_int("test_int", Settings.IntegerType, (Integer)100);
public static final int BooleanType = 0;
public static final int StringType = 1;
public static final int IntegerType = 2;
public static final int LongType = 3;
public static final int FloatType = 4;
public static final int DoubleType = 5;
final public String keyName;
final public int settingType;
final public Object defaultValue;
public int cacheIndex; // do not modify manually, initialized from SettingsDao static scope
private Settings(@NonNull String keyName, int settingType, @NonNull Object defaultValue) {
this.keyName = keyName;
this.settingType = settingType;
this.defaultValue = defaultValue;
switch (this.settingType) {
case BooleanType:
if ((defaultValue instanceof Boolean) == false) {
throw new AssertionError(StringBuilderPool.concat("type mismatch=", keyName));
}
break;
case StringType:
if ((defaultValue instanceof String) == false) {
throw new AssertionError(StringBuilderPool.concat("type mismatch=", keyName));
}
break;
case IntegerType:
if ((defaultValue instanceof Integer) == false) {
throw new AssertionError(StringBuilderPool.concat("type mismatch=", keyName));
}
break;
case LongType:
if ((defaultValue instanceof Long) == false) {
throw new AssertionError(StringBuilderPool.concat("type mismatch=", keyName));
}
break;
case FloatType:
if ((defaultValue instanceof Float) == false) {
throw new AssertionError(StringBuilderPool.concat("type mismatch=", keyName));
}
break;
case DoubleType:
if ((defaultValue instanceof Double) == false) {
throw new AssertionError(StringBuilderPool.concat("type mismatch=", keyName));
}
break;
default:
throw new AssertionError(StringBuilderPool.concat("unsupported key type=", keyName));
}
}
}
SettingsDao.java : 세팅 값을 읽고, 쓰고, 캐시하는 유틸리티 클래스
package com.mdiwebma.base.settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.SparseArray;
import com.mdiwebma.base.ApplicationKeeper;
import com.mdiwebma.base.logging.Dlog;
import com.mdiwebma.base.task.CommonExecutors;
import io.realm.Realm;
/**
* @author djkim
*/
public class SettingsDao {
private static final String KEY_NAME = "keyName"; // from SettingRO.keyName (member variable's name)
private static final SparseArray<Object> cacheArray = new SparseArray<>(Settings.values().length);
static {
int cacheIndex = 0;
for (Settings setting : Settings.values()) {
setting.cacheIndex = cacheIndex++;
}
}
public static synchronized boolean getBoolean(@NonNull final Settings setting) {
Dlog.assertNotNull(setting);
Dlog.assertTrue(setting.settingType == Settings.BooleanType);
if (cacheArray.valueAt(setting.cacheIndex) == null) {
String value = readValueFromRealm(setting);
if (value == null) {
cacheArray.setValueAt(setting.cacheIndex, (Boolean)setting.defaultValue);
} else {
cacheArray.setValueAt(setting.cacheIndex, Boolean.valueOf(value));
}
}
Object data = cacheArray.valueAt(setting.cacheIndex);
return (boolean)data;
}
@Nullable
private static String readValueFromRealm(@NonNull final Settings setting) {
Dlog.assertNotNull(setting);
Realm realm = Realm.getInstance(ApplicationKeeper.get());
try {
SettingRO settingRO = realm.where(SettingRO.class).equalTo(KEY_NAME, setting.keyName).findFirst();
if (settingRO != null) {
return settingRO.getValue();
}
} catch (Exception ex) {
Dlog.notReached(ex);
} finally {
realm.close();
}
return null;
}
public static synchronized void putBoolean(@NonNull final Settings setting, final boolean value) {
Dlog.assertNotNull(setting);
Dlog.assertTrue(setting.settingType == Settings.BooleanType);
if (value != getBoolean(setting)) {
cacheArray.setValueAt(setting.cacheIndex, (Boolean)value);
writeValueToRealm(setting, String.valueOf(value));
}
}
private static void writeValueToRealm(@NonNull final Settings setting, @NonNull final String value) {
Dlog.assertNotNull(setting);
Dlog.assertNotNull(value);
CommonExecutors.execute(new Runnable() {
@Override
public void run() {
Realm realm = Realm.getInstance(ApplicationKeeper.get());
try {
realm.beginTransaction();
SettingRO settingRo = realm.where(SettingRO.class).equalTo(KEY_NAME, setting.keyName).findFirst();
if (settingRo == null) {
settingRo = realm.createObject(SettingRO.class);
settingRo.setKeyName(setting.keyName);
}
settingRo.setValue(value);
realm.commitTransaction();
} catch (Exception ex) {
Dlog.notReached(ex);
} finally {
realm.close();
}
}
});
}
@NonNull
public static synchronized String getString(@NonNull final Settings setting) {
Dlog.assertNotNull(setting);
Dlog.assertTrue(setting.settingType == Settings.StringType);
if (cacheArray.valueAt(setting.cacheIndex) == null) {
String value = readValueFromRealm(setting);
if (value == null) {
cacheArray.setValueAt(setting.cacheIndex, (String)setting.defaultValue);
} else {
cacheArray.setValueAt(setting.cacheIndex, value);
}
}
Object data = cacheArray.valueAt(setting.cacheIndex);
return (String)data;
}
public static synchronized void putString(@NonNull final Settings setting, @NonNull final String param) {
Dlog.assertNotNull(setting);
Dlog.assertTrue(setting.settingType == Settings.StringType);
//Dlog.assertNotNull(param);
// avoid NPE from null value
final String value = param != null ? param : "";
if (value.equals(getString(setting)) == false) {
cacheArray.setValueAt(setting.cacheIndex, value);
writeValueToRealm(setting, value);
}
}
public static synchronized int getInteger(@NonNull final Settings setting) {
Dlog.assertNotNull(setting);
Dlog.assertTrue(setting.settingType == Settings.IntegerType);
if (cacheArray.valueAt(setting.cacheIndex) == null) {
String value = readValueFromRealm(setting);
if (value == null) {
cacheArray.setValueAt(setting.cacheIndex, (Integer)setting.defaultValue);
} else {
cacheArray.setValueAt(setting.cacheIndex, Integer.valueOf(value));
}
}
Object data = cacheArray.valueAt(setting.cacheIndex);
return (int)data;
}
public static synchronized void putInteger(@NonNull final Settings setting, final int value) {
Dlog.assertNotNull(setting);
Dlog.assertTrue(setting.settingType == Settings.IntegerType);
if (getInteger(setting) != value) {
cacheArray.setValueAt(setting.cacheIndex, (Integer)value);
writeValueToRealm(setting, String.valueOf(value));
}
}
public static synchronized void deleteSetting(@NonNull final Settings setting) {
Dlog.assertNotNull(setting);
cacheArray.setValueAt(setting.cacheIndex, null);
CommonExecutors.execute(new Runnable() {
@Override
public void run() {
Realm realm = Realm.getInstance(ApplicationKeeper.get());
try {
realm.beginTransaction();
SettingRO settingRo = realm.where(SettingRO.class).equalTo(KEY_NAME, setting.keyName).findFirst();
if (settingRo != null) {
settingRo.removeFromRealm();
}
realm.commitTransaction();
} catch (Exception ex) {
Dlog.notReached(ex);
} finally {
realm.close();
}
}
});
}
}
테스트 코드:
void test() {
int i = SettingsDao.getInteger(Settings.test_int);
Dlog.l("test_int=" + i);
SettingsDao.putInteger(Settings.test_int, 103);
i = SettingsDao.getInteger(Settings.test_int);
Dlog.l("test_int=" + i);
String s = SettingsDao.getString(Settings.test_string);
Dlog.l("test_str=" + s);
SettingsDao.putString(Settings.test_string, "abcd");
s = SettingsDao.getString(Settings.test_string);
Dlog.l("test_str=" + s);
SettingsDao.putString(Settings.test_string, null);
s = SettingsDao.getString(Settings.test_string);
Dlog.l("test_str=" + s);
SettingsDao.putString(Settings.test_string, "ddd");
s = SettingsDao.getString(Settings.test_string);
Dlog.l("test_str=" + s);
boolean b = SettingsDao.getBoolean(Settings.test_bool);
Dlog.l("test_bool=" + b);
SettingsDao.putBoolean(Settings.test_bool, true);
b = SettingsDao.getBoolean(Settings.test_bool);
Dlog.l("test_bool=" + b);
SettingsDao.putBoolean(Settings.test_bool, false);
b = SettingsDao.getBoolean(Settings.test_bool);
Dlog.l("test_bool=" + b);
SettingsDao.deleteSetting(Settings.test_int);
SettingsDao.deleteSetting(Settings.test_string);
SettingsDao.deleteSetting(Settings.test_bool);
i = SettingsDao.getInteger(Settings.test_int);
Dlog.l("atest_int=" + i);
s = SettingsDao.getString(Settings.test_string);
Dlog.l("atest_str=" + s);
b = SettingsDao.getBoolean(Settings.test_bool);
Dlog.l("atest_bool=" + b);
}