Flutterでテキスト検索機能を実装する方法

image

はじめに

今回は、Flutterで検索機能を実装する方法をご紹介します。
配列の中から、キーワードが含まれる要素だけを表示したいケースがよくありますよね。
そんな時にサクッとコピペで実装できる、テキスト検索の方法をご紹介します。
この記事では、

  • 完全一致での検索
  • 部分一致での検索
  • 検索機能で使えるテクニック
    を説明します。

開発環境

  • Flutter version 2.10.3
  • Dart version 2.16.1

前提

['Bob', 'John', 'Fred', 'Emma','Charlotte']という名前の配列を使って、検索機能を説明していきます。 まずは、名前が入っている配列(データの配列)と検索結果が入る配列の2つを用意します。

  static const nameList = ['Bob', 'John', 'Fred', 'Emma', 'Charlotte'];
  List<String> searchedNames = [];

今回の例では、以下のような実装をしています。
TextFieldonChanged部分のsearchの処理によって、検索結果searchedNamesが変化します。

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('検索'), centerTitle: true),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('検索フォーム', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
            TextField(
onChanged: search,
decoration: InputDecoration(contentPadding: EdgeInsets.symmetric(horizontal: 16)), ), const SizedBox(height: 16), Text('検索結果', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Text('Result : ${searchedName.toString()}') ], ), ), ); }

完全一致での検索

all match

  • 検索のキーワードが空の場合は、検索結果を空にする
  • その他は完全一致している名前を抽出する (statefulWidgetを使っているので、setStateが入っています)
  void search(String text) {
    setState(() {
      if (text.trim().isEmpty) {
        searchedNames = [];
      } else {
searchedNames = nameList.where((element) => element == text).toList();
} }); }

部分一致での検索

partial match

  • 検索のキーワードが空の場合は、検索結果を空にする
  • その他はcontainsを使って部分一致している名前を抽出する
  void search(String text) {
    setState(() {
      if (text.trim().isEmpty) {
        searchedNames = [];
      } else {
searchedNames = nameList.where((element) => element.contains(text)).toList();
} }); }

検索機能で使えるテクニック

検索処理にAPIを利用する場合は、TextFieldonChangedのイベントが走るたびに、APIを叩くことになります。
結果的に動作が重くなったり、検索自体がうまくいかない場合があります。
そんな時には、ユーザーが入力し終えたら検索処理をするという実装をすると良いと思います。
「ユーザーが最後の文字を打ってから、1秒経過したら検索をかける」という場合の例をご紹介します。

search delayed

具体例

まずは、遅らせる秒数を定義します。(各自で調整してください)
そして、最後に文字を入力した日時をStatefulWidgetのStateに保持します。

static const searchDelayMillSec = 1000;
DateTime _lastChangedDate = DateTime.now();

検索を遅らせる処理は以下の通りです。

  • 文字を打つたびに_lastChangedDateが更新される
  • その1秒後にFuture.delayedの中の検索処理が走る という流れです。
 void delayedSearch(String text) {
    Future.delayed(const Duration(milliseconds: searchDelayMillSec), () {
      final nowDate = DateTime.now();
if (nowDate.difference(_lastChangedDate).inMilliseconds > searchDelayMillSec) {
_lastChangedDate = nowDate; search(text); } }); //キーワードが入力されるごとに、検索処理を待たずに_lastChangedDateを更新する
_lastChangedDate = DateTime.now();
}

Future.delayedの中の検索処理が走る前に次の文字を入力すると、_lastChangedDateが再度更新され、

if (nowDate.difference(_lastChangedDate).inMilliseconds > searchDelayMillSec)

という条件はfalseになります。
したがって、1秒以内に次の文字が入力された場合には、検索処理は走りません。
以下にコード全文をコピペ用に置いておきます。
少し難しいので、実際に動かしてみるとイメージがつきやすいかもしれません。

コード全文

import 'package:flutter/material.dart';

void main() => runApp(const SearchApp());

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

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: SearchScreen(),
    );
  }
}

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

  
  State<SearchScreen> createState() => _SearchScreenState();
}

class _SearchScreenState extends State<SearchScreen> {
  static const searchDelayMillSec = 1000;
  static const nameList = ['Bob', 'John', 'Fred', 'Emma', 'Charlotte'];
  List<String> searchedNames = [];
  DateTime _lastChangedDate = DateTime.now();

  void search(String text) {
    setState(() {
      if (text.trim().isEmpty) {
        searchedNames = [];
      } else {
        searchedNames = nameList.where((element) => element.contains(text)).toList();
      }
    });
  }

  void delayedSearch(String text) {
    Future.delayed(const Duration(milliseconds: searchDelayMillSec), () {
      final nowDate = DateTime.now();
      if (nowDate.difference(_lastChangedDate).inMilliseconds > searchDelayMillSec) {
        _lastChangedDate = nowDate;
        search(text);
      }
    });
    //キーワードが入力されるごとに、検索処理を待たずに_lastChangedDateを更新する
    _lastChangedDate = DateTime.now();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('テキスト検索'), centerTitle: true),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('検索フォーム', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
            TextField(
              onChanged: delayedSearch,
              decoration: InputDecoration(contentPadding: EdgeInsets.symmetric(horizontal: 16)),
            ),
            const SizedBox(height: 16),
            Text('検索結果', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
            const SizedBox(height: 16),
            Text('Result : ${searchedNames.toString()}')
          ],
        ),
      ),
    );
  }
}

最後に

いかがでしたか。
StatefulWidgetを用いて、原始的な方法でFlutterでのテキスト検索を実装してみました。
こちらの例を参考にカスタマイズしていただけたら幸いです。

関連

テキストフィールドを実装するに当たってのコツは、こちらで紹介しています。 https://www.kamo-it.org/blog/textfield-focus/

お知らせ

可茂IT塾ではFlutterインターンを募集しています!

可茂IT塾ではFlutterインターンを募集しています!

可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。

Read More
U30可茂ITインターンハッカソン

U30可茂ITインターンハッカソン

12月28,29日開催。2日間でアプリ開発の企画から完成までを目指す!U30可茂ITインターンハッカソンを開催します。

Read More

タグ

Flutter (110)初心者向け (28)イベント (18)Google Apps Script (15)Nextjs (12)可茂IT塾 (8)Firebase (7)riverpod (6)React (6)ChatGPT (5)vscode (5)デザイン (5)新卒 (4)就活 (4)Figma (4)Dart (4)JavaScript (4)お知らせ (4)FlutterWeb (3)Prisma (3)NestJS (3)Slack (3)TypeScript (3)ワーケーション (3)インターン (3)設計 (2)線型計画法 (2)事例 (2)Git (2)Image (2)File (2)Material Design (2)画像 (2)iOS (2)アプリ開発 (2)React Hooks (2)tailwindcss (2)社会人 (2)大学生 (2)RSS (1)Google (1)Web (1)CodeRunner (1)個人開発 (1)Android (1)Unity (1)WebView (1)Twitter (1)フルリモート (1)TextScaler (1)textScaleFactor (1)学生向け (1)supabase (1)Java (1)Spring Boot (1)shell script (1)正規表現 (1)パワーポイント (1)趣味 (1)モンスターボール (1)CSS (1)SCSS (1)Cupertino (1)ListView (1)就活浪人 (1)既卒 (1)保守性 (1)iPad (1)シェアハウス (1)スクレイピング (1)PageView (1)画面遷移 (1)flutter_hooks (1)Gmail (1)GoogleWorkspace (1)ShaderMask (1)google map (1)Google Places API (1)GCPコンソール (1)Google_ML_Kit (1)Vercel (1)Google Domains (1)DeepLeaning (1)深層学習 (1)Google Colab (1)コード生成 (1)GitHub Copilot (1)オンラインオフィス (1)オブジェクト指向 (1)クラスの継承 (1)ポリモーフィズム (1)LINE (1)Bitcoin (1)bitFlyer (1)コミュニティー (1)文系エンジニア (1)Freezed (1)permission_handler (1)flutter_local_notifications (1)markdown (1)GlobalKey (1)ValueKey (1)Key (1)アイコン (1)go_router (1)debug (1)datetime_picker (1)Apple Store Connect (1)FlutterGen (1)デバッグ (1)Widget Inspector (1)検索機能 (1)Shader (1)Navigator (1)メール送信 (1)Firebase App Distribution (1)Fastlane (1)Dio (1)CustomClipper (1)ClipPath (1)カスタム認証 (1)アニメーション (1)Arduino (1)ESP32 (1)経験談 (1)フリーランス (1)mac (1)csv (1)docker (1)GithubActions (1)Dialog (1)BI (1)LifeHack (1)ショートカット (1)Chrome (1)高校生 (1)キャリア教育 (1)非同期処理 (1)生体認証 (1)BackdropFilter (1)レビュー (1)getAuth (1)Algolia (1)コンサルティング (1)Symbol (1)

お知らせ

可茂IT塾ではFlutterインターンを募集しています!

可茂IT塾ではFlutterインターンを募集しています!

可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。

Read More
U30可茂ITインターンハッカソン

U30可茂ITインターンハッカソン

12月28,29日開催。2日間でアプリ開発の企画から完成までを目指す!U30可茂ITインターンハッカソンを開催します。

Read More