Skip to content
This repository has been archived by the owner on Jun 23, 2023. It is now read-only.

Android support #1

Merged
merged 9 commits into from
Nov 26, 2019
Merged

Android support #1

merged 9 commits into from
Nov 26, 2019

Conversation

lynn
Copy link
Contributor

@lynn lynn commented Nov 19, 2019

Adds Android support to the gamepad plugin.

@apecoraro @nir-ziv To test:

  • Build the example app in this package on your Android device.
  • Connect a Bluetooth gamepad to your device.
  • Right before the first registered button press, you should see a "connected gamepad" event in the example app.
  • Test all the buttons: A, B, X, Y, shoulders, stick buttons, Menu, and the directional pad. They should all produce log entries in the app. The B button should not close the app.
    • ("Select/Back" will always perform a UI action on Android, I'm afraid.)
  • Test the joysticks and trigger buttons. Moving them should also produce log entries in the app.
  • Test the "Call gamepads()" button in the example app: your controller's name should show up.

Basically, you should see something like this:

image

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Contributor

@apecoraro apecoraro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few questions and possibly some changes.


val view: FlutterView = registrar.view()
fun viewField(name: String): Field {
val field = FlutterView::class.java.getDeclaredField(name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need a try/catch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See below: if this process goes wrong for someone after a Flutter-internal change, we can't really save anything and probably want to let that error propagate so they can tell us it broke.

val rendererField = viewField("flutterRenderer")
val renderer = rendererField.get(view) as FlutterRenderer
val touchProcessor = GamepadAndroidTouchProcessor(renderer)
touchProcessorField.set(view, touchProcessor)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Hack" comment seems to suggest that this solution is tenuous in that it could possibly break if Flutter changes something about how it is implemented. For example if the "androidTouchProcessor" field changes names perhaps? If something like that were to happen would this code throw an uncaught exception? If so, should we try to catch the exception here and deal with it gracefully?

Copy link
Contributor Author

@lynn lynn Nov 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — it's relying on the presence of a (private!) field with that name in a class in the Flutter engine, which could change in a future version.

This patching step is crucial to the working of the plugin: if it goes wrong, then the plug-in can't possibly do its job (because we will have no access to key/motion events). In a sense, crashing feels like the “right” thing to do, then: users of the plug-in will make an issue here on GitHub, and it will be up to us to fix and accommodate the new Flutter version.

If we preempt such a failure and Log.warning("uh-oh, blah blah blah, here's a stacktrace") and do nothing else, we will just be making the failure a little less obvious.

It would be a different story if the Flutter version wasn't “baked into” an app. That is: if the trick works (for the user) in a given build of an app, then it won't suddenly stop working. So users of this plug-in (i.e. Flutter app developers) don't have to worry about this hack suddenly breaking on them if it works once for them.

Copy link
Member

@andrewmd5 andrewmd5 Nov 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this what RawKeyboard is for in combination with InputDevice using a SOURCE_GAMEPAD so you don't need to call private methods ,and rather handle the Raw key events?

https://api.flutter.dev/flutter/services/RawKeyEventDataAndroid-class.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you (as a Flutter app developer) can use a RawKeyboard Flutter widget to get gamepad button events.

However, at the time of writing, that works only on Android, and only buttons are supported: the motion events from thumbsticks are discarded by AndroidTouchProcessor. (The events also don't seem to have a deviceId field, only vendorId and productId, so theoretically you couldn't tell two identical Dualshocks connected to the device apart. This is kind of a nitpick.)

If we want to offer a plug-in that has the same API on both iOS and Android, i.e. if we want to put button events onto the flutter_gamepad event stream, then we need the trick, because Flutter widgets are immaterial to us at the plug-in level. So RawKeyboard's existence does not help us much.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this PR, but just had the thought that it might be a good idea to specify on this https://pub.dev/packages/flutter_gamepad that it is tested and works with specific versions of Flutter and that it is potentially not compatible with versions that haven't been tested.

Also I'm curious, if the flutter_gamepad plugin did cause a crash due to incompatibility with the version of Flutter that the Flutter app developer is using it with, would it be obvious to the developer why/where it crashed (assuming the developer has no background in platform specific development so doing something like running the app through XCode is too foreign for them to try)? In other words, if it were to crash after said app developer did a "flutter run" on their app, would they be able to tell that it is the gamepad_plugin that is malfunctioning?

val keyEventChannel = keyEventChannelField.get(view) as KeyEventChannel
val textInputPlugin = textInputPluginField.get(view) as TextInputPlugin
val keyProcessor = GamepadAndroidKeyProcessor(keyEventChannel, textInputPlugin)
keyProcessorField.set(view, keyProcessor)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here regarding "Hack".

* An extension of Flutter's AndroidKeyProcessor that delegates KeyEvents to the GamepadStreamHandler.
*/
class GamepadAndroidKeyProcessor(keyEventChannel: KeyEventChannel, textInputPlugin: TextInputPlugin) : AndroidKeyProcessor(keyEventChannel, textInputPlugin) {
override fun onKeyDown(keyEvent: KeyEvent) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but why/how do gamepads trigger key events?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, on Android a gamepad button press is a kind of KeyEvent. :)

(And a gamepad thumbstick movement is a kind of MotionEvent, just like a mouse or pointer movement.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added comments explaining this

Copy link
Contributor

@apecoraro apecoraro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@lynn lynn merged commit 78fb617 into master Nov 26, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants