【Flutter】Flutter SDKリポジトリ内で使われている気になるパッケージ10選
概要
今回はFlutter SDKのリポジトリ内で使っているパッケージの中から、メジャーなものは避けて個人的に気になる面白そうなものを10個ピックアップして試して見ました。
この記事はFlutter Advent Calendar 2025の12月25日分の記事です。
対象
今回対象となるのは以下Flutter公式リポジトリのルートにあるで pubspec.yaml の現時点(2025/12)で使われているパッケージを調べて気になったものをピックアップしてます。
動作環境
# Dartでの動作環境時
$ dart --version
Dart SDK version: 3.10.1 (stable) (Tue Nov 18 11:02:31 2025 -0800) on "macos_arm64"
# Flutterでの動作環境時
$ flutter --version
Flutter 3.38.5 • channel stable
Tools • Dart 3.10.4 • DevTools 2.51.1
パッケージ
async
非同期処理を扱うために、dart:async スタイルに準拠したユーティリティクラス
執筆時のバージョン: 2.13.0
- 主なユーティリティ
-
-
時間のかかる非同期計算の結果を一定期間キャッシュしてくれる
import 'package:async/async.dart' as async; Future<void> demoAsyncCache() async { var counter = 0; final cache = async.AsyncCache<int>(const Duration(seconds: 2)); Future<int> load() async { counter++; print(' expensive computation... (#$counter)'); await Future<void>.delayed(const Duration(milliseconds: 100)); return counter; } final v1 = await cache.fetch(load); final v2 = await cache.fetch(load); print(' v1=$v1, v2=$v2 (キャッシュされているので counter=1 のまま)'); // キャッシュの有効期限切れ後 await Future<void>.delayed(const Duration(seconds: 3)); final v3 = await cache.fetch(load); print(' v3=$v3 (有効期限切れ後なので再計算)'); } /* 実行結果 expensive computation... (#1) v1=1, v2=1 (キャッシュされているので counter=1 のまま) expensive computation... (#2) v3=2 (有効期限切れ後なので再計算) */
-
-
-
非同期操作をメモ化してくれる
import 'package:async/async.dart' as async; Future<void> demoAsyncMemoizer() async { final memoizer = async.AsyncMemoizer<String>(); Future<String> loadConfig() => memoizer.runOnce(() async { print(' 実際のロードは一回だけ'); await Future<void>.delayed(const Duration(milliseconds: 100)); return 'CONFIG-${DateTime.now().millisecondsSinceEpoch}'; }); final c1 = await loadConfig(); final c2 = await loadConfig(); print(' c1=$c1'); print(' c2=$c2 (同じ値・2回目は中の処理は走らない)'); } /* 実行結果 実際のロードは一回だけ c1=CONFIG-1765349468315 c2=CONFIG-1765349468315 (同じ値・2回目は中の処理は走らない) */
-
-
-
一連の futures が完了するまで待機してくれる
import 'package:async/async.dart' as async; Future<void> demoFutureGroup() async { final group = async.FutureGroup<String>(); Future<String> fetchUser() async { await Future<void>.delayed(const Duration(milliseconds: 300)); return 'User'; } Future<String> fetchPosts() async { await Future<void>.delayed(const Duration(milliseconds: 500)); return 'Posts'; } group.add(fetchUser()); group.add(fetchPosts()); // もう追加しないことを宣言 group.close(); final results = await group.future; print(' FutureGroup results=$results'); } /* 実行結果 FutureGroup results=[User, Posts] */ -
他にもStream系のユーティリティが多々あります
-
-
boolean_selector
簡略化された Dart 風の文法で書かれたブール式を扱うためのライブラリ。
セレクターには、識別子、括弧、および ||, &&, !, ?: などのブール演算子を含めることができます。有効な Dart 識別子はすべて許可されており、識別子にはハイフンを含めることもできます。例)chrome,chrome || content-shell,js || (vm && linux)
執筆時のバージョン: 2.1.2
-
サンプル
import 'package:boolean_selector/boolean_selector.dart'; void main(List<String> args) { var selector = BooleanSelector.parse("(x && y) || z"); print(selector.evaluate((variable) => args.contains(variable))); }-
BooleanSelector.parseで文字列を解析しevaluate()で評価する
-
-
どこで使われているか?
-
Dart の
testパッケージの platform selector で、このboolean_selectorの構文が使われている@TestOn('vm && !chrome') void main() { // このテストは Dart VM かつ Chrome ではない環境でのみ実行 }- ここの
'vm && !chrome'文字列が、内部的にはboolean_selectorの構文としてパース・評価される
- ここの
-
characters
文字列を、ユーザーが認識する文字の連なりとして扱う事ができるパッケージ
執筆時のバージョン: 1.4.1
Dart の String は UTF-16 のコードユニット列なので、絵文字や濁点つきの文字などをsubstring や length で扱うと文字の途中でバラバラにしてしまう事があります。
void main() {
// 「か」+「濁点」
final text = 'が';
print('text: $text');
print('code unit length: ${text.length}');
final firstUnit = text.substring(0, 1);
final secondUnit = text.substring(1, 2);
print('first unit: $firstUnit'); // 「か」だけ
print('second unit: $secondUnit'); // 濁点だけ(表示されない or 変な位置に出る)
}
/* 実行結果
text: が
code unit length: 2
first unit: か
second unit: ゙
*/
上記を実行すると文字が分解されてしまいます。 characters を使うと以下のように1文字として扱うことができます。
import 'package:characters/characters.dart';
void main() {
final text = 'が';
final chars = text.characters;
print('grapheme length: ${chars.length}');
print('first grapheme: "${chars.first}"');
// 先頭1文字を切り出す
final firstGraphemeOnly = text.characters.take(1).toString();
print('take(1) as grapheme: "$firstGraphemeOnly"');
}
/* 実行結果
grapheme length: 1
first grapheme: "が"
take(1) as grapheme: "が"
*/
flutter_staggered_grid_view
Flutterで様々なグリッドレイアウト(スタッガード、 masonry、 quilted、 wovenなど)を実現するためのコレクションを提供します。
執筆時のバージョン: 0.7.0
-
pubspec.yamlに以下を追加dependencies: flutter_staggered_grid_view: ^0.7.0
このパッケージには以下のGrid Layoutが含まれています。
-
Staggered
-
各タイルの「幅×高さセル数」を自由に決められる、不揃いタイルのグリッド
-
サンプル

ソースコード
import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class StaggeredLayoutScreen extends StatelessWidget { const StaggeredLayoutScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Staggered Layout')), body: Center( // 横方向を4分割したグリッド child: StaggeredGrid.count( crossAxisCount: 4, mainAxisSpacing: 4, // 縦のすき間 crossAxisSpacing: 4, // 横のすき間 children: [ StaggeredGridTile.count( // 2x2マス分の大きなタイル crossAxisCellCount: 2, mainAxisCellCount: 2, child: _tile(0), ), StaggeredGridTile.count( // 2x1マス分(横長) crossAxisCellCount: 2, mainAxisCellCount: 1, child: _tile(1), ), StaggeredGridTile.count( // 1x1マス分(普通サイズ) crossAxisCellCount: 1, mainAxisCellCount: 1, child: _tile(2), ), StaggeredGridTile.count( crossAxisCellCount: 1, mainAxisCellCount: 1, child: _tile(3), ), StaggeredGridTile.count( crossAxisCellCount: 4, mainAxisCellCount: 2, child: _tile(4), ), ], ), ), ); } Widget _tile(int index) { return Container( color: Colors.blue[(index + 1) * 100], child: Center(child: Text('Tile $index')), ); } }
-
-
Masonry
-
Pinterest 風の「高さバラバラのカード」を縦に詰めていくレイアウト
-
サンプル

ソースコード
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class MasonryLayoutScreen extends StatelessWidget { const MasonryLayoutScreen({super.key}); @override Widget build(BuildContext context) { final rnd = Random(); return Scaffold( appBar: AppBar(title: const Text('Masonry Layout')), body: MasonryGridView.count( crossAxisCount: 2, // 列数 mainAxisSpacing: 8, // 縦のすき間 crossAxisSpacing: 8, itemCount: 20, itemBuilder: (context, index) { final height = 80 + rnd.nextInt(120); // アイテムごとに高さを変える return Container( height: height.toDouble(), color: Colors.green[(index % 8 + 1) * 100], child: Center(child: Text('Item $index\n$height px')), ); }, ), ); } }
-
-
Quilted
-
「大きいタイル + 小さいタイル」を繰り返す“キルト”パターンのレイアウト
-
サンプル

ソースコード
import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class QuiltedLayoutScreen extends StatelessWidget { const QuiltedLayoutScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Quilted Layout')), // Quilted: 「大きい+小さい」タイルをパターンで繰り返す例 body: GridView.custom( padding: const EdgeInsets.all(8), gridDelegate: SliverQuiltedGridDelegate( crossAxisCount: 4, mainAxisSpacing: 4, crossAxisSpacing: 4, // パターンを反転しながら繰り返す repeatPattern: QuiltedGridRepeatPattern.inverted, pattern: const [ QuiltedGridTile(2, 2), // 2x2 の大きなタイル QuiltedGridTile(1, 1), QuiltedGridTile(1, 1), QuiltedGridTile(1, 2), // 1x2 の横長タイル ], ), childrenDelegate: SliverChildBuilderDelegate( (context, index) => Container( color: Colors.orange[(index % 8 + 1) * 100], child: Center(child: Text('Item $index')), ), childCount: 20, ), ), ); } }
-
-
Woven
-
幅・高さ比率 (
crossAxisRatio/aspectRatio) をパターンで変えつつ、左右に少しずつずらしながら「織物」のようなリズムを作るレイアウト -
サンプル

ソースコード
import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class WovenLayoutScreen extends StatelessWidget { const WovenLayoutScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Woven Layout')), // Woven: 幅と位置を少し変えてリズム感のあるグリッドにする例 body: GridView.custom( padding: const EdgeInsets.all(8), gridDelegate: SliverWovenGridDelegate.count( crossAxisCount: 2, mainAxisSpacing: 8, crossAxisSpacing: 8, pattern: const [ WovenGridTile(1), // 通常 WovenGridTile( 5 / 7, // 縦横比(やや縦長) crossAxisRatio: 0.9, // 少し細め alignment: AlignmentDirectional.centerEnd, // 右寄せ ), ], ), childrenDelegate: SliverChildBuilderDelegate( (context, index) => Container( color: Colors.purple[(index % 8 + 1) * 100], child: Center(child: Text('Item $index')), ), childCount: 20, ), ), ); } }
-
-
Staired
-
タイルごとにサイズ比率を変え、かつ少しずつずらして「階段状」に配置するレイアウト
-
サンプル

ソースコード
import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class StairedLayoutScreen extends StatelessWidget { const StairedLayoutScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Staired Layout')), // Staired: 大小タイルを階段状にずらして並べる例 body: GridView.custom( padding: const EdgeInsets.all(8), gridDelegate: SliverStairedGridDelegate( crossAxisSpacing: 16, // 横方向のずれを作る間隔 mainAxisSpacing: 16, startCrossAxisDirectionReversed: true, // 階段の向きを反転 pattern: const [ StairedGridTile(0.5, 1.0), StairedGridTile(0.5, 0.75), StairedGridTile(1.0, 2.0), ], ), childrenDelegate: SliverChildBuilderDelegate( (context, index) => Container( color: Colors.red[(index % 8 + 1) * 100], child: Center(child: Text('Item $index')), ), childCount: 20, ), ), ); } }
-
-
Aligned
-
「同じ行の中では一番背の高いカードに合わせて高さを揃える」CSS Grid 風レイアウト
-
サンプル

ソースコード
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; class AlignedLayoutScreen extends StatelessWidget { const AlignedLayoutScreen({super.key}); @override Widget build(BuildContext context) { final rnd = Random(); return Scaffold( appBar: AppBar(title: const Text('Aligned Layout')), // Aligned: 行ごとに一番高いアイテムに合わせて高さをそろえる例 body: AlignedGridView.count( crossAxisCount: 3, mainAxisSpacing: 8, crossAxisSpacing: 8, itemCount: 20, itemBuilder: (context, index) { final lines = 1 + rnd.nextInt(3); // 行数を変えて高さに差をつける return Container( padding: const EdgeInsets.all(8), color: Colors.teal[(index % 8 + 1) * 100], child: Text('Item $index\n' * lines), ); }, ), ); } }
-
pool
poolパッケージは、限られたリソースのプールを簡単に管理するためのPoolクラスを提供します。
執筆時のバージョン: 1.5.2
各ユーティリティ
-
Poolクラス-
Pool(this._maxAllocatedResources, {Duration? timeout})-
_maxAllocatedResources: 一度に割り当てることができるリソースの最大数 -
timeout: 設定されている場合、指定された時間が経過しても何も操作が行われなければ、保留中のすべてのfutureはTimeoutExceptionをスローする
-
- 使用例
final pool = new Pool(10, timeout: new Duration(seconds: 30));- 一度に10個のリソースしか割り当てないプールを作成、全てのリソースがチェックアウトされた状態で30秒間操作がない場合エラーを発生させる
-
-
withResource-
コールバックの前にPoolのリソースを確保し、終了後に自動で
releaseしてくれるFuture<void> demoWithResource() async { final pool = Pool(2); // 同時に最大 2 つまで Future<void> heavyTask(int id) async { print(' [start ] task $id'); await Future<void>.delayed(const Duration(seconds: 1)); print(' [finish] task $id'); } // Pool.withResource は「このコールバックの間だけリソースを借りる」という意味 pool.withResource(() => heavyTask(1)); pool.withResource(() => heavyTask(2)); pool.withResource(() => heavyTask(3)); await pool.close(); } /* 実行結果 [start ] task 1 [start ] task 2 [finish] task 1 [start ] task 3 [finish] task 2 [finish] task 3 */
-
shelf
Shelfは、Webサーバーやその一部を簡単に作成・構成できるようにするツールです。
執筆時のバージョン: 1.4.2
Dart の HTTP サーバを「Handler」と「Middleware」で組み立てるための軽量フレームワークで、 Request -> Future<Response> 関数を積み重ねていくだけなので、挙動が理解しやすいです。
-
Echo Serverのサンプル
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; void main() async { var handler = const Pipeline().addMiddleware(logRequests()).addHandler(_echoRequest); var server = await shelf_io.serve(handler, 'localhost', 8080); // Enable content compression server.autoCompress = true; print('Serving at http://${server.address.host}:${server.port}'); } Response _echoRequest(Request request) => Response.ok('Request for "${request.headers}"');上記を実行してアクセスすると以下の様になります。
$ curl http://localhost:8080 Request for "{user-agent: curl/8.7.1, accept: */*, host: localhost:8080}" -
Routing + JSON + POST Bodyサンプル
import 'dart:convert'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; void main() async { var handler = const Pipeline() .addMiddleware(logRequests()) .addHandler(_router); var server = await shelf_io.serve(handler, 'localhost', 8080); // Enable content compression server.autoCompress = true; print('Serving at http://${server.address.host}:${server.port}'); } Future<Response> _router(Request request) async { final path = request.url.path; final method = request.method; // GET / if (path.isEmpty && method == 'GET') { return Response.ok('Hello Shelf!'); } // GET /json if (path == 'json' && method == 'GET') { final body = jsonEncode({ 'message': 'Hello JSON', 'time': DateTime.now().toIso8601String(), }); return Response.ok( body, headers: {'content-type': 'application/json; charset=utf-8'}, ); } // POST /echo (ボディをそのまま返す) if (path == 'echo' && method == 'POST') { final body = await request.readAsString(); return Response.ok('you posted: $body'); } // その他は 404 return Response.notFound('Not Found'); }実際のアクセス結果がこちら
$ curl http://localhost:8080 Hello Shelf! $ curl http://localhost:8080/json {"message":"Hello JSON","time":"2025-12-15T21:11:06.678950"} $ curl -X POST -d "Hello from curl" localhost:8080/echo you posted: Hello from curl
stack_trace
このライブラリは、Dart実装によって生成されたスタックトレースの解析、検査、操作を可能にします。また、ネイティブのStackTrace実装よりも読みやすい形式でスタックトレースの文字列表現を生成する関数も提供します
執筆時のバージョン: 1.12.1
サンプルのプロジェクトを作成して試してみます。
dart create stack_trace_example
-
未使用時のStackTrace
-
ソースコード (bin/stack_trace_example.dart)
import 'dart:async'; void main() { _scheduleAsync(); } void _scheduleAsync() { Future.delayed(Duration(seconds: 1)).then((_) => _runAsync()); } void _runAsync() { throw 'oh no!'; } -
出力
$ dart run Building package executable... Built stack_trace_example:stack_trace_example. Unhandled exception: oh no! #0 _runAsync (file:///Users/xxxxx/stack_trace_example/bin/stack_trace_example.dart:12:3) #1 _scheduleAsync.<anonymous closure> (file:///Users/xxxxx/stack_trace_example/bin/stack_trace_example.dart:8:52) #2 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:948:45) #3 Future._propagateToListeners (dart:async/future_impl.dart:977:13) #4 Future._complete (dart:async/future_impl.dart:711:7) #5 new Future.delayed.<anonymous closure> (dart:async/future.dart:436:16) #6 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15) #7 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:423:19) #8 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:454:5) #9 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)
-
-
使用した場合
-
pubspec.yaml
dependencies: stack_trace: ^1.12.1 -
ソースコード
import 'dart:async'; import 'package:stack_trace/stack_trace.dart'; void main() { Chain.capture(_scheduleAsync); } void _scheduleAsync() { Future.delayed(Duration(seconds: 1)).then((_) => _runAsync()); } void _runAsync() { throw 'oh no!'; } -
出力
$ dart run Building package executable... Built stack_trace_example:stack_trace_example. Unhandled exception: oh no! bin/stack_trace_example.dart 14:3 _runAsync bin/stack_trace_example.dart 10:52 _scheduleAsync.<fn> package:stack_trace/src/stack_zone_specification.dart 127:36 StackZoneSpecification._registerUnaryCallback.<fn>.<fn> package:stack_trace/src/stack_zone_specification.dart 207:15 StackZoneSpecification._run package:stack_trace/src/stack_zone_specification.dart 127:24 StackZoneSpecification._registerUnaryCallback.<fn> dart:async/zone.dart 1538:47 _rootRunUnary dart:async/zone.dart 1429:19 _CustomZone.runUnary dart:async/future_impl.dart 948:45 Future._propagateToListeners.handleValueCallback ...通常のStackTraceより詳細な情報が追加され、見やすくなっていますが若干長いので
Chain#terseを使うと簡潔にしてくれます。# 使い方 #void main() { # Chain.capture(_scheduleAsync, onError: (error, chain) => print(chain.terse)); #} $ dart run Building package executable... Built stack_trace_example:stack_trace_example. bin/stack_trace_example.dart 14:3 _runAsync bin/stack_trace_example.dart 10:52 _scheduleAsync.<fn> ===== asynchronous gap =========================== dart:async Future.then bin/stack_trace_example.dart 10:40 _scheduleAsync package:stack_trace Chain.capture bin/stack_trace_example.dart 6:9 main
-
retry
このパッケージは、非同期関数のリトライを簡単に行う方法を提供します。これは、接続の切断や一時的なサーバの過負荷など、断続的に発生するエラーによるクラッシュを回避するのに役立ちます。
執筆時のバージョン: 3.1.2
「Exponential Backoff」(再試行(リトライ)までの待機時間を「1秒、2秒、4秒、8秒…」のように指数関数的に増やしていく)を伴うリトライ処理はシンプルに以下の様に書けます。
Future<void> demoSimpleRetry() async {
final random = Random();
var callCount = 0;
final result = await retry<String>(
() async {
callCount++;
print(' try #$callCount');
await Future<void>.delayed(const Duration(milliseconds: 200));
if (random.nextBool()) {
throw TimeoutException('temporary timeout');
}
return 'success!';
},
retryIf: (e) => e is TimeoutException, // TimeoutException のときにリトライする
);
print(' result = $result\n');
}
/* 実行結果
try #1
try #2
result = success!
*/
ちなみに待機時間が大体どれくらい増えるか確認するには以下で確認ができます。
const r = RetryOptions(); // デフォルト設定
for (var attempt = 1; attempt <= r.maxAttempts - 1; attempt++) {
final delay = r.delay(attempt);
print(' attempt #$attempt -> delay = $delay');
}
/* 実行結果
attempt #1 -> delay = 0:00:00.362201
attempt #2 -> delay = 0:00:00.828308
attempt #3 -> delay = 0:00:01.402243
attempt #4 -> delay = 0:00:03.631579
attempt #5 -> delay = 0:00:06.643290
attempt #6 -> delay = 0:00:14.554009
attempt #7 -> delay = 0:00:27.117237
*/
file_testing
package:fileと連携するためのテストユーティリティ。
執筆時のバージョン: 3.0.2
使えるユーティリティ
-
exists-
FileSystemEntity.existsSync()が true になるものをマッチ
-
-
isFile/isDirectory/isLink/isFileSystemEntity/isFileStat- それぞれ
File,Directory,Link,FileSystemEntity,FileStat型かどうかをマッチ
- それぞれ
-
hasPath(path)-
entity.path == pathになるかをマッチする Matcher を返す
-
-
isFileSystemException([osErrorCode])-
FileSystemExceptionであること(必要なら OS エラーコードも)をチェックする Matcher
-
-
throwsFileSystemException([osErrorCode])- 「関数 or Future が
FileSystemExceptionを投げる」ことを検証する Matcher
- 「関数 or Future が
-
expectFileSystemException(osErrorCode, callback)-
callbackが指定された OS エラーコードを持つFileSystemExceptionを投げることをexpect相当でチェックするヘルパー
-
実際にテストを書いて試してみます。 pubspec.yaml は以下の様に設定してまうす。
dependencies:
file: ^7.0.1
dev_dependencies:
test: ^1.25.6
file_testing: ^3.0.2
package:file の MemoryFileSystem を使って実ファイルを作らずにテストを書いてみました。
-
実際のテスト
import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:test/test.dart'; void main() { late MemoryFileSystem fs; setUp(() { fs = MemoryFileSystem(); fs.file('/foo.txt').writeAsStringSync('hello'); fs.directory('/dir').createSync(); fs.link('/link-to-foo').createSync('/foo.txt'); }); group('基本の型系 Matcher', () { test('exists / isFile / isDirectory / isLink / isFileSystemEntity', () { final file = fs.file('/foo.txt'); final dir = fs.directory('/dir'); final link = fs.link('/link-to-foo'); // /foo.txt が存在するファイルである expect(file, exists); expect(file, isFile); expect(file, isFileSystemEntity); // /dir が存在するディレクトリである expect(dir, exists); expect(dir, isDirectory); expect(dir, isFileSystemEntity); // /link-to-foo がシンボリックリンク expect(link, exists); expect(link, isLink); expect(link, isFileSystemEntity); }); test('isFileStat', () { final stat = fs.file('/foo.txt').statSync(); expect(stat, isFileStat); }); test('hasPath', () { final file = fs.file('/foo.txt'); final dir = fs.directory('/dir'); // パスが /foo.txt である File expect(file, hasPath('/foo.txt')); // パスが /dir である Directory expect(dir, hasPath('/dir')); }); }); group('FileSystemException 系', () { /// 必ず FileSystemException を投げる処理 void throwNoSuchFile() { fs.file('/no/such/file').readAsStringSync(); } test('isFileSystemException', () { try { throwNoSuchFile(); fail('例外が発生するはず'); } on FileSystemException catch (e) { // FileSystemException であること expect(e, isFileSystemException()); } }); test('throwsFileSystemException (関数)', () { // コールバックが FileSystemException を投げること expect(() => throwNoSuchFile(), throwsFileSystemException()); }); test('throwsFileSystemException (Future)', () async { Future<void> asyncThrow() async { throwNoSuchFile(); } // Future が FileSystemException で失敗すること expect(asyncThrow(), throwsFileSystemException()); }); test('expectFileSystemException + ErrorCodes.ENOENT', () { // こちらは元のままで OK(実際に FileSystemException が投げられるパターン) expectFileSystemException(ErrorCodes.ENOENT, () { fs.directory('/no/such/dir').resolveSymbolicLinksSync(); }); }); }); }
quiver
多くのDartライブラリをより簡単に、より便利に使えるようにしたり、追加機能を提供したりする、Dart用の便利なユーティリティライブラリ集
執筆時のバージョン: 3.2.2
以下主なユーティリティになります。
quiver.async
Future / Stream / タイマーなど、非同期処理を扱うためのユーティリティ
- 一定間隔で残り時間を流してくれるCountdownTimerのサンプル
import 'package:quiver/async.dart';
void main() {
final timer = CountdownTimer(
const Duration(seconds: 3), // 合計時間
const Duration(seconds: 1), // tick 間隔
);
timer.listen(
(event) {
print('残り: ${event.remaining.inMilliseconds} ミリ秒');
},
onDone: () {
print('終了!');
},
);
}
/* 実行結果
残り: 1992 ミリ秒 ※ 少しの誤差あり
残り: 998 ミリ秒
残り: -1 ミリ秒
終了!
*/
quiver.cache
非同期アクセス前提のキー→値キャッシュ(Cache / MapCache)
- 非同期 API で get/set する Map ベースのキャッシュサンプル
import 'package:quiver/cache.dart';
Future<void> main() async {
final cache = MapCache<String, int>();
await cache.set('user:1', 42);
final value = await cache.get('user:1');
print(value); // 42
// LRU キャッシュにしたい場合
final lruCache = MapCache<String, int>.lru(maximumSize: 2);
await lruCache.set('a', 1);
await lruCache.set('b', 2);
await lruCache.get('a'); // a を最近アクセスに
await lruCache.set('c', 3); // 古い b が追い出される
print(await lruCache.get('b')); // null
}
/* 実行結果
42
null
*/
quiver.check
checkArgument / checkState など、前提条件・状態チェック用ヘルパー
- 0除算を防いだり・状態チェックをするサンプル
import 'package:quiver/check.dart';
int divide(int a, int b) {
checkArgument(b != 0, message: 'b must not be 0');
return a ~/ b;
}
void changeState(bool ready) {
checkState(ready, message: 'Not ready yet');
}
void main() {
divide(10, 0); // ArgumentError が投げられる
changeState(false); // StateError が投げられる
}
/* 実行結果
Unhandled exception:
Invalid argument(s): b must not be 0
Unhandled exception:
Bad state: Not ready yet
*/
quiver.collection
Multimap / BiMap / LruMap など、コレクション系の追加データ構造
- Multimapのサンプル
import 'package:quiver/collection.dart';
void main() {
final tags = Multimap<String, String>();
tags.add('fruit', 'apple');
tags.add('fruit', 'banana');
tags.add('drink', 'water');
print(tags['fruit']);
print(tags['drink']);
}
/* 実行結果
[apple, banana]
[water]
*/
quiver.core
Optional や hashObjects など、汎用的な小さなコアユーティリティ
- 自前クラスの
hashCode実装を安全に書くためのヘルパーのサンプル
import 'package:quiver/core.dart';
class Point {
final int x;
final int y;
Point(this.x, this.y);
@override
bool operator ==(Object other) =>
other is Point && other.x == x && other.y == y;
@override
int get hashCode => hash2(x, y); // or hashObjects
}
void main() {
final a = Point(1, 2);
final b = Point(1, 2);
print(a.hashCode);
print(b.hashCode);
print(a == b);
}
/* 実行結果
472247166
472247166
true
*/
quiver.iterables
Iterable を連結・分割・生成するためのヘルパー(partition / merge など)
-
rangeのサンプル
import 'package:quiver/iterables.dart';
void main() {
final nums = range(1, 10); // 1〜9
for (final n in nums) {
print(n);
}
final down = range(10, 0, -2); // step を -2 に
print(down.toList());
}
/* 実行結果
1
2
3
4
5
6
7
8
9
[10, 8, 6, 4, 2]
*/
quiver.pattern
Pattern / RegExp / Glob を扱うためのユーティリティ
- 正規表現とファイルパスの簡単なマッチサンプル
import 'package:quiver/pattern.dart';
void main() {
// 正規表現
print(matchesFull(RegExp(r'\d+'), '123'));
print(matchesFull(RegExp(r'\d+'), '123a'));
// ファイルパスの簡単なマッチ
final glob = Glob('lib/**/*.dart');
print(glob.hasMatch('lib/src/main.dart'));
print(glob.hasMatch('test/main_test.dart'));
}
/* 実行結果
true
false
true
false
*/
quiver.strings
文字列の判定・整形・比較用ユーティリティ(isBlank / center など)
-
isBlank/equalsIgnoreCase/centerサンプル
import 'package:quiver/strings.dart';
void main() {
print(isBlank(' '));
print(isBlank('hello'));
print(equalsIgnoreCase('Abc', 'aBC'));
print(center('hi', 6, '*'));
}
/* 実行結果
true
false
true
**hi**
*/
quiver.time
Clock や aSecond など、時間・期間を扱いやすくするユーティリティ
-
Clockを使ったサンプル
import 'package:quiver/time.dart';
void main() {
final clock = Clock();
final now = clock.now();
final tomorrow = now.add(aDay); // 1日後
final in30Seconds = now.add(aSecond * 30);
print('now: $now');
print('tomorrow: $tomorrow');
print('in 30 sec: $in30Seconds');
}
/* 実行結果
now: 2025-12-17 07:58:08.483232
tomorrow: 2025-12-18 07:58:08.483232
in 30 sec: 2025-12-17 07:58:38.483232
*/
まとめ
今回メジャーなパッケージ以外をピックアップしたら、Dartの便利系パッケージが多くなりましたが、どれも実用的で、プロジェクトに組み込みやすいものばかりですので、活用できそうだなと思いました。
手間かかりますが、こうやって一つ一つ試してみると、パッケージの理解が深まりますし、実際に使うイメージも湧きやすくなりますのでオススメです。
Discussion