Merge pull request 'FeatureFlag実装' (#64) from feature/feature_flag into main

Reviewed-on: #64
This commit is contained in:
Fujimatsu 2024-06-13 07:02:51 +00:00
commit f9e8e78d84
6 changed files with 308 additions and 2 deletions

View File

@ -2,6 +2,7 @@ package one.nem.kidshift.feature.debug;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
@ -9,17 +10,22 @@ import dagger.hilt.EntryPoint;
import dagger.hilt.InstallIn;
import dagger.hilt.android.AndroidEntryPoint;
import dagger.hilt.android.components.FragmentComponent;
import one.nem.kidshift.utils.FeatureFlag;
import one.nem.kidshift.utils.KSLogger;
import one.nem.kidshift.utils.models.LogModel;
import one.nem.kidshift.utils.models.feature.FeatureFlagItemModel;
public class DebugCommandProcessor {
KSLogger ksLogger;
FeatureFlag featureFlag;
public DebugCommandProcessor(
KSLogger ksLogger
KSLogger ksLogger,
FeatureFlag featureFlag
) {
this.ksLogger = ksLogger;
this.featureFlag = featureFlag;
}
public String execute(String command) {
@ -44,6 +50,8 @@ public class DebugCommandProcessor {
return executeEcho(commandArray);
case "log":
return executeLog(commandArray);
case "flag":
return executeFlag(commandArray);
default:
throw new InvalidCommandException();
}
@ -93,6 +101,70 @@ public class DebugCommandProcessor {
}
}
private String executeFlag(String[] commandArray) {
commandArray = shiftArray(commandArray);
switch (commandArray[0]) {
case "get":
commandArray = shiftArray(commandArray);
if (Objects.equals(commandArray[0], "all")) {
StringBuilder flagString = new StringBuilder();
for (FeatureFlagItemModel featureFlagItemModel : featureFlag.getFeatureFlagMap().values()) {
flagString.append(makeFeatureFlagResponse(featureFlagItemModel));
flagString.append("\n");
}
return flagString.toString();
}
FeatureFlagItemModel featureFlagItemModel = featureFlag.getFeatureFlagMap().get(commandArray[0]);
return makeFeatureFlagResponse(featureFlagItemModel);
case "set":
commandArray = shiftArray(commandArray);
try {
featureFlag.setOverride(commandArray[0], Boolean.parseBoolean(commandArray[1]));
return "Flag set!";
} catch (IllegalArgumentException e) {
return e.getMessage();
} catch (Exception e) {
return "Something went wrong! \n" + e.getMessage();
}
case "reset":
commandArray = shiftArray(commandArray);
if (Objects.equals(commandArray[0], "all")) {
featureFlag.resetAllOverrides();
return "All flags reset!";
}
try {
featureFlag.resetOverride(commandArray[0]);
return "Flag reset!";
} catch (IllegalArgumentException e) {
return e.getMessage();
} catch (Exception e) {
return "Something went wrong! \n" + e.getMessage();
}
default:
// debug
if (this.featureFlag == null) {
return "Feature Flag is null";
} else {
return "Feature Flag is not null";
}
}
}
private String[] shiftArray(String[] array, int shift) {
return Arrays.copyOfRange(array, shift, array.length);
}
private String[] shiftArray(String[] array) {
return shiftArray(array, 1);
}
private String makeFeatureFlagResponse(FeatureFlagItemModel featureFlagItemModel) {
return "Key: " + featureFlagItemModel.getKey() + "\n" +
"\tValue: " + featureFlagItemModel.getValue() + "\n" +
"\tDefault Value: " + featureFlagItemModel.getDefaultValue() + "\n" +
"\tIs Override Allowed: " + featureFlagItemModel.getIsOverrideAllowed();
}
private String executeEcho(String[] commandArray) {
String[] echoArray = Arrays.copyOfRange(commandArray, 1, commandArray.length);
return String.join(" ", echoArray);

View File

@ -19,6 +19,7 @@ import dagger.hilt.android.AndroidEntryPoint;
import one.nem.kidshift.feature.debug.adapter.DebugCommandListItemAdapter;
import one.nem.kidshift.feature.debug.adapter.DebugMenuListItemAdapter;
import one.nem.kidshift.feature.debug.model.DebugCommandItemModel;
import one.nem.kidshift.utils.FeatureFlag;
import one.nem.kidshift.utils.KSLogger;
/**
@ -32,6 +33,9 @@ public class DebugDebugConsoleFragment extends Fragment {
@Inject
KSLogger ksLogger;
@Inject
FeatureFlag featureFlag;
private final List<DebugCommandItemModel> debugCommandItemModels = new ArrayList<>();
DebugCommandListItemAdapter debugCommandItemAdapter;
@ -98,7 +102,7 @@ public class DebugDebugConsoleFragment extends Fragment {
TextView debugCommandInput = view.findViewById(R.id.debugCommandEditText);
view.findViewById(R.id.debugCommandExecuteButton).setOnClickListener(v -> {
DebugCommandProcessor debugCommandProcessor = new DebugCommandProcessor(
ksLogger);
ksLogger, featureFlag);
debugCommandItemModels.add(
new DebugCommandItemModel(
debugCommandInput.getText().toString(),

View File

@ -0,0 +1,18 @@
package one.nem.kidshift.utils;
import java.util.Map;
import one.nem.kidshift.utils.models.feature.FeatureFlagItemModel;
public interface FeatureFlag {
public boolean isEnabled(String key);
public void setOverride(String key, boolean value) throws IllegalArgumentException;
public void resetOverride(String key) throws IllegalArgumentException;
public void resetAllOverrides();
public Map<String, FeatureFlagItemModel> getFeatureFlagMap();
}

View File

@ -0,0 +1,139 @@
package one.nem.kidshift.utils.impl;
import android.content.Context;
import android.content.SharedPreferences;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import dagger.hilt.android.qualifiers.ApplicationContext;
import one.nem.kidshift.utils.models.feature.FeatureFlagItemModel;
import one.nem.kidshift.utils.FeatureFlag;
public class FeatureFlagImpl implements FeatureFlag {
private final Context applicationContext;
private final SharedPreferences sharedPreferences;
// ここを書き換えてプロファイルを書き換え
private final Profile currentProfile = Profile.DEVELOP;
enum Profile {
DEVELOP("develop"),
BETA("beta"),
PRODUCTION("production");
private final String name;
Profile(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
@Inject
public FeatureFlagImpl(@ApplicationContext Context applicationContext) {
this.applicationContext = applicationContext;
this.sharedPreferences = applicationContext.getSharedPreferences("feat_flg", Context.MODE_PRIVATE);
init();
}
private void init() {
initBase();
switch (currentProfile) {
case DEVELOP:
initDevelop();
break;
case BETA:
initBeta();
break;
case PRODUCTION:
break;
}
restoreOverride();
}
private HashMap<String, FeatureFlagItemModel> featureFlagMap = new HashMap<>();
// init
private void initBase() { // ベース, (= Production)
setFlag("isBaseEnabled", true, false);
setFlag("isBetaEnabled", false, false);
setFlag("isDevelopEnabled", false, false);
setFlag("overrideTest", false, true);
}
private void initBeta() { // 上書き
setFlag("isBaseEnabled", false, true);
setFlag("isBetaEnabled", true, true);
setFlag("isDevelopEnabled", false, true);
}
private void initDevelop() { // 上書き
setFlag("isBaseEnabled", false, true);
setFlag("isBetaEnabled", false, true);
setFlag("isDevelopEnabled", true, true);
}
// utils
private void setFlag(String key, boolean defaultValue, boolean isOverrideAllowed) {
featureFlagMap.put(key, new FeatureFlagItemModel(key, defaultValue, isOverrideAllowed));
}
// Restore override from shared preferences
private void restoreOverride() {
// 前回起動時からプロファイルが変わっている場合はオーバーライドをリセット
if (sharedPreferences.contains("last_profile") && !sharedPreferences.getString("last_profile", "").equals(currentProfile.getName())) {
sharedPreferences.edit().clear().apply();
sharedPreferences.edit().putString("last_profile", currentProfile.getName()).apply();
return;
}
sharedPreferences.edit().putString("last_profile", currentProfile.getName()).apply();
for (String key : featureFlagMap.keySet()) {
Objects.requireNonNull(featureFlagMap.get(key))
.setValue(sharedPreferences.getBoolean(key, Objects.requireNonNull(featureFlagMap.get(key)).getValue()));
}
}
@Override
public Map<String, FeatureFlagItemModel> getFeatureFlagMap() {
return featureFlagMap;
}
@Override
public boolean isEnabled(String key) {
return Objects.requireNonNull(featureFlagMap.get(key)).state();
}
@Override
public void setOverride(String key, boolean value) throws IllegalArgumentException {
// 存在しないキーなら例外
if (!featureFlagMap.containsKey(key)) {
throw new IllegalArgumentException("Invalid key");
}
if (!Objects.requireNonNull(featureFlagMap.get(key)).getIsOverrideAllowed()) {
throw new IllegalArgumentException("Not allowed to override");
}
Objects.requireNonNull(featureFlagMap.get(key)).setValue(value);
sharedPreferences.edit().putBoolean(key, value).apply();
}
@Override
public void resetOverride(String key) throws IllegalArgumentException {
Objects.requireNonNull(featureFlagMap.get(key)).setValue(Objects.requireNonNull(featureFlagMap.get(key)).getDefaultValue());
sharedPreferences.edit().putBoolean(key, Objects.requireNonNull(featureFlagMap.get(key)).getDefaultValue()).apply();
}
@Override
public void resetAllOverrides() {
sharedPreferences.edit().clear().apply();
init();
}
}

View File

@ -0,0 +1,57 @@
package one.nem.kidshift.utils.models.feature;
public class FeatureFlagItemModel {
private String key;
private boolean value;
private boolean defaultValue;
private boolean isOverrideAllowed;
public FeatureFlagItemModel(String key, boolean value, boolean defaultValue, boolean isOverrideAllowed) {
this.key = key;
this.value = value;
this.defaultValue = defaultValue;
this.isOverrideAllowed = isOverrideAllowed;
}
public FeatureFlagItemModel(String key, boolean defaultValue, boolean isOverrideAllowed) {
this.key = key;
this.defaultValue = defaultValue;
this.isOverrideAllowed = isOverrideAllowed;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public boolean getValue() {
return value;
}
public void setValue(boolean value) {
this.value = value;
}
public boolean getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(boolean defaultValue) {
this.defaultValue = defaultValue;
}
public boolean getIsOverrideAllowed() {
return isOverrideAllowed;
}
public void setIsOverrideAllowed(boolean isOverrideAllowed) {
this.isOverrideAllowed = isOverrideAllowed;
}
public boolean state() {
return isOverrideAllowed ? value : defaultValue;
}
}

View File

@ -0,0 +1,16 @@
package one.nem.kidshift.utils.modules;
import dagger.Binds;
import dagger.Module;
import dagger.hilt.InstallIn;
import dagger.hilt.components.SingletonComponent;
import one.nem.kidshift.utils.FeatureFlag;
import one.nem.kidshift.utils.impl.FeatureFlagImpl;
@Module
@InstallIn(SingletonComponent.class)
abstract public class FeatureFlagModule {
@Binds
public abstract FeatureFlag bindFeatureFlag(FeatureFlagImpl featureFlagImpl);
}