LoginSignup
12
7

More than 1 year has passed since last update.

【Flutter】AutocompleteWidgetでフォームの自動補完機能を実装する

Posted at

Googleの検索窓やECサイトへのログイン時に自動で候補が表示されるオートコンプリート機能って便利ですよね。Flutterにもこれを簡単に実装できるものがないかな〜と思っていた時に丁度Flutter公式YoutubeチャンネルののWidget of the Weekでそれらしきものが紹介されていました。

今回はこちらのWidgetをいじってみたので紹介していきたいと思います。

基本的な使い方

基本的な使い方は下記の通りです。コピペで動きます。

class AutocompleteExample extends StatelessWidget {
  const AutocompleteExample({Key? key}) : super(key: key);

  static const _sports = <String>[
    'football',
    'baseball',
    'tennis',
    'swimming',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Autocomplete<String>(
          optionsBuilder: (TextEditingValue textEditingValue) {
            if (textEditingValue.text == '') {
              return const Iterable<String>.empty();
            }
            return _sports.where((String option) {
              return option.contains(textEditingValue.text.toLowerCase());
            });
          },
          onSelected: (String value) {
            // 候補選択時に呼び出されます。
            print('$valueを選択しました!');
          },
        ),
      ),
    );
  }
}

TextFromFieldの代わりにAutocompleteを指定します。optionsBuilderは現在入力されている値を確認し、一致する文字を含む候補を表示してくれます。onSelectedはユーザーが候補を選択した時に呼び出されます。

また、候補一覧はString型以外にも指定することが可能で、例えばUserというクラスのnameで候補一覧を表示したいといったことも可能です。

class User {
  User({
    required this.name,
    required this.mail,
  });
  String name;
  String mail;
}

class AutocompleteExample extends StatelessWidget {
  AutocompleteExample({Key? key}) : super(key: key);

  final users = <User>[
    User(name: 'Aくん', mail: 'akun@sample.com'),
    User(name: 'Bくん', mail: 'bkun@sample.com'),
    User(name: 'Cくん', mail: 'ckun@sample.com'),
  ];
  // 何のプロパティを候補に表示するか指定
  static String _displayStringForOption(User option) => option.name;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Autocomplete<User>(
          displayStringForOption: _displayStringForOption,
          optionsBuilder: (TextEditingValue textEditingValue) {
            if (textEditingValue.text == '') {
              return const Iterable<User>.empty();
            }
            return users.where((User option) {
              return option.name.toString().contains(textEditingValue.text);
            });
          },
          // 選択した値が入る
          onSelected: (User user) {
            print('${user.name}を選択しました!');
          },
        ),
      ),
    );
  }
}

簡単に使えて良いですね!

候補リストをカスタマイズ

optionsViewBuilderプロパティで候補一覧のスタイルを変更することができます。今回はListView.builderでリスト形式で表示するように実装してみました。

class AutocompleteExample extends StatelessWidget {
  AutocompleteExample({Key? key}) : super(key: key);

  final users = <User>[
    User(name: 'Aくん', mail: 'akun@sample.com'),
    User(name: 'Bくん', mail: 'bkun@sample.com'),
    User(name: 'Cくん', mail: 'ckun@sample.com'),
  ];

  static String _displayStringForOption(User option) => option.name;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Autocomplete<User>(
            displayStringForOption: _displayStringForOption,
            optionsBuilder: (TextEditingValue textEditingValue) {
              if (textEditingValue.text == '') {
                return const Iterable<User>.empty();
              }
              return users.where((User option) {
                return option.name.toString().contains(textEditingValue.text);
              });
            },
            onSelected: (User user) {
              print('${user.name}を選択しました');
            },
            optionsViewBuilder: (context, onSelected, users) {
              return ListView.builder(
                itemCount: users.length,
                itemBuilder: ((context, index) {
                  // iterable to list
                  final userList = users.toList();
                  return MyWidget(userList[index]);
                }),
              );
            }),
      ),
    );
  }
}

class MyWidget extends StatelessWidget {
  MyWidget(this.user);
  User user;
  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        title: Text(user.name),
        subtitle: Text(user.mail),
      ),
    );
  }
}

フォームをカスタマイズ

fieldViewBuilderプロパティでフォームのスタイルを変更することができます(スタイルだけというか、通常のTextFormFiledと同じようなことがここで指定できます)

class AutocompleteExample extends StatelessWidget {
  const AutocompleteExample({Key? key}) : super(key: key);

  static const _sports = <String>[
    'football',
    'baseball',
    'tennis',
    'swimming',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Autocomplete<String>(
          optionsBuilder: (TextEditingValue textEditingValue) {
            // 未入力の場合は自動補完を走らせない
            if (textEditingValue.text == '') {
              return const Iterable<String>.empty();
            }
            return _sports.where((String option) {
              return option.contains(textEditingValue.text.toLowerCase());
            });
          },
          fieldViewBuilder:
              (context, textEditingController, focusNode, onFieldSubmitted) {
            return TextFormField(
              controller: textEditingController,
              focusNode: focusNode,
              onFieldSubmitted: (String value) {
                onFieldSubmitted();
              },
              decoration: InputDecoration(
                focusedBorder: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(16),
                  borderSide: const BorderSide(
                    color: Colors.green,
                    width: 2.0,
                  ),
                ),
                labelStyle: TextStyle(
                  fontSize: 12,
                  color: Colors.green[300],
                ),
                labelText: 'フレームあり、ラベルあり',
                floatingLabelStyle: const TextStyle(fontSize: 12),
                enabledBorder: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(16),
                  borderSide: BorderSide(
                    color: Colors.green[100]!,
                    width: 1.0,
                  ),
                ),
              ),
            );
          },
          // 選択した値が入る
          onSelected: (String selection) {
            print('$selectionを選択しました!');
          },
        ),
      ),
    );
  }
}

活用例

SharedPreferenceなどのパッケージを活用してユーザーのメールアドレスを永続化しておけば、次回ユーザーがログインする時の負担を減らすことができます(ECサイトとかでよく見るやつです)

class AutocompleteSample extends StatefulWidget {
  const AutocompleteSample({Key? key}) : super(key: key);

  @override
  State<AutocompleteSample> createState() => _AutocompleteSampleState();
}

class _AutocompleteSampleState extends State<AutocompleteSample> {
  List<String> mails = [];

  Future<void> getMail() async {
    final prefs = await SharedPreferences.getInstance();
    final mail = prefs.getString('mail');
    if (mail != null) {
      mails.add(mail);
    }
  }

  @override
  void initState() {
    super.initState();
    getMail();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Autocomplete<String>(
          optionsBuilder: (TextEditingValue textEditingValue) {
            if (textEditingValue.text == '') {
              return const Iterable<String>.empty();
            }
            return mails.where((String option) {
              return option.contains(textEditingValue.text.toLowerCase());
            });
          },
          onSelected: (String selection) {
            print('$selectionを選択しました!');
          },
        ),
      ),
    );
  }
}

自分で検索ロジックを書く必要がないので検索フォームの実装の負担がかなり減るなと思いました!みなさんもぜひ使ってみてください!

参考

12
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7