Skip to content

Commit

Permalink
feat: Revised Usage of Remote Parameter Fetcher (#18)
Browse files Browse the repository at this point in the history
* fix: activateAndRefetch method to call
activate() instead of fetchAndActivate()

* fix: Refactor onConfigUpdated to Property for Consistent Stream Instance

* refactor: Refactor RemoteParameterFetcher to use
RemoteParameter class

* fix: Fix remote_parameter_fetcher example

* refactor: Implement Use of ValueChanged

Co-authored-by: Ryunosuke Muramatsu <[email protected]>

---------

Co-authored-by: Ryunosuke Muramatsu <[email protected]>
  • Loading branch information
naipaka and riscait authored Nov 12, 2023
1 parent 108edd2 commit e44f4d1
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 202 deletions.
37 changes: 14 additions & 23 deletions packages/flutterfire_remote_parameter_fetcher/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,28 @@ class MainApp extends StatefulWidget {
class _MainAppState extends State<MainApp> {
late final rpf = widget.rpf;

late final _intRemoteParameter = rpf.getIntParameter('int_parameter');
late RemoteParameter<int> _intRemoteParameter;

late int _intParameterValue;

void listen(int value) {
debugPrint('remoteParameter value changed: $value');
_intParameterValue = value;
}

@override
void initState() {
super.initState();
_intRemoteParameter = rpf.getIntParameter(
'int_parameter',
onConfigUpdated: (value) {
debugPrint('remoteParameter value changed: $value');
setState(() {
_intParameterValue = value;
});
},
);
_intParameterValue = _intRemoteParameter.value;
_intRemoteParameter.addListener(listen);
}

@override
void dispose() {
// TODO(riscait): adding
// remoteParameter.removeListener(listen);
Future<void> dispose() async {
await _intRemoteParameter.dispose();
super.dispose();
}

Expand All @@ -57,19 +59,8 @@ class _MainAppState extends State<MainApp> {
appBar: AppBar(title: const Text('FlutterFireRemoteParameterFetcher')),
body: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('RemoteParameter value: $_intParameterValue'),
const SizedBox(height: 16),
FilledButton(
onPressed: () async {
await _intRemoteParameter.activateAndRefetch();
},
child: const Text('Activate and refetch'),
),
],
child: Center(
child: Text('RemoteParameter value: $_intParameterValue'),
),
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/// Remote Parameter Fetcher
library remote_parameter_fetcher;

export '../src/remote_parameter.dart';
export '../src/remote_parameter_fetcher.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,19 @@ import 'dart:async';
class RemoteParameter<T> {
RemoteParameter({
required T value,
required this.onConfigUpdated,
required this.activateAndRefetch,
}) : _value = value;
required StreamSubscription<void> subscription,
}) : _value = value,
_subscription = subscription;

/// A Stream of updated parameter information.
Stream<void> onConfigUpdated;

/// A function that will activate the fetched config and refetch the value.
/// This is useful for when you want to force a refetch of the value.
final Future<T> Function() activateAndRefetch;
/// The subscription to the stream of updated parameter information.
final StreamSubscription<void> _subscription;

/// The current value of the parameter.
T get value => _value;
T _value;

final List<void Function(T value)> _listeners = [];

late StreamSubscription<void> _subscription;

/// Add a listener to be notified when the value changes.
/// Executed when the remote value is updated.
void addListener(void Function(T value) listener) {
_listeners.add(listener);

if (_listeners.length == 1) {
// When the listener is added for the first time,
// monitor the onConfigUpdated stream.
_subscription = onConfigUpdated.listen((_) async {
_value = await activateAndRefetch();
_notifyListeners();
});
}
}

/// Remove a listener.
Future<void> removeListener(void Function(T value) listener) async {
_listeners.remove(listener);

if (_listeners.isEmpty) {
// When the last listener is removed,
// cancel the subscription to the onConfigUpdated stream.
await _subscription.cancel();
}
}
final T _value;

void _notifyListeners() {
for (final listener in _listeners) {
listener(_value);
}
/// Closes the [RemoteParameter].
Future<void> dispose() async {
await _subscription.cancel();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:meta/meta.dart';

import 'remote_parameter.dart';

typedef ValueChanged<T> = void Function(T value);

/// A class that wraps Remote Config.
/// Its role is to "fetch the configured parameters from remote and provide
/// them".
Expand Down Expand Up @@ -49,9 +51,17 @@ class RemoteParameterFetcher {
}

/// Provide a Stream of updated parameter information.
///
/// NOTE: In the method form, each call inadvertently creates
/// a different Stream, resulting in only the latest one being functional.
/// To address this issue, we use a property form to ensure a unique and
/// consistent Stream for each instance.
@visibleForTesting
Stream<RemoteConfigUpdate> get onConfigUpdated {
return _rc.onConfigUpdated;
late final Stream<RemoteConfigUpdate> onConfigUpdated = _rc.onConfigUpdated;

/// Filter the Stream of updated parameter information by key.
Stream<RemoteConfigUpdate> filteredOnConfigUpdated(String key) {
return onConfigUpdated.where((config) => config.updatedKeys.contains(key));
}

@visibleForTesting
Expand Down Expand Up @@ -104,103 +114,101 @@ class RemoteParameterFetcher {
}

/// Returns a [RemoteParameter] of type [String].
RemoteParameter<String> getStringParameter(String key) {
RemoteParameter<String> getStringParameter(
String key, {
required ValueChanged<String> onConfigUpdated,
}) {
return RemoteParameter<String>(
value: getString(key),
onConfigUpdated: onConfigUpdated.where(
(config) => config.updatedKeys.contains(key),
),
activateAndRefetch: () async {
await fetchAndActivate();
return getString(key);
},
subscription: filteredOnConfigUpdated(key).listen((event) async {
await activate();
onConfigUpdated(getString(key));
}),
);
}

/// Returns a [RemoteParameter] of type [int].
RemoteParameter<int> getIntParameter(String key) {
RemoteParameter<int> getIntParameter(
String key, {
required ValueChanged<int> onConfigUpdated,
}) {
return RemoteParameter<int>(
value: getInt(key),
onConfigUpdated: onConfigUpdated.where(
(config) => config.updatedKeys.contains(key),
),
activateAndRefetch: () async {
await fetchAndActivate();
return getInt(key);
},
subscription: filteredOnConfigUpdated(key).listen((event) async {
await activate();
onConfigUpdated(getInt(key));
}),
);
}

/// Returns a [RemoteParameter] of type [double].
RemoteParameter<double> getDoubleParameter(String key) {
RemoteParameter<double> getDoubleParameter(
String key, {
required ValueChanged<double> onConfigUpdated,
}) {
return RemoteParameter<double>(
value: getDouble(key),
onConfigUpdated: onConfigUpdated.where(
(config) => config.updatedKeys.contains(key),
),
activateAndRefetch: () async {
await fetchAndActivate();
return getDouble(key);
},
subscription: filteredOnConfigUpdated(key).listen((event) async {
await activate();
onConfigUpdated(getDouble(key));
}),
);
}

/// Returns a [RemoteParameter] of type [bool].
RemoteParameter<bool> getBoolParameter(String key) {
RemoteParameter<bool> getBoolParameter(
String key, {
required ValueChanged<bool> onConfigUpdated,
}) {
return RemoteParameter<bool>(
value: getBool(key),
onConfigUpdated: onConfigUpdated.where(
(config) => config.updatedKeys.contains(key),
),
activateAndRefetch: () async {
await fetchAndActivate();
return getBool(key);
},
subscription: filteredOnConfigUpdated(key).listen((event) async {
await activate();
onConfigUpdated(getBool(key));
}),
);
}

/// Returns a [RemoteParameter] of type [Map].
RemoteParameter<Map<String, Object?>> getJsonParameter(String key) {
RemoteParameter<Map<String, Object?>> getJsonParameter(
String key, {
required ValueChanged<Map<String, Object?>> onConfigUpdated,
}) {
return RemoteParameter<Map<String, Object?>>(
value: getJson(key),
onConfigUpdated: onConfigUpdated.where(
(config) => config.updatedKeys.contains(key),
),
activateAndRefetch: () async {
await fetchAndActivate();
return getJson(key);
},
subscription: filteredOnConfigUpdated(key).listen((event) async {
await activate();
onConfigUpdated(getJson(key));
}),
);
}

/// Returns a [RemoteParameter] of type [List] of [Map].
RemoteParameter<List<Map<String, Object?>>> getListJsonParameter(String key) {
RemoteParameter<List<Map<String, Object?>>> getListJsonParameter(
String key, {
required ValueChanged<List<Map<String, Object?>>> onConfigUpdated,
}) {
return RemoteParameter<List<Map<String, Object?>>>(
value: getListJson(key),
onConfigUpdated: onConfigUpdated.where(
(config) => config.updatedKeys.contains(key),
),
activateAndRefetch: () async {
await fetchAndActivate();
return getListJson(key);
},
subscription: filteredOnConfigUpdated(key).listen((event) async {
await activate();
onConfigUpdated(getListJson(key));
}),
);
}

/// Returns a [RemoteParameter] of type [T].
RemoteParameter<T> getDataParameter<T extends Object>({
required String key,
RemoteParameter<T> getDataParameter<T extends Object>(
String key, {
required T Function(Map<String, Object?>) fromJson,
required ValueChanged<T> onConfigUpdated,
}) {
return RemoteParameter<T>(
value: getData<T>(key: key, fromJson: fromJson),
onConfigUpdated: onConfigUpdated.where(
(config) => config.updatedKeys.contains(key),
),
activateAndRefetch: () async {
await fetchAndActivate();
return getData<T>(key: key, fromJson: fromJson);
},
subscription: filteredOnConfigUpdated(key).listen((event) async {
await activate();
onConfigUpdated(getData(key: key, fromJson: fromJson));
}),
);
}
}
Loading

0 comments on commit e44f4d1

Please sign in to comment.