📶
画像のダウンロードができないときにエラー処理をしたい
🤔やってみたいこと
インターネットから画像をダウンロードするアプリを作る。メモリに保存するだけなので後で消えますけどね。Pythonの本で例外処理のページを呼んでいてインターネットにつながっていないときに例外処理が出てきてるのを見て、ネットにつながってない処理は書いたことないのに気づいた😅
Dart3.0のsealed class
使えないかと試してみた。KotlinだとよくAPI通信の例外処理でsealed class
を使うのを見かける。
たまたまFeedに流れてきた記事が気になり作ってみた。
🚀やってみたこと
dio
を使って画像をインターネットからダウンロードするだけの機能を実装してみた。
機能
- pixabayから画像をダウンロード
- ダウンロードした画像をアプリ内で表示
- エラーハンドリング(接続エラー、タイムアウトなど)
技術スタック
- Flutter/Dart
- dio - HTTPクライアント
- Material Design 3
アーキテクチャ
アプリケーションは以下の3つの主要なファイルで構成されています:
-
main.dart
- アプリケーションのUIとメインロジック -
image_download_service.dart
- 画像ダウンロード処理を担当 -
api_response.dart
- APIレスポンスの型定義(Success/Failure)
エラーハンドリング
以下のエラーケースに対応しています:
- インターネット接続なし
- 接続タイムアウト
- サーバーエラー
- その他の予期せぬエラー
使い方
- アプリを起動
- 画面右下のFloating Action Buttonをタップ
- ランダムな画像がダウンロードされ、画面中央に表示されます
注意点
- ダウンロードした画像はメモリ上にのみ保存され、アプリ終了時に消去されます
- 永続的な保存機能は実装されていません
sealed class
でAPIレスポンスの型定義(Success/Failure)をする。
api_response.dart
sealed class ApiResponse<T> {
const ApiResponse();
}
final class Success<T> extends ApiResponse<T> {
const Success(this.data);
final T data;
}
final class Failure extends ApiResponse<Never> {
const Failure(this.message);
final String message;
}
画像をダウンロード処理のビジネスロジックを定義する。
image_download_service.dart
import 'package:dio/dio.dart';
import 'api_response.dart';
class ImageDownloadService {
final Dio _dio = Dio();
Future<ApiResponse<List<int>>> downloadImage(String url) async {
try {
final response = await _dio.get<List<int>>(
url,
options: Options(responseType: ResponseType.bytes),
);
return Success(response.data!);
} on DioException catch (e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return const Failure('接続がタイムアウトしました');
case DioExceptionType.connectionError:
return const Failure('インターネット接続がありません');
case DioExceptionType.badResponse:
return Failure('サーバーエラー: ${e.response?.statusCode}');
default:
return Failure('ダウンロードに失敗しました: ${e.message}');
}
} catch (e) {
return Failure('予期せぬエラーが発生しました: $e');
}
}
}
ユーザーが画像をダウンロードする操作をする機能を実装したコード。
main.dart
import 'package:flutter/material.dart';
import 'image_download_service.dart';
import 'api_response.dart';
import 'dart:typed_data';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: '画像ダウンローダー',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: '画像ダウンローダー'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ImageDownloadService _service = ImageDownloadService();
Uint8List? _imageBytes;
String? _errorMessage;
Future<void> _downloadImage() async {
setState(() {
_imageBytes = null;
_errorMessage = null;
});
final response = await _service.downloadImage(
'https://cdn.pixabay.com/photo/2024/09/19/07/30/wild-horse-9057944_1280.jpg',
);
switch (response) {
case Success(data: final bytes):
setState(() {
_imageBytes = Uint8List.fromList(bytes);
});
case Failure(message: final message):
setState(() {
_errorMessage = message;
});
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
if (_imageBytes != null)
Image.memory(_imageBytes!)
else if (_errorMessage != null)
Text(
_errorMessage!,
style: const TextStyle(
color: Colors.red,
fontSize: 16,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
)
else
const Text('画像をダウンロードするにはボタンを押してください')
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _downloadImage,
tooltip: 'Download Image',
child: const Icon(Icons.download),
),
);
}
}
🙂最後に
API通信をするときに画像のダウンロードをする処理は実装したことなかったのでハードコーディングですが実装してみました。シンプルなデモアプリですがエラー処理の学習に最適かなと思い試しに作ってみました。
Discussion