feature_core 1.0.1 feature_core: ^1.0.1 copied to clipboard
Core classes and contracts for managing features.
feature_core #
Manages feature flags in your application. This is the core library.
Creating a feature | Creating a feature source | Creating the manager
Use with Flutter | Use wrappers
Additional packages #
There are several additional libraries created to make this one work. I hope this list will be expanded in the future.
- feature_flutter - integration and management of features in the flutter application.
Wrappers #
- feature_source_retain - adds ability to locally save state of features
Feature sources #
- feature_source_firebase - implementation for remote firebase config
Concept #
-
There are three main entities:
Feature
,FeatureSource
,FeatureManager
. -
The entity represents some kind of customization for the application.
Feature
has a subtype,FeatureToggle
, which is actuallyFeature<bool>
. -
FeatureSource
implements a function that retrieves functions from some repository and handles their updates. It can work withoutFeatureManager
. -
The
FeatureManager
combines all feature streams provided by the sources into a singleStream<Map<String, Feature>>
. Also provides methods for accessing features. (Manager API)
Usage #
Creating a feature #
To create a feature, just inherit from the Feature
class and specify the value type of the feature. For example Feature<bool>
for toggles, or Feature<String>
for settings. When creating a toggle, inherit from FeatureToggle
for convenience.
Important note: in order to enable/disable the feature, the creator()
method must be implemented - it must return a copy of the current object. (google it: `downcast')
class UseNewComponent extends FeatureToggle {
UseNewComponent(bool value) : super(value: value, enabled: true);
@override
Feature<bool> creator() => UseNewComponent(value);
}
class FirebaseFeature<T extends dynamic> extends Feature<T> {
FirebaseFeature({
required String key,
required T value,
}) : super(key: key, value: value);
@override
Feature<T> creator() => FirebaseFeature(key: key, value: value);
}
Creating a feature source #
To add your own feature source, inherit from FeatureSource
and implement the pullFeatures()
method.
The rest is up to you.
Important note: if you need to update the features from within FeatureSource (for example, if you get updates from the backend), use notifyNeedUpdate()
to reacquire the features.
class FirebaseFeatureSource extends FeatureSource {
final FirebaseRemoteConfig _remoteConfig;
FirebaseFeatureSource({
required FirebaseRemoteConfig remoteConfig,
}) : _remoteConfig = remoteConfig {
_remoteConfig
..addListener(_onUpdate)
..fetchAndActivate();
}
Future<void> _onUpdate() async {
notifyNeedUpdate();
}
@override
void dispose() {
super.dispose();
_remoteConfig.removeListener(_onUpdate);
}
@override
@protected
FutureOr<Iterable<Feature>> pullFeatures() async {
/// pulling features ...
}
}
Creating the manager #
First, create a FeaturesManager and populate it with your sources.
The library provides a default source named LocalFeatureSource
.
Once created, you need to init()
the manager - it will pull all the features from the sources into a single repository.
final featuresManager = FeaturesManager(
sources: {
LocalFeatureSource(
features: [
TestLocalFeature(),
TestTextLocalFeature(),
],
),
...
},
);
await featuresManager.init();
You can also connect the manager instance to the global scope for easier access. The global entry point Features
is a proxy for the instance and has the same public api.
The connect()
method returns the instance you passed.
Features.connect(
instance: FeaturesManager(...),
);
/// Instance available by
Features.I
Use with Flutter #
With the feature_flutter package you can integrate features into the Flutter application.
The package provides three main widgets: FeaturesProvider
, FeatureWidget
, DebugFeaturesWidget
.
FeaturesProvider
Passes the manager down the tree.
void main() {
final FeaturesManager featuresManager = FeaturesManager(...);
runApp(MaterialApp(
home: FeaturesProvider(
manager: featuresManager,
),
...
));
}
FeatureWidget
.
Allows you to show/hide widgets depending on the value of a feature.
You can use a simplified variant
FeatureWidget(
feature: featuresManager.getFeature('feature_key'),
child: Text(
'child when feature: has 'false' value,'
'OR is disabled OR not found in manager',
),
activatedChild: Text(
'child when feature found in manager AND is enabled.'
),
visible: true, // default, totally shows/hides widget
)
Or you can handle feature values more flexibly through the builder.
Important note: By default, if the bilder returns nothing, an empty SizedBox
is used.
FeatureWidget.builder(
feature: featuresManager.getFeatureByType<SomeFeature>(),
builder: (BuildContext context, Feature? feature) {
if (feature == null) {
return // your widget here
}
if (feature.enabled) {
return // your widget here
}
},
)
DebugFeaturesWidget
Just a handy widget which depends on the manager and gives access to enable/disable features.
Use wrappers #
Section in progress
Manager API #
/// Stream with actual features combined from all sources.
/// It will produce new data every time some source updated.
Stream<Map<String, Feature>> get stream;
/// Actual manager's features.
Map<String, Feature> get features;
/// Sources where manager gets features.
Set<FeatureSource> get sources;
/// Returns stream which provides updates
/// only for particular feature.
/// Null if there is not exists feature with provided key.
Stream<Feature>? featureStream(String key)
/// Gets the feature and compare its value with passed.
bool check(String key, dynamic value);
/// Returns feature's value.
dynamic value(String key);
Feature? getFeature(String key);
T? getFeatureByType<T extends Feature>();
T? getSource<T extends FeatureSource>();
Future<void> init();
void dispose();