Skip to content

Commit 4429a2d

Browse files
committed
Added feature manager with unit tests
1 parent 052bc4a commit 4429a2d

15 files changed

+1686
-55
lines changed

lib/feature_manager.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
library feature_manager;
22

3-
/// A Calculator.
4-
class Calculator {
5-
/// Returns [value] plus 1.
6-
int addOne(int value) => value + 1;
7-
}
3+
export 'src/domain/models/feature.dart';
4+
export 'src/presentation/features_screen.dart';
5+
export 'src/utils/feature_manager.dart';

lib/src/bloc/features_cubit.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import 'dart:async';
2+
3+
import 'package:feature_manager/src/data/feature_repository.dart';
4+
import 'package:feature_manager/src/domain/models/feature.dart';
5+
import 'package:flutter_bloc/flutter_bloc.dart';
6+
7+
part 'features_state.dart';
8+
9+
class FeaturesCubit extends Cubit<FeaturesState> {
10+
FeaturesCubit(this.repository) : super(FeaturesInitial());
11+
12+
final FeatureRepository repository;
13+
StreamSubscription<List<Feature>>? _featuresSubscription;
14+
15+
Future<void> getFeatures() async {
16+
try {
17+
emit(FeaturesLoading());
18+
19+
_listenFeatures();
20+
} catch (e) {
21+
print('FeatureManager error >> $e');
22+
emit(FeaturesError());
23+
}
24+
}
25+
26+
Future<void> changeFeature(
27+
Feature feature,
28+
Object? value,
29+
) async {
30+
try {
31+
await repository.putValue(feature, value);
32+
} catch (e) {
33+
print('FeatureManager error >> $e');
34+
emit(FeaturesError());
35+
}
36+
}
37+
38+
void _listenFeatures() {
39+
_featuresSubscription ??= repository.getFeaturesStream().distinct().listen(
40+
(List<Feature> features) {
41+
if (features.isNotEmpty) {
42+
emit(FeaturesSuccess(features));
43+
} else {
44+
emit(FeaturesEmpty());
45+
}
46+
},
47+
onError: (e) {
48+
print('FeatureManager error >> $e');
49+
emit(FeaturesError());
50+
},
51+
);
52+
}
53+
54+
@override
55+
Future<void> close() {
56+
_featuresSubscription?.cancel();
57+
_featuresSubscription = null;
58+
return super.close();
59+
}
60+
}

lib/src/bloc/features_state.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
part of 'features_cubit.dart';
2+
3+
abstract class FeaturesState {}
4+
5+
class FeaturesInitial extends FeaturesState {}
6+
7+
class FeaturesSuccess extends FeaturesState {
8+
FeaturesSuccess(this.features);
9+
10+
final List<Feature> features;
11+
}
12+
13+
class FeaturesLoading extends FeaturesState {}
14+
15+
class FeaturesEmpty extends FeaturesState {}
16+
17+
class FeaturesError extends FeaturesState {}

