diff --git a/CHANGELOG.md b/CHANGELOG.md index c04e7b2..3867518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.7.0 + +* Feat: manual control over updateToggles and sendMetrics + + ## 1.6.0 * Feat: save network calls when context fields don't change diff --git a/README.md b/README.md index 867a622..2b3f53b 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,10 @@ final unleash = UnleashClient( * If `bootstrapOverride` is `true` (by default), any local cached data will be overridden with the bootstrap specified. * If `bootstrapOverride` is `false` any local cached data will not be overridden unless the local cache is empty. +## Manage your own refresh mechanism + +You can opt out of the Unleash feature flag auto-refresh mechanism and metrics update by settings the `refreshInterval` and/or `metricsInterval` options to `0`. +In this case, it becomes your responsibility to call `updateToggles` and/or `sendMetrics` methods. ## Release guide * Run tests: `flutter test` diff --git a/lib/metrics.dart b/lib/metrics.dart index 6d36b21..c0eefd9 100644 --- a/lib/metrics.dart +++ b/lib/metrics.dart @@ -74,7 +74,7 @@ class Metrics { : bucket = Bucket(clock); Future start() async { - if (disableMetrics) { + if (disableMetrics || metricsInterval == 0) { return; } @@ -119,14 +119,14 @@ class Metrics { } } - void sendMetrics() async { + Future sendMetrics() async { bucket.closeBucket(); if (bucket.isEmpty()) { return; } final localBucket = bucket; - // For now, accept that a failing request will lose the metrics. + // For now, accept that a failing request will loose the metrics. bucket = Bucket(clock); try { diff --git a/lib/unleash_proxy_client_flutter.dart b/lib/unleash_proxy_client_flutter.dart index 933e81c..59e42e2 100644 --- a/lib/unleash_proxy_client_flutter.dart +++ b/lib/unleash_proxy_client_flutter.dart @@ -321,6 +321,18 @@ class UnleashClient extends EventEmitter { } } + Future sendMetrics() async { + await metrics.sendMetrics(); + } + + Future updateToggles() async { + if (clientState != ClientState.ready) { + await _waitForEvent('ready'); + await _fetchToggles(); + } + await _fetchToggles(); + } + void _updateContextField(String field, String value) { if (field == 'userId') { context.userId = value; @@ -358,7 +370,7 @@ class UnleashClient extends EventEmitter { emit(readyEvent); } - if (!disableRefresh) { + if (!disableRefresh && refreshInterval > 0) { timer = Timer.periodic(Duration(seconds: refreshInterval), (timer) { _fetchToggles(); }); diff --git a/pubspec.yaml b/pubspec.yaml index dbba715..b6a1b69 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: A Flutter/Dart client that can be used together with the unleash-pr homepage: https://github.com/Unleash/unleash_proxy_client_flutter repository: https://github.com/Unleash/unleash_proxy_client_flutter issue_tracker: https://github.com/Unleash/unleash_proxy_client_flutter -version: 1.6.0 +version: 1.7.0 environment: sdk: ">=2.18.0 <4.0.0" diff --git a/test/unleash_proxy_client_flutter_test.dart b/test/unleash_proxy_client_flutter_test.dart index 98f652d..39214a9 100644 --- a/test/unleash_proxy_client_flutter_test.dart +++ b/test/unleash_proxy_client_flutter_test.dart @@ -444,6 +444,100 @@ void main() { }); }); + test('can manually update toggles', () async { + final getMock = GetMock(); + final unleash = UnleashClient( + url: url, + clientKey: 'proxy-123', + appName: 'flutter-test', + disableRefresh: true, + storageProvider: InMemoryStorageProvider(), + fetcher: getMock); + + var updateEventCount = 0; + unleash.on('update', (_) { + updateEventCount += 1; + }); + + await unleash.start(); + expect(getMock.calledTimes, 1); + expect(updateEventCount, 1); + + await unleash.updateToggles(); + expect(getMock.calledTimes, 2); + expect(updateEventCount, 2); + + await unleash.updateToggles(); + expect(getMock.calledTimes, 3); + expect(updateEventCount, 3); + }); + + test('should not update toggles when not started', () async { + final getMock = GetMock(); + final unleash = UnleashClient( + url: url, + clientKey: 'proxy-123', + appName: 'flutter-test', + refreshInterval: 0, + storageProvider: InMemoryStorageProvider(), + fetcher: getMock); + + unleash.updateToggles(); + expect(getMock.calledTimes, 0); + }); + + test('update toggles should wait on asynchronous start', () async { + final getMock = GetMock(); + final unleash = UnleashClient( + url: url, + clientKey: 'proxy-123', + appName: 'flutter-test', + refreshInterval: 0, + storageProvider: InMemoryStorageProvider(), + fetcher: getMock); + + final completer = Completer(); + // Ready should be registered before we start the client. + unleash.on('ready', (_) { + completer.complete(); + }); + + unleash.start(); + unleash.updateToggles(); + + await completer.future; + + expect(getMock.calledTimes, 1); + + await unleash.updateToggles(); + + expect(getMock.calledTimes, 3); + }); + + test('can manually send metrics', () async { + var postMock = PostMock(payload: '''{}''', status: 200, headers: {}); + final getMock = GetMock(body: mockData, status: 200, headers: {}); + final unleash = UnleashClient( + url: url, + clientKey: 'proxy-123', + appName: 'flutter-test', + refreshInterval: 0, + metricsInterval: 0, + storageProvider: InMemoryStorageProvider(), + fetcher: getMock, + poster: postMock); + + await unleash.start(); + + // no buckets to send + await unleash.sendMetrics(); + expect(postMock.calledTimes, 0); + + unleash.isEnabled('flutter-on'); + await unleash.sendMetrics(); + expect(postMock.calledTimes, 1); + }); + test('stopping client should cancel the timer', () { fakeAsync((async) { final getMock = GetMock();