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

Duit-Foundation / flutter_duit / 21026233449

15 Jan 2026 09:26AM UTC coverage: 80.184% (-8.3%) from 88.441%
21026233449

push

github

web-flow
feat: Capability-based API migration pt2 (#323)

162 of 408 new or added lines in 23 files covered. (39.71%)

328 existing lines in 17 files now uncovered.

4354 of 5430 relevant lines covered (80.18%)

34.33 hits per line

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

0.0
/lib/src/duit_impl/driver.dart
1
import "dart:async";
2

3
import "package:duit_kernel/duit_kernel.dart";
4
import "package:flutter/material.dart";
5
import "package:flutter/services.dart";
6
import "package:flutter_duit/flutter_duit.dart";
7
import "package:flutter_duit/src/capabilities/index.dart";
8
import "package:flutter_duit/src/ui/index.dart";
9
import "package:flutter_duit/src/view_manager/index.dart";
10
import "package:flutter_duit/src/transport/index.dart";
11

12
@Deprecated("Will be removed in the next major release. Use [XDriver] instead")
13
// ignore: missing_override_of_must_be_overridden
14
final class DuitDriver extends UIDriver with DriverHooks {
15
  @visibleForTesting
16
  @override
17
  final String source;
18

19
  @visibleForTesting
20
  @override
21
  Transport? transport;
22

23
  @visibleForTesting
24
  @override
25
  TransportOptions transportOptions;
26

27
  @visibleForTesting
28
  @override
29
  late BuildContext buildContext;
30

UNCOV
31
  @override
×
32
  // ignore: avoid_setters_without_getters
33
  set context(BuildContext value) {
UNCOV
34
    buildContext = value;
×
35
  }
36

37
  final _eventStreamController = StreamController<UIDriverEvent>.broadcast();
38

UNCOV
39
  @override
×
UNCOV
40
  Stream<UIDriverEvent> get eventStream => _eventStreamController.stream;
×
41

42
  @visibleForTesting
43
  @override
44
  ExternalEventHandler? externalEventHandler;
45

46
  @visibleForTesting
47
  @override
48
  ScriptRunner? scriptRunner;
49

50
  @visibleForTesting
51
  final Map<String, dynamic>? initialRequestPayload;
52

53
  late final bool _useStaticContent;
54
  bool _isChannelInitialized = false, _isDriverInitialized = false;
55

56
  @visibleForTesting
57
  late final Map<String, dynamic>? content;
58

59
  @visibleForTesting
60
  @override
61
  late final ActionExecutor actionExecutor;
62

63
  @visibleForTesting
64
  @override
65
  late final EventResolver eventResolver;
66

67
  @visibleForTesting
68
  @override
69
  MethodChannel? driverChannel;
70

71
  @visibleForTesting
72
  @override
73
  late final bool isModule;
74

75
  @visibleForTesting
76
  @override
77
  @Deprecated("Use [LoggingCapabilityDelegate] instead")
78
  DebugLogger? logger;
79

80
  late ViewManager _viewManager;
81

82
  final _dataSources = <int, StreamSubscription<ServerEvent>>{};
83

84
  final _focusNodeManager = DuitFocusNodeManager();
85
  final LoggingCapabilityDelegate _loggingManager;
86

UNCOV
87
  DuitDriver(
×
88
    this.source, {
89
    required this.transportOptions,
90
    this.externalEventHandler,
91
    this.initialRequestPayload,
92
    this.logger,
93
    EventResolver? customEventResolver,
94
    ActionExecutor? customActionExecutor,
95
    @Deprecated("Use [LoggingCapabilityDelegate] instead")
96
    DebugLogger? customLogger,
97
    bool shared = false,
98
    LoggingCapabilityDelegate? loggingManager,
99
  }) : _loggingManager = loggingManager ?? const LoggingManager() {
UNCOV
100
    _useStaticContent = false;
×
UNCOV
101
    actionExecutor = customActionExecutor ??
×
UNCOV
102
        DefaultActionExecutor(
×
103
          driver: this,
104
          // ignore: deprecated_member_use_from_same_package
UNCOV
105
          logger: logger,
×
106
        );
UNCOV
107
    eventResolver = customEventResolver ??
×
UNCOV
108
        DefaultEventResolver(
×
109
          driver: this,
110
          // ignore: deprecated_member_use_from_same_package
UNCOV
111
          logger: logger,
×
112
        );
UNCOV
113
    isModule = false;
×
UNCOV
114
    _viewManager = shared ? MultiViewManager() : SimpleViewManager();
×
115
  }
116

117
  /// Creates a new instance of [DuitDriver] with the specified [content] without establishing a initial transport connection.
UNCOV
118
  DuitDriver.static(
×
119
    this.content, {
120
    required this.transportOptions,
121
    this.externalEventHandler,
122
    this.logger,
123
    EventResolver? customEventResolver,
124
    ActionExecutor? customActionExecutor,
125
    @Deprecated("Use [LoggingCapabilityDelegate] instead")
126
    DebugLogger? customLogger,
127
    this.source = "",
128
    this.initialRequestPayload,
129
    bool shared = false,
130
    LoggingCapabilityDelegate? loggingManager,
131
  }) : _loggingManager = loggingManager ?? const LoggingManager() {
UNCOV
132
    _useStaticContent = true;
×
UNCOV
133
    isModule = false;
×
UNCOV
134
    eventResolver = customEventResolver ??
×
UNCOV
135
        DefaultEventResolver(
×
136
          driver: this,
137
          // ignore: deprecated_member_use_from_same_package
UNCOV
138
          logger: logger,
×
139
        );
UNCOV
140
    actionExecutor = customActionExecutor ??
×
UNCOV
141
        DefaultActionExecutor(
×
142
          driver: this,
143
          // ignore: deprecated_member_use_from_same_package
UNCOV
144
          logger: logger,
×
145
        );
UNCOV
146
    _viewManager = shared ? MultiViewManager() : SimpleViewManager();
×
147
  }
148

149
  /// Creates a new [DuitDriver] instance that is controlled from native code
150
  DuitDriver.module()
×
151
      : _useStaticContent = false,
152
        source = "",
153
        initialRequestPayload = null,
154
        isModule = true,
155
        externalEventHandler = null,
156
        transportOptions = EmptyTransportOptions(),
×
157
        driverChannel = const MethodChannel("duit:driver"),
NEW
158
        _viewManager = SimpleViewManager(),
×
159
        _loggingManager = const LoggingManager();
160

UNCOV
161
  @protected
×
162
  @override
163
  void attachController(String id, UIElementController controller) =>
UNCOV
164
      _viewManager.addController(id, controller);
×
165

UNCOV
166
  @protected
×
167
  @override
168
  void detachController(String id) =>
UNCOV
169
      _viewManager.removeController(id)?.dispose();
×
170

UNCOV
171
  @protected
×
172
  @override
173
  UIElementController? getController(String id) =>
UNCOV
174
      _viewManager.getController(id);
×
175

UNCOV
176
  Future<Map<String, dynamic>> _connect() async {
×
177
    Map<String, dynamic>? json;
178

179
    try {
UNCOV
180
      if (_useStaticContent) {
×
UNCOV
181
        assert(content != null && content!.isNotEmpty);
×
UNCOV
182
        json = content!;
×
183
      } else {
UNCOV
184
        json = await transport?.connect(
×
UNCOV
185
          initialData: initialRequestPayload,
×
186
        );
187
      }
188
    } catch (e, s) {
NEW
189
      logError(
×
190
        "Failed conneting to server",
191
        e,
192
        s,
193
      );
UNCOV
194
      _eventStreamController.addError(e);
×
195
    }
196

UNCOV
197
    if (transport is Streamer) {
×
UNCOV
198
      final streamer = transport! as Streamer;
×
UNCOV
199
      streamer.eventStream.listen(
×
200
        (d) async {
×
201
          try {
202
            if (buildContext.mounted) {
×
203
              await eventResolver.resolveEvent(buildContext, d);
×
204
            }
205
          } catch (e, s) {
NEW
206
            logError(
×
207
              "Error while processing event from transport stream",
208
              e,
209
              s,
210
            );
211
            _eventStreamController.addError(e);
×
212
          }
213
        },
214
      );
215
    }
216

UNCOV
217
    return json ?? {};
×
218
  }
219

UNCOV
220
  @override
×
221
  Future<void> init() async {
UNCOV
222
    if (!_isDriverInitialized) {
×
UNCOV
223
      _isDriverInitialized = true;
×
224
    } else {
225
      return;
226
    }
227

UNCOV
228
    _viewManager.driver = this;
×
UNCOV
229
    _addParsers();
×
230

UNCOV
231
    onInit?.call();
×
232

UNCOV
233
    if (isModule && !_isChannelInitialized) {
×
234
      await _initMethodChannel();
×
235
    }
236

UNCOV
237
    transport ??= _getTransport(
×
UNCOV
238
      transportOptions.type,
×
239
    );
240

UNCOV
241
    await scriptRunner?.initWithTransport(transport!);
×
242

UNCOV
243
    final json = await _connect();
×
244

245
    try {
UNCOV
246
      final view = await _viewManager.prepareLayout(json);
×
247

248
      if (view != null) {
UNCOV
249
        _eventStreamController.add(
×
UNCOV
250
          UIDriverViewEvent(view),
×
251
        );
252
      } else {
UNCOV
253
        final err = FormatException(
×
UNCOV
254
          "Invalid layout structure. Received map keys: ${json.keys}",
×
255
        );
256
        throw err;
257
      }
258
    } catch (e, s) {
NEW
259
      logError(
×
260
        "Layout parse failed",
261
        e,
262
        s,
263
      );
UNCOV
264
      _eventStreamController.addError(
×
UNCOV
265
        UIDriverErrorEvent(
×
266
          "Layout parse failed",
267
          error: e,
268
          stackTrace: s,
269
        ),
270
      );
271
    }
272
  }
273

UNCOV
274
  @override
×
275
  void dispose() {
UNCOV
276
    onDispose?.call();
×
UNCOV
277
    transport?.dispose();
×
UNCOV
278
    _eventStreamController.close();
×
UNCOV
279
    for (final subscription in _dataSources.values) {
×
280
      subscription.cancel();
×
281
    }
UNCOV
282
    _dataSources.clear();
×
283
  }
284

UNCOV
285
  @override
×
286
  Widget? build() {
UNCOV
287
    return _viewManager.build();
×
288
  }
289

UNCOV
290
  void _addParsers() {
×
291
    try {
UNCOV
292
      ServerAction.setActionParser(const DefaultActionParser());
×
UNCOV
293
      ServerEvent.eventParser = const DefaultEventParser();
×
294
    } catch (e) {
295
      //Safely handle the case of assigning parsers during
296
      //multiple driver initializations as part of running tests
NEW
297
      logWarning(e);
×
298
    }
299
  }
300

UNCOV
301
  @visibleForTesting
×
302
  @override
303
  Future<void> execute(ServerAction action) async {
UNCOV
304
    beforeActionCallback?.call(action);
×
305

306
    try {
UNCOV
307
      final event = await actionExecutor.executeAction(
×
308
        action,
309
      );
310

UNCOV
311
      if (event != null && buildContext.mounted) {
×
UNCOV
312
        eventResolver.resolveEvent(buildContext, event);
×
313
      }
314
    } catch (e, s) {
NEW
315
      logError(
×
316
        "Error executing action",
317
        e,
318
        s,
319
      );
320
    } finally {
UNCOV
321
      afterActionCallback?.call();
×
322
    }
323
  }
324

UNCOV
325
  Future<void> _resolveComponentUpdate(
×
326
    UIElementController controller,
327
    Map<String, dynamic> json,
328
  ) async {
UNCOV
329
    final tag = controller.tag;
×
UNCOV
330
    final description = DuitRegistry.getComponentDescription(tag!);
×
331

332
    if (description != null) {
UNCOV
333
      final component = ComponentBuilder.build(
×
334
        description,
335
        json,
336
      );
337

UNCOV
338
      controller.updateState(component);
×
339
    }
340
  }
341

UNCOV
342
  @visibleForTesting
×
343
  @override
UNCOV
344
  Future<void> evalScript(String source) async => scriptRunner?.eval(source);
×
345

346
  /// Returns a transport based on the specified transport type.
347
  ///
348
  /// This method is used internally to create and return a transport object based
349
  /// on the specified [type]. It switches on the [type] parameter and returns an
350
  /// instance of the corresponding transport class.
351
  ///
352
  /// Parameters:
353
  /// - [type]: The transport type.
354
  ///
355
  /// Returns:
356
  /// - An instance of the transport class based on the specified [type].
357
  /// - If the [type] is not recognized, it returns an instance of [HttpTransport].
UNCOV
358
  Transport _getTransport(String type) {
×
UNCOV
359
    if (isModule) {
×
360
      return NativeTransport(this);
×
361
    }
362

363
    return switch (type) {
UNCOV
364
      TransportType.http => HttpTransport(
×
UNCOV
365
          source,
×
UNCOV
366
          options: transportOptions as HttpTransportOptions,
×
367
        ),
UNCOV
368
      TransportType.ws => WSTransport(
×
369
          source,
×
370
          options: transportOptions as WebSocketTransportOptions,
×
371
        ),
UNCOV
372
      _ => EmptyTransport(),
×
373
    };
374
  }
375

376
  /// Initializes the driver as a module.
377
  Future<void> _initMethodChannel() async {
×
378
    driverChannel?.setMethodCallHandler((call) async {
×
379
      switch (call.method) {
×
380
        case "duit_event":
×
381
          await eventResolver.resolveEvent(
×
382
            buildContext,
×
383
            call.arguments as Map<String, dynamic>,
×
384
          );
385
          break;
386
        case "duit_layout":
×
387
          final json = call.arguments as Map<String, dynamic>;
×
388
          final view = await _viewManager.prepareLayout(json);
×
389
          if (view != null) {
390
            _eventStreamController.add(
×
391
              UIDriverViewEvent(view),
×
392
            );
393
          }
394
          break;
395
        default:
396
          break;
397
      }
398
    });
399
    _isChannelInitialized = true;
×
400
  }
401

UNCOV
402
  @override
×
403
  @visibleForTesting
UNCOV
404
  int get controllersCount => _viewManager.controllersCount;
×
405

UNCOV
406
  @visibleForTesting
×
407
  @override
408
  Map<String, dynamic> preparePayload(
409
    Iterable<ActionDependency> dependencies,
410
  ) {
UNCOV
411
    final payload = <String, dynamic>{};
×
412

UNCOV
413
    if (dependencies.isNotEmpty) {
×
UNCOV
414
      for (final dependency in dependencies) {
×
UNCOV
415
        final controller = _viewManager.getController(dependency.id);
×
416
        if (controller != null) {
UNCOV
417
          final attribute = controller.attributes.payload;
×
UNCOV
418
          payload[dependency.target] = attribute["value"];
×
419
        }
420
      }
421
    }
422

423
    return payload;
424
  }
425

UNCOV
426
  @visibleForTesting
×
427
  @override
428
  Future<void> updateAttributes(
429
    String controllerId,
430
    Map<String, dynamic> json,
431
  ) async {
UNCOV
432
    final controller = _viewManager.getController(controllerId);
×
433
    if (controller != null) {
UNCOV
434
      if (controller.type == ElementType.component.name) {
×
UNCOV
435
        await _resolveComponentUpdate(controller, json);
×
436
        return;
437
      }
UNCOV
438
      controller.updateState(json);
×
439
    }
440
  }
441

UNCOV
442
  @override
×
443
  void notifyWidgetDisplayStateChanged(
444
    String viewTag,
445
    int state,
446
  ) {
UNCOV
447
    _viewManager.notifyWidgetDisplayStateChanged(viewTag, state);
×
NEW
448
    logInfo(
×
UNCOV
449
      "Widget with tag ${viewTag.isEmpty ? "*root*" : viewTag} state changed to $state",
×
450
    );
451
  }
452

UNCOV
453
  @override
×
454
  bool isWidgetReady(String viewTag) {
UNCOV
455
    return _viewManager.isWidgetReady(viewTag);
×
456
  }
457

458
  /// Adds an event stream to be listened to and processed by the driver.
459
  ///
460
  /// Each element of the stream must be a Map<String, dynamic>, which will be converted into a [ServerEvent].
461
  /// If the event is a [NullEvent], a [MissingFocusNodeException] will be thrown.
462
  /// For all other events, [eventResolver.resolveEvent] is called with the current [buildContext].
463
  ///
464
  /// The stream subscription is stored in the internal [_dataSources] list for lifecycle management.
465
  ///
466
  /// [stream] - the stream of events coming from the server or another data source.
UNCOV
467
  @override
×
468
  void addExternalEventStream(
469
    Stream<Map<String, dynamic>> stream,
470
  ) {
UNCOV
471
    final id = stream.hashCode;
×
472

UNCOV
473
    _dataSources[id] = stream.map(ServerEvent.parseEvent).listen(
×
UNCOV
474
      (event) {
×
UNCOV
475
        if (event is NullEvent) {
×
476
          throw const NullEventException("NullEvent received from data source");
477
        }
UNCOV
478
        eventResolver.resolveEvent(
×
479
          // ignore: use_build_context_synchronously
UNCOV
480
          buildContext,
×
481
          event,
482
        );
483
      },
UNCOV
484
      onDone: () => _cancelSub(id),
×
485
      onError: (e, s) => _cancelSub(id),
×
486
    );
487
  }
488

UNCOV
489
  void _cancelSub(int code) {
×
UNCOV
490
    _dataSources.remove(code)?.cancel();
×
491
  }
492

UNCOV
493
  @override
×
494
  @preferInline
495
  void attachFocusNode(String nodeId, FocusNode node) =>
UNCOV
496
      _focusNodeManager.attachFocusNode(nodeId, node);
×
497

UNCOV
498
  @override
×
499
  @preferInline
500
  void detachFocusNode(String nodeId) =>
UNCOV
501
      _focusNodeManager.detachFocusNode(nodeId);
×
502

UNCOV
503
  @override
×
504
  @preferInline
505
  bool focusInDirection(String nodeId, TraversalDirection direction) =>
UNCOV
506
      _focusNodeManager.focusInDirection(nodeId, direction);
×
507

UNCOV
508
  @override
×
509
  @preferInline
UNCOV
510
  bool nextFocus(String nodeId) => _focusNodeManager.nextFocus(nodeId);
×
511

UNCOV
512
  @override
×
513
  @preferInline
UNCOV
514
  bool previousFocus(String nodeId) => _focusNodeManager.previousFocus(nodeId);
×
515

UNCOV
516
  @override
×
517
  @preferInline
518
  void requestFocus(String nodeId, {String? targetNodeId}) =>
UNCOV
519
      _focusNodeManager.requestFocus(
×
520
        nodeId,
521
        targetNodeId: targetNodeId,
522
      );
523

UNCOV
524
  @override
×
525
  @preferInline
526
  void unfocus(
527
    String nodeId, {
528
    UnfocusDisposition disposition = UnfocusDisposition.scope,
529
  }) =>
UNCOV
530
      _focusNodeManager.unfocus(
×
531
        nodeId,
532
        disposition: disposition,
533
      );
534

UNCOV
535
  @override
×
536
  @preferInline
UNCOV
537
  FocusNode? getNode(Object? key) => _focusNodeManager.getNode(key);
×
538

NEW
539
  @override
×
540
  @preferInline
541
  void logCritical(message, [Object? exception, StackTrace? stackTrace]) =>
NEW
542
      _loggingManager.logCritical(message, exception, stackTrace);
×
543

NEW
544
  @override
×
545
  @preferInline
546
  void logDebug(message, [Object? exception, StackTrace? stackTrace]) =>
NEW
547
      _loggingManager.logDebug(message, exception, stackTrace);
×
548

NEW
549
  @override
×
550
  @preferInline
551
  void logError(message, [Object? exception, StackTrace? stackTrace]) =>
NEW
552
      _loggingManager.logError(message, exception, stackTrace);
×
553

NEW
554
  @override
×
555
  @preferInline
556
  void logInfo(message, [Object? exception, StackTrace? stackTrace]) =>
NEW
557
      _loggingManager.logInfo(message, exception, stackTrace);
×
558

NEW
559
  @override
×
560
  @preferInline
561
  void logVerbose(message, [Object? exception, StackTrace? stackTrace]) =>
NEW
562
      _loggingManager.logVerbose(message, exception, stackTrace);
×
563

NEW
564
  @override
×
565
  @preferInline
566
  void logWarning(message, [Object? exception, StackTrace? stackTrace]) =>
NEW
567
      _loggingManager.logWarning(message, exception, stackTrace);
×
568
}
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