• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

optimizely / optimizely-flutter-sdk / 22760640003

06 Mar 2026 11:03AM UTC coverage: 85.786% (-1.3%) from 87.113%
22760640003

Pull #98

github

web-flow
Merge d65a9d81b into 89bbc6d21
Pull Request #98: fix: ensure FlutterResult and invokeMethod are always called on main thread (iOS 16)

40 of 50 new or added lines in 2 files covered. (80.0%)

688 of 802 relevant lines covered (85.79%)

1.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.09
/lib/src/optimizely_client_wrapper.dart
1
/// **************************************************************************
2
/// Copyright 2022-2023, Optimizely, Inc. and contributors                   *
3
///                                                                          *
4
/// Licensed under the Apache License, Version 2.0 (the "License");          *
5
/// you may not use this file except in compliance with the License.         *
6
/// You may obtain a copy of the License at                                  *
7
///                                                                          *
8
///    http://www.apache.org/licenses/LICENSE-2.0                            *
9
///                                                                          *
10
/// Unless required by applicable law or agreed to in writing, software      *
11
/// distributed under the License is distributed on an "AS IS" BASIS,        *
12
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13
/// See the License for the specific language governing permissions and      *
14
/// limitations under the License.                                           *
15
///**************************************************************************/
16

17
import 'dart:async';
18
import 'package:flutter/foundation.dart';
19
import 'package:flutter/services.dart';
20
import 'package:optimizely_flutter_sdk/optimizely_flutter_sdk.dart';
21
import 'package:optimizely_flutter_sdk/package_info.dart';
22
import 'package:optimizely_flutter_sdk/src/data_objects/activate_listener_response.dart';
23
import 'package:optimizely_flutter_sdk/src/data_objects/activate_response.dart';
24
import 'package:optimizely_flutter_sdk/src/data_objects/base_response.dart';
25
import 'package:optimizely_flutter_sdk/src/data_objects/get_variation_response.dart';
26
import 'package:optimizely_flutter_sdk/src/data_objects/get_vuid_response.dart';
27
import 'package:optimizely_flutter_sdk/src/data_objects/optimizely_config_response.dart';
28
import 'package:optimizely_flutter_sdk/src/utils/constants.dart';
29
import 'package:optimizely_flutter_sdk/src/utils/utils.dart';
30

31
enum ListenerType { activate, track, decision, logEvent, projectConfigUpdate }
32

33
enum ClientPlatform { iOS, android }
34

35
typedef ActivateNotificationCallback = void Function(
36
    ActivateListenerResponse msg);
37
typedef DecisionNotificationCallback = void Function(
38
    DecisionListenerResponse msg);
39
typedef TrackNotificationCallback = void Function(TrackListenerResponse msg);
40
typedef LogEventNotificationCallback = void Function(
41
    LogEventListenerResponse msg);
42
typedef MultiUseCallback = void Function(dynamic msg);
43

