はじめに
Flutterアプリから外部サイトへ遷移させたい場合で、遷移まで時間がかかる(APIリクエストしてresponseの情報を元に遷移するなど)際の処理として
- ローディングアイコンを表示して、遷移まではボタンを非活性化する
- 遷移するまでの間にユーザーが画面を移動しないようにする
などの制御ニーズが発生する。
Flutter と Riverpod を利用してローディングの状態管理を行い、ボタンの非活性化と、ナビゲーションの戻るボタンの無効化を行ったので、備忘録記載。
コード全体像
Dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:url_launcher/url_launcher.dart';
final loadingProvider = StateProvider<bool>((ref) => false);
class DataRetrievalPage extends HookConsumerWidget {
const DataRetrievalPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoading = ref.watch(loadingProvider);
return WillPopScope(
onWillPop: () async => !isLoading,
child: Scaffold(
appBar: AppBar(
title: const Text('テスト画面'),
leading: isLoading
? null
: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
),
body: const Padding(
padding: EdgeInsets.all(16.0),
child: _Body(),
),
),
);
}
}
class _Body extends ConsumerWidget {
const _Body();
@override
Widget build(BuildContext context, WidgetRef ref) {
final isLoading = ref.watch(loadingProvider);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'外部サイトに遷移します',
style: TextStyle(
fontSize: 16,
height: 1.5,
),
),
const SizedBox(height: 24),
Center(
child: Stack(
alignment: Alignment.center,
children: [
ElevatedButton(
onPressed: isLoading ? null : () async {
ref.read(loadingProvider.notifier).state = true;
try {
final urlString = await fetchExternalUrl();
final url = Uri.parse(urlString);
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
throw Exception('Could not launch $url');
}
} catch (e) {
ref.watch(exceptionHelperProvider).showMessage(e);
} finally {
ref.read(loadingProvider.notifier).state = false;
}
},
child: const Text('外部サイトに遷移'),
),
if (isLoading)
const Positioned(
child: CircularProgressIndicator(),
),
],
),
),
],
);
}
}
ローディング状態の管理
loadingProvider
という StateProvider
を使用してローディングの状態を管理。
Dart
final loadingProvider = StateProvider<bool>((ref) => false);
- これにより、ローディング状態はアプリの状態管理システム内で保持され、どのウィジェットからでも読み書きができるようになる。
- 初期状態は
false
と定義し、これは「ローディングしていない」という状態を表す。
ボタンの非活性化と外部URLへのリンク処理
ElevatedButton
の onPressed
プロパティを使って、ローディング中はボタンが押せないようにし、外部リンクを開く処理を行う。
isLoading
がtrue
の場合、onPressed
にnull
を渡すことでボタンは非活性化される。- ボタンが押されたとき (
isLoading
がfalse
の場合)、非同期処理を開始し、loadingProvider
の状態をtrue
に設定してローディング状態に入る。 canLaunchUrl
でURLが開けるかどうかを確認し、開ける場合はlaunchUrl
を呼び出して実際にURLを開く。try-catch-finally
ブロックを使用して、例外が発生した場合でも、発生しなかった場合でも、最終的にfinally
ブロックでローディング状態をfalse
に戻す。
Dart
onPressed: isLoading ? null : () async {
ref.read(loadingProvider.notifier).state = true;
try {
// 外部リソースからURLを取得する想定のコード
final urlString = await fetchExternalUrl();
final url = Uri.parse(urlString);
// URLを開く処理。成功したら外部ブラウザが起動し、失敗した場合は例外を投げます。
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
throw Exception('Could not launch $url');
}
} catch (e) {
// エラーハンドリング。例外が発生した場合は、それをユーザーに通知します。
ref.watch(exceptionHelperProvider).showMessage(e);
} finally {
// 例外発生の有無にかかわらず、最終的にローディング状態を終了します。
ref.read(loadingProvider.notifier).state = false;
}
},
child: const Text('データ取得開始'),
AppBarの戻るボタンの非活性化
WillPopScope
ウィジェットを使用して、ローディング中に物理的な戻るボタンを無効にする。また、AppBar
の leading
ウィジェットもローディング状態に基づいて制御。
onWillPop
に渡された関数は、isLoading
がtrue
の時にfalse
を返すことで、ローディング中は画面の戻る操作を無効化。- AppBar の
leading
には、isLoading
がtrue
の時は何も表示しない (null
) ことで、AppBar の戻るボタンも非活性化。
Dart
return WillPopScope(
onWillPop: () async => !isLoading,
child: Scaffold(
appBar: AppBar(
// ...
leading: isLoading
? null
: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
),
// ...
),
);
ローディング中の進捗表示
Stack
ウィジェットを使用して、ローディング中に CircularProgressIndicator
をボタンの中央に表示。
- ローディング状態 (
isLoading
) がtrue
の場合、CircularProgressIndicator
が表示される。 Stack
のalignment
プロパティをAlignment.center
に設定することで、プログレスインジケータはボタンの中央に表示される。
Dart
Center(
child: Stack(
alignment: Alignment.center,
children: [
ElevatedButton(
// ...
),
if (isLoading)
const Positioned(
child: CircularProgressIndicator(),
),
],
),
),
結果
外部サイトに遷移というボタンをクリックしたら、ローディングが表示されて、ボタンは非活性化され、またアプリのナビゲーションバーの戻るボタンも無効化することができた。
コメント