lib/src/data/feature_repository.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import 'dart:async';
2+
3+
import 'package:feature_manager/src/domain/models/feature.dart';
4+
import 'package:shared_preferences/shared_preferences.dart';
5+
6+
class FeatureRepository {
7+
FeatureRepository(this.featuresList) {
8+
_featuresStreamController = StreamController<List<Feature>>.broadcast();
9+
}
10+
11+
final List<Feature> featuresList;
12+
13+
late StreamController<List<Feature>> _featuresStreamController;
14+
15+
Stream<List<Feature>> getFeaturesStream() => _featuresStreamController.stream;
16+
17+
Future<List<Feature>> getFeatures() async {
18+
final List<Feature> updatedFeatures = [];
19+
final SharedPreferences sharedPreferences =
20+
await SharedPreferences.getInstance();
21+
22+
for (final Feature feature in featuresList) {
23+
updatedFeatures.add(
24+
feature.copyWith(
25+
value: _getValue(sharedPreferences, feature),
26+
),
27+
);
28+
}
29+
30+
_featuresStreamController.add(updatedFeatures);
31+
32+
return updatedFeatures;
33+
}
34+
35+
Future<void> putValue(
36+
Feature feature,
37+
Object? value,
38+
) async {
39+
final SharedPreferences sharedPreferences =
40+
await SharedPreferences.getInstance();
41+
42+
switch (feature.valueType) {
43+
case FeatureValueType.toggle:
44+
await sharedPreferences.setBool(feature.key, value as bool);
45+
break;
46+
case FeatureValueType.doubleNumber:
47+
await sharedPreferences.setDouble(feature.key, value as double);
48+
break;
49+
case FeatureValueType.integerNumber:
50+
await sharedPreferences.setInt(feature.key, value as int);
51+
break;
52+
case FeatureValueType.text:
53+
await sharedPreferences.setString(feature.key, value as String);
54+
break;
55+
}
56+
57+
getFeatures();
58+
}
59+
60+
Object? _getValue(SharedPreferences sharedPreferences, Feature feature) =>
61+
sharedPreferences.get(feature.key) ?? feature.defaultValue;
62+
}

lib/src/domain/models/feature.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
class Feature {
2+
const Feature({
3+
required this.key,
4+
required this.type,
5+
required this.valueType,
6+
required this.title,
7+
this.remoteKey = '',
8+
this.description = '',
9+
this.remoteSourceKey = '',
10+
this.value,
11+
this.defaultValue,
12+
});
13+
14+
final String key;
15+
final FeatureType type;
16+
final FeatureValueType valueType;
17+
final String title;
18+
final String remoteKey;
19+
final String description;
20+
final String remoteSourceKey;
21+
final Object? value;
22+
final Object? defaultValue;
23+
24+
Feature copyWith({
25+
String? key,
26+
FeatureType? type,
27+
FeatureValueType? valueType,
28+
String? remoteKey,
29+
String? title,
30+
String? description,
31+
String? remoteSourceKey,
32+
Object? value,
33+
Object? defaultValue,
34+
}) {
35+
return Feature(
36+
key: key ?? this.key,
37+
type: type ?? this.type,
38+
valueType: valueType ?? this.valueType,
39+
remoteKey: remoteKey ?? this.remoteKey,
40+
title: title ?? this.title,
41+
description: description ?? this.description,
42+
remoteSourceKey: remoteSourceKey ?? this.remoteSourceKey,
43+
value: value ?? this.value,
44+
defaultValue: defaultValue ?? this.defaultValue,
45+
);
46+
}
47+
48+
@override
49+
bool operator ==(Object other) =>
50+
identical(this, other) ||
51+
other is Feature &&
52+
runtimeType == other.runtimeType &&
53+
key == other.key &&
54+
type == other.type &&
55+
valueType == other.valueType &&
56+
remoteKey == other.remoteKey &&
57+
title == other.title &&
58+
description == other.description &&
59+
remoteSourceKey == other.remoteSourceKey &&
60+
value == other.value &&
61+
defaultValue == other.defaultValue;
62+
63+
@override
64+
int get hashCode =>
65+
key.hashCode ^
66+
type.hashCode ^
67+
valueType.hashCode ^
68+
remoteKey.hashCode ^
69+
title.hashCode ^
70+
description.hashCode ^
71+
remoteSourceKey.hashCode ^
72+
value.hashCode ^
73+
defaultValue.hashCode;
74+
75+
@override
76+
String toString() {
77+
return 'Feature{key: $key, type: $type, valueType: $valueType, remoteKey: $remoteKey, title: $title, description: $description, remoteSourceKey: $remoteSourceKey, value: $value, defaultValue: $defaultValue}';
78+
}
79+
}
80+
81+
enum FeatureType { feature, experiment }
82+
enum FeatureValueType { text, toggle, doubleNumber, integerNumber }

0 commit comments

Comments
 (0)