Skip to content

Commit 6e7a26d

Browse files
authored
feat: Add support for hooks. (#220)
Add support for hooks to the SDK. Example of a hook with all methods that logs things. ```dart final class TestHook extends Hook { final HookMetadata _metadata = const HookMetadata(name: "test-hook"); @OverRide HookMetadata get metadata => _metadata; @OverRide UnmodifiableMapView<String, LDValue> beforeEvaluation( EvaluationSeriesContext hookContext, UnmodifiableMapView<String, LDValue> data) { print( 'TestHook.beforeEvaluation called with: hookContext=$hookContext, data=$data'); return super.beforeEvaluation(hookContext, data); } @OverRide UnmodifiableMapView<String, LDValue> afterEvaluation( EvaluationSeriesContext hookContext, UnmodifiableMapView<String, LDValue> data, LDEvaluationDetail<LDValue> detail) { print( 'TestHook.afterEvaluation called with: hookContext=$hookContext, data=$data, detail=$detail'); return super.afterEvaluation(hookContext, data, detail); } @OverRide UnmodifiableMapView<String, LDValue> beforeIdentify( IdentifySeriesContext hookContext, UnmodifiableMapView<String, LDValue> data) { print( 'TestHook.beforeIdentify called with: hookContext=$hookContext, data=$data'); return super.beforeIdentify(hookContext, data); } @OverRide UnmodifiableMapView<String, LDValue> afterIdentify( IdentifySeriesContext hookContext, UnmodifiableMapView<String, LDValue> data, IdentifyResult result) { print( 'TestHook.afterIdentify called with: hookContext=$hookContext, data=$data, result=$result'); return super.afterIdentify(hookContext, data, result); } @OverRide void afterTrack(TrackSeriesContext hookContext) { print('TestHook.afterTrack called with: hookContext=$hookContext'); super.afterTrack(hookContext); } } ``` Minimal hook: ```dart Example of a hook with all methods that logs things. ```dart final class TestHook extends Hook { final HookMetadata _metadata = const HookMetadata(name: "test-hook"); @OverRide HookMetadata get metadata => _metadata; ```
1 parent 71b522b commit 6e7a26d

File tree

13 files changed

+1761
-65
lines changed

13 files changed

+1761
-65
lines changed

apps/flutter_client_contract_test_service/pubspec.lock

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -388,29 +388,26 @@ packages:
388388
source: hosted
389389
version: "6.8.0"
390390
launchdarkly_common_client:
391-
dependency: transitive
391+
dependency: "direct overridden"
392392
description:
393-
name: launchdarkly_common_client
394-
sha256: a48e52d8aebf8a12fb8816b4776a75d68dffeaaeb3b9a9b35189467218fecd35
395-
url: "https://pub.dev"
396-
source: hosted
393+
path: "../../packages/common_client"
394+
relative: true
395+
source: path
397396
version: "1.6.2"
398397
launchdarkly_dart_common:
399-
dependency: transitive
398+
dependency: "direct overridden"
400399
description:
401-
name: launchdarkly_dart_common
402-
sha256: a5275ba48635364690181d7172d679889fffca501a7dbec74992c4057357ac6f
403-
url: "https://pub.dev"
404-
source: hosted
400+
path: "../../packages/common"
401+
relative: true
402+
source: path
405403
version: "1.6.1"
406404
launchdarkly_event_source_client:
407-
dependency: transitive
405+
dependency: "direct overridden"
408406
description:
409-
name: launchdarkly_event_source_client
410-
sha256: "7931ffed3d38272db1bf768e351bc49bda8a391b6811a6e4a7004149c2aee1d0"
411-
url: "https://pub.dev"
412-
source: hosted
413-
version: "1.2.1"
407+
path: "../../packages/event_source_client"
408+
relative: true
409+
source: path
410+
version: "2.0.1"
414411
launchdarkly_flutter_client_sdk:
415412
dependency: "direct main"
416413
description:

apps/sse_contract_test_service/pubspec.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ packages:
311311
path: "../../packages/event_source_client"
312312
relative: true
313313
source: path
314-
version: "2.0.0"
314+
version: "2.0.1"
315315
lints:
316316
dependency: "direct dev"
317317
description:

packages/common_client/lib/launchdarkly_common_client.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,11 @@ export 'src/config/credential/credential_source.dart' show CredentialSource;
4646
export 'src/connection_mode.dart' show ConnectionMode;
4747
export 'src/data_sources/data_source_status.dart'
4848
show DataSourceStatusErrorInfo, DataSourceStatus, DataSourceState;
49+
50+
export 'src/hooks/hook.dart'
51+
show
52+
Hook,
53+
HookMetadata,
54+
EvaluationSeriesContext,
55+
IdentifySeriesContext,
56+
TrackSeriesContext;
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import 'dart:collection';
2+
3+
import 'package:launchdarkly_dart_common/launchdarkly_dart_common.dart'
4+
show LDValue, LDContext, LDEvaluationDetail;
5+
6+
import '../ld_common_client.dart' show IdentifyResult;
7+
8+
/// Meta-data about a hook implementation.
9+
final class HookMetadata {
10+
/// The name of the hook.
11+
final String name;
12+
13+
/// Construct a new hook metadata instance.
14+
/// Implementation note: If more fields are added then they must not be
15+
/// required constructor parameters for compatibility purposes.
16+
const HookMetadata({required this.name});
17+
18+
@override
19+
String toString() {
20+
return 'HookMetadata{name: $name}';
21+
}
22+
}
23+
24+
/// Contextual information provided to the evaluation stages.
25+
final class EvaluationSeriesContext {
26+
/// The flag key the evaluation is for.
27+
final String flagKey;
28+
29+
/// The context for the evaluation. Optional in case an evaluation is
30+
/// performed before the SDK has been started, or an invalid context is
31+
/// used.
32+
final LDContext? context;
33+
34+
/// The default value that was provided for the evaluation.
35+
final LDValue defaultValue;
36+
37+
/// The name of the method that was invoked to perform the evaluation.
38+
final String method;
39+
40+
/// The environment ID associated with the evaluation if available.
41+
final String? environmentId;
42+
43+
EvaluationSeriesContext.internal(
44+
{required this.flagKey,
45+
required this.context,
46+
required this.defaultValue,
47+
required this.method,
48+
required this.environmentId});
49+
50+
@override
51+
String toString() {
52+
return 'EvaluationSeriesContext{flagKey: $flagKey, context: $context,'
53+
' defaultValue: $defaultValue, method: $method,'
54+
' environmentId: $environmentId}';
55+
}
56+
}
57+
58+
/// Contextual information provided to identify stages.
59+
final class IdentifySeriesContext {
60+
/// The context associated with the identify operation.
61+
final LDContext context;
62+
63+
// Implementation note: Timeout not managed by SDK, so not included.
64+
// If the timeout does become managed by the SDK, then it should be
65+
// added here.
66+
67+
IdentifySeriesContext.internal({required this.context});
68+
69+
@override
70+
String toString() {
71+
return 'IdentifySeriesContext{context: $context}';
72+
}
73+
}
74+
75+
/// Contextual information provided to track stages.
76+
final class TrackSeriesContext {
77+
/// The key for the event being tracked.
78+
final String key;
79+
80+
/// The context associated with the track operation.
81+
final LDContext context;
82+
83+
/// The data associated with the track operation.
84+
final LDValue? data;
85+
86+
/// The metric value associated with the track operation.
87+
final num? numericValue;
88+
89+
TrackSeriesContext.internal(
90+
{required this.key, required this.context, this.data, this.numericValue});
91+
92+
@override
93+
String toString() {
94+
return 'TrackSeriesContext{key: $key, context: $context,'
95+
' data: $data, numericValue: $numericValue}';
96+
}
97+
}
98+
99+
/// Base class for extending SDK functionality via hooks.
100+
/// All hook implementations must derive from this class.
101+
///
102+
/// Default implementations are provided for each stage and an implementer
103+
/// should override at least one of the stage methods.
104+
///
105+
/// All implementations must implement the metadata getter.
106+
abstract base class Hook {
107+
/// Metadata associated with this hook.
108+
///
109+
/// Hook implementations must implement this property.
110+
/// ```dart
111+
/// final _metadata = HookMetadata(name: 'MyHookName');
112+
///
113+
/// @override
114+
/// HookMetadata get metadata => _metadata;
115+
/// ```
116+
HookMetadata get metadata;
117+
118+
/// Construct a new hook instance.
119+
Hook();
120+
121+
/// This method is called during the execution of a variation method before
122+
/// the flag value has been determined. The method is executed synchronously.
123+
///
124+
/// [hookContext] Contains information about the evaluation being performed.
125+
/// [data] A record associated with each stage of hook invocations. Each stage
126+
/// is called with the data of the previous stage for a series. The input
127+
/// record should not be modified.
128+
///
129+
/// Returns data to use when executing the next state of the hook in the
130+
/// evaluation series. It is recommended to expand the previous input into the
131+
/// return. This helps ensure your stage remains compatible moving forward as
132+
/// more stages are added.
133+
///
134+
/// ```dart
135+
/// Map<String, LDValue> newData = Map.from(data);
136+
/// newData['new-key'] = LDValue.ofString('new-value');
137+
/// return UnmodifiableMapView(newData);
138+
/// ```
139+
UnmodifiableMapView<String, LDValue> beforeEvaluation(
140+
EvaluationSeriesContext hookContext,
141+
UnmodifiableMapView<String, LDValue> data) {
142+
return data;
143+
}
144+
145+
/// This method is called during the execution of the variation method
146+
/// after the flag value has been determined. The method is executed
147+
/// synchronously.
148+
///
149+
/// [hookContext] Contains information about the evaluation being performed.
150+
/// [data] A record associated with each stage of hook invocations. Each stage
151+
/// is called with the data of the previous stage for a series. The input
152+
/// record should not be modified.
153+
/// [detail] The result of the evaluation.
154+
///
155+
/// Returns data to use when executing the next state of the hook in the
156+
/// evaluation series. It is recommended to expand the previous input into the
157+
/// return. This helps ensure your stage remains compatible moving forward as
158+
/// more stages are added.
159+
///
160+
/// ```dart
161+
/// Map<String, LDValue> newData = Map.from(data);
162+
/// newData['new-key'] = LDValue.ofString('new-value');
163+
/// return UnmodifiableMapView(newData);
164+
/// ```
165+
UnmodifiableMapView<String, LDValue> afterEvaluation(
166+
EvaluationSeriesContext hookContext,
167+
UnmodifiableMapView<String, LDValue> data,
168+
LDEvaluationDetail<LDValue> detail) {
169+
return data;
170+
}
171+
172+
/// This method is called during the execution of the identify process before
173+
/// the operation completes, but after any context modifications are
174+
/// performed.
175+
///
176+
/// [hookContext] Contains information about the evaluation being performed.
177+
/// [data] A record associated with each stage of hook invocations. Each stage
178+
/// is called with the data of the previous stage for a series. The input
179+
/// record should not be modified.
180+
///
181+
/// Returns data to use when executing the next state of the hook in the
182+
/// evaluation series. It is recommended to expand the previous input into the
183+
/// return. This helps ensure your stage remains compatible moving forward as
184+
/// more stages are added.
185+
///
186+
/// ```dart
187+
/// Map<String, LDValue> newData = Map.from(data);
188+
/// newData['new-key'] = LDValue.ofString('new-value');
189+
/// return UnmodifiableMapView(newData);
190+
/// ```
191+
UnmodifiableMapView<String, LDValue> beforeIdentify(
192+
IdentifySeriesContext hookContext,
193+
UnmodifiableMapView<String, LDValue> data) {
194+
return data;
195+
}
196+
197+
/// This method is called during the execution of the identify process, after
198+
/// the operation completes.
199+
///
200+
/// [hookContext] Contains information about the evaluation being performed.
201+
/// [data] A record associated with each stage of hook invocations. Each stage
202+
/// is called with the data of the previous stage for a series. The input
203+
/// record should not be modified.
204+
/// [result] The result of the identify operation.
205+
///
206+
/// Returns data to use when executing the next state of the hook in the
207+
/// evaluation series. It is recommended to expand the previous input into the
208+
/// return. This helps ensure your stage remains compatible moving forward as
209+
/// more stages are added.
210+
///
211+
/// ```dart
212+
/// Map<String, LDValue> newData = Map.from(data);
213+
/// newData['new-key'] = LDValue.ofString('new-value');
214+
/// return UnmodifiableMapView(newData);
215+
/// ```
216+
UnmodifiableMapView<String, LDValue> afterIdentify(
217+
IdentifySeriesContext hookContext,
218+
UnmodifiableMapView<String, LDValue> data,
219+
IdentifyResult result) {
220+
return data;
221+
}
222+
223+
/// This method is called during the execution of the track process after the
224+
/// event has been enqueued.
225+
///
226+
/// [hookContext] Contains information about the track operation being
227+
/// performed. This is not mutable.
228+
void afterTrack(TrackSeriesContext hookContext) {}
229+
}

0 commit comments

Comments
 (0)