2

I'm developing an Android TV app using Flutter. I am able to run the app(the sample app for now). But I can't select anything using d pad select button.

I found that I have to use something like this inorder to achieve this

Shortcuts(

    shortcuts: {
      LogicalKeySet(LogicalKeyboardKey.select):
          const Intent(ActivateAction.key)
    },
    child: MaterialApp())

But it is giving me an error in the code. What am I doing wrong?

Any help is appreciated. Thank you.

7 Answers7

9

Adding this code definitely helps for Android TV OK/Select button and can be extended for other mappings. Builds and works perfectly for flutter 1.24-dev

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent>{
        LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
      },
      child: MaterialApp( 
apc
  • 1,152
  • 12
  • 9
1

Instead of Shortcuts may be you can RawKeyboardListener, I have used below code:

 body: RawKeyboardListener(
        autofocus: true,
        focusNode: _focusNode,
        onKey: _onKey,
        child:Center())
 

    void _onKey(RawKeyEvent e) {
    controls();

    if (e.runtimeType.toString() == 'RawKeyDownEvent') {
      switch (e.logicalKey.debugName) {
        case 'Media Play Pause':
        case 'Select':
          setState(() {
            if (_videoViewController.value.isPlaying) {
              _videoViewController.pause();
            } else {
              _videoViewController.play();
            }
          });
          break;
      }
    }`

Now if you wanted to have a fast forward or back just use the RawKeyboardListener with specific case to handle it.

You can also use your D-PAD keys to perform such action.

Prahlad Awasthi
  • 288
  • 4
  • 6
0

May be this is a late answer. But I have faced the same problem on a TV app. Those above mentioned steps are applicable, if there are no textfeilds in your app. If you are using textfeilds in your app, you should use FocasableActionDetector widget. The FocusableActionDetector includes Shortcuts, Actions and Focus widgets. The way I handled things was, I created a list of FocusNodes and assign them to focusable widgets in the page. then I would wrap all the focusable widgets with FocusableActionDetector give the necessary Keypress logic in the action parameter of the FocusableActionDetector. Here is an example of how to implement it.

    class LoginPage extends StatefulWidget {
      LoginPage({Key? key}) : super(key: key);
    
      @override
      State<LoginPage> createState() => _LoginPageState();
    }

 class _LoginPageState extends State<LoginPage> {
  GlobalKey<FormState> get loginFormKey => formKey ??= GlobalKey<FormState>();

  int loginIndex = 0;

  list<FocusNode> loginNodes = <FocusNode>[];

 


  @override
  void initState() {
  for (var i = 0; i < 5; i++) {
      loginNodes.add(FocusNode(debugLabel: 'loginNode $i'));
    }

    super.initState();
  }

  @override
  void dispose() {
  loginNodes.dispose();

    super.dispose();
  }

  Map<ShortcutActivator, Intent> navigationIntents = {
    LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
    LogicalKeySet(LogicalKeyboardKey.arrowRight): const RightbuttonIntent(),
    LogicalKeySet(LogicalKeyboardKey.arrowLeft): const LeftbuttonIntent(),
    LogicalKeySet(LogicalKeyboardKey.arrowUp): const UpbuttonIntent(),
    LogicalKeySet(LogicalKeyboardKey.arrowDown): const DownbuttonIntent(),
    LogicalKeySet(LogicalKeyboardKey.goBack): const BackButtonIntent()
    //LogicalKeySet(LogicalKeyboardKey.back)
  };

 

  @override
  Widget build(BuildContext context) {
   

    return
      
    
     FocusableActionDetector(
        shortcuts: navigationIntents,
        actions: <Type, Action<Intent>>{
          DownbuttonIntent: CallbackAction<DownbuttonIntent>(
            onInvoke: (intent) {
              // if (loginIndex <= 0) loginIndex = 0;

              if (loginIndex <= loginNodes.length - 1) {
                loginIndex++;
                log('index in downIntent if is $loginIndex');
                FocusScope.of(context).requestFocus(loginNodes[loginIndex]);
              } else if (loginIndex > loginNodes.length - 1) {
                loginIndex = 4;
                log('index in downIntent else is $loginIndex');
                FocusScope.of(context).requestFocus(loginNodes[3]);
              }
              // log('index in downIntent is $loginIndex');

              return KeyEventResult.ignored;
            },
          ),
          UpbuttonIntent: CallbackAction<UpbuttonIntent>(
            onInvoke: (intent) {
              if (loginIndex >= 0) {
                loginIndex--;
                FocusScope.of(context).requestFocus(loginNodes[loginIndex]);
                log('index in upIntent else is $loginIndex');
              } else if (loginIndex <= 0) {
                loginIndex = 0;
                log('index in upIntent if is $loginIndex');
                FocusScope.of(context).requestFocus(loginNodes[0]);
                loginNodes.refresh();
              }

              return KeyEventResult.ignored;
            },
          ),
        },
        child: AuthScreenWrapper(
        
          authForm: AuthForm(
            formKey: loginFormKey,
            title: 'login'.tr,
            subtitle: 'login_to_continue'.tr,
            formComponents: [
              EmailInputField(emailFocus: loginNodes[0]),

              PasswordInputField(passwordFocus: loginNodes[1]),

              KeepMeForgotButtons(
                rememberNode: loginNodes[2],
              ),

           
              LoginButton(
                btnNode: loginNodes[3],
              ),

              
            ],
          ),
        ),
      );
  
  }
}

I hope this help for someone who has faced the same problem as mine

al_rus
  • 75
  • 1
  • 2
  • 9
0

My workaround to this was to use a Focus widget and use it's onKey function to simulate key press.

                  Focus(
                    onKey: (node, event) {
                      if (event.logicalKey == LogicalKeyboardKey.select && node.hasFocus) {
                        setState(() {
                          _debug = 'Pressed';
                        });
                        return KeyEventResult.handled;
                      }
                      return KeyEventResult.ignored;
                    },
                    child: ElevatedButton(onPressed: () {}, child: Text(_debug)),
                  ),
0

Use FocusableActionDetector instead of Shortcuts

Scaffold(
      body: FocusableActionDetector(
        shortcuts: {
          LogicalKeySet(LogicalKeyboardKey.select): ActivateIntent(),
        },
        actions: <Type, Action<Intent>>{
          ActivateIntent: CallbackAction<ActivateIntent>(
            onInvoke: (intent) {
              // Do something when the select key is pressed.
            },
          ),
        },
        child: Text('This text will be focused when the select key is pressed.'),
      ),
    );
0
return Shortcuts(
      shortcuts: <LogicalKeySet, Intent>{
        LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
      },
      child: Actions(
        actions: <Type, Action<Intent>>{
          ActivateIntent: CallbackAction<ActivateIntent>(
            onInvoke: (intent) {
              // Only start the long-press timer if it is not already active
              if (longPressTimer == null || !longPressTimer!.isActive) {
                longPressTimer = Timer(Duration(milliseconds: 500), () {
                  // Execute an action after 500 milliseconds (adjust the duration as needed)
                  print("Select LongPress");

                  // Add your long-press action here
                });
              }
            },
          ),
        },
        child: RawKeyboardListener(
            focusNode: FocusNode(),
            onKey: (event) {
              if (event is RawKeyUpEvent) {
                if (event.logicalKey == LogicalKeyboardKey.select) {
                  // Cancel the long-press timer and execute the action for short press
                  if (longPressTimer != null && longPressTimer!.isActive) {
                    longPressTimer!.cancel();
                    print("Select Short Press");
                    const ActivateIntent();
                    // Add your short-press action here
                  }
                }
              }
            },

this way you can use the longpress and short press with shortcut

0

Using the method of a friend above, this way we can utilize both long press and tap functionalities with the D-pad on TV. Just use this in main.dart

class MyApp extends StatelessWidget with RouteAware {
  final String? filePath;

  const MyApp({Key? key, this.filePath}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent>{
        LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
      },

And in the focus of your longpress This

    onKey: (node, event) {
            if (event.logicalKey == LogicalKeyboardKey.select &&
                node.hasFocus) {
              if (event is RawKeyDownEvent) {
                _longPressTimer =
                    Timer(Duration(milliseconds: 500), () {
                  _isLongPressDetected = true;
                  print("Long press detected");
                  // Implemente a ação que deseja realizar para o long press
                });
              } else if (event is RawKeyUpEvent) {
                _longPressTimer?.cancel();
                if (!_isLongPressDetected) {
                  print("Short press detected");
                  // Implemente a ação que deseja realizar para o short press
                }
                _isLongPressDetected =
                    false; // Reseta a variável de controle para o próximo pressionamento
                return KeyEventResult.handled;
              }
            }
            return KeyEventResult.ignored;
          },

Now you can use the D-pad with long press and press functionality