4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

hooks_riverpodについて学んでみる

Last updated at Posted at 2022-03-28

riverpodのhooksってなにか?

ReackHooksを真似て作ったらしい?

開発環境

  • flutter2.8.0
  • hooks_riverpod: ^1.0.3

hooks_riverpod と flutter_riverpod の書き方の違い

どちらのRiverpodパッケージをインストールするかによって、Widgetからの利用時に書き方の違いがあります。

グローバル変数としてのProvider定義の書き方は共通です。
flutter_riverpodと同じだよ!

final appNameProvider = Provider((ref) => 'Special App!');

hooks_riverpodの書き方の例
pub.devのリンク
https://pub.dev/packages/hooks_riverpod

main.dart

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

void main() {
  runApp(
    /// [MyApp] is wrapped in a [ProviderScope].
    /// This widget is where the state of most of our providers will be stored.
    /// This replaces `MultiProvider` if you've used `provider` before.
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

/// A provider that creates and listen to a [StateNotifier].
///
/// Providers are declared as global variables.
/// This does not hinder testability, as the state of a provider is instead
/// stored inside a [ProviderScope].
final counterProvider = StateNotifierProvider<Counter, int>((_) => Counter());

/// A simple [StateNotifier] that implements a counter.
///
/// It doesn't have to be a [StateNotifier], and could be anything else such as:
/// - [ChangeNotifier], with [ChangeNotifierProvider]
/// - [Stream], with [StreamProvider]
/// ...
class Counter extends StateNotifier<int> {
  Counter() : super(0);

  void increment() => state++;
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}
// StatelessWidgetが、HookConsumerWidgetに変更されている。
class MyHomePage extends HookConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod counter example'),
      ),
      body: Center(
        child: Text(
          '$count',
          style: Theme.of(context).textTheme.headline4,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(counterProvider.notifier).increment(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

flutter_riverpodの書き方の例

pub.devのリンク
https://pub.dev/packages/flutter_riverpod

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// A Counter example implemented with riverpod

void main() {
  runApp(
    // Adding ProviderScope enables Riverpod for the entire project
    const ProviderScope(child: MyApp()),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Home());
  }
}

/// Providers are declared globally and specifies how to create a state
final counterProvider = StateProvider((ref) => 0);

// StatelessWidgetが、ConsumerWidgetに変更されている。
class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        // Consumer is a widget that allows you reading providers.
        // You could also use the hook "ref.watch(" if you uses flutter_hooks
        child: Consumer(builder: (context, ref, _) {
          final count = ref.watch(counterProvider.state).state;
          return Text('$count');
        }),
      ),
      floatingActionButton: FloatingActionButton(
        // The read method is an utility to read a provider without listening to it
        onPressed: () => ref.read(counterProvider.state).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

今回作ってみた学習サンプル

ディレクトリ構造

lib
├── components
│   └── importer.dart
├── main.dart
├── models
│   └── counter_view_model.dart
└── views
    └── home_page.dart

今回は、exportという面白いDartの機能を使ってみた。import文を使って各ページで、同じpackageを毎回読み込むのが面倒臭い!!!
で、1つのファイルにexport文を使ってまとめる方法を思い出したので使ってみた。

components/import.dart

export 'package:flutter/material.dart';
export 'package:hooks_riverpod/hooks_riverpod.dart';
export 'package:hook_sample_app/models/counter_view_model.dart';
export 'package:hook_sample_app/views/home_page.dart';

models/counter_view_model.dart

import 'package:hook_sample_app/components/importer.dart';

//①Riverpodのみ設定

final countViewModel = ChangeNotifierProvider((_) => CountViewModel());

//CountViewModelの中身は一緒
class CountViewModel extends ChangeNotifier {
  final String title = 'hooks_riverpod';

  int _counter = 0;
  get counter => _counter;

  void incrementCounter() {
    _counter++;
    notifyListeners();
  }
}

views/home_page.dart

import 'package:hook_sample_app/components/importer.dart';

// StatelessWidgetをHookConsumerWidgetに変更
class MyHomePage extends HookConsumerWidget {
  MyHomePage({Key? key}) : super(key: key);
  @override
  // 第2引数にWidgetRef refを追加
  Widget build(BuildContext context, WidgetRef ref) {
    //④countViewModelを参照することを定義、viewModelでViewModelを参照する
    final viewModel = ref.watch(countViewModel);
    return Scaffold(
      appBar: AppBar(
        title: Text('${viewModel.title}'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${viewModel.counter}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: viewModel.incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

main.dart

import 'components/importer.dart';

void main() {
  runApp(
    // MyAppをProviderScopeで囲む
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hooks riverpod!',
      theme: ThemeData(
        primarySwatch: Colors.indigo,
      ),
      home: MyHomePage(),
    );
  }
}

ご覧いただくと各ページで、import文が今のところ1つしかない!
今まで作ってきたアプリより見た目がスッキリして、補修もしやすくなった気がした...
もっと速く使うべきだった!!!

おまけ

カウンターばっかりだと面白くないから、ボタンを押したら文字が変わる機能を追加してみた!

models/counter_view_model.dart

import 'package:hook_sample_app/components/importer.dart';

// 定数の中にクラスを格納する
final countViewModel = ChangeNotifierProvider((_) => CountViewModel());

//CountViewModelの中身は一緒
class CountViewModel extends ChangeNotifier {
  final String title = 'hooks_riverpod!';

  int _counter = 0;
  get counter => _counter;

  String _privateText1 = 'これは多分ゲッターだよ???';
  get privateText => _privateText1;

  String _privateText2 = 'これはプライベートなプロパティだよ!';
  get privateText2 => _privateText2;

  String _greeting = 'あいさつ?';
  get greeting => _greeting;

  void incrementCounter() {
    _counter++;
    notifyListeners();
  }

  void sayMethod() {
    // ボタンを押すと変数に文字を代入する
    _greeting = 'Hello Riverpos!!!';
    notifyListeners();
  }
}

views/home_page.dart

import 'package:hook_sample_app/components/importer.dart';

// StatelessWidgetをHookConsumerWidgetに変更
class MyHomePage extends HookConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);
  @override
  // 第2引数にWidgetRef refを追加
  Widget build(BuildContext context, WidgetRef ref) {
    //④countViewModelを参照することを定義、viewModelでViewModelを参照する
    final viewModel = ref.watch(countViewModel);
    return Scaffold(
      appBar: AppBar(
        title: Text('${viewModel.title}'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('${viewModel.privateText}'),
            const SizedBox(height: 10),
            Text('${viewModel.privateText2}'),
            const SizedBox(height: 10),
            Text('${viewModel.greeting}'),
            const SizedBox(height: 10),
            Text(
              '${viewModel.counter}',
              style: const TextStyle(fontSize: 30, fontWeight: FontWeight.bold, color: Colors.blueGrey),
            ),
            ElevatedButton(
                onPressed: viewModel.sayMethod,
                child: const Text('あいさつをする')),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: viewModel.incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

hooks_riverpod.gif

4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?