44
/// The internal client class for the Optimizely Flutter SDK used by the main OptimizelyFlutterSdk class.
45
class OptimizelyClientWrapper {
46
  static const MethodChannel _channel = MethodChannel('optimizely_flutter_sdk');
47
  static int nextCallbackId = 0;
48
  static Map<String, Map<int, ActivateNotificationCallback>>
49
      activateCallbacksById = {};
6✔
50
  static Map<String, Map<int, DecisionNotificationCallback>>
51
      decisionCallbacksById = {};
2✔
52
  static Map<String, Map<int, TrackNotificationCallback>> trackCallbacksById =
2✔
53
      {};
×
54
  static Map<String, Map<int, LogEventNotificationCallback>>
55
      logEventCallbacksById = {};
2✔
56
  static Map<String, Map<int, MultiUseCallback>> configUpdateCallbacksById = {};
2✔
57

58
  /// Starts Optimizely SDK (Synchronous) with provided sdkKey and options.
59
  static Future<BaseResponse> initializeClient(
2✔
60
      String sdkKey,
61
      EventOptions eventOptions,
62
      int datafilePeriodicDownloadInterval,
63
      Map<ClientPlatform, DatafileHostOptions> datafileHostOptions,
64
      Set<OptimizelyDecideOption> defaultDecideOptions,
65
      OptimizelyLogLevel defaultLogLevel,
66
      SDKSettings sdkSettings,
67
      CmabConfig? cmabConfig,
68
      OptimizelyLogger? logger) async {
69
    _channel.setMethodCallHandler(methodCallHandler);
2✔
70
    final convertedOptions = Utils.convertDecideOptions(defaultDecideOptions);
2✔
71
    final convertedLogLevel = Utils.convertLogLevel(defaultLogLevel);
2✔
72
    const sdkVersion = PackageInfo.version;
73

74
    Map<String, dynamic> requestDict = {
2✔
75
      Constants.sdkKey: sdkKey,
76
      Constants.sdkVersion: sdkVersion,
77
      Constants.datafilePeriodicDownloadInterval:
78
          datafilePeriodicDownloadInterval,
79
      Constants.optimizelyDecideOption: convertedOptions,
80
      Constants.defaultLogLevel: convertedLogLevel,
81
      Constants.eventBatchSize: eventOptions.batchSize,
2✔
82
      Constants.eventTimeInterval: eventOptions.timeInterval,
2✔
83
      Constants.eventMaxQueueSize: eventOptions.maxQueueSize,
2✔
84
    };
85

86
    // Odp Request params
87
    Map<String, dynamic> optimizelySdkSettings = {
2✔
88
      Constants.segmentsCacheSize: sdkSettings.segmentsCacheSize,
2✔
89
      Constants.segmentsCacheTimeoutInSecs:
90
          sdkSettings.segmentsCacheTimeoutInSecs,
2✔
91
      Constants.timeoutForSegmentFetchInSecs:
92
          sdkSettings.timeoutForSegmentFetchInSecs,
2✔
93
      Constants.timeoutForOdpEventInSecs: sdkSettings.timeoutForOdpEventInSecs,
2✔
94
      Constants.disableOdp: sdkSettings.disableOdp,
2✔
95
      Constants.enableVuid: sdkSettings.enableVuid,
2✔
96
    };
97
    requestDict[Constants.optimizelySdkSettings] = optimizelySdkSettings;
2✔
98

99
    // CMAB Config params
100
    if (cmabConfig != null) {
101
      Map<String, dynamic> cmabConfigMap = {
1✔
102
        Constants.cmabCacheSize: cmabConfig.cacheSize,
1✔
103
        Constants.cmabCacheTimeoutInSecs: cmabConfig.cacheTimeoutInSecs,
1✔
104
      };
105
      if (cmabConfig.predictionEndpoint != null) {
1✔
106
        cmabConfigMap[Constants.cmabPredictionEndpoint] =
1✔
107
            cmabConfig.predictionEndpoint;
1✔
108
      }
109
      requestDict[Constants.cmabConfig] = cmabConfigMap;
1✔
110
    }
111

112
    // clearing notification listeners, if they are mapped to the same sdkKey.
113
    activateCallbacksById.remove(sdkKey);
4✔
114
    decisionCallbacksById.remove(sdkKey);
4✔
115
    trackCallbacksById.remove(sdkKey);
4✔
116
    logEventCallbacksById.remove(sdkKey);
4✔
117
    configUpdateCallbacksById.remove(sdkKey);
4✔
118

119
    datafileHostOptions.forEach((platform, datafileoptions) {
3✔
120
      // Pass datafile host only if non empty value for current platform is provided
121
      if (platform.name == defaultTargetPlatform.name &&
4✔
122
          datafileoptions.datafileHostPrefix.isNotEmpty &&
2✔
123
          datafileoptions.datafileHostSuffix.isNotEmpty) {
2✔
124
        requestDict[Constants.datafileHostPrefix] =
1✔
125
            datafileoptions.datafileHostPrefix;
1✔
126
        requestDict[Constants.datafileHostSuffix] =
1✔
127
            datafileoptions.datafileHostSuffix;
1✔
128
      }
129
    });
130

131
    final result = await _invoke(Constants.initializeMethod, requestDict);
2✔
132
    return BaseResponse(result);
2✔
133
  }
134

135
  /// Use the activate method to start an experiment.
136
  ///  The activate call will conditionally activate an experiment for a user based on the provided experiment key and a randomized hash of the provided user ID.
137
  ///  If the user satisfies audience conditions for the experiment and the experiment is valid and running, the function returns the variation the user is bucketed into.
138
  ///  Otherwise, activate returns empty variationKey. Make sure that your code adequately deals with the case when the experiment is not activated (e.g. execute the default variation).
139
  static Future<ActivateResponse> activate(
1✔
140
      String sdkKey, String experimentKey, String userId,
141
      [Map<String, dynamic> attributes = const {}]) async {
142
    final result = await _invoke(Constants.activate, {
2✔
143
      Constants.sdkKey: sdkKey,
144
      Constants.experimentKey: experimentKey,
145
      Constants.userId: userId,
146
      Constants.attributes: Utils.convertToTypedMap(attributes)
1✔
147
    });
148
    return ActivateResponse(result);
1✔
149
  }
150

151
  /// Get variation for experiment and user ID with user attributes.
152
  static Future<GetVariationResponse> getVariation(
1✔
153
      String sdkKey, String experimentKey, String userId,
154
      [Map<String, dynamic> attributes = const {}]) async {
155
    final result = await _invoke(Constants.getVariation, {
2✔
156
      Constants.sdkKey: sdkKey,
157
      Constants.experimentKey: experimentKey,
158
      Constants.userId: userId,
159
      Constants.attributes: Utils.convertToTypedMap(attributes)
1✔
160
    });
161
    return GetVariationResponse(result);
1✔
162
  }
163

164
  /// Get forced variation for experiment and user ID.
165
  static Future<GetVariationResponse> getForcedVariation(
1✔
166
      String sdkKey, String experimentKey, String userId) async {
167
    final result = await _invoke(Constants.getForcedVariation, {
2✔
168
      Constants.sdkKey: sdkKey,
169
      Constants.experimentKey: experimentKey,
170
      Constants.userId: userId,
171
    });
172
    return GetVariationResponse(result);
1✔
173
  }
174

175
  /// Set forced variation for experiment and user ID to variationKey.
176
  static Future<BaseResponse> setForcedVariation(
1✔
177
      String sdkKey, String experimentKey, String userId,
178
      [String variationKey = ""]) async {
179
    Map<String, dynamic> request = {
1✔
180
      Constants.sdkKey: sdkKey,
181
      Constants.experimentKey: experimentKey,
182
      Constants.userId: userId,
183
    };
184
    if (variationKey != "") {
1✔
185
      request[Constants.variationKey] = variationKey;
1✔
186
    }
187
    final result = await _invoke(Constants.setForcedVariation, request);
1✔
188
    return BaseResponse(result);
1✔
189
  }
190

191
  /// Returns a snapshot of the current project configuration.
192
  static Future<OptimizelyConfigResponse> getOptimizelyConfig(
1✔
193
      String sdkKey) async {
194
    final result = await _invoke(
1✔
195
        Constants.getOptimizelyConfigMethod, {Constants.sdkKey: sdkKey});
1✔
196
    return OptimizelyConfigResponse(result);
1✔
197
  }
198

199
  /// Send an event to the ODP server.
200
  static Future<BaseResponse> sendOdpEvent(String sdkKey, String action,
1✔
201
      {String? type,
202
      Map<String, String> identifiers = const {},
203
      Map<String, dynamic> data = const {}}) async {
204
    Map<String, dynamic> request = {
1✔
205
      Constants.sdkKey: sdkKey,
206
      Constants.action: action,
207
      Constants.identifiers: identifiers,
208
      Constants.data: Utils.convertToTypedMap(data)
1✔
209
    };
210
    if (type != null) {
211
      request[Constants.type] = type;
1✔
212
    }
213

214
    final result = await _invoke(Constants.sendOdpEventMethod, request);
1✔
215
    return BaseResponse(result);
1✔
216
  }
217

218
  /// Returns the device vuid (read only)
219
  static Future<GetVuidResponse> getVuid(String sdkKey) async {
1✔
220
    final result = await _invoke(Constants.getVuidMethod, {
2✔
221
      Constants.sdkKey: sdkKey,
222
    });
223
    return GetVuidResponse(result);
1✔
224
  }
225

226
  /// Remove notification listener by notification id.
227
  static Future<BaseResponse> removeNotificationListener(
1✔
228
      String sdkKey, int id) async {
229
    Map<String, dynamic> request = {Constants.sdkKey: sdkKey, Constants.id: id};
1✔
230

231
    activateCallbacksById[sdkKey]?.remove(id);
3✔
232
    decisionCallbacksById[sdkKey]?.remove(id);
3✔
233
    logEventCallbacksById[sdkKey]?.remove(id);
3✔
234
    configUpdateCallbacksById[sdkKey]?.remove(id);
3✔
235
    trackCallbacksById[sdkKey]?.remove(id);
3✔
236

237
    final result = await _invoke(
1✔
238
        Constants.removeNotificationListenerMethod, request);
239
    return BaseResponse(result);
1✔
240
  }
241

242
  /// Remove notification listeners by notification type.
243
  static Future<BaseResponse> clearNotificationListeners(
1✔
244
      String sdkKey, ListenerType listenerType) async {
245
    var callbackIds = _clearAllCallbacks(sdkKey, listenerType);
1✔
246
    Map<String, dynamic> request = {
1✔
247
      Constants.sdkKey: sdkKey,
248
      Constants.type: listenerType.name,
1✔
249
      Constants.callbackIds: callbackIds
250
    };
251
    final result = await _invoke(
1✔
252
        Constants.clearNotificationListenersMethod, request);
253
    return BaseResponse(result);
1✔
254
  }
255

256
  /// Removes all notification listeners.
257
  static Future<BaseResponse> clearAllNotificationListeners(
1✔
258
      String sdkKey) async {
259
    var callbackIds = _clearAllCallbacks(sdkKey);
1✔
260
    Map<String, dynamic> request = {
1✔
261
      Constants.sdkKey: sdkKey,
262
      Constants.callbackIds: callbackIds
263
    };
264
    final result = await _invoke(
1✔
265
        Constants.clearAllNotificationListenersMethod, request);
266
    return BaseResponse(result);
1✔
267
  }
268

269
  /// Returns a success true if optimizely client closed successfully.
270
  static Future<BaseResponse> close(String sdkKey) async {
1✔
271
    final result = await _invoke(
1✔
272
        Constants.close, {Constants.sdkKey: sdkKey});
1✔
273
    return BaseResponse(result);
1✔
274
  }
275

276
  /// Creates a context of the user for which decision APIs will be called.
277
  ///
278
  /// A user context will only be created successfully when the SDK is fully configured using initializeClient.
279
  static Future<OptimizelyUserContext?> createUserContext(String sdkKey,
2✔
280
      {String? userId, Map<String, dynamic> attributes = const {}}) async {
281
    Map<String, dynamic> request = {
2✔
282
      Constants.sdkKey: sdkKey,
283
      Constants.attributes: Utils.convertToTypedMap(attributes)
2✔
284
    };
285
    if (userId != null) {
286
      request[Constants.userId] = userId;
2✔
287
    }
288
    final result = await _invoke(Constants.createUserContextMethod, request);
2✔
289

290
    if (result[Constants.responseSuccess] == true) {
4✔
291
      final response =
292
          Map<String, dynamic>.from(result[Constants.responseResult]);
4✔
293
      return OptimizelyUserContext(
2✔
294
          sdkKey, response[Constants.userContextId], _channel);
2✔
295
    }
296
    return null;
297
  }
298

299
  static List<int> _clearAllCallbacks(String sdkKey,
1✔
300
      [ListenerType? listenerType]) {
301
    var callbackIds = <int>[];
1✔
302
    if (listenerType == null || listenerType == ListenerType.activate) {
1✔
303
      if (activateCallbacksById.containsKey(sdkKey)) {
2✔
304
        callbackIds.addAll(activateCallbacksById[sdkKey]!.keys);
4✔
305
        activateCallbacksById[sdkKey]!.clear();
3✔
306
      }
307
    }
308
    if (listenerType == null || listenerType == ListenerType.decision) {
1✔
309
      if (decisionCallbacksById.containsKey(sdkKey)) {
2✔
310
        callbackIds.addAll(decisionCallbacksById[sdkKey]!.keys);
4✔
311
        decisionCallbacksById[sdkKey]!.clear();
3✔
312
      }
313
    }
314
    if (listenerType == null || listenerType == ListenerType.logEvent) {
1✔
315
      if (logEventCallbacksById.containsKey(sdkKey)) {
2✔
316
        callbackIds.addAll(logEventCallbacksById[sdkKey]!.keys);
4✔
317
        logEventCallbacksById[sdkKey]!.clear();
3✔
318
      }
319
    }
320
    if (listenerType == null ||
321
        listenerType == ListenerType.projectConfigUpdate) {
1✔
322
      if (configUpdateCallbacksById.containsKey(sdkKey)) {
2✔
323
        callbackIds.addAll(configUpdateCallbacksById[sdkKey]!.keys);
4✔
324
        configUpdateCallbacksById[sdkKey]!.clear();
3✔
325
      }
326
    }
327
    if (listenerType == null || listenerType == ListenerType.track) {
1✔
328
      if (trackCallbacksById.containsKey(sdkKey)) {
2✔
329
        callbackIds.addAll(trackCallbacksById[sdkKey]!.keys);
4✔
330
        trackCallbacksById[sdkKey]!.clear();
3✔
331
      }
332
    }
333
    return callbackIds;
334
  }
335

336
  static bool checkCallBackExist(String sdkKey, dynamic callback) {
1✔
337
    if (activateCallbacksById.containsKey(sdkKey)) {
2✔
338
      for (var k in activateCallbacksById[sdkKey]!.keys) {
4✔
339
        if (activateCallbacksById[sdkKey]![k] == callback) {
4✔
340
          return true;
341
        }
342
      }
343
    }
344
    if (decisionCallbacksById.containsKey(sdkKey)) {
2✔
345
      for (var k in decisionCallbacksById[sdkKey]!.keys) {
4✔
346
        if (decisionCallbacksById[sdkKey]![k] == callback) {
4✔
347
          return true;
348
        }
349
      }
350
    }
351
    if (trackCallbacksById.containsKey(sdkKey)) {
2✔
352
      for (var k in trackCallbacksById[sdkKey]!.keys) {
4✔
353
        if (trackCallbacksById[sdkKey]![k] == callback) {
4✔
354
          return true;
355
        }
356
      }
357
    }
358
    if (logEventCallbacksById.containsKey(sdkKey)) {
2✔
359
      for (var k in logEventCallbacksById[sdkKey]!.keys) {
4✔
360
        if (logEventCallbacksById[sdkKey]![k] == callback) {
4✔
361
          return true;
362
        }
363
      }
364
    }
365
    if (configUpdateCallbacksById.containsKey(sdkKey)) {
2✔
366
      for (var k in configUpdateCallbacksById[sdkKey]!.keys) {
4✔
367
        if (configUpdateCallbacksById[sdkKey]![k] == callback) {
4✔
368
          return true;
369
        }
370
      }
371
    }
372
    return false;
373
  }
374

375
  static Future<int> addActivateNotificationListener(
1✔
376
      String sdkKey, ActivateNotificationCallback callback) async {
377
    _channel.setMethodCallHandler(methodCallHandler);
1✔
378

379
    if (checkCallBackExist(sdkKey, callback)) {
1✔
380
      // ignore: avoid_print
381
      return -1;
1✔
382
    }
383

384
    int currentListenerId = nextCallbackId++;
1✔
385
    activateCallbacksById.putIfAbsent(sdkKey, () => {});
4✔
386
    activateCallbacksById[sdkKey]?[currentListenerId] = callback;
3✔
387
    final listenerTypeStr = ListenerType.activate.name;
1✔
388
    await _invoke(Constants.addNotificationListenerMethod, {
2✔
389
      Constants.sdkKey: sdkKey,
390
      Constants.id: currentListenerId,
391
      Constants.type: listenerTypeStr
392
    });
393
    // Returning an id that allows the user to remove the added notification listener.
394
    return currentListenerId;
395
  }
396

397
  static Future<int> addDecisionNotificationListener(
1✔
398
      String sdkKey, DecisionNotificationCallback callback) async {
399
    _channel.setMethodCallHandler(methodCallHandler);
1✔
400

401
    if (checkCallBackExist(sdkKey, callback)) {
1✔
402
      // ignore: avoid_print
403
      print("callback already exists.");
×
404
      return -1;
×
405
    }
406

407
    int currentListenerId = nextCallbackId++;
1✔
408
    decisionCallbacksById.putIfAbsent(sdkKey, () => {});
4✔
409
    decisionCallbacksById[sdkKey]?[currentListenerId] = callback;
3✔
410
    final listenerTypeStr = ListenerType.decision.name;
1✔
411
    await _invoke(Constants.addNotificationListenerMethod, {
2✔
412
      Constants.sdkKey: sdkKey,
413
      Constants.id: currentListenerId,
414
      Constants.type: listenerTypeStr
415
    });
416
    // Returning an id that allows the user to remove the added notification listener
417
    return currentListenerId;
418
  }
419

420
  static Future<int> addTrackNotificationListener(
1✔
421
      String sdkKey, TrackNotificationCallback callback) async {
422
    _channel.setMethodCallHandler(methodCallHandler);
1✔
423

424
    if (checkCallBackExist(sdkKey, callback)) {
1✔
425
      // ignore: avoid_print
426
      return -1;
1✔
427
    }
428

429
    int currentListenerId = nextCallbackId++;
1✔
430
    trackCallbacksById.putIfAbsent(sdkKey, () => {});
4✔
431
    trackCallbacksById[sdkKey]?[currentListenerId] = callback;
3✔
432
    final listenerTypeStr = ListenerType.track.name;
1✔
433
    await _invoke(Constants.addNotificationListenerMethod, {
2✔
434
      Constants.sdkKey: sdkKey,
435
      Constants.id: currentListenerId,
436
      Constants.type: listenerTypeStr
437
    });
438
    // Returning an id that allows the user to remove the added notification listener
439
    return currentListenerId;
440
  }
441

442
  static Future<int> addLogEventNotificationListener(
1✔
443
      String sdkKey, LogEventNotificationCallback callback) async {
444
    _channel.setMethodCallHandler(methodCallHandler);
1✔
445

446
    if (checkCallBackExist(sdkKey, callback)) {
1✔
447
      // ignore: avoid_print
448
      return -1;
1✔
449
    }
450

451
    int currentListenerId = nextCallbackId++;
1✔
452
    logEventCallbacksById.putIfAbsent(sdkKey, () => {});
4✔
453
    logEventCallbacksById[sdkKey]?[currentListenerId] = callback;
3✔
454
    final listenerTypeStr = ListenerType.logEvent.name;
1✔
455
    await _invoke(Constants.addNotificationListenerMethod, {
2✔
456
      Constants.sdkKey: sdkKey,
457
      Constants.id: currentListenerId,
458
      Constants.type: listenerTypeStr
459
    });
460
    // Returning an id that allows the user to remove the added notification listener
461
    return currentListenerId;
462
  }
463

464
  /// Allows user to listen to supported notifications.
465
  static Future<int> addConfigUpdateNotificationListener(
1✔
466
      String sdkKey, MultiUseCallback callback) async {
467
    _channel.setMethodCallHandler(methodCallHandler);
1✔
468

469
    if (checkCallBackExist(sdkKey, callback)) {
1✔
470
      // ignore: avoid_print
471
      return -1;
1✔
472
    }
473

474
    int currentListenerId = nextCallbackId++;
1✔
475
    configUpdateCallbacksById.putIfAbsent(sdkKey, () => {});
4✔
476
    configUpdateCallbacksById[sdkKey]?[currentListenerId] = callback;
3✔
477
    final listenerTypeStr = ListenerType.projectConfigUpdate.name;
1✔
478
    await _invoke(Constants.addNotificationListenerMethod, {
2✔
479
      Constants.sdkKey: sdkKey,
480
      Constants.id: currentListenerId,
481
      Constants.type: listenerTypeStr
482
    });
483
    // Returning an id that allows the user to remove the added notification listener
484
    return currentListenerId;
485
  }
486

487
  /// Safe wrapper around [MethodChannel.invokeMethod].
488
  ///
489
  /// Returns a [Map<String, dynamic>] in all cases — never throws.
490
  /// - If native returns `null` (e.g. dropped response on iOS 16 when
491
  ///   FlutterResult is called from a background thread), returns a
492
  ///   `{success: false}` map instead of crashing with [TypeError].
493
  /// - If native throws [PlatformException], returns `{success: false}`
494
  ///   instead of propagating an unhandled exception to the caller.
495
  ///
496
  /// Every [MethodChannel.invokeMethod] call in this class must go through
497
  /// [_invoke] so that new methods added in future automatically inherit
498
  /// these safety guarantees.
499
  static Future<Map<String, dynamic>> _invoke(
2✔
500
      String method, [dynamic args]) async {
501
    try {
502
      final raw = await _channel.invokeMethod(method, args);
2✔
503
      if (raw == null) {
NEW
504
        return {
×
505
          Constants.responseSuccess: false,
506
          Constants.responseReason:
NEW
507
              'Native channel returned null for method: $method',
×
508
        };
509
      }
510
      return Map<String, dynamic>.from(raw);
2✔
NEW
511
    } on PlatformException catch (e) {
×
NEW
512
      return {
×
513
        Constants.responseSuccess: false,
NEW
514
        Constants.responseReason: e.message ?? e.toString(),
×
515
      };
516
    }
517
  }
518

519
  static Future<void> methodCallHandler(MethodCall call) async {
1✔
520
    final id = call.arguments[Constants.id];
2✔
521
    final sdkKey = call.arguments[Constants.sdkKey];
2✔
522
    final payload = call.arguments[Constants.payload];
2✔
523
    if (id is int && payload != null) {
1✔
524
      switch (call.method) {
1✔
525
        case Constants.activateCallBackListener:
1✔
526
          final response =
527
              ActivateListenerResponse(Map<String, dynamic>.from(payload));
2✔
528
          if (activateCallbacksById.containsKey(sdkKey) &&
2✔
529
              activateCallbacksById[sdkKey]!.containsKey(id)) {
3✔
530
            activateCallbacksById[sdkKey]![id]!(response);
4✔
531
          }
532
          break;
533
        case Constants.decisionCallBackListener:
1✔
534
          final response =
535
              DecisionListenerResponse(Map<String, dynamic>.from(payload));
2✔
536
          if (decisionCallbacksById.containsKey(sdkKey) &&
2✔
537
              decisionCallbacksById[sdkKey]!.containsKey(id)) {
3✔
538
            decisionCallbacksById[sdkKey]![id]!(response);
4✔
539
          }
540
          break;
541
        case Constants.trackCallBackListener:
1✔
542
          final response =
543
              TrackListenerResponse(Map<String, dynamic>.from(payload));
2✔
544
          if (trackCallbacksById.containsKey(sdkKey) &&
2✔
545
              trackCallbacksById[sdkKey]!.containsKey(id)) {
3✔
546
            trackCallbacksById[sdkKey]![id]!(response);
4✔
547
          }
548
          break;
549
        case Constants.logEventCallbackListener:
1✔
550
          final response =
551
              LogEventListenerResponse(Map<String, dynamic>.from(payload));
2✔
552
          if (logEventCallbacksById.containsKey(sdkKey) &&
2✔
553
              logEventCallbacksById[sdkKey]!.containsKey(id)) {
3✔
554
            logEventCallbacksById[sdkKey]![id]!(response);
4✔
555
          }
556
          break;
557
        case Constants.configUpdateCallBackListener:
1✔
558
          if (configUpdateCallbacksById.containsKey(sdkKey) &&
2✔
559
              configUpdateCallbacksById[sdkKey]!.containsKey(id)) {
3✔
560
            configUpdateCallbacksById[sdkKey]![id]!(payload);
4✔
561
          }
562
          break;
563
        default:
564
          // ignore: avoid_print
565
          print('Method ${call.method} not implemented.');
×
566
      }
567
    }
568
  }
569
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc