diff --git a/packages/flutterfire_remote_parameter_fetcher/.gitignore b/packages/flutterfire_remote_parameter_fetcher/.gitignore new file mode 100644 index 0000000..96486fd --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/packages/flutterfire_remote_parameter_fetcher/.metadata b/packages/flutterfire_remote_parameter_fetcher/.metadata new file mode 100644 index 0000000..9d75c7e --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "ead455963c12b453cdb2358cad34969c76daf180" + channel: "stable" + +project_type: package diff --git a/packages/flutterfire_remote_parameter_fetcher/CHANGELOG.md b/packages/flutterfire_remote_parameter_fetcher/CHANGELOG.md new file mode 100644 index 0000000..61e46ea --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* initial release. diff --git a/packages/flutterfire_remote_parameter_fetcher/README.md b/packages/flutterfire_remote_parameter_fetcher/README.md new file mode 100644 index 0000000..02fe8ec --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/packages/flutterfire_remote_parameter_fetcher/analysis_options.yaml b/packages/flutterfire_remote_parameter_fetcher/analysis_options.yaml new file mode 100644 index 0000000..4446f0e --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/analysis_options.yaml @@ -0,0 +1 @@ +include: package:altive_lints/altive_lints.yaml diff --git a/packages/flutterfire_remote_parameter_fetcher/lib/remote_parameter_fetcher.dart b/packages/flutterfire_remote_parameter_fetcher/lib/remote_parameter_fetcher.dart new file mode 100644 index 0000000..a032160 --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/lib/remote_parameter_fetcher.dart @@ -0,0 +1,4 @@ +/// Remote Parameter Fetcher +library remote_parameter_fetcher; + +export '../src/remote_parameter_fetcher.dart'; diff --git a/packages/flutterfire_remote_parameter_fetcher/lib/src/remote_parameter_fetcher.dart b/packages/flutterfire_remote_parameter_fetcher/lib/src/remote_parameter_fetcher.dart new file mode 100644 index 0000000..f0bc6bc --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/lib/src/remote_parameter_fetcher.dart @@ -0,0 +1,94 @@ +import 'dart:convert'; + +import 'package:firebase_remote_config/firebase_remote_config.dart'; + +/// A class that wraps Remote Config. +/// Its role is to "fetch the configured parameters from remote and provide +/// them". +/// +/// Exposes [fetchAndActivate] and configuration methods for Remote Config. +class RemoteParameterFetcher { + RemoteParameterFetcher({ + FirebaseRemoteConfig? rc, + }) : _rc = rc ?? FirebaseRemoteConfig.instance; + + final FirebaseRemoteConfig _rc; + + /// Fetch and activate parameters from remote. + Future fetchAndActivate() async { + return _rc.fetchAndActivate(); + } + + /// Activate parameters fetched from remote. + Future activate() async { + return _rc.activate(); + } + + /// Configure settings related to parameter fetching. + Future configure({ + required Duration fetchTimeout, + required Duration minimumFetchInterval, + }) async { + await _rc.setConfigSettings( + RemoteConfigSettings( + fetchTimeout: fetchTimeout, + minimumFetchInterval: minimumFetchInterval, + ), + ); + } + + /// Set default values for when parameters cannot be fetched from remote. + Future setDefaults(Map defaultParameters) async { + for (final p in defaultParameters.values) { + assert(p is String || p is int || p is double || p is bool); + } + await _rc.setDefaults(defaultParameters); + } + + /// Provide a Stream of updated parameter information. + Stream get onConfigUpdated { + return _rc.onConfigUpdated; + } + + String getString(String key) { + final value = _rc.getString(key); + return value; + } + + int getInt(String key) { + final value = _rc.getInt(key); + return value; + } + + double getDouble(String key) { + final value = _rc.getDouble(key); + return value; + } + + bool getBool(String key) { + final value = _rc.getBool(key); + return value; + } + + /// Returns a JSON string converted to a [Map] type. + Map getJson(String key) { + final value = _rc.getString(key); + return json.decode(value) as Map; + } + + /// Returns a List JSON string converted to a [List] type. + List> getListJson(String key) { + final value = _rc.getString(key); + final list = json.decode(value) as List; + return list.map((dynamic e) => e as Map).toList(); + } + + /// Class Object from JSON. + T getData({ + required String key, + required T Function(Map) fromJson, + }) { + final json = getJson(key); + return fromJson(json); + } +} diff --git a/packages/flutterfire_remote_parameter_fetcher/pubspec.yaml b/packages/flutterfire_remote_parameter_fetcher/pubspec.yaml new file mode 100644 index 0000000..04afa23 --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/pubspec.yaml @@ -0,0 +1,16 @@ +name: flutterfire_remote_parameter_fetcher +description: flutterfire_remote_parameter_fetcher with FlutterFire Remote Config. +publish_to: "none" +version: 0.1.0 + +environment: + sdk: ^3.0.0 + +dependencies: + firebase_remote_config: ^4.3.3 + meta: ^1.9.1 + +dev_dependencies: + altive_lints: ^1.8.1 + flutter_test: + sdk: flutter \ No newline at end of file diff --git a/packages/flutterfire_remote_parameter_fetcher/test/data_class.dart b/packages/flutterfire_remote_parameter_fetcher/test/data_class.dart new file mode 100644 index 0000000..502f604 --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/test/data_class.dart @@ -0,0 +1,24 @@ +import 'package:meta/meta.dart'; + +@immutable +class DataClass { + const DataClass({required this.value}); + + factory DataClass.fromJson(Map json) => + DataClass(value: json['value'] as String); + + final String value; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DataClass && + runtimeType == other.runtimeType && + value == other.value; + + @override + int get hashCode => value.hashCode; + + @override + String toString() => '_Data(value: $value)'; +} diff --git a/packages/flutterfire_remote_parameter_fetcher/test/src/remote_parameter_fetcher_test.dart b/packages/flutterfire_remote_parameter_fetcher/test/src/remote_parameter_fetcher_test.dart new file mode 100644 index 0000000..bea0635 --- /dev/null +++ b/packages/flutterfire_remote_parameter_fetcher/test/src/remote_parameter_fetcher_test.dart @@ -0,0 +1,214 @@ +import 'package:firebase_remote_config/firebase_remote_config.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutterfire_remote_parameter_fetcher/remote_parameter_fetcher.dart'; + +import '../data_class.dart'; + +void main() { + group('setDefaults', () { + test('Error occurs when setting a Class instance as a value', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + expect( + () async => + target.setDefaults({'key': const DataClass(value: 'value')}), + throwsAssertionError, + ); + }); + + test('Error occurs when setting a List type as a value', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + expect( + () async => target.setDefaults({ + 'key': ['ABC', 'DEF'], + }), + throwsAssertionError, + ); + }); + + test('Error occurs when setting a Map type as a value', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + expect( + () async => target.setDefaults({ + 'key': {'key': 'value'}, + }), + throwsAssertionError, + ); + }); + }); + + group('getString', () { + test('Can retrieve the Remote string corresponding to the key', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + const key = 'string_001'; + + final value = target.getString(key); + expect(value, equals('string_value')); + }); + }); + + group('getInt', () { + test('Can retrieve the Remote integer corresponding to the key', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + const key = 'int_001'; + + final value = target.getInt(key); + expect(value, equals(1)); + }); + }); + + group('getDouble', () { + test( + 'Can retrieve the Remote floating-point number corresponding to the key', + () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + const key = 'double_001'; + + final value = target.getDouble(key); + expect(value, equals(0.1)); + }, + ); + }); + + group('getBool', () { + test('Can retrieve the boolean value corresponding to the key', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + const key = 'bool_001'; + + final value = target.getBool(key); + expect(value, isTrue); + }); + }); + + group('getJson', () { + test('Can retrieve the JSON (Map) corresponding to the key', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + const key = 'json_001'; + + final value = target.getJson(key); + expect(value, { + 'value_1': '01', + 'value_2': 2, + 'value_3': 3.0, + 'value_4': true, + }); + }); + }); + + group('getListJson', () { + test('Can retrieve the list of JSON (Map) corresponding to the key', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + const key = 'list_json_001'; + + final value = target.getListJson(key); + expect( + value, + [ + { + 'value_1a': '01a', + 'value_2a': 2, + 'value_3a': 3.0, + 'value_4a': true, + }, + { + 'value_1b': '01b', + 'value_2b': 20, + 'value_3b': 3.5, + 'value_4b': false, + } + ], + ); + }); + }); + + group('getData', () { + test('Can retrieve the class object corresponding to the key', () { + final mockRC = _FakeRemoteConfig(); + final target = RemoteParameterFetcher(rc: mockRC); + + const key = 'data_001'; + + final value = target.getData(key: key, fromJson: DataClass.fromJson); + + expect(value, const DataClass(value: 'tokyo')); + }); + }); +} + +class _FakeRemoteConfig extends Fake implements FirebaseRemoteConfig { + @override + String getString(String key) { + switch (key) { + case 'string_001': + return 'string_value'; + + case 'json_001': + return ''' + { + "value_1": "01", + "value_2": 2, + "value_3": 3.0, + "value_4": true + } + '''; + + case 'list_json_001': + return ''' + [ + { + "value_1a": "01a", + "value_2a": 2, + "value_3a": 3.0, + "value_4a": true + }, + { + "value_1b": "01b", + "value_2b": 20, + "value_3b": 3.5, + "value_4b": false + } + ] + '''; + + case 'data_001': + return ''' + { + "value": "tokyo" + } + '''; + } + throw UnimplementedError(); + } + + @override + int getInt(String key) { + return 1; + } + + @override + double getDouble(String key) { + return 0.1; + } + + @override + bool getBool(String key) { + return true; + } +}