-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathld_client.dart
More file actions
361 lines (331 loc) · 15.4 KB
/
ld_client.dart
File metadata and controls
361 lines (331 loc) · 15.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
import 'package:launchdarkly_common_client/launchdarkly_common_client.dart';
import 'config/defaults/flutter_default_config.dart';
import 'config/ld_config.dart';
import 'connection_manager.dart';
import 'flutter_state_detector.dart';
import 'persistence/shared_preferences_persistence.dart';
import 'platform_env_reporter.dart';
const sdkName = 'FlutterClientSdk';
const sdkVersion = '4.15.0'; // x-release-please-version
/// The main interface for the LaunchDarkly Flutter SDK.
///
/// To setup the SDK before use, construct an [LDConfig] and
/// an initial [LDContext] with [LDContextBuilder].
/// These should be passed to [LDClient(config, context)] and then [start]
/// should be called. A basic example:
/// ```dart
/// final config = LDConfig(CredentialSource.fromEnvironment(),
/// AutoEnvAttributes.enabled);
/// final context = LDContextBuilder()
/// .kind("user", <USER_KEY>)
/// .kind("company", <COMP_KEY>)
/// .build();
/// final client = LDClient(config, context);
/// await client.start().timeout(const Duration(seconds: 5), onTimeout: () => false);
/// ```
///
/// After initialization, the SDK can evaluate feature flags from the
/// LaunchDarkly dashboard against the current context, record custom events,
/// and provides various status configuration and monitoring utilities.
///
/// See the individual class and method documentation for more details.
///
/// This is an interface class so that it can be mocked for testing, but it
/// cannot be extended.
interface class LDClient {
late final LDCommonClient _client;
late final ConnectionManager _connectionManager;
/// Stream which emits data source status changes.
///
/// You can start listening to data source changes before calling the
/// [start] method. Events will be emitted for states beyond the default
/// initializing state.
Stream<DataSourceStatus> get dataSourceStatusChanges {
return _client.dataSourceStatusChanges;
}
/// Get the current data source status.
DataSourceStatus get dataSourceStatus => _client.dataSourceStatus;
/// Stream which emits flag changes.
///
/// You can start listening for flag changes before calling [start]. If you
/// do, then you will get change notifications for all flags, including
/// those that are loaded from cache.
Stream<FlagsChangedEvent> get flagChanges {
return _client.flagChanges;
}
/// Construct the client instance.
///
/// For detailed instructions please refer to the class [LDClient] documentation.
LDClient(LDConfig config, LDContext context) {
final platformImplementation = CommonPlatform(
persistence: SharedPreferencesPersistence(),
platformEnvReporter: PlatformEnvReporter(),
autoEnvAttributes:
config.autoEnvAttributes == AutoEnvAttributes.enabled);
final pluginHooks = safeGetHooks(config.plugins, config.logger);
final combined = combineHooks(config.hooks, pluginHooks);
_client = LDCommonClient(config, platformImplementation, context,
DiagnosticSdkData(name: sdkName, version: sdkVersion),
hooks: combined);
_connectionManager = ConnectionManager(
logger: _client.logger,
config: ConnectionManagerConfig(
initialConnectionMode: config.offline
? ConnectionMode.offline
: config.dataSourceConfig.initialConnectionMode,
disableAutomaticBackgroundHandling:
config.offline || !config.applicationEvents.backgrounding,
disableAutomaticNetworkHandling:
config.offline || !config.applicationEvents.networkAvailability,
runInBackground:
FlutterDefaultConfig.connectionManagerConfig.runInBackground),
destination: DartClientAdapter(_client),
detector: FlutterStateDetector());
final sdkPluginMetadata =
PluginSdkMetadata(name: sdkName, version: sdkVersion);
safeRegisterPlugins(
this,
PluginEnvironmentMetadata(
sdk: sdkPluginMetadata,
application: config.applicationInfo,
credential: PluginCredentialInfo(
type: _client.credentialType, value: config.sdkCredential)),
config.plugins,
config.logger);
}
/// Initialize the SDK.
///
/// This should be called before using the SDK to evaluate flags. Note that
/// the SDK requires the flutter bindings to allow use of native plugins for
/// handling device state and storage. In order to start the SDK before
/// `runApp` is called, you must ensure the binding is initialized with
/// `WidgetsFlutterBinding.ensureInitialized`.
///
/// During startup, if the initial context contains any anonymous contexts
/// without keys (i.e. [LDAttributesBuilder.anonymous] was set to true and
/// no key was provided to [LDContextBuilder.kind]), the SDK will
/// automatically generate and persist a stable key for each such context.
///
/// The [start] function can take an indeterminate amount of time to
/// complete. For instance if the SDK is started while a device is in airplane
/// mode, then it may not complete until some time in the future when the
/// device leaves airplane mode. For this reason it is recommended to use
/// a timeout when waiting for SDK initialization.
///
/// For example:
/// ```dart
/// await client.start().timeout(const Duration(seconds: 30));
/// ```
/// The [waitForNetworkResults] parameters, when true, indicates that the SDK
/// will attempt to wait for values from LaunchDarkly instead of depending
/// on cached values. The cached values will still be loaded, but the future
/// returned by this function will not resolve as a result of those cached
/// values being loaded. Generally this option should NOT be used and instead
/// flag changes should be listened to. It the client is set to offline mode,
/// then this option is ignored.
///
/// If [waitForNetworkResults] is true, and an error is encountered, then
/// false may be returned even if cached values were loaded.
Future<bool> start({bool waitForNetworkResults = false}) async {
return _client.start(waitForNetworkResults: waitForNetworkResults);
}
/// Changes the active context.
///
/// When the context is changed, the SDK will load flag values for the context
/// from a local cache if available, while initiating a connection to retrieve
/// the most current flag values. An event will be queued to be sent to the
/// service containing the public [LDContext] fields for indexing on the
/// dashboard.
///
/// If the provided context contains any anonymous contexts without keys
/// (i.e. [LDAttributesBuilder.anonymous] was set to true and no key was
/// provided to [LDContextBuilder.kind]), the SDK will automatically generate
/// and persist a stable key for each such context before processing the
/// identify.
///
/// A context with the same kinds and same keys will use the same cached
/// context.
///
/// This returned future can be awaited to wait for the identify process to
/// be complete. As with [start] this can take an extended period if there
/// is not network availability, so a timeout is recommended.
///
/// The [waitForNetworkResults] parameters, when true, indicates that the SDK
/// will attempt to wait for values from LaunchDarkly instead of depending
/// on cached values. The cached values will still be loaded, but the future
/// returned by this function will not resolve as a result of those cached
/// values being loaded. Generally this option should NOT be used and instead
/// flag changes should be listened to. It the client is set to offline mode,
/// then this option is ignored.
///
/// If [waitForNetworkResults] is true, and an error is encountered, then
/// [IdentifyError] may be returned even if cached values were loaded.
///
/// The identify will complete with 1 of three possible values:
/// [IdentifyComplete], [IdentifySuperseded], or [IdentifyError].
///
/// [IdentifyComplete] means that the SDK has managed to identify the user
/// and either is using cached values or has received new values from
/// LaunchDarkly.
///
/// [IdentifySuperseded] means that additional [identify] calls have been made
/// and this specific call has been cancelled. If identify multiple contexts
/// without waiting for the previous identify to complete, then you may get
/// this result. For instance if you called identify 10 times rapidly, then
/// it is likely that 2 total identifies would complete, the first one and the
/// last one. The intermediates would be cancelled for performance.
///
/// [IdentifyError] this means that the identify has permanently failed. For
/// instance the SDK key is no longer valid.
Future<IdentifyResult> identify(LDContext context,
{bool waitForNetworkResults = false}) async {
return _client.identify(context,
waitForNetworkResults: waitForNetworkResults);
}
/// Track custom events associated with the current context for data export or
/// experimentation.
///
/// The [eventName] is the key associated with the event or experiment.
/// [data] is an optional parameter for additional data to include in the
/// event for data export. [metricValue] can be used to record numeric metric
/// for experimentation.
void track(String eventName, {LDValue? data, num? metricValue}) {
_client.track(eventName, data: data, metricValue: metricValue);
}
/// Returns the value of flag [flagKey] for the current context as a bool.
///
/// Will return the provided [defaultValue] if the flag is missing, not a
/// bool, or if some error occurs.
bool boolVariation(String flagKey, bool defaultValue) {
return _client.boolVariation(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as a bool,
/// along with information about the resultant value.
///
/// See [LDEvaluationDetail] for more information on the returned value.
/// Note that [DataSourceConfig.evaluationReasons] must have been set to `true`
/// to request the additional evaluation information from the backend.
LDEvaluationDetail<bool> boolVariationDetail(
String flagKey, bool defaultValue) {
return _client.boolVariationDetail(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as an int.
///
/// Will return the provided [defaultValue] if the flag is missing, not a
/// number, or if some error occurs.
int intVariation(String flagKey, int defaultValue) {
return _client.intVariation(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as an int,
/// along with information about the resultant value.
///
/// See [LDEvaluationDetail] for more information on the returned value.
/// Note that [DataSourceConfig.evaluationReasons] must have been set to `true`
/// to request the additional evaluation information from the backend.
LDEvaluationDetail<int> intVariationDetail(String flagKey, int defaultValue) {
return _client.intVariationDetail(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as a double.
///
/// Will return the provided [defaultValue] if the flag is missing, not a
/// number, or if some error occurs.
double doubleVariation(String flagKey, double defaultValue) {
return _client.doubleVariation(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as a double,
/// along with information about the resultant value.
///
/// See [LDEvaluationDetail] for more information on the returned value. Note
/// that [DataSourceConfig.evaluationReasons] must have been set to `true` to
/// request the additional evaluation information from the backend.
LDEvaluationDetail<double> doubleVariationDetail(
String flagKey, double defaultValue) {
return _client.doubleVariationDetail(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as a string.
///
/// Will return the provided [defaultValue] if the flag is missing, not a
/// string, or if some error occurs.
String stringVariation(String flagKey, String defaultValue) {
return _client.stringVariation(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as a string,
/// along with information about the resultant value.
///
/// See [LDEvaluationDetail] for more information on the returned value. Note
/// that [DataSourceConfig.evaluationReasons] must have been set to `true` to
/// request the additional evaluation information from the backend.
LDEvaluationDetail<String> stringVariationDetail(
String flagKey, String defaultValue) {
return _client.stringVariationDetail(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as an [LDValue].
///
/// Will return the provided [defaultValue] if the flag is missing, or if some
/// error occurs.
LDValue jsonVariation(String flagKey, LDValue defaultValue) {
return _client.jsonVariation(flagKey, defaultValue);
}
/// Returns the value of flag [flagKey] for the current context as an
/// [LDValue], along with information about the resultant value.
///
/// See [LDEvaluationDetail] for more information on the returned value.
/// Note that [DataSourceConfig.evaluationReasons] must have been set to `true`
/// to request the additional evaluation information from the backend.
LDEvaluationDetail<LDValue> jsonVariationDetail(
String flagKey, LDValue defaultValue) {
return _client.jsonVariationDetail(flagKey, defaultValue);
}
/// Returns a map of all feature flags for the current context, without
/// sending evaluation events to LaunchDarkly.
///
/// The resultant map contains an entry for each known flag, the key being
/// the flag's key and the value being its
/// value as an [LDValue].
Map<String, LDValue> allFlags() {
return _client.allFlags();
}
/// Triggers immediate sending of pending events to LaunchDarkly.
///
/// Note that the future completes after the native SDK is requested to
/// perform a flush, not when the said flush completes.
Future<void> flush() async {
return _client.flush();
}
/// Returns whether the SDK is currently configured not to make network
/// connections.
///
/// This is specifically if the client has been set offline, or has been
/// instructed to never go online.
///
/// For more detailed status information use [dataSourceStatus].
bool get offline => _client.offline;
/// Set the SDK to be offline/offline. When the SDK is set offline it will
/// stop receiving updates and sending analytic and diagnostic events.
set offline(bool offline) {
_connectionManager.offline = offline;
}
/// Check if the SDK has finished initialization.
///
/// This does not indicate that initialization was successful, but that it is
/// finished. It has either completed successfully, or encountered an
/// unrecoverable error.
///
/// Generally the future returned from [start] should be used instead or this
/// property.
bool get initialized => _client.initialized;
/// Permanently shuts down the client.
///
/// It's not normally necessary to explicitly shut down the client.
Future<void> close() async {
await _client.close();
_connectionManager.dispose();
}
/// Add a hook to SDK instance.
///
/// Hooks allow for the addition of SDK observability at specific points
/// of execution.
void addHook(Hook hook) {
_client.addHook(hook);
}
}