FocusableActionDetector Flutter Widget: How to use it

Tue, Mar 14, 2023

Read in 4 minutes

In this post we learn how to use FocusableActionDetector widget in flutter.

FocusableActionDetector is a Flutter widget that allows you to detect and handle focus and action events within a widget tree. It is commonly used in building accessible user interfaces where users can navigate through different widgets using keyboard, screen readers, or other input methods. In this tutorial, we will explore how to use FocusableActionDetector in detail with code examples.

Basic usage

The simplest way to use FocusableActionDetector is to wrap it around a widget that you want to make focusable and actionable. Here’s an example:

FocusableActionDetector(
  actions: {
    ActivateAction.key: CallbackAction(
      onInvoke: () {
        // Do something when the widget is activated
      },
    ),
  },
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
  ),
);

In this example, we have wrapped a Container widget with FocusableActionDetector. We have also defined an actions map that maps a key to a CallbackAction. The ActivateAction.key is a built-in key that represents the “activate” action. When the user activates the widget (e.g. by pressing the Enter key or clicking on it), the CallbackAction will be invoked and we can do something inside its onInvoke callback.

Adding focus traversal keys

In addition to the “activate” action, FocusableActionDetector allows you to specify focus traversal keys that enable users to move the focus to other widgets using keyboard shortcuts. Here’s an example:

FocusableActionDetector(
  focusTraversalKeys: {
    // Move the focus to the next widget when the user presses the Tab key
    LogicalKeyboardKey.tab: TraversalDirectionalAction.increment,
    // Move the focus to the previous widget when the user presses the Shift + Tab keys
    LogicalKeyboardKey.shiftLeft: TraversalDirectionalAction.decrement,
  },
  actions: {
    ActivateAction.key: CallbackAction(
      onInvoke: () {
        // Do something when the widget is activated
      },
    ),
  },
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
  ),
);

In this example, we have added two focus traversal keys to the focusTraversalKeys map: LogicalKeyboardKey.tab and LogicalKeyboardKey.shiftLeft. The former moves the focus to the next widget in the focus order, while the latter moves the focus to the previous widget.

Handling focus events

FocusableActionDetector also allows you to handle focus events, such as when the widget gains or loses focus. Here’s an example:

FocusableActionDetector(
  onFocusChange: (hasFocus) {
    if (hasFocus) {
      // Do something when the widget gains focus
    } else {
      // Do something when the widget loses focus
    }
  },
  actions: {
    ActivateAction.key: CallbackAction(
      onInvoke: () {
        // Do something when the widget is activated
      },
    ),
  },
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
  ),
);

In this example, we have defined an onFocusChange callback that is called when the widget gains or loses focus. Inside the callback, we can do something based on the hasFocus boolean value.

Combining focusable widgets

You can combine multiple focusable widgets in a widget tree by wrapping each of them with FocusableActionDetector. Here’s an example:

FocusableActionDetector(
  onFocusChange: (hasFocus) {
    if (hasFocus) {
      // Do something when the widget gains focus
    } else {
      // Do something when the widget loses focus
    }
  },
  actions: {
    ActivateAction.key: CallbackAction(
      onInvoke: () {
        // Do something when the widget is activated
      },
    ),
  },
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
  ),
);

In this example, we have added two `FocusableActionDetector` widgets that wrap two `Container` widgets with different colors and texts. Each `FocusableActionDetector` widget has its own `actions` map that defines the “activate” action for each widget.

Here’s a complete code example that combines all the previous examples:

## Full code example

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FocusableActionDetector Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('FocusableActionDetector Demo'),
      ),
      body: Column(
        children: [
          FocusableActionDetector(
            actions: {
              ActivateAction.key: CallbackAction(
                onInvoke: () {
                  print('Widget 1 activated');
                },
              ),
            },
            onFocusChange: (hasFocus) {
              if (hasFocus) {
                print('Widget 1 has focus');
              } else {
                print('Widget 1 lost focus');
              }
            },
            focusTraversalKeys: {
              LogicalKeyboardKey.tab: TraversalDirectionalAction.increment,
              LogicalKeyboardKey.shiftLeft: TraversalDirectionalAction.decrement,
            },
            child: Container(
              width: 100,
              height: 100,
              color: Colors.blue,
              child: Center(
                child: Text('Widget 1'),
              ),
            ),
          ),
          FocusableActionDetector(
            actions: {
              ActivateAction.key: CallbackAction(
                onInvoke: () {
                  print('Widget 2 activated');
                },
              ),
            },
            onFocusChange: (hasFocus) {
              if (hasFocus) {
                print('Widget 2 has focus');
              } else {
                print('Widget 2 lost focus');
              }
            },
            focusTraversalKeys: {
              LogicalKeyboardKey.tab: TraversalDirectionalAction.increment,
              LogicalKeyboardKey.shiftLeft: TraversalDirectionalAction.decrement,
            },
            child: Container(
              width: 100,
              height: 100,
              color: Colors.green,
              child: Center(
                child: Text('Widget 2'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

In this example, we have defined two FocusableActionDetector widgets that wrap two Container widgets with different colors and texts. We have also added onFocusChange callbacks to handle focus events and focusTraversalKeys maps to enable focus traversal using keyboard shortcuts. Finally, we have added ActivateAction callbacks to handle widget activation events.


Shohruh AK





See Also

How to Use 'sqflite_ffi' Database Package in Flutter
What Skills a Flutter Developer Should Have
Should I Learn Web Or Mobile Development in 2023?
What Programming Language You Should Learn
OpenAI's GPT-3 Language Model: Explained