0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Flutter 状態管理:Riverpod

0
Last updated at Posted at 2026-05-26

Flutter Riverpod 入門まとめ|使い方・選び方・よくあるバグ

はじめに

Riverpod は Flutter の状態管理ライブラリです。
似た名前の provider パッケージとは別物です。

パッケージ import
旧来の状態管理 (provider) package:provider/provider.dart
Riverpod package:flutter_riverpod/flutter_riverpod.dart

1. セットアップ

# pubspec.yaml
dependencies:
  flutter_riverpod: ^2.x.x

アプリ全体を ProviderScope で囲む(これを忘れると動かない)。

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

2. Provider の選び方

Provider 用途
Provider 他 Provider から計算する読み取り専用の値
StateProvider 単純な値(bool, int など)を外から変更したい
NotifierProvider add() / delete() など独自メソッドで state を操作したい
AsyncNotifierProvider 非同期処理(DB・API)を含む state
StreamProvider リアルタイム更新(Firestore など)

選び方のポイント

  • 「他の Provider の値から計算できる」→ Provider
  • 「外からメソッドで操作したい」→ NotifierProvider
  • 「非同期が絡む」→ AsyncNotifierProvider

3. Widget 側の書き方

ConsumerWidget(StatelessWidget の代替)

class BalanceCard extends ConsumerWidget {
  const BalanceCard({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final balance = ref.watch(balanceProvider);
    return Text('残高: $balance 円');
  }
}

ConsumerStatefulWidget(StatefulWidget の代替)

class HomeScreen extends ConsumerStatefulWidget {
  const HomeScreen({super.key});

  @override
  ConsumerState<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends ConsumerState<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    // ref はそのまま使える(引数不要)
    final balance = ref.watch(balanceProvider);
    return Text('残高: $balance 円');
  }
}

4. ref.watch と ref.read の使い分け

Riverpod で最も重要なルールです。

使う場所 用途
ref.watch build() 値の変化を監視して再描画
ref.read コールバック内 一回だけ値を取得 / メソッドを呼ぶ
@override
Widget build(BuildContext context) {
  // ✅ build() 内は ref.watch
  final balance = ref.watch(balanceProvider);
  final transactions = ref.watch(transactionProvider);

  return Column(
    children: [
      Text('残高: $balance 円'),
      ElevatedButton(
        // ✅ コールバック内は ref.read
        onPressed: () {
          ref.read(transactionProvider.notifier).add(newTransaction);
        },
        child: Text('追加'),
      ),
    ],
  );
}
// ❌ コールバック内で ref.watch は NG(監視ライフサイクルが壊れる)
onPressed: () {
  ref.watch(balanceProvider); // NG
}

コールバックに渡す場合

// ✅ notifier は安定したオブジェクトなので ref.read で渡してOK
TransactionList(
  onDelete: ref.read(transactionProvider.notifier).delete,
)

5. Provider の書き方

読み取り専用(派生値)

他 Provider の値から計算できる場合は Provider を使います。
Notifier を使う必要はありません。

// ✅ 取引リストから残高を自動計算
final balanceProvider = Provider<int>((ref) {
  final transactions = ref.watch(transactionProvider);
  return transactions.fold(0, (sum, ts) => sum + ts.amount);
});

独自メソッドあり(NotifierProvider)

final transactionProvider =
    NotifierProvider<TransactionProvider, List<Transaction>>(
      TransactionProvider.new,
    );

class TransactionProvider extends Notifier<List<Transaction>> {
  @override
  List<Transaction> build() => []; // ← ref.watch はここでのみ使える

  void add(Transaction ts) {
    state = [...state, ts]; // state への代入で Widget に通知される
  }

  void delete(int index) {
    state = [...state]..removeAt(index);
  }
}

Notifier 内での ref の使い方

class SomeNotifier extends Notifier<SomeState> {
  @override
  SomeState build() {
    // ✅ build() 内は ref.watch OK
    final other = ref.watch(otherProvider);
    return SomeState(other);
  }

  void doSomething() {
    // ❌ build() 以外で ref.watch は NG
    // ✅ ref.read を使う
    final other = ref.read(otherProvider);
  }
}

6. よくあるバグ

state に代入していない

// ❌ 計算結果を捨てている(state は変わらない)
void updateBalance(int amount) => state + amount;

// ✅ state への代入が必要
void updateBalance(int amount) => state += amount;

複数の Provider を手動で同期させる

// ❌ add と updateBalance を別々に呼ぶ → ズレが起きる
ref.read(transactionProvider.notifier).add(ts);
ref.read(balanceProvider.notifier).updateBalance(amount);

// ✅ 派生 Provider にして自動計算させる
final balanceProvider = Provider<int>((ref) {
  return ref.watch(transactionProvider).fold(0, (s, ts) => s + ts.amount);
});
// → transactionProvider が更新されると balanceProvider も自動で再計算

delete の実装ミス

// ❌ 指定インデックスの要素「だけ」残す(逆になっている)
void delete(int index) {
  state = state.where((ts) => state[index] == ts).toList();
}

// ✅
void delete(int index) {
  state = [...state]..removeAt(index);
}

7. 実務に向けた次のステップ

autoDispose(メモリリーク防止)

画面を閉じたときに Provider を自動破棄します。

final transactionProvider =
    NotifierProvider.autoDispose<TransactionProvider, List<Transaction>>(
      TransactionProvider.new,
    );

family(パラメータ付き Provider)

IDなど引数によって異なるデータを取得したい場合。

final transactionByIdProvider = Provider.family<Transaction?, int>((ref, id) {
  return ref.watch(transactionProvider).firstWhereOrNull((ts) => ts.id == id);
});

// 使う側
final transaction = ref.watch(transactionByIdProvider(42));

AsyncNotifier(非同期処理)

DB や API を使う場合は AsyncNotifier に切り替えます。

class TransactionProvider extends AsyncNotifier<List<Transaction>> {
  @override
  Future<List<Transaction>> build() async {
    return await repository.fetchAll(); // DB・API から取得
  }

  Future<void> add(Transaction ts) async {
    await repository.save(ts);
    ref.invalidateSelf(); // 再取得
  }
}

ProviderContainer(ユニットテスト)

Widget を使わずに Provider 単体でテストできます。

test('取引を追加すると残高が増える', () {
  final container = ProviderContainer();
  addTearDown(container.dispose);

  container.read(transactionProvider.notifier).add(
    Transaction(title: '給料', amount: 300000, date: DateTime.now()),
  );

  expect(container.read(balanceProvider), 300000);
});

まとめ

やること 使うもの
監視して再描画 ref.watch(build内)
一回だけ取得・操作 ref.read(コールバック内)
計算できる値 Provider
独自メソッドで操作 NotifierProvider
非同期処理 AsyncNotifierProvider
画面破棄時に解放 autoDispose
引数付き family
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?