Skip to content

Commit

Permalink
Refactor feature flags code and fix feature flag groups
Browse files Browse the repository at this point in the history
  • Loading branch information
Process-ing committed Nov 11, 2024
1 parent dca0002 commit 98e1d79
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 156 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,90 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uni/controller/feature_flags/feature_flag_info.dart';
import 'package:uni/controller/feature_flags/feature_flag_state_controller.dart';
import 'package:uni/model/feature_flags/feature_flag.dart';
import 'package:uni/model/feature_flags/feature_flag_group.dart';
import 'package:uni/model/feature_flags/generic_feature_flag.dart';

class FeatureFlagController {
FeatureFlagController(this.preferences);
static final List<GenericFeatureFlag> _featureFlags = []; // To preserve order
static final Map<String, GenericFeatureFlag> _featureFlagsMap =
{}; // For fast lookup
static FeatureFlagStateController? _stateController;

final SharedPreferences preferences;
static const _flagPrefix = '__feature_flag__';
static void parseFeatureFlagTable(
List<GenericFeatureFlagInfo> featureFlagInfos,
) {
for (final featureFlagInfo in featureFlagInfos) {
final featureFlag = featureFlagInfo is FeatureFlagInfo
? _createFeatureFlag(featureFlagInfo)
: _createFeatureFlagGroup(featureFlagInfo as FeatureFlagGroupInfo);

String _getKey(String code) => '$_flagPrefix$code';
_featureFlags.add(featureFlag);
_featureFlagsMap[featureFlag.code] = featureFlag;

bool isEnabled(String code) {
return preferences.getBool(_getKey(code)) ?? false;
if (featureFlag is FeatureFlagGroup) {
for (final subFeatureFlag in featureFlag.getFeatureFlags()) {
_featureFlagsMap[subFeatureFlag.code] = subFeatureFlag;
}
}
}
}

Future<void> saveEnabled(String code, {required bool enabled}) {
return preferences.setBool(_getKey(code), enabled);
static bool _isEnabled(String code) {
if (_stateController == null) {
throw Exception('FeatureFlagStateController is not initialized.');
}

return _stateController!.isEnabled(code);
}

static Future<void> _saveEnabled(String code, {required bool enabled}) async {
if (_stateController == null) {
throw Exception('FeatureFlagStateController is not initialized.');
}

await _stateController!.saveEnabled(code, enabled: enabled);
}

static FeatureFlag _createFeatureFlag(FeatureFlagInfo featureFlagInfo) {
final code = featureFlagInfo.code;
final getName = featureFlagInfo.getName;

return FeatureFlag(
code: code,
getName: getName,
isEnabled: () => _isEnabled(code),
saveEnabled: ({required enabled}) => _saveEnabled(code, enabled: enabled),
);
}

static GenericFeatureFlag _createFeatureFlagGroup(
FeatureFlagGroupInfo featureFlagGroupInfo,
) {
final code = featureFlagGroupInfo.code;
final getName = featureFlagGroupInfo.getName;
final featureFlags =
featureFlagGroupInfo.featureFlags.map(_createFeatureFlag).toList();

final featureFlagGroup = FeatureFlagGroup(
code: code,
getName: getName,
isEnabled: () => _isEnabled(code),
saveEnabled: ({required enabled}) => _saveEnabled(code, enabled: enabled),
featureFlags: featureFlags,
);

return featureFlagGroup;
}

static GenericFeatureFlag? getFeatureFlag(String code) {
return _featureFlagsMap[code];
}

static void setStateController(FeatureFlagStateController stateController) {
_stateController = stateController;
}

static List<GenericFeatureFlag> getFeatureFlags() {
return _featureFlags;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import 'package:shared_preferences/shared_preferences.dart';

class FeatureFlagStateController {
FeatureFlagStateController(this.preferences);

final SharedPreferences preferences;
static const _flagPrefix = '__feature_flag__';

String _getKey(String code) => '$_flagPrefix$code';

bool isEnabled(String code) {
return preferences.getBool(_getKey(code)) ?? false;
}

Future<void> saveEnabled(String code, {required bool enabled}) {
return preferences.setBool(_getKey(code), enabled);
}
}
101 changes: 1 addition & 100 deletions packages/uni_app/lib/controller/feature_flags/feature_flag_table.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import 'package:uni/controller/feature_flags/feature_flag_controller.dart';
import 'package:uni/controller/feature_flags/feature_flag_info.dart';
import 'package:uni/generated/l10n.dart';
import 'package:uni/model/feature_flags/feature_flag.dart';
import 'package:uni/model/feature_flags/feature_flag_group.dart';
import 'package:uni/model/feature_flags/generic_feature_flag.dart';

List<GenericFeatureFlagInfo> featureFlagInfos = [
List<GenericFeatureFlagInfo> featureFlagTable = [
FeatureFlagInfo(
code: 'library_modules',
getName: (context) => S.of(context).library_modules,
Expand All @@ -25,98 +21,3 @@ List<GenericFeatureFlagInfo> featureFlagInfos = [
],
),
];

class FeatureFlagTable {
static final List<GenericFeatureFlag> _featureFlags =
_toFeatureFlags(featureFlagInfos); // To preserve order
static final Map<String, GenericFeatureFlag> _featureFlagsMap =
_toFeatureFlagsMap(_featureFlags); // For fast lookup
static FeatureFlagController? _controller;

static List<GenericFeatureFlag> _toFeatureFlags(
List<GenericFeatureFlagInfo> featureFlagInfos,
) {
final featureFlags = <GenericFeatureFlag>[];

for (final featureFlagInfo in featureFlagInfos) {
final featureFlag = featureFlagInfo is FeatureFlagInfo
? _createFeatureFlag(featureFlagInfo)
: _createFeatureFlagGroup(featureFlagInfo as FeatureFlagGroupInfo);

featureFlags.add(featureFlag);
}

return featureFlags;
}

static Map<String, GenericFeatureFlag> _toFeatureFlagsMap(
List<GenericFeatureFlag> featureFlags,
) {
final featureFlagsMap = <String, GenericFeatureFlag>{};

for (final featureFlag in featureFlags) {
featureFlagsMap[featureFlag.code] = featureFlag;
}

return featureFlagsMap;
}

static bool _isEnabled(String code) {
if (_controller == null) {
throw Exception('FeatureFlagController is not initialized.');
}

return _controller!.isEnabled(code);
}

static Future<void> _saveEnabled(String code, {required bool enabled}) async {
if (_controller == null) {
throw Exception('FeatureFlagController is not initialized.');
}

await _controller!.saveEnabled(code, enabled: enabled);
}

static FeatureFlag _createFeatureFlag(FeatureFlagInfo featureFlagInfo) {
final code = featureFlagInfo.code;
final getName = featureFlagInfo.getName;

return FeatureFlag(
code: code,
getName: getName,
isEnabled: () => _isEnabled(code),
saveEnabled: ({required enabled}) => _saveEnabled(code, enabled: enabled),
);
}

static GenericFeatureFlag _createFeatureFlagGroup(
FeatureFlagGroupInfo featureFlagGroupInfo,
) {
final code = featureFlagGroupInfo.code;
final getName = featureFlagGroupInfo.getName;
final featureFlags =
featureFlagGroupInfo.featureFlags.map(_createFeatureFlag).toList();

final featureFlagGroup = FeatureFlagGroup(
code: code,
getName: getName,
isEnabled: () => _isEnabled(code),
saveEnabled: ({required enabled}) => _saveEnabled(code, enabled: enabled),
featureFlags: featureFlags,
);

return featureFlagGroup;
}

static GenericFeatureFlag? getFeatureFlag(String code) {
return _featureFlagsMap[code];
}

static void setController(FeatureFlagController featureFlagController) {
_controller = featureFlagController;
}

static List<GenericFeatureFlag> getFeatureFlags() {
return _featureFlags;
}
}
8 changes: 5 additions & 3 deletions packages/uni_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:ua_client_hints/ua_client_hints.dart';
import 'package:uni/controller/background_workers/background_callback.dart';
import 'package:uni/controller/cleanup.dart';
import 'package:uni/controller/feature_flags/feature_flag_controller.dart';
import 'package:uni/controller/feature_flags/feature_flag_state_controller.dart';
import 'package:uni/controller/feature_flags/feature_flag_table.dart';
import 'package:uni/controller/fetchers/terms_and_conditions_fetcher.dart';
import 'package:uni/controller/local_storage/preferences_controller.dart';
Expand Down Expand Up @@ -185,9 +186,10 @@ Future<void> main() async {
},
);

final featureFlagController =
FeatureFlagController(PreferencesController.prefs);
FeatureFlagTable.setController(featureFlagController);
final featureFlagStateController =
FeatureFlagStateController(PreferencesController.prefs);
FeatureFlagController.setStateController(featureFlagStateController);
FeatureFlagController.parseFeatureFlagTable(featureFlagTable);
}

/// Manages the state of the app.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ExperimentalFeatureWrapper extends StatelessWidget {
static Widget _defaultOnDisabled(BuildContext context) {
return Container();
}

@override
Widget build(BuildContext context) {
return featureFlag.isEnabled() ? onEnabled(context) : onDisabled(context);
Expand Down
53 changes: 37 additions & 16 deletions packages/uni_app/lib/view/library/library.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
import 'package:provider/provider.dart';
import 'package:uni/controller/feature_flags/feature_flag_controller.dart';
import 'package:uni/generated/l10n.dart';
import 'package:uni/model/entities/library_occupation.dart';
import 'package:uni/model/providers/lazy/library_occupation_provider.dart';
import 'package:uni/utils/navigation_items.dart';
import 'package:uni/view/common_widgets/experimental_feature_wrapper.dart';
import 'package:uni/view/common_widgets/page_title.dart';
import 'package:uni/view/common_widgets/pages_layouts/secondary/secondary.dart';
import 'package:uni/view/lazy_consumer.dart';
Expand All @@ -18,25 +20,44 @@ class LibraryPage extends StatefulWidget {
}

class LibraryPageState extends SecondaryPageViewState<LibraryPage> {
static final libraryFeatureFlag =
FeatureFlagController.getFeatureFlag('library')!;
static final libraryOccupationFeatureFlag =
FeatureFlagController.getFeatureFlag('library_occupation')!;
static final libraryFloorsFeatureFlag =
FeatureFlagController.getFeatureFlag('library_floors')!;

@override
Widget getBody(BuildContext context) {
return ListView(
shrinkWrap: true,
children: [
LibraryOccupationCard(),
PageTitle(name: S.of(context).floors),
LazyConsumer<LibraryOccupationProvider, LibraryOccupation>(
builder: getFloorRows,
hasContent: (occupation) => occupation.floors.isNotEmpty,
onNullContent: Center(
child: Text(
S.of(context).no_library_info,
style: const TextStyle(fontSize: 18),
return ExperimentalFeatureWrapper(
featureFlag: libraryFeatureFlag,
onEnabled: (context) {
return ListView(
shrinkWrap: true,
children: [
ExperimentalFeatureWrapper(
featureFlag: libraryOccupationFeatureFlag,
onEnabled: (_) => LibraryOccupationCard(),
),
),
contentLoadingWidget: const CircularProgressIndicator(),
),
],
PageTitle(name: S.of(context).floors),
ExperimentalFeatureWrapper(
featureFlag: libraryFloorsFeatureFlag,
onEnabled: (_) =>
LazyConsumer<LibraryOccupationProvider, LibraryOccupation>(
builder: getFloorRows,
hasContent: (occupation) => occupation.floors.isNotEmpty,
onNullContent: Center(
child: Text(
S.of(context).no_library_info,
style: const TextStyle(fontSize: 18),
),
),
contentLoadingWidget: const CircularProgressIndicator(),
),
),
],
);
},
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:uni/controller/feature_flags/feature_flag_table.dart';
import 'package:uni/controller/feature_flags/feature_flag_controller.dart';

import 'package:uni/generated/l10n.dart';
import 'package:uni/view/settings/widgets/feature_flags/feature_switch_tile.dart';
Expand All @@ -13,7 +13,7 @@ class FeatureFlagsDialog extends StatelessWidget {
title: Text(S.of(context).feature_flags),
content: Column(
mainAxisSize: MainAxisSize.min,
children: FeatureFlagTable.getFeatureFlags()
children: FeatureFlagController.getFeatureFlags()
.map((featureFlag) => FeatureSwitchTile(featureFlag: featureFlag))
.toList(),
),
Expand Down
Loading

0 comments on commit 98e1d79

Please sign in to comment.