# これは知っておきたい! Flutter × Riverpod で学ぶ基礎ノートアプリ開発― 初心者でも実務に活かせるポイント術
内容を更新
これは知っておきたい! Flutter × Riverpod で学ぶ基礎ノートアプリ開発― 初心者でも実務に活かせるポイント術
はじめに
こんにちは!Flutterを学び始めたナカです。
この記事では「状態管理ライブラリ Riverpod」を使って、シンプルなノートアプリを作りながら学んだ内容をまとめます。
この記事で学べること
・Riverpodを使った状態管理の基礎
・非同期処理(APIやDBの読み書き)の扱い方
・テストとCI/CDを導入して品質を高める方法
・実務に繋げられる「設計の考え方」
初心者の方にも分かるように コード+解説+つまずきやすいポイント を書いています。
実務を意識した学習をしたい方や、企業の方に「理解力」をアピールしたい方の参考になれば嬉しいです。
プロジェクト環境構築
必要環境
・Flutter SDK
・Dart
・エディタ(VSCode推奨)
依存パッケージの追加
ターミナルで以下を実行します。
flutter pub add flutter_riverpod dio hive
間違いポイント
・flutter_riverpod → 状態管理
・dio → API通信用
・hive → 軽量データベース
👉 よくある間違いは、pubspec.yamlを直接編集してスペースを間違えること。ターミナルコマンドを使う方が安全です。
データモデルとリポジトリ設計
まずは「ノート」のデータ構造を作ります。
設計コード
class Note {
final String id; // 一意のID
final String title; // タイトル
final String content; // 本文
Note({
required this.id,
required this.title,
required this.content,
});
}
リポジトリ層
リポジトリは 「データ取得の窓口」 です。
実務でもAPIやDBを切り替えやすい設計をするために用います。
設計コード
abstract class NoteRepository {
Future<List<Note>> fetchNotes(); // データ一覧を取得
Future<void> addNote(Note note); // データを追加
}
📌 ここでの学びポイント
「直接UIでデータを触らない」のが重要。
こうすることで 後からAPI → DBに変更してもUIはそのまま使える。
Riverpodプロバイダの作成
Riverpodでは「状態をどこで管理するか」を明確にします。
今回は AsyncNotifier を使って非同期処理を扱いやすくします。
設計コード
final noteRepositoryProvider = Provider<NoteRepository>((ref) {
throw UnimplementedError(); // 実装は後で差し替える
});
class NoteNotifier extends AsyncNotifier<List<Note>> {
Future<List<Note>> build() async {
final repo = ref.read(noteRepositoryProvider);
return repo.fetchNotes();
}
Future<void> add(Note note) async {
final repo = ref.read(noteRepositoryProvider);
await repo.addNote(note);
// 既存のリストに新しいノートを追加
state = AsyncData([...state.value ?? [], note]);
}
}
final noteProvider =
AsyncNotifierProvider<NoteNotifier, List<Note>>(NoteNotifier.new);
ワンポイント解説
・Provider → データを提供する仕組み
・AsyncNotifier → 非同期処理付きの状態管理
・state → 画面のUIと紐づく「今の状態」
👉 よくあるエラーは「stateがnullのまま追加しようとして落ちる」ケース。
そのため state.value ?? [] のように安全に扱います。
UIの構築
Flutterアプリは「Widget」で画面を作ります。
今回のアプリでは、
・ノート一覧を表示
・追加ボタンで新しいノートを登録
というシンプルな機能を実装します。
main.dart
アプリ全体を ProviderScope でラップして、Riverpodを使えるようにします。
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: const HomePage(),
);
}
}
ワンポイント
ProviderScope がないと、ref.watch() が使えずエラーになります。
初心者がよくハマるのは「No Provider found」エラー。これは大抵 ProviderScope の書き忘れです。
class HomePage extends ConsumerWidget {
const HomePage({super.key});
Widget build(BuildContext context, WidgetRef ref) {
final notes = ref.watch(noteProvider);
return Scaffold(
appBar: AppBar(title: const Text('My Notes')),
body: notes.when(
data: (items) => ListView.builder(
itemCount: items.length,
itemBuilder: (_, i) => ListTile(
title: Text(items[i].title),
subtitle: Text(items[i].content),
),
),
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, _) => Center(child: Text('エラーが発生しました: $err')),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
ref.read(noteProvider.notifier).add(
Note(
id: DateTime.now().toString(),
title: 'New Note',
content: '書きかけ...',
),
);
},
child: const Icon(Icons.add),
),
);
}
}
・ConsumerWidget → Riverpodの状態を監視するWidget
・ref.watch(noteProvider) → 現在のノート一覧を購読
・when() → ロード中 / エラー / 正常表示 を安全に分岐
👉 実務でも「ロード中のスピナー」「エラー表示」を作れる人は評価されやすいです。
UIだけでなく「ユーザー体験」を意識することが大事。
非同期処理とエラーハンドリング
アプリでは必ず「待ち時間」が発生します。
例えばAPI通信やDBアクセスです。
Riverpodの AsyncValue を使うと、状態を3種類に整理できます。
loading → データ取得中(ぐるぐる表示)
error → 失敗(エラーメッセージ表示)
data → 成功(ノート一覧表示)
これをUIで分けて書けるのが大きなメリットです。
初心者がよくするミス
FutureBuilder を直接使ってしまい、コードが複雑になる
エラーハンドリングを忘れてクラッシュさせる
👉 Riverpodではこの AsyncValue を使えば、失敗してもUIが落ちない安全な実装になります。
テストの追加
実務で一番大事なのは「動くコード」ではなく「壊れにくいコード」です。
そのためにテストを書きます。
Riverpodは「プロバイダの差し替え」が簡単なので、テストに強いです。
サンプルテスト
testWidgets('ノートが表示される', (tester) async {
final fakeRepo = FakeNoteRepository(); // テスト用のダミーリポジトリ
await tester.pumpWidget(
ProviderScope(
overrides: [
noteRepositoryProvider.overrideWithValue(fakeRepo),
],
child: const MyApp(),
),
);
expect(find.text('テストノート'), findsOneWidget);
});
ポイント
・overrideWithValue を使うことで、本物のDBやAPIを使わずテスト可能。
・UIテスト (testWidgets) は、実際に画面を動かすテスト。
👉 初心者は「テストは難しい」と思いがちですが、まずは「ダミーデータを表示できるか」から始めるのがコツです。
最後に、自動でテストを回す仕組みを紹介します。
GitHub Actionsを使うと、コードをプッシュした瞬間にテストが実行されます。
.github/workflows/flutter_ci.yml
name: Flutter CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout
- uses: subosito/flutter-action
with:
flutter-version: '3.22.0'
- run: flutter pub get
- run: flutter analyze
- run: flutter test
📌 メリット
・プッシュのたびに 自動でテスト & 静的解析
・「レビュー前に壊れたコードが混ざらない」
・チーム開発で信頼されやすい
👉 実務ではCI/CDがあるだけで「品質意識がある人」と見てもらえます。
まとめと次のステップ
今回の記事で学んだこと:
Riverpodを使って状態管理の基礎を理解した
非同期処理とエラーハンドリングをシンプルに扱えた
テストとCI/CDで品質を高める第一歩を踏み出せた
📌 次の学習テーマ候補
状態管理の比較(Bloc, Riverpod, Provider)
実際のAPI連携(Dioを使って外部サービスと接続)
Hiveを使ったローカル保存
👉 これらを学んでいけば、実務でも通用するFlutterアプリ開発に近づけます。
✅ この記事の狙いは「初心者でも理解できる」+「企業にアピールできる設計力」を両立させることでした。
自分の学習ログとしても、ポートフォリオとしても使えるので、ぜひ何度も読み返して活用してください!
分かりにくい場合などありましたらコメントのほう宜しくお願いいたします!
自分も勉強になりますので!
Discussion