前提・実現したいこと
以下の処理を実装したいです。
0. TextFieldで本のタイトルを入力
0. ボタンを押して画面遷移と同時に本のタイトルでAPI通信を行う
0. 結果によって表示するWidgetを変える
0. 検索中に画面をpopするとAPI通信を中断
実装方法としては、
0. TextButtonタップ時にBookTitleNotifierのStateを更新し画面遷移
0. FutureProvider.autoDisposeの中で1.の値の更新を検知しそれをリクエストのクエリパラメータに設定
0. キャンセルトークンtokenを生成
0. 複数(例のコードは2回)のAPI通信を行い、結果をreturn
0. 遷移先の画面をpopするとref.onDisposeの中に書いたtoken.cancelを実行。通信中の処理がキャンセルされプロバイダーの処理も中断される
タイトルの通り、FutureProviderの処理の中で複数のAPI通信を行う際、キャンセルトークンを使ってキャンセルしても処理が続いてしまいます。
console
1I/flutter ( 486): 検索開始: 猫 2I/flutter ( 486): 通信1 開始 3I/flutter ( 486): 通信1 完了 4I/flutter ( 486): 通信2 開始 5I/flutter ( 486): 通信1を中断 6E/flutter ( 486): 7I/flutter ( 486): 通信2 完了 8I/flutter ( 486): 通信2を中断
該当のソースコード
問題を簡略化するため、同じ検索を2回繰り返しています。
また、返却するデータも本来のデータとは違います。
本のタイトル設定画面
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter/material.dart'; import 'package:test_search_logic/provider.dart'; import 'screen_2.dart'; class Screen1 extends HookWidget { const Screen1({Key? key}) : super(key: key); @override Widget build(BuildContext context) { late String searchTitle; return Scaffold( appBar: AppBar(title: Text('本のタイトルを設定'),), body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( // 入力したタイトル onSubmitted: (title) { searchTitle = title; }, ), SizedBox(height: 30), TextButton( child: Text('検索開始'), onPressed: () { // タイトル設定 context .read(bookTitleProvider.notifier) .changeState(searchTitle); // 遷移 Navigator.push( context, MaterialPageRoute( builder: (builder) => Screen2(), ), ); }, ), ], ), ); } }
検索結果画面
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:test_search_logic/provider.dart'; class Screen2 extends HookWidget { const Screen2({Key? key}) : super(key: key); @override Widget build(BuildContext context) { // 検索処理プロバイダーを変数にセット final result = useProvider(searchProvider); return Scaffold( appBar: AppBar( title: Text('検索結果'), ), body: Center( // プロバイダーの値でWidgetを変える child: result.when( data: (data) => Text(data), loading: () => CircularProgressIndicator(), error: (error, _) => Text('エラー発生'), ), ), ); } }
プロバイダー
import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; // 検索する本のタイトルプロバイダー final bookTitleProvider = StateNotifierProvider<BookTitleNotifier, String>( (_) => BookTitleNotifier()); // State class BookTitleNotifier extends StateNotifier<String> { BookTitleNotifier() : super(''); void changeState(newTitle) => state = newTitle; } // 検索処理のプロバイダー // 画面がpopされると破棄される final searchProvider = FutureProvider.autoDispose((ref) async { final title = ref.watch(bookTitleProvider); // タイトルを検知 final token = CancelToken(); // キャンセルトークン ref.onDispose(token.cancel); // プロバイダーが破棄されたら検索を中断 print('検索開始: $title'); // 検索のURL const url = 'https://app.rakuten.co.jp/services/api/BooksTotal/Search/20170404'; // 検索条件 final query = { 'format': 'json', 'keyword': title, 'booksGenreId': '001', 'applicationId': '1081246808762900104', }; // 通信開始 キャンセルされたらメッセージを出力 // 通信1 print('通信1 開始'); await Future.delayed(const Duration(seconds: 3)); // 3秒待ち final response1 = Dio() .get(url, queryParameters: query, cancelToken: token) .catchError((cancel) { if (CancelToken.isCancel(cancel)) { print('通信1を中断'); } }); print('通信1 完了'); // 通信2 print('通信2 開始'); await Future.delayed(const Duration(seconds: 3)); // 3秒待ち final response2 = Dio() .get(url, queryParameters: query, cancelToken: token) .catchError((cancel) { if (CancelToken.isCancel(cancel)) { print('通信2を中断'); } }); print('通信2 完了'); return '検索完了'; });
考察
3秒待機中にonDisposeが実行されているかもしれません。が、実際のアプリで実装する場合は、現在の処理にかかわらずキャンセルが実行されたら以降の処理をしない、というコードを書くと思います。
一つの通信に一つのプロバイダー、という処理に変えてもどのように実装すれば正常に機能するのかわかりません。
開発環境
% flutter doctor Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel master, 2.6.0-12.0.pre.522, on macOS 11.6 20G165 darwin-x64, locale ja-JP) [✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0) [!] Xcode - develop for iOS and macOS (Xcode 12.5.1) ! Flutter recommends a minimum Xcode version of 13.0.0. Download the latest version or update via the Mac App Store. [✓] Chrome - develop for the web [✓] Android Studio (version 2020.3) [✓] VS Code (version 1.47.3) [✓] Connected device (2 available) ! Doctor found issues in 1 category.
あなたの回答
tips
プレビュー