Flutterでショートカット

5 min read読了の目安(約4900字

こんなことをしたい

アイコン長押しで出てくるあれです。

個人的には全く使わない機能ですが、実装してると気配りの利いたできる男感が出そうなのでやってみます。

知らんけど。

quick_actions

以下のパッケージを使います。

https://pub.dev/packages/quick_actions

iOSは今のところいくつか不具合があるようです。
Androidは見たところ問題なさそうです。

やってみよう

  1. quick_actionsを追加

    pubspec.yaml
    dependencies:
      flutter:
        sdk: flutter
      quick_actions: ^0.4.0+10
    
  2. 初期化
    アプリ内の早い段階で初期化しておきます。

    String shortcut;
    
    
    void initState() {
      super.initState();
    
      QuickActions()
        ..initialize((String shortcutType) {
          // 選択されたショートカットをもとに後で表示する画面を切り替えたいので保持しておきます
          setState(() => shortcut = shortcutType);
        })
        ..setShortcutItems(<ShortcutItem>[
          ShortcutItem(
            type: 'action_one', // ユニークな識別子
            localizedTitle: 'Action one', // アイコン長押しで表示されるタイトル
            icon: 'ic_launcher', // drawable(Android)内/xcassets(iOS)内のアイコン名
          ),
          ShortcutItem(
            type: 'action_two',
            localizedTitle: 'Action two',
            icon: 'ic_launcher',
          ),
        ]);
    }
    
  3. (iOSで上手くいかなかったら)
    GitHub Issuesより

    AppDelegate.swift
    override func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
      GeneratedPluginRegistrant.register(with: self)
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
    
    //以下を追加
    
    @available(iOS 9.0, *)
    override func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
      let controller = window.rootViewController as? FlutterViewController
      
      let channel = FlutterMethodChannel(name: "plugins.flutter.io/quick_actions", binaryMessenger: controller! as! FlutterBinaryMessenger)
      channel.invokeMethod("launch", arguments: shortcutItem.type)
    }
    

    or

    FLTQuickActionsPlugin.m
    - (BOOL)application:(UIApplication *)application
      performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
      completionHandler:(void (^)(BOOL succeeded))completionHandxler
      API_AVAILABLE(ios(9.0)) {
        [self.channel invokeMethod:@"launch" arguments:shortcutItem.type];
        return YES;
      }
    

コード全体

長いので折りたたみました
main.dart
import 'package:flutter/material.dart';
import 'package:quick_actions/quick_actions.dart';

import './screen_one.dart';
import './screen_two.dart';

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

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Quick Actions Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String shortcut;

  
  void initState() {
    super.initState();

    QuickActions()
      ..initialize((String shortcutType) {
        setState(() => shortcut = shortcutType);
      })
      ..setShortcutItems(<ShortcutItem>[
        const ShortcutItem(
          type: 'action_one',
          localizedTitle: 'Action one',
          icon: 'ic_launcher',
        ),
        const ShortcutItem(
          type: 'action_two',
          localizedTitle: 'Action two',
          icon: 'ic_launcher',
        ),
      ]);
  }

  
  Widget build(BuildContext context) {
    switch (shortcut) {
      case 'action_one':
        return ScreenOne();
      case 'action_two':
        return ScreenTwo();
      default:
        return Scaffold(
          appBar: AppBar(
            title: Text('Home'),
          ),
          body: Center(),
        );
    }
  }
}
screen_one.dart
import 'package:flutter/material.dart';

class ScreenOne extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.green,
        title: Text('Screen 1'),
      ),
    );
  }
}
screen_two.dart
import 'package:flutter/material.dart';

class ScreenTwo extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.orange,
        title: Text('Screen 2'),
      ),
    );
  }
}

できた!