flutter / flutter

Flutter makes it easy and fast to build beautiful apps for mobile and beyond
https://flutter.dev
BSD 3-Clause "New" or "Revised" License
165.58k stars 27.34k forks source link

Volume keys can't be caught by focusScope with autofocus #71144

Open tom8zds opened 3 years ago

tom8zds commented 3 years ago

I use code like this to catch volume key events and prevent system behavior, but it can't listen to the volume key unless normal keys like 'A' pressed or other events takes the focus( like call up a keyboard),

MaterialApp(
        home: Scaffold(
            body: FocusScope(
                autofocus: true,
                child: Focus(
                    autofocus: true,
                    canRequestFocus: true,
                    onKey: (data, event) {
                      if (event
                          .isKeyPressed(LogicalKeyboardKey.audioVolumeUp)) {
                        print("Volume up");
                        return true;
                      }
                      if (event
                          .isKeyPressed(LogicalKeyboardKey.audioVolumeDown)) {
                        print("Volume down");
                        return true;
                      }
                      return false;
                    },
                    child: Text("Hallochen")))));
tom8zds commented 3 years ago

get the same issue on official example logicalkeyboardkey modify the build method by require focus before return and look like this

 @override
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;
    FocusScope.of(context).requestFocus(_focusNode);

and here it shows the widget have got the focus and present text 'press a key'

if (!_focusNode.hasFocus) {
                return GestureDetector(
                  onTap: () {
                    FocusScope.of(context).requestFocus(_focusNode);
                  },
                  child: const Text('Tap to focus'),
                );
              }

but the hardware keys like volume key will not be caught unless you press a keyboard key

pedromassangocode commented 3 years ago

Hi @tom8zds Can you please provide your flutter doctor -v and a minimal complete reproducible code sample? Thank you

tom8zds commented 3 years ago

flutter doctor -v [✓] Flutter (Channel stable, 1.22.4, on Linux, locale zh_CN.UTF-8) • Flutter version 1.22.4 at /home/yunjie/snap/flutter/common/flutter • Framework revision 1aafb3a8b9 (11 天前), 2020-11-13 09:59:28 -0800 • Engine revision 2c956a31c0 • Dart version 2.10.4 • Pub download mirror https://pub.flutter-io.cn

tom8zds commented 3 years ago
Code ``` /// Flutter code sample for PhysicalKeyboardKey // This example shows how to detect if the user has selected the physical key // to the right of the CAPS LOCK key. import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(MyApp()); /// This is the main application widget. class MyApp extends StatelessWidget { static const String _title = 'Flutter Code Sample'; @override Widget build(BuildContext context) { return MaterialApp( title: _title, home: Scaffold( appBar: AppBar(title: const Text(_title)), body: MyStatefulWidget(), ), ); } } /// This is the stateful widget that the main application instantiates. class MyStatefulWidget extends StatefulWidget { MyStatefulWidget({Key key}) : super(key: key); @override _MyStatefulWidgetState createState() => _MyStatefulWidgetState(); } /// This is the private State class that goes with MyStatefulWidget. class _MyStatefulWidgetState extends State { // The node used to request the keyboard focus. final FocusNode _focusNode = FocusNode(); // The message to display. String _message; // Focus nodes need to be disposed. @override void dispose() { _focusNode.dispose(); super.dispose(); } // Handles the key events from the RawKeyboardListener and update the // _message. void _handleKeyEvent(RawKeyEvent event) { setState(() { if (event.physicalKey == PhysicalKeyboardKey.keyA) { _message = 'Pressed the key next to CAPS LOCK!'; } else { _message = 'Wrong key.'; } }); } @override Widget build(BuildContext context) { final TextTheme textTheme = Theme.of(context).textTheme; FocusScope.of(context).requestFocus(_focusNode); return Container( color: Colors.white, alignment: Alignment.center, child: DefaultTextStyle( style: textTheme.headline4, child: RawKeyboardListener( focusNode: _focusNode, onKey: _handleKeyEvent, child: AnimatedBuilder( animation: _focusNode, builder: (BuildContext context, Widget child) { if (!_focusNode.hasFocus) { return GestureDetector( onTap: () { FocusScope.of(context).requestFocus(_focusNode); }, child: Text('Tap to focus'), ); } return Text(_message ?? 'Press a key'); }, ), ), ), ); } } ```
pedromassangocode commented 3 years ago

Hi @tom8zds Are you building for Linux or Android? Please share the steps to reproduce the issue as well. Thank you

tom8zds commented 3 years ago

android, here is what i get on sdk emulator

untitled issue
pedromassangocode commented 3 years ago

Reproducible on latest beta as well. The issue is that once the app starts you need to press any keyboard key or request focus again for the app to detect the volume physical keys (see the video above).

Cc @gspencergoog

flutter doctor -v ``` [✓] Flutter (Channel beta, 1.24.0-10.2.pre, on Mac OS X 10.15.7 19H2 darwin-x64, locale en) • Flutter version 1.24.0-10.2.pre at /Users/pedromassango/dev/SDKs/flutter_beta • Framework revision 022b333a08 (6 days ago), 2020-11-18 11:35:09 -0800 • Engine revision 07c1eed46b • Dart version 2.12.0 (build 2.12.0-29.10.beta) [✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2) • Android SDK at /Users/pedromassango/Library/Android/sdk • Platform android-30, build-tools 30.0.2 • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 12.1) • Xcode at /Applications/Xcode.app/Contents/Developer • Xcode 12.1, Build version 12A7403 • CocoaPods version 1.9.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 4.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495) [✓] IntelliJ IDEA Community Edition (version 2020.2.3) • IntelliJ at /Applications/IntelliJ IDEA CE.app • Flutter plugin version 51.0.3 • Dart plugin version 202.8070 [✓] VS Code (version 1.51.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.16.0 [✓] Connected device (3 available) • sdk gphone x86 arm (mobile) • emulator-5554 • android-x86 • Android 11 (API 30) (emulator) • Web Server (web) • web-server • web-javascript • Flutter Tools • Chrome (web) • chrome • web-javascript • Google Chrome 86.0.4240.198 • No issues found! ```
tom8zds commented 3 years ago

simulate key press not help, seems only calling keyboard is functional, because hardkeys will call virtual keyboard too

 Future pressKey() async {
    await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
    await simulateKeyUpEvent(LogicalKeyboardKey.keyA);
    print('A');
  }

  @override
  Widget build(BuildContext context) {
    pressKey();
    return FocusScope(
        autofocus: true,
        child: Focus(
            autofocus: true,
            canRequestFocus: true,
            onKey: (data, event) {
              print(event.data.physicalKey.debugName);
              if (event.isKeyPressed(LogicalKeyboardKey.audioVolumeUp)) {
                print("Volume up");
                return true;
              }
              if (event.isKeyPressed(LogicalKeyboardKey.audioVolumeDown)) {
                print("Volume down");
                return true;
              }
              return false;
            },
            child: TextButton(
              onPressed: pressKey,
              child: Text('press'),
            )));
  }
gspencergoog commented 3 years ago

I believe this is an issue with the emulator only. When I try either your initial code, or the modified example code, both of them work properly on an actual device: it detects the volume keys, even if I switch away from the app and come back, it continues to receive the volume events without needing to press a key.

pedromassangocode commented 3 years ago

Just tested o my physical device (Xiaomi 5Plus) and can confirm that it works!

tom8zds commented 3 years ago

I believe this is an issue with the emulator only. When I try either your initial code, or the modified example code, both of them work properly on an actual device: it detects the volume keys, even if I switch away from the app and come back, it continues to receive the volume events without needing to press a key.

my phone is not working, samsung s10e video my asus zenpad with android 7 work as expected, my testing phone with android 9 not working, android sdk version may matter

jongkb commented 3 years ago

Just to chime in, the volume keys were detected on the Oppo R9s (Android 6.0.1) but not on my Xiaomi Mi A2 (Android 10) device. On the Mi A2, it's only after pressing a normal key (like 'A') that the volume keys were detected.

Kozubi commented 3 years ago

Huawei Honor - Android 5.1 - working fine Huawei p8 pro - Android 6.0 - working fine Huawei p20 lite - Android 9.1 - not working Redmi Note 8 pro - Android 11 - not working

Maybe it's connected to Android version

91086

pmella16 commented 2 years ago

same issue here !! xiaomi mia3

jiangtian616 commented 2 years ago

Same issue in Xiaomi K40, Android 11. I must do something like tap a TextField to show virtual keyboard first, and then volume key events can be caught by widgets like KeyboardListener.

mfa95 commented 1 year ago

this problem still persists. Tested on Xiaomi 11t (physical device). it doesn't detect the volume up/down keys without pressing a key on the computer or opening the keyboard on the phone. From where? (translated by google translate)

live9080 commented 1 year ago

It reproduce on pixel6(Android 13). The "volume button" can not be caught until a editable TextField is focused or other key "A-Z" is pressed.

A native onKeyDown on MainActivity: FlutterActivity() can catch the "volume button" directly.

class MainActivity: FlutterActivity(), CanListenVolumeKey {
    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        return when (keyCode) {
            KeyEvent.KEYCODE_VOLUME_MUTE,
            KeyEvent.KEYCODE_VOLUME_UP,
            KeyEvent.KEYCODE_VOLUME_DOWN -> {
                /// something your code

            }
            else -> {
                super.onKeyDown(keyCode, event)

            }
        }
    }
}
peakandyuri commented 1 year ago

It reproduce on pixel6(Android 13). The "volume button" can not be caught until a editable TextField is focused or other key "A-Z" is pressed.

A native onKeyDown on MainActivity: FlutterActivity() can catch the "volume button" directly.

class MainActivity: FlutterActivity(), CanListenVolumeKey {
    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        return when (keyCode) {
            KeyEvent.KEYCODE_VOLUME_MUTE,
            KeyEvent.KEYCODE_VOLUME_UP,
            KeyEvent.KEYCODE_VOLUME_DOWN -> {
                /// something your code

            }
            else -> {
                super.onKeyDown(keyCode, event)

            }
        }
    }
}

It needs to be fixed at a low priority because volume button detection is not working on ios

han1548772930 commented 1 year ago

I also encountered the same problem on Android phones with buttons Unable to listen to F12 function buttons while listening for keyboard events But when I press any number key, I can hear F12 So when I don't press the number keys, I won't hear buttons like F12 anymore

Myrmillion commented 11 months ago

I confirm that this problem still persists. I do not encounter the issue on the emulator, but I do on my physical device (a Samsung XCover6 Pro, running on Android 13.0).

If I don't press any key of the keyboard at least once, my onKey callback function will simply not be called when using the volume up/down physical keys. However, once I click a keyboard key (and even if I do so in a completely different page and the page which holds my callback function hasn't even been built yet) it will work until I restart the app.

EDIT: Actually, I realized that for my onKey callback function to be called, I do not need to press a key of the keyboard first. The keyboard just has to open at least once. Knowing this, I used a somewhat ugly workaround to :

  1. Open the keyboard by autofocusing on a TextField widget.
  2. And then request the focus back to the Focus widget (automatically closing the keyboard as well).
final keyNode = FocusNode();
final textNode = FocusNode();
textNode.addListener(() => {if (textNode.hasFocus) keyNode.requestFocus()});

...

Visibility.maintain(
  visible: false,
  child: Focus(
    focusNode: keyNode,
    onKey: (data, event) { /* callback function */ },
    child: TextField(
      focusNode: textNode,
      autofocus: true,
    ),
  ),
),
LastxTemplar commented 9 months ago

Are there any updates on this? Just stumbled upon this myself. Seems like if I focus on a textfield at any point in my app and then go to the screen with the Keyboard Listener then it works, otherwise it doesn't detect any key. This is very weird and unexpected behaviour.

dam-ease commented 8 months ago

Reproducible on the latest master and stable channels.

Code Sample

```dart import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; /// Flutter code sample for [LogicalKeyboardKey]. void main() => runApp(const KeyExampleApp()); class KeyExampleApp extends StatelessWidget { const KeyExampleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('Key Handling Example')), body: const MyKeyExample(), ), ); } } class MyKeyExample extends StatefulWidget { const MyKeyExample({super.key}); @override State createState() => _MyKeyExampleState(); } class _MyKeyExampleState extends State { // The node used to request the keyboard focus. final FocusNode _focusNode = FocusNode(); // The message to display. String? _message; // Focus nodes need to be disposed. @override void dispose() { _focusNode.dispose(); super.dispose(); } // Handles the key events from the Focus widget and updates the // _message. KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) { setState(() { if (event.logicalKey == LogicalKeyboardKey.keyQ) { _message = 'Pressed the "Q" key!'; } else { if (kReleaseMode) { _message = 'Not a Q: Pressed 0x${event.logicalKey.keyId.toRadixString(16)}'; } else { // As the name implies, the debugName will only print useful // information in debug mode. _message = 'Not a Q: Pressed ${event.logicalKey.debugName}'; } } }); return event.logicalKey == LogicalKeyboardKey.keyQ ? KeyEventResult.handled : KeyEventResult.ignored; } @override Widget build(BuildContext context) { final TextTheme textTheme = Theme.of(context).textTheme; return Container( color: Colors.white, alignment: Alignment.center, child: DefaultTextStyle( style: textTheme.headlineMedium!, child: Focus( focusNode: _focusNode, onKeyEvent: _handleKeyEvent, child: ListenableBuilder( listenable: _focusNode, builder: (BuildContext context, Widget? child) { if (!_focusNode.hasFocus) { return GestureDetector( onTap: () { FocusScope.of(context).requestFocus(_focusNode); }, child: const Text('Click to focus'), ); } return Text(_message ?? 'Press a key'); }, ), ), ), ); } } ```

Screen Record

https://github.com/flutter/flutter/assets/53122008/a89c5e90-9eb2-4750-a2cc-90108be903f9

stable, master flutter doctor -v

``` [!] Flutter (Channel stable, 3.19.0, on macOS 14.2.1 23C71 darwin-arm64, locale en-NG) • Flutter version 3.19.0 on channel stable at /Users/damilolaalimi/sdks/flutter ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/3.1.5/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/damilolaalimi/sdks/flutter. Consider adding /Users/damilolaalimi/sdks/flutter/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision bae5e49bc2 (2 days ago), 2024-02-13 17:46:18 -0800 • Engine revision 04817c99c9 • Dart version 3.3.0 • DevTools version 2.31.1 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/damilolaalimi/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • ANDROID_HOME = /Users/damilolaalimi/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 15C500b • CocoaPods version 1.14.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2022.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694) [!] Android Studio (version unknown) • Android Studio at /Users/damilolaalimi/Downloads/Android Studio Preview.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to determine Android Studio version. • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314) [✓] VS Code (version 1.86.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.78.0 [✓] VS Code (version 1.83.1) • VS Code at /Users/damilolaalimi/Downloads/Visual Studio Code.app/Contents • Flutter extension version 3.78.0 [✓] Connected device (5 available) • sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34) (emulator) • Damilola’s iPhone (mobile) • 00008110-001964480AE1801E • ios • iOS 17.1.1 21B91 • iPhone 15 (mobile) • DBCC5388-DA57-470E-8F0E-477EA0AA3C3A • ios • com.apple.CoreSimulator.SimRuntime.iOS-17-2 (simulator) • macOS (desktop) • macos • darwin-arm64 • macOS 14.2.1 23C71 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 121.0.6167.184 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 2 categories. ``` ``` [!] Flutter (Channel master, 3.20.0-7.0.pre.83, on macOS 14.2.1 23C71 darwin-arm64, locale en-NG) • Flutter version 3.20.0-7.0.pre.83 on channel master at /Users/damilolaalimi/fvm/versions/master ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart/3.1.5/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/damilolaalimi/fvm/versions/master. Consider adding /Users/damilolaalimi/fvm/versions/master/bin to the front of your path. • Upstream repository https://github.com/flutter/flutter.git • Framework revision 6ee7e24bfe (3 hours ago), 2024-02-16 00:51:06 -0500 • Engine revision dd530f1556 • Dart version 3.4.0 (build 3.4.0-148.0.dev) • DevTools version 2.33.0-dev.6 • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades. [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/damilolaalimi/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • ANDROID_HOME = /Users/damilolaalimi/Library/Android/sdk • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694) • All Android licenses accepted. [✓] Xcode - develop for iOS and macOS (Xcode 15.2) • Xcode at /Applications/Xcode.app/Contents/Developer • Build 15C500b • CocoaPods version 1.14.3 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2022.2) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694) [!] Android Studio (version unknown) • Android Studio at /Users/damilolaalimi/Downloads/Android Studio Preview.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart ✗ Unable to determine Android Studio version. • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314) [✓] VS Code (version 1.86.1) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.78.0 [✓] VS Code (version 1.83.1) • VS Code at /Users/damilolaalimi/Downloads/Visual Studio Code.app/Contents • Flutter extension version 3.78.0 [✓] Connected device (6 available) • sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34) (emulator) • Damilola’s iPhone (mobile) • 00008110-001964480AE1801E • ios • iOS 17.1.1 21B91 • iPhone 15 (mobile) • DBCC5388-DA57-470E-8F0E-477EA0AA3C3A • ios • com.apple.CoreSimulator.SimRuntime.iOS-17-2 (simulator) • macOS (desktop) • macos • darwin-arm64 • macOS 14.2.1 23C71 darwin-arm64 • Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin • macOS 14.2.1 23C71 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 121.0.6167.184 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 2 categories. ```

khjde1207 commented 8 months ago

IOS use flutter_volume_controller

https://github.com/yosemiteyss/flutter_volume_controller

KacterLin commented 5 months ago

In Android, you can call "TextInput.show" and "TextInput.hide" when flutter starts.

SystemChannels.textInput.invokeMethod("TextInput.show"); 
SystemChannels.textInput.invokeMethod("TextInput.hide");
ammarjai commented 1 month ago

In Android, you can call "TextInput.show" and "TextInput.hide" when flutter starts.

SystemChannels.textInput.invokeMethod("TextInput.show"); 
SystemChannels.textInput.invokeMethod("TextInput.hide");

Happen to me on Android 11 as well. While using KacterLin solution helps, its not the best.

tulioccalazans commented 1 month ago

In Android, you can call "TextInput.show" and "TextInput.hide" when flutter starts.

SystemChannels.textInput.invokeMethod("TextInput.show"); 
SystemChannels.textInput.invokeMethod("TextInput.hide");

Happen to me on Android 11 as well. While using KacterLin solution helps, its not the best.

Good workaround!

Here is how I did it:

  @override
  void initState() {  
    Future.delayed(Duration.zero, () async {  
      SystemChannels.textInput.invokeMethod('TextInput.show');  
      SystemChannels.textInput.invokeMethod('TextInput.hide');  
    });  
    super.initState();  
  }