Skip to content

feat: Add connect and disconnect event streams - #132

Open
spydon wants to merge 2 commits into
mainfrom
worktree-131
Open

feat: Add connect and disconnect event streams#132
spydon wants to merge 2 commits into
mainfrom
worktree-131

Conversation

@spydon

@spydon spydon commented Sep 1, 2026

Copy link
Copy Markdown
Member

Closes #131

Every platform already detected gamepads being plugged in and unplugged, but kept it internal. This surfaces it to Dart.

API

Gamepads.connectionEvents  // Stream<GamepadConnectionEvent>, both kinds
Gamepads.onConnected       // filtered to connected
Gamepads.onDisconnected    // filtered to disconnected

The issue suggested Stream<GamepadController>. Events carry a GamepadConnectionEvent with gamepadId, name and a GamepadConnectionType instead, because a GamepadController is a resource that subscribes on construction and must be disposed, so emitting one per event, especially per disconnect, would either leak subscriptions or hand back an already-dead object. The event mirrors GamepadEvent, and its gamepadId matches the id from list() and from input events.

Platform interface

The event StreamController plumbing was duplicated in MethodChannelGamepadsPlatformInterface and GamepadsWeb. Both streams now live in GamepadsPlatformInterface with emitGamepadEvent / emitGamepadConnectionEvent and a dispose that closes both. gamepadEventsStream went from abstract to concrete, which is backwards compatible for anyone overriding it.

Natives

All five send a new onGamepadConnectionEvent method call from hooks they already had:

  • iOS: the // Optional: send disconnection event TODO is now done.
  • macOS: GamepadsListener gained a connectionListener. joystickDidDisconnect now captures the index before removing, since that index is the id reported to Dart.
  • Android: DeviceListener got add/remove helpers that emit only on real transitions, so onInputDeviceChanged does not double-report. Removal reads the name from the cache, since InputDevice.getDevice returns null once the device is gone.
  • Linux: the connect/disconnect branch used gamepads[key], which default-inserts, so if (existingGamepad) was always true; switched to find so disconnects only fire for tracked devices and carry the real name.
  • Windows: a connection_emitter alongside event_emitter. The name is copied before the read thread frees the GamepadData.

Verification

flutter analyze clean, all 85 tests pass, including three new ones covering connectionEvents, onConnected and onDisconnected filtering. Web and Android compile against these sources (flutter build web and flutter build apk --debug in a scratch app with path overrides).

The Swift files parse (swiftc -parse) but were not type-checked or built, since Xcode is not fully installed on the machine this was written on, and the Linux and Windows C++ could not be compiled from macOS. Those five native files are reviewed but unbuilt and want a CI or local check.

Also updates the README with a connection-events section, and the example app to log connects/disconnects and refresh its list from them.

Closes #131

Every platform already detected gamepads being plugged in and unplugged, but
kept it internal. Surface it as `Gamepads.connectionEvents`, with
`Gamepads.onConnected` and `Gamepads.onDisconnected` as filtered views.

Events carry a `GamepadConnectionEvent` with the `gamepadId`, the user-facing
`name` and a `GamepadConnectionType`, instead of a `GamepadController`, which
is a resource that subscribes on construction and has to be disposed.

The event stream plumbing was duplicated between the method channel and web
implementations, so it moved into `GamepadsPlatformInterface` alongside the new
connection stream.

Two pre-existing bugs got in the way and are fixed here:
- On Linux, `gamepads[key]` default-inserts an entry, so the disconnect branch
  always thought it knew the device; it now uses `find`.
- On macOS, the disconnect handler dropped the gamepad without knowing its
  index, which is the id reported to Dart.
- macOS reported the disconnect id by looking the gamepad up in the array at
  disconnect time, which shifts as earlier gamepads are removed, so it could
  differ from the id the gamepad's own input events carried. The id assigned
  at connect is now remembered and replayed.
- macOS derived the disconnect name from the gamepad's `GCDevice`, which is
  usually torn down by then, yielding "Unknown device". The name is now
  cached at connect.
- Windows copied the gamepad name after setting `stop_thread`, so the reader
  thread could exit and free the gamepad first. The name is now copied before
  the flag is set.
- iOS read `vendorName` at disconnect time, where it can be nil, dropping the
  event for a controller whose connection had been reported. The name is now
  cached when the controller is set up.
- Android registered a new `DeviceListener` on every reattach without ever
  unregistering the previous one, so after a configuration change each
  connection was reported once per registration. It now registers once.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Add listeners for gamepad connect / disconnect events

1 participant