Skip to content

Commit f452d64

Browse files
committed
refactor: Improve disposal checks and listener notifications in view models
1 parent 06483b0 commit f452d64

14 files changed

Lines changed: 225 additions & 154 deletions

File tree

client/lib/core/services/devices/device_manager.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ abstract class IDeviceManager implements IDisposable {
2525
int get wotThingCount;
2626

2727
// Abstract methods
28-
Future<void> initialize();
28+
Future<void> initialize({CancellationToken? cancelToken});
2929
bool isBound(String deviceID);
3030
BoundDevice getBoundDevice(String deviceID);
3131
Iterable<BoundDevice> getBoundDevicesInCurrentScene();
32-
Future<void> reloadAllDevices();
32+
Future<void> reloadAllDevices({CancellationToken? cancelToken});
3333
Future<bool> tryBind(DeviceEntity device);
3434
Future<void> bind(DeviceEntity device);
3535
Future<void> unbind(String deviceID);

client/lib/core/services/devices/device_manager_impl.dart

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,19 +77,19 @@ final class DeviceManagerImpl extends IDeviceManager {
7777
Iterable<BoundDevice> get boundDevices => _kernel.boundDevices;
7878

7979
@override
80-
Future<void> initialize() async {
80+
Future<void> initialize({CancellationToken? cancelToken}) async {
8181
assert(!_isInitialized);
8282

8383
logger?.i('Initializing DeviceManagerImpl...');
8484
try {
85-
final devices = await fetchAllDevicesInScene();
85+
final devices = await fetchAllDevicesInScene().asCancellable(cancelToken);
8686
_kernel.registerDevices(devices.map((x) => BoundDeviceDescriptor(device: x, driverID: x.driverID)));
8787
await _kernel.start();
8888

8989
unawaited(() async {
9090
// Load WotThings for current scene alongside device load, regardless of online state.
91-
await _loadWotThingsForCurrentScene();
92-
await _rebindAll(devices);
91+
await _loadWotThingsForCurrentScene(cancelToken: cancelToken);
92+
await _rebindAll(devices, cancelToken: cancelToken);
9393

9494
final currentScene = _sceneManager.current;
9595
_globalBus.fire(CurrentSceneDevicesReloadedEvent(currentScene));
@@ -126,24 +126,26 @@ final class DeviceManagerImpl extends IDeviceManager {
126126
}
127127

128128
@override
129-
Future<void> reloadAllDevices() async {
130-
await _deviceOperLock.synchronized(() async {
131-
await _kernel.unbindAll();
132-
_kernel.unregisterAllDevices();
133-
final devices = await fetchAllDevicesInScene();
134-
_kernel.registerDevices(devices.map((x) => BoundDeviceDescriptor(device: x, driverID: x.driverID)));
135-
await _reloadWotThingsForCurrentScene();
136-
await _rebindAll(devices);
137-
});
129+
Future<void> reloadAllDevices({CancellationToken? cancelToken}) async {
130+
await _deviceOperLock
131+
.synchronized(() async {
132+
await _kernel.unbindAll(cancelToken: cancelToken);
133+
_kernel.unregisterAllDevices();
134+
final devices = await fetchAllDevicesInScene().asCancellable(cancelToken);
135+
_kernel.registerDevices(devices.map((x) => BoundDeviceDescriptor(device: x, driverID: x.driverID)));
136+
await _reloadWotThingsForCurrentScene(cancelToken: cancelToken);
137+
await _rebindAll(devices, cancelToken: cancelToken);
138+
})
139+
.asCancellable(cancelToken);
138140
}
139141

140-
Future<void> _rebindAll(Iterable<DeviceEntity> devices) async {
142+
Future<void> _rebindAll(Iterable<DeviceEntity> devices, {CancellationToken? cancelToken}) async {
141143
await _kernel.unbindAll();
142144
final futures = <Future>[];
143145
for (var device in devices) {
144146
futures.add(tryBind(device));
145147
}
146-
await Future.wait(futures);
148+
await Future.wait(futures).asCancellable(cancelToken);
147149
_globalBus.fire(DeviceManagerReadyEvent());
148150
}
149151

@@ -400,12 +402,12 @@ final class DeviceManagerImpl extends IDeviceManager {
400402
}
401403

402404
/// Reload WotThings for current scene only
403-
Future<void> _reloadWotThingsForCurrentScene() async {
405+
Future<void> _reloadWotThingsForCurrentScene({CancellationToken? cancelToken}) async {
404406
// Dispose of all existing WotThings
405407
_disposeAllWotThings();
406408

407409
// Load WotThings for devices in current scene
408-
await _loadWotThingsForCurrentScene();
410+
await _loadWotThingsForCurrentScene(cancelToken: cancelToken);
409411

410412
// Fire event to notify that devices for current scene have been reloaded
411413
final currentScene = _sceneManager.current;
@@ -440,7 +442,7 @@ final class DeviceManagerImpl extends IDeviceManager {
440442
}
441443

442444
/// Load WotThings for devices in current scene
443-
Future<void> _loadWotThingsForCurrentScene() async {
445+
Future<void> _loadWotThingsForCurrentScene({CancellationToken? cancelToken}) async {
444446
try {
445447
final devices = await fetchAllDevicesInScene();
446448
logger?.d('Loading WotThings for ${devices.length} devices in current scene');
@@ -449,7 +451,7 @@ final class DeviceManagerImpl extends IDeviceManager {
449451
try {
450452
final metaModule = _deviceModuleRegistry.metaModules[device.driverID];
451453
if (metaModule != null) {
452-
final wotThing = await metaModule.createWotThing(device, this, logger: logger);
454+
final wotThing = await metaModule.createWotThing(device, this, logger: logger, cancelToken: cancelToken);
453455
_wotThings[device.id] = wotThing;
454456

455457
// If device is bound, sync WotThing with actual device state
@@ -469,11 +471,15 @@ final class DeviceManagerImpl extends IDeviceManager {
469471
}
470472

471473
/// Load WotThing for a single device
472-
Future<void> _loadWotThingForDevice(DeviceEntity device, {bool replaceExisting = true}) async {
474+
Future<void> _loadWotThingForDevice(
475+
DeviceEntity device, {
476+
bool replaceExisting = true,
477+
CancellationToken? cancelToken,
478+
}) async {
473479
try {
474480
final metaModule = _deviceModuleRegistry.metaModules[device.driverID];
475481
if (metaModule != null) {
476-
final wotThing = await metaModule.createWotThing(device, this, logger: logger);
482+
final wotThing = await metaModule.createWotThing(device, this, logger: logger, cancelToken: cancelToken);
477483
if (!replaceExisting && _wotThings.containsKey(device.id)) {
478484
return;
479485
}
@@ -490,7 +496,7 @@ final class DeviceManagerImpl extends IDeviceManager {
490496

491497
// If device is bound, sync WotThing with actual device state
492498
if (isBound(device.id)) {
493-
await _syncWotThingWithBoundDevice(device.id, wotThing);
499+
await _syncWotThingWithBoundDevice(device.id, wotThing, cancelToken: cancelToken);
494500
}
495501
logger?.d('Successfully loaded WotThing for device ${device.id}');
496502
}
@@ -500,10 +506,13 @@ final class DeviceManagerImpl extends IDeviceManager {
500506
}
501507

502508
/// Sync WotThing properties with actual device state
503-
Future<void> _syncWotThingWithBoundDevice(String deviceID, WotThing wotThing) async {
509+
Future<void> _syncWotThingWithBoundDevice(
510+
String deviceID,
511+
WotThing wotThing, {
512+
CancellationToken? cancelToken,
513+
}) async {
504514
try {
505-
// This is where we would sync WotThing properties with actual device state
506-
// For now, we'll leave this as a placeholder for future implementation
515+
wotThing.sync(cancelToken: cancelToken);
507516
logger?.d('WotThing synced for device $deviceID');
508517
} catch (e) {
509518
logger?.w('Failed to sync WotThing for device $deviceID: $e');

client/lib/devices/borneo/lyfi/manifest.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:borneo_app/core/services/devices/device_manager.dart';
1111
import 'package:borneo_app/core/services/app_notification_service.dart';
1212
import 'package:borneo_kernel/drivers/borneo/lyfi/models.dart';
1313
import 'package:borneo_wot/borneo/lyfi/wot_thing.dart';
14+
import 'package:cancellation_token/cancellation_token.dart';
1415
import 'package:event_bus/event_bus.dart';
1516
import 'package:fl_chart/fl_chart.dart';
1617
import 'package:flutter/material.dart';
@@ -130,11 +131,12 @@ class LyfiDeviceModuleMetadata extends DeviceModuleMetadata {
130131
}
131132
}
132133

133-
static Future<WotThing> _createWotThing(DeviceEntity device, IDeviceManager deviceManager, {Logger? logger}) async {
134-
final lyfiThing = LyfiThing(kernel: deviceManager.kernel, deviceId: device.id, title: device.name, logger: logger);
135-
await lyfiThing.initialize();
136-
return lyfiThing;
137-
}
134+
static Future<WotThing> _createWotThing(
135+
DeviceEntity device,
136+
IDeviceManager deviceManager, {
137+
Logger? logger,
138+
CancellationToken? cancelToken,
139+
}) async => LyfiThing(kernel: deviceManager.kernel, deviceId: device.id, title: device.name, logger: logger);
138140
}
139141

140142
/// A compact bar chart that displays Lyfi per-channel brightness.

client/lib/devices/borneo/lyfi/view_models/summary_device_view_model.dart

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import 'package:borneo_kernel/drivers/borneo/lyfi/models.dart';
55
import 'package:lw_wot/wot.dart';
66

77
class LyfiSummaryDeviceViewModel extends BaseBorneoSummaryDeviceViewModel {
8-
bool _disposed = false;
98
final ValueNotifier<LyfiState?> ledState = ValueNotifier(null);
109
final ValueNotifier<LyfiMode?> ledMode = ValueNotifier(null);
1110
final ValueNotifier<List<int>?> channelBrightness = ValueNotifier(null);
@@ -17,17 +16,11 @@ class LyfiSummaryDeviceViewModel extends BaseBorneoSummaryDeviceViewModel {
1716
super.globalEventBus, {
1817
required super.gt,
1918
super.logger,
20-
}) {
21-
_syncFromThing();
22-
wotThing?.addSubscriber(_onStateChanged);
23-
wotThing?.addSubscriber(_onModeChanged);
24-
wotThing?.addSubscriber(_onColorChanged);
25-
wotThing?.addSubscriber(_onDeviceInfoChanged);
26-
}
19+
});
2720

2821
@override
2922
void dispose() {
30-
if (!_disposed) {
23+
if (!isDisposed) {
3124
wotThing?.removeSubscriber(_onStateChanged);
3225
wotThing?.removeSubscriber(_onModeChanged);
3326
wotThing?.removeSubscriber(_onColorChanged);
@@ -37,7 +30,6 @@ class LyfiSummaryDeviceViewModel extends BaseBorneoSummaryDeviceViewModel {
3730
channelBrightness.dispose();
3831
lyfiDeviceInfo.dispose();
3932
super.dispose();
40-
_disposed = true;
4133
}
4234
}
4335

client/lib/devices/view_models/abstract_device_summary_view_model.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ abstract class AbstractDeviceSummaryViewModel extends BaseViewModel with ViewMod
4848
_deviceUpdatedSub = deviceManager.allDeviceEvents.on<DeviceEntityUpdatedEvent>().listen(_onDeviceUpdated);
4949
_loadingFailedEventSub = deviceManager.allDeviceEvents.on<LoadingDriverFailedEvent>().listen(_onLoadingFailed);
5050
_sceneReloadedSub = globalEventBus.on<CurrentSceneDevicesReloadedEvent>().listen(_onSceneReloaded);
51-
wotThing?.addSubscriber(_onPowerPropertyChanged);
5251

5352
_refreshWotThing();
5453
}
5554

5655
@override
5756
void dispose() {
57+
if (isDisposed) return;
5858
_boundEventSub.cancel();
5959
_removedEventSub.cancel();
6060
_deviceUpdatedSub.cancel();

client/lib/features/devices/models/device_module_metadata.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:borneo_app/devices/view_models/abstract_device_summary_view_mode
22
import 'package:borneo_app/features/devices/models/device_entity.dart';
33
import 'package:borneo_app/core/services/devices/device_manager.dart';
44
import 'package:borneo_app/features/devices/view_models/base_device_view_model.dart';
5+
import 'package:cancellation_token/cancellation_token.dart';
56
import 'package:event_bus/event_bus.dart';
67
import 'package:flutter/widgets.dart';
78
import 'package:flutter_gettext/flutter_gettext/gettext_localizations.dart';
@@ -22,7 +23,8 @@ abstract class DeviceModuleMetadata {
2223
final List<Widget> Function(BuildContext, AbstractDeviceSummaryViewModel) secondaryStatesBuilder;
2324
final AbstractDeviceSummaryViewModel Function(DeviceEntity, IDeviceManager, EventBus, GettextLocalizations)
2425
createSummaryVM;
25-
final Future<WotThing> Function(DeviceEntity, IDeviceManager, {Logger? logger}) createWotThing;
26+
final Future<WotThing> Function(DeviceEntity, IDeviceManager, {Logger? logger, CancellationToken? cancelToken})
27+
createWotThing;
2628

2729
/// Optional: supply a custom center widget for the device card.
2830
/// When null the card falls back to [deviceIconBuilder].

client/lib/features/devices/view_models/device_discovery_view_model.dart

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ class DeviceDiscoveryViewModel extends AbstractScreenViewModel {
4747
late final List<DeviceGroupEntity> _availableGroups = [];
4848
List<DeviceGroupEntity> get availableGroups => _availableGroups;
4949

50-
@override
51-
late final Future<void> initFuture;
52-
5350
bool get isDiscovering => _isRefreshing;
5451

5552
// Platform check
@@ -76,10 +73,6 @@ class DeviceDiscoveryViewModel extends AbstractScreenViewModel {
7673

7774
@override
7875
Future<void> onInitialize() async {
79-
initFuture = _initialize();
80-
}
81-
82-
Future<void> _initialize() async {
8376
try {
8477
_availableGroups.addAll(await _groupManager.fetchAllGroupsInCurrentScene());
8578
// 延迟到下一帧,确保 UI 完全初始化

client/lib/features/devices/view_models/group_view_model.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class GroupViewModel extends BaseViewModel with ViewModelEventBusMixin {
7676
}
7777
_devices = [];
7878
_updateModified();
79-
notifyListeners();
79+
if (!isDisposed) notifyListeners();
8080
}
8181

8282
@override

client/lib/features/devices/view_models/grouped_devices_view_model.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ class GroupedDevicesViewModel extends BaseViewModel with ViewModelEventBusMixin,
133133

134134
void _clearAllItems() {
135135
for (final g in _groups) {
136-
g.clearDevices();
137136
g.dispose();
138137
}
139138
_groups.clear();
@@ -242,8 +241,13 @@ class GroupedDevicesViewModel extends BaseViewModel with ViewModelEventBusMixin,
242241
// Remove the deleted device from UI
243242
if (changedGroupIndex != -1) {
244243
final changedGroup = _groups[changedGroupIndex];
245-
final deviceToRemove = changedGroup.devices.firstWhere((d) => d.deviceEntity.id == event.id);
246-
deviceToRemove.dispose();
244+
final deviceIndex = changedGroup.devices.indexWhere((d) => d.deviceEntity.id == event.id);
245+
if (deviceIndex != -1) {
246+
final deviceToRemove = changedGroup.devices[deviceIndex];
247+
if (!deviceToRemove.isDisposed) {
248+
deviceToRemove.dispose();
249+
}
250+
}
247251
changedGroup.removeDeviceById(event.id);
248252
// Notify only when necessary
249253
if (!isDisposed) {

0 commit comments

Comments
 (0)