@@ -59,7 +59,31 @@ class IOSDevices extends PollingDeviceDiscovery {
5959 @override
6060 bool get requiresExtendedWirelessDeviceDiscovery => true ;
6161
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 >> {};
6387
6488 @override
6589 Future <void > startPolling () async {
@@ -75,16 +99,13 @@ class IOSDevices extends PollingDeviceDiscovery {
7599 deviceNotifier ?? = ItemListNotifier <Device >();
76100
77101 // 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 ();
83104
84105 // cancel any outstanding subscriptions.
85106 await _observedDeviceEventsSubscription? .cancel ();
86107 _observedDeviceEventsSubscription = xcdevice.observedDeviceEvents ()? .listen (
87- _onDeviceEvent ,
108+ onDeviceEvent ,
88109 onError: (Object error, StackTrace stack) {
89110 _logger.printTrace ('Process exception running xcdevice observe:\n $error \n $stack ' );
90111 }, onDone: () {
@@ -98,32 +119,89 @@ class IOSDevices extends PollingDeviceDiscovery {
98119 );
99120 }
100121
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 {
104124 final ItemListNotifier <Device >? notifier = deviceNotifier;
105125 if (notifier == null ) {
106126 return ;
107127 }
108128 Device ? knownDevice;
109129 for (final Device device in notifier.items) {
110- if (device.id == deviceIdentifier) {
130+ if (device.id == event. deviceIdentifier) {
111131 knownDevice = device;
112132 }
113133 }
114134
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+ }
120179
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 ;
126186 }
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 );
127205 }
128206
129207 @override
0 commit comments