Skip to content

Commit 34d2c8d

Browse files
authored
Better support for wireless devices in IDEs (#123716)
Better support for wireless devices in IDEs
1 parent e3bc8ef commit 34d2c8d

File tree

4 files changed

+394
-176
lines changed

4 files changed

+394
-176
lines changed

packages/flutter_tools/lib/src/ios/devices.dart

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)