最後の文字を入力して1秒経過したら検索をかける方法

ロジック

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

part 'home_screen_notifier.freezed.dart';

@freezed
class HomeScreenState with _$HomeScreenState {
  const factory HomeScreenState({
    @Default(<String>['Adam', 'Bob', 'Cody', 'Dave', 'Fred', 'Emma'])
        List<String> nameList,
    List<String>? searchedNames,
  }) = _HomeScreenState;
}

final homeScreenProvider =
    StateNotifierProvider.autoDispose<HomeScreenNotifier, HomeScreenState>(
  (ref) => HomeScreenNotifier(),
);

class HomeScreenNotifier extends StateNotifier<HomeScreenState> {
  HomeScreenNotifier() : super(const HomeScreenState());

  final searchDelayMilliSeconds = 1000;
  DateTime _lastChangedDate = DateTime.now();

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

  void search({required String text}) {
    if (text.trim().isEmpty) {
      state = state.copyWith(searchedNames: null);
    } else {
      final searchedNames = state.nameList.where((element) {
        final name = element.toLowerCase();
        final input = text.toLowerCase();
        return name.contains(input);
      }).toList();
      state = state.copyWith(searchedNames: searchedNames);
    }
  }
}

呼び出し側

TextField(
    onChanged: (String? text) => ref
       .read(homeScreenProvider.notifier)
      .delayedSearch(text: text!),
),

キャプチャ

全体コード

HomeScreenNotifier

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

part 'home_screen_notifier.freezed.dart';

@freezed
class HomeScreenState with _$HomeScreenState {
  const factory HomeScreenState({
    @Default(<String>['Adam', 'Bob', 'Cody', 'Dave', 'Fred', 'Emma'])
        List<String> nameList,
    List<String>? searchedNames,
  }) = _HomeScreenState;
}

final homeScreenProvider =
    StateNotifierProvider.autoDispose<HomeScreenNotifier, HomeScreenState>(
  (ref) => HomeScreenNotifier(),
);

class HomeScreenNotifier extends StateNotifier<HomeScreenState> {
  HomeScreenNotifier() : super(const HomeScreenState());

  final searchDelayMilliSeconds = 1000;
  DateTime _lastChangedDate = DateTime.now();

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

  void search({required String text}) {
    if (text.trim().isEmpty) {
      state = state.copyWith(searchedNames: null);
    } else {
      final searchedNames = state.nameList.where((element) {
        final name = element.toLowerCase();
        final input = text.toLowerCase();
        return name.contains(input);
      }).toList();
      state = state.copyWith(searchedNames: searchedNames);
    }
  }
}

HomeScreen

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      appBar: MyAppBar(title: 'Home'),
      body: _Body(),
    );
  }
}

class _Body extends HookConsumerWidget {
  const _Body();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final nameList =
        ref.watch(homeScreenProvider.select((value) => value.nameList));
    final searchedNames = ref.watch(
      homeScreenProvider.select((value) => value.searchedNames),
    );

    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const _Title(title: '検索フォーム'),
          TextField(
            onChanged: (String? text) => ref
                .read(homeScreenProvider.notifier)
                .delayedSearch(text: text!),
          ),
          const SizedBox(height: 16),
          const _Title(title: '検索結果'),
          const SizedBox(height: 16),
          if (searchedNames == null) _SearchResult(names: nameList),
          if (searchedNames != null && searchedNames.isNotEmpty)
            _SearchResult(names: searchedNames),
          if (searchedNames != null && searchedNames.isEmpty) const _NotFound(),
        ],
      ),
    );
  }
}

class _Title extends StatelessWidget {
  const _Title({required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    return Text(
      title,
      style: const TextStyle(
        fontSize: 16,
        fontWeight: FontWeight.bold,
      ),
    );
  }
}

class _SearchResult extends StatelessWidget {
  const _SearchResult({required this.names});

  final List<String> names;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: ListView.builder(
        itemCount: names.length,
        itemBuilder: (context, index) {
          final name = names[index];
          return _ListTile(name: name);
        },
      ),
    );
  }
}

class _ListTile extends StatelessWidget {
  const _ListTile({required this.name});

  final String name;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      title: Text(name),
    );
  }
}

class _NotFound extends StatelessWidget {
  const _NotFound();

  @override
  Widget build(BuildContext context) {
    return const Expanded(
      child: Center(
        child: Text(
          'Not found',
          style: TextStyle(
            fontSize: 30,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }
}

参考


この記事が気に入ったらサポートをしてみませんか?