web analytics

การใช้ Actions และ Shortcuts Widget ใน Flutter

สวัสดีครับ บล็อกนี้ขอต่อเนื่องเรื่อง Shortcut ใน Flutter Desktop ในตอนที่แล้ว เราได้ลองใช้งาน widget ที่ชื่อว่า RawKeyboardListener เป็น widget เกี่ยวกับการจัดการกับ Key event ในแต่ละ widget ที่ต้องการ ซึ่งถือว่าตอบโจทย์ของการทำ Shortcut ต่างๆได้แล้ว แต่ทว่า Flutter ก็เล็งเห็นว่าการทำ Shortcut โดยใช้ RawKeyboardListener ในแอปพลิเคชันขนาดใหญ่ มีความซับซ้อน อาจจะทำให้จัดการได้ยาก เกิดความไม่เป็นระเบียบ จำเป็นต้องมีระบบหรือกลไกบางอย่างที่รองรับมากขึ้นกับการทำงานเรื่องนี้ เป็นที่มาของเนื้อหาในบทความนี้

เนื้อหาในตอนก่อนหน้านี้

เริ่มต้น

ในบทความนี้ผมจะสรุปและเรียบเรียงใหม่ตามความเข้าใจ โดยอ้างอิงจาก document ในเว็บของ Flutter อีกทีครับ โดยหัวข้อ Actions และ Shortcuts สามารถอ่านได้จาก Official Document ของ Flutter ได้เลยตามลิงค์ข้างล่างนี้ครับ
https://docs.flutter.dev/development/ui/advanced/actions_and_shortcuts

ในตัวอย่างนี้ เราจะจำลองการใช้งาน Shortcut Widget
โดยเราจะมี TextField ตัวหนึ่ง และเมื่อกดปุ่ม ESC จะทำให้ข้อความใน TextField ถูกล้างออกไป

Intent

ก่อนอื่นต้องรู้จักกับ Intent ก่อน Intent จะเป็น generic type ของ Action ยกตัวอย่างให้เห็นภาพ เปรียบเสมือนเราจะเดินทางกลับบ้าน เราก็มีวิธีการเลือกเดินทางได้หลายวิธี ในแต่ละวิธีก็จะมีรายละเอียด ขั้นตอนของตัวเอง

Intent = เดินทางกลับบ้าน
Action = เดินทางโดยรถเมล์ (เดินไปป้ายรถเมล์ -> รอรถเมล์ -> ขึ้นรถเมล์ -> … )
Action = เดินทางโดยการเดินกลับ (เดินเลี้ยวซ้าย -> ข้ามถนน -> เดินตรงไป > ขึ้นสะพานลอย -> … )

ดังนั้น Action หลายอัน อาจจะมี Intent เดียวกันก็ได้ เพราะเป็นเรื่องที่ต้องการผลลัพธ์เหมือนกัน
กลับมาที่ตัวอย่างของเรา เราจะเขียน class Intent ได้ดังนี้

class ClearIntent extends Intent {
  const ClearIntent();
}

Action

ต่อมาส่วนของ Action เป็นส่วนที่เราต้องอธิบายการทำงานของ Intent ซึ่งในที่นี้คือการ Clear ข้อความใน TextField โดยการทำงานของ Action จะเขียนใน methos ที่ชื่อว่า invoke() ซึ่งสังเกตว่าเราจะต้อง extends Action<T> ซึ่ง generic type เป็น Intent นั่นเอง

class ClearAction extends Action<ClearIntent> {
  ClearAction(this.controller);

  final TextEditingController controller;

  @override
  Object? invoke(covariant ClearIntent intent) {
    controller.clear();
  }
}

Shortcuts Widget

เมื่อเราได้ Intent และ Action แล้ว ถึงเวลาที่เราจะใช้ Shortcut Widget กัน
วิธีการใช้งานง่ายมากเพียงเพิ่มมันไปเป็น parent ของ widget ที่ต้องการ จากนั้นกำหนด ปุ่ม Key shortcuts ที่ต้องการได้เลย
โดยจะ type จะเป็น Map<LogicalKeySet, Intent>

ในตัวอย่างนี้ เรากำหนด ปุ่ม ESC สำหรับ ClearIntent

Shortcuts(
        shortcuts: <LogicalKeySet, Intent>{
          LogicalKeySet(LogicalKeyboardKey.escape): const ClearIntent(),
        },
        child: MyTextField(),
      )

Actions Widget

ขั้นตอนสุดท้าย เราจะต้องกำหนด Widget ที่ชื่อว่า Actions ให้กับ widget ของเราด้วย (อยู่ภายใต้ Shortcut widget แต่เป็น parent ของ TextField) โดยจะมี field ที่ชื่อว่า actions ที่ต้องกำหนดว่าเราจะยอมรับ Action อะไรบ้าง ในที่นี้คือ ClearAction

class _MyTextFieldState extends State<MyTextField> {

  late TextEditingController controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Actions(
      actions: <Type, Action<Intent>>{
        ClearIntent: ClearAction(controller),
      },
      child: Builder(builder: (BuildContext context) {
         ...
      })
    );
  }
}

หรือหากต้องการทำปุ่มเพื่อเรียกใช้ Action โดยตรงสามารถใช้คำสั่งด้านล่างนี้ได้เหมือนกัน

Actions.handler<ClearIntent>(context, const ClearIntent())

Shortcut Manager

หากเราต้องการ handle บางอย่าง เกี่ยวกับการกด key เราสามารถ implement คลาสที่ชื่อว่า ShortcutManager

class LoggingShortcutManager extends ShortcutManager {
  @override
  KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) {
    final KeyEventResult result = super.handleKeypress(context, event);
    if (result == KeyEventResult.handled) {
      print('Handled shortcut $event in $context');
    }
    return result;
  }
}

จากนั้นนำไปเพิ่มใน Shortcuts ที่ field ชื่อว่า manager

Shortcuts(
        manager: LoggingShortcutManager(),
        ...

Action Dispatcher

การ handle Action ก็สามารถทำได้เหมือน ShortcutManager โดย implement คลาสที่ชื่อว่า ActionDispatcher เพื่อ handle ทุก Action ที่เกิดขึ้น

class LoggingActionDispatcher extends ActionDispatcher {
  @override
  Object? invokeAction(
      covariant Action<Intent> action,
      covariant Intent intent, [
        BuildContext? context,
      ]) {
    print('Action invoked: $action($intent) from $context');
    super.invokeAction(action, intent, context);
  }
}

จากนั้นเพิ่ม ActionDispatcher ไปที่ field ชื่อว่า dispatcher ของ Actions

Actions(
      dispatcher: LoggingActionDispatcher(),
      ...

จบแล้ว

ในบทความนี้เราได้ลองเล่น Actions และ Shortcuts Widget เป็นกลไกที่ช่วยให้เราสามารถทำ Shortcut ได้อย่างเป็นระเบียบใน Flutter หวังว่าบทความนี้จะมีประโยชน์กับผู้อ่านนะครับ ขอบคุณที่ติดตามอ่านจนจบครับ

ขอบคุณครับ