-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Revised Usage of Remote Parameter Fetcher #18
Changes from all commits
0240a8d
ada9fab
cdee8dc
c784440
2ed9fdb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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(); | ||
} | ||
|
||
|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, as of now, |
||
}, | ||
child: const Text('Activate and refetch'), | ||
), | ||
], | ||
child: Center( | ||
child: Text('RemoteParameter value: $_intParameterValue'), | ||
), | ||
), | ||
), | ||
|
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 |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By passing |
||
|
||
/// 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 |
---|---|---|
|
@@ -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". | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As noted in the comment, the original method format created different Streams each time it was called, resulting in only the latest Stream functioning effectively. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
/// Filter the Stream of updated parameter information by key. | ||
Stream<RemoteConfigUpdate> filteredOnConfigUpdated(String key) { | ||
return onConfigUpdated.where((config) => config.updatedKeys.contains(key)); | ||
} | ||
|
||
@visibleForTesting | ||
|
@@ -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, | ||
}) { | ||
Comment on lines
+117
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of using @riverpod
String stringParameter(StringParameterRef ref) {
final fetcher = ref.watch(remoteParameterFetcherProvider);
final parameter = fetcher.getStringParameter(
stringParameterKey,
onConfigUpdated: (value) => ref.state = value,
);
ref.onDispose(parameter.dispose);
return Uri.parse(parameter.value);
} |
||
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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://firebase.google.com/docs/remote-config/get-started?platform=flutter#add-real-time-listener |
||
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)); | ||
}), | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the implementation!