質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

並列処理

複数の計算が同時に実行される手法

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

Q&A

0回答

1395閲覧

FutureProvider + Dio でAPI通信を複数行う際、キャンセルを実行しても処理が続いてしまう。

massanmesu

総合スコア36

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

並列処理

複数の計算が同時に実行される手法

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

API

APIはApplication Programming Interfaceの略です。APIはプログラムにリクエストされるサービスがどのように動作するかを、デベロッパーが定めたものです。

0グッド

0クリップ

投稿2021/11/16 03:12

前提・実現したいこと

以下の処理を実装したいです。
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.

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問