@@ -59,7 +59,31 @@ class IOSDevices extends PollingDeviceDiscovery {
59
59
@override
60
60
bool get requiresExtendedWirelessDeviceDiscovery => true ;
61
61
62
- StreamSubscription <Map <XCDeviceEvent , String >>? _observedDeviceEventsSubscription;
62
+ StreamSubscription <XCDeviceEventNotification >? _observedDeviceEventsSubscription;
63
+
64
+ /// Cache for all devices found by `xcdevice list` , including not connected
65
+ /// devices. Used to minimize the need to call `xcdevice list` .
66
+ ///
67
+ /// Separate from `deviceNotifier` since `deviceNotifier` should only contain
68
+ /// connected devices.
69
+ final Map <String , IOSDevice > _cachedPolledDevices = < String , IOSDevice > {};
70
+
71
+ /// Maps device id to a map of the device's observed connections. When the
72
+ /// mapped connection is `true` , that means that observed events indicated
73
+ /// the device is connected via that particular interface.
74
+ ///
75
+ /// The device id must be missing from the map or both interfaces must be
76
+ /// false for the device to be considered disconnected.
77
+ ///
78
+ /// Example:
79
+ /// {
80
+ /// device-id: {
81
+ /// usb: false,
82
+ /// wifi: false,
83
+ /// },
84
+ /// }
85
+ final Map <String , Map <XCDeviceEventInterface , bool >> _observedConnectionsByDeviceId =
86
+ < String , Map <XCDeviceEventInterface , bool >> {};
63
87
64
88
@override
65
89
Future <void > startPolling () async {
@@ -75,16 +99,13 @@ class IOSDevices extends PollingDeviceDiscovery {
75
99
deviceNotifier ?? = ItemListNotifier <Device >();
76
100
77
101
// Start by populating all currently attached devices.
78
- final List <Device > devices = await pollingGetDevices ();
79
-
80
- // Only show connected devices.
81
- final List <Device > filteredDevices = devices.where ((Device device) => device.isConnected == true ).toList ();
82
- deviceNotifier! .updateWithNewList (filteredDevices);
102
+ _updateCachedDevices (await pollingGetDevices ());
103
+ _updateNotifierFromCache ();
83
104
84
105
// cancel any outstanding subscriptions.
85
106
await _observedDeviceEventsSubscription? .cancel ();
86
107
_observedDeviceEventsSubscription = xcdevice.observedDeviceEvents ()? .listen (
87
- _onDeviceEvent ,
108
+ onDeviceEvent ,
88
109
onError: (Object error, StackTrace stack) {
89
110
_logger.printTrace ('Process exception running xcdevice observe:\n $error \n $stack ' );
90
111
}, onDone: () {
@@ -98,32 +119,89 @@ class IOSDevices extends PollingDeviceDiscovery {
98
119
);
99
120
}
100
121
101
- Future <void > _onDeviceEvent (Map <XCDeviceEvent , String > event) async {
102
- final XCDeviceEvent eventType = event.containsKey (XCDeviceEvent .attach) ? XCDeviceEvent .attach : XCDeviceEvent .detach;
103
- final String ? deviceIdentifier = event[eventType];
122
+ @visibleForTesting
123
+ Future <void > onDeviceEvent (XCDeviceEventNotification event) async {
104
124
final ItemListNotifier <Device >? notifier = deviceNotifier;
105
125
if (notifier == null ) {
106
126
return ;
107
127
}
108
128
Device ? knownDevice;
109
129
for (final Device device in notifier.items) {
110
- if (device.id == deviceIdentifier) {
130
+ if (device.id == event. deviceIdentifier) {
111
131
knownDevice = device;
112
132
}
113
133
}
114
134
115
- // Ignore already discovered devices (maybe populated at the beginning).
116
- if (eventType == XCDeviceEvent .attach && knownDevice == null ) {
117
- // There's no way to get details for an individual attached device,
118
- // so repopulate them all.
119
- final List <Device > devices = await pollingGetDevices ();
135
+ final Map <XCDeviceEventInterface , bool > deviceObservedConnections =
136
+ _observedConnectionsByDeviceId[event.deviceIdentifier] ??
137
+ < XCDeviceEventInterface , bool > {
138
+ XCDeviceEventInterface .usb: false ,
139
+ XCDeviceEventInterface .wifi: false ,
140
+ };
141
+
142
+ if (event.eventType == XCDeviceEvent .attach) {
143
+ // Update device's observed connections.
144
+ deviceObservedConnections[event.eventInterface] = true ;
145
+ _observedConnectionsByDeviceId[event.deviceIdentifier] = deviceObservedConnections;
146
+
147
+ // If device was not already in notifier, add it.
148
+ if (knownDevice == null ) {
149
+ if (_cachedPolledDevices[event.deviceIdentifier] == null ) {
150
+ // If device is not found in cache, there's no way to get details
151
+ // for an individual attached device, so repopulate them all.
152
+ _updateCachedDevices (await pollingGetDevices ());
153
+ }
154
+ _updateNotifierFromCache ();
155
+ }
156
+ } else {
157
+ // Update device's observed connections.
158
+ deviceObservedConnections[event.eventInterface] = false ;
159
+ _observedConnectionsByDeviceId[event.deviceIdentifier] = deviceObservedConnections;
160
+
161
+ // If device is in the notifier and does not have other observed
162
+ // connections, remove it.
163
+ if (knownDevice != null &&
164
+ ! _deviceHasObservedConnection (deviceObservedConnections)) {
165
+ notifier.removeItem (knownDevice);
166
+ }
167
+ }
168
+ }
169
+
170
+ /// Adds or updates devices in cache. Does not remove devices from cache.
171
+ void _updateCachedDevices (List <Device > devices) {
172
+ for (final Device device in devices) {
173
+ if (device is ! IOSDevice ) {
174
+ continue ;
175
+ }
176
+ _cachedPolledDevices[device.id] = device;
177
+ }
178
+ }
120
179
121
- // Only show connected devices.
122
- final List <Device > filteredDevices = devices.where ((Device device) => device.isConnected == true ).toList ();
123
- notifier.updateWithNewList (filteredDevices);
124
- } else if (eventType == XCDeviceEvent .detach && knownDevice != null ) {
125
- notifier.removeItem (knownDevice);
180
+ /// Updates notifier with devices found in the cache that are determined
181
+ /// to be connected.
182
+ void _updateNotifierFromCache () {
183
+ final ItemListNotifier <Device >? notifier = deviceNotifier;
184
+ if (notifier == null ) {
185
+ return ;
126
186
}
187
+ // Device is connected if it has either an observed usb or wifi connection
188
+ // or it has not been observed but was found as connected in the cache.
189
+ final List <Device > connectedDevices = _cachedPolledDevices.values.where ((Device device) {
190
+ final Map <XCDeviceEventInterface , bool >? deviceObservedConnections =
191
+ _observedConnectionsByDeviceId[device.id];
192
+ return (deviceObservedConnections != null &&
193
+ _deviceHasObservedConnection (deviceObservedConnections)) ||
194
+ (deviceObservedConnections == null && device.isConnected);
195
+ }).toList ();
196
+
197
+ notifier.updateWithNewList (connectedDevices);
198
+ }
199
+
200
+ bool _deviceHasObservedConnection (
201
+ Map <XCDeviceEventInterface , bool > deviceObservedConnections,
202
+ ) {
203
+ return (deviceObservedConnections[XCDeviceEventInterface .usb] ?? false ) ||
204
+ (deviceObservedConnections[XCDeviceEventInterface .wifi] ?? false );
127
205
}
128
206
129
207
@override
0 commit comments