[Flutter] バックグラウンドからの復帰時にキーボードを出したい

テキスト入力がメインの、例えばお問い合わせ画面のようにテキストボックスが多々あるような画面の場合、最初からキーボードを出しておくケースがあると思います。

テキストボックスがある画面で自動的にキーボードを出したい場合は以下のようにTextFieldのautofocusパラメータをtrudで設定します。

TextField(
  autofocus: true,
),

これでキーボードは表示されるのですが、例えば「他のアプリにある情報を参考に入力したい」といったん他のアプリを開いてテキストをコピーして再び自分のアプリに戻ってきたとき、autofocusがtrueでもキーボードが出ていないケースがありました。

そのさいには自前でキーボードを出してあげる必要があるのですが、そこには二手間ぐらいのコードが必要でした。

  • アプリがバックグラウンドから戻ってきたことを検知するウィジェットを作成
  • バックグラウンドから戻ってきた場合に、そのテキストボックスにフォーカスを当てる

具体的なコードは以下のようになりました。

// アプリの状態(バックグラウンド遷移、復帰等)を検知するウィジェット
class LifecycleWidget extends StatefulWidget {
  final Widget child;
  final Future<void> Function()? onResumed;
  final Future<void> Function()? onInactive;
  final Future<void> Function()? onPaused;
  final Future<void> Function()? onDetached;

  const LifecycleWidget({
    Key? key,
    required this.child,
    this.onResumed,
    this.onInactive,
    this.onPaused,
    this.onDetached,
  }) : super(key: key);

  @override
  LifeCycleWidgetState createState() => LifeCycleWidgetState();
}

class LifeCycleWidgetState extends State<LifecycleWidget> with WidgetsBindingObserver {
  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    switch (state) {
      case AppLifecycleState.resumed:
        if (widget.onResumed != null) {
          await widget.onResumed!();
        }
        break;
      case AppLifecycleState.inactive:
        if (widget.onInactive != null) {
          await widget.onInactive!();
        }
        break;
      case AppLifecycleState.paused:
        if (widget.onPaused != null) {
          await widget.onPaused!();
        }
        break;
      case AppLifecycleState.detached:
        if (widget.onDetached != null) {
          await widget.onDetached!();
        }
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: widget.child,
    );
  }
}
  • 参考にしたのは以下のissueです
  • WidgetsBindingObserverクラスを継承
    • これによりアプリの状態やシステムの変更を検知できるようになります
  • WidgetsBinding.instance.addObserver、WidgetsBinding.instance.removeObserverでアプリ状態・システム変更の検知をバインド・解除しています
  • didChangeAppLifecycleStateメソッド
    • パラメータのAppLifecycleStateの値によって何が起きたかを検知できます
    • AppLifecycleState.inactive
      • アプリがバックグラウンドに遷移した
    • AppLifecycleState.paused
      • アプリがバックグラウンドに遷移して停止した
    • AppLifecycleState.resumed
      • アプリがバックグラウンドから復帰した
    • AppLifecycleState.detached
      • アプリが破棄された

上のコードで作成したウィジェットを使って画面を作るさいに、resumedでキーボードを出す処理を実装します。

:
final FocusNode _fn = FocusNode();
:
@override
Widget build(BuildContext context) {
  return LifecycleWidget(
    child: TextField(
      autofocus: true,
      focusNode: _fn,
    ),
    onResumed: () async {
      await Future.delayed(const Duration(milliseconds: 200));
      if (mounted) {
        WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
          if (_fn.hasFocus) {
            _fn.unfocus();
          }
          _fn.requestFocus();
        });
      }
    },
  );
);
:
  • TextFieldは上記で作成したLifecycleWidgetの子ウィジェットにします
  • FocusNodeをTextFieldにセットします
  • LifecycleWidget.onResumedでTextFieldにフォーカスをセットします
  • テキストボックスにフォーカスが当たったのでキーボードが表示されます

これでだいたい動作していたのですが、あるタイミングで動かない時がありました。

そういう時のおまじないとしてFuture.delayedを入れています。本当におまじないなので、うまくいかない時があるかもしれませんが。

以上、バックグラウンドからの復帰時にキーボードを出すコード例でした。