
ここまでのSTEPで、Flutterを使った業務画面は一通り作れるようになりました。
APIを叩き、
非同期でデータを取得し、
一覧に表示する。
この時点で「小さなアプリ」は完成しています。
ただし、ここから画面が増え、ロジックが増えた瞬間に、setStateベースの設計は一気に限界を迎えます。
このSTEPでは、その限界を越えるために、状態管理ライブラリを導入します。
Provider / Riverpodの思想理解
Flutterの状態管理ライブラリは数多くありますが、
Providerと
Riverpodは「思想」を理解するのに非常に適しています。
共通しているのは、「状態をWidgetの外に逃がし、UIをただの描画に専念させる」という考え方です。
setStateでは、「どこで状態が変わったのか」「どのWidgetが影響を受けるのか」が、画面規模とともに見えなくなっていきます。
Provider系の設計では、状態は明確なクラスとして定義され、UIはそれを購読するだけになります。
これはReactのContextや、VueのStore、バックエンドで言えばService層を明示的に切る感覚に近く、経験者ほど違和感なく受け入れられる構造です。
Viewとロジックの分離
状態管理ライブラリを導入すると、Flutterコードの見た目が大きく変わります。
buildメソッドの中から、API通信やビジネスロジックが消えていきます。
Viewは「今の状態をどう表示するか」だけを考え、
ロジック側は「どうやって状態を作るか」だけを考える。
この分離ができると、
・UI修正が怖くなくなる
・テストを書けるようになる
・画面追加時の影響範囲が読める
といった、実務で非常に大きなメリットが出てきます。
実務で壊れにくい構成
業務アプリで一番避けたいのは、「この画面を触ると、なぜか別の画面が壊れる」という状態です。
Providerを使った構成では、
・状態は明示的に定義
・依存関係はコード上で見える
・画面は状態の利用者でしかない
という構造になるため、変更の影響範囲を追いやすくなります。
Flutterは
UIフレームワークでありながら、設計を真面目にやるほど楽になる珍しい技術だと感じる人も多いはずです。
成果物:中規模画面構成
ここでは、
・状態管理にProviderを使用
・API取得ロジックをViewから分離
・一覧画面を構成
という「中規模を見据えた最小構成」を作ります。
プログラム構成
lib/
├ main.dart
├ models/
│ └ user.dart
├ view_models/
│ └user_view_model.dart
└ screens/
└ user_list_screen.dart
事前にコマンドで実行
flutter pub add provider http
lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'view_models/user_view_model.dart';
import 'screens/user_list_screen.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => UserViewModel()..fetchUsers(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: UserListScreen(),
);
}
}
lib/models/user.dart
class User {
final int id;
final String name;
final String email;
const User({
required this.id,
required this.name,
required this.email,
});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
email: json['email'] as String,
);
}
}
lib/view_models/user_view_model.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../models/user.dart';
class UserViewModel extends ChangeNotifier {
bool isLoading = false;
List<User> users = [];
String? error;
Future<void> fetchUsers() async {
isLoading = true;
error = null;
notifyListeners();
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/users'),
);
if (response.statusCode != 200) {
throw Exception('status error');
}
final List<dynamic> jsonList =
jsonDecode(response.body) as List<dynamic>;
users = jsonList
.map((e) => User.fromJson(e as Map<String, dynamic>))
.toList();
} catch (_) {
error = 'ユーザー取得に失敗しました';
} finally {
isLoading = false;
notifyListeners();
}
}
}
lib/screens/user_list_screen.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../view_models/user_view_model.dart';
class UserListScreen extends StatelessWidget {
const UserListScreen({super.key});
@override
Widget build(BuildContext context) {
final vm = context.watch<UserViewModel>();
return Scaffold(
appBar: AppBar(title: const Text('ユーザー一覧')),
body: Builder(
builder: (_) {
if (vm.isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (vm.error != null) {
return Center(
child: Text(vm.error!),
);
}
return ListView.builder(
itemCount: vm.users.length,
itemBuilder: (context, index) {
final user = vm.users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.email),
);
},
);
},
),
);
}
}
このSTEPのまとめ
STEP7では、Flutter開発を「趣味」から「業務」に引き上げるための分岐点である、
状態管理ライブラリを扱いました。
この段階に来ると、Flutterは「UIが書きやすいフレームワーク」ではなく、
「設計をちゃんと書けるアプリケーション基盤」として見えてくるはずです。
次のSTEPでは、
・より大規模な構成
・責務分割の粒度
・テストや拡張を前提にした設計
といった、「長く運用するサービス開発」に踏み込んでいく予定です。
0 件のコメント:
コメントを投稿