LoginSignup
14
10

More than 3 years have passed since last update.

[Flutter] [Pragmatic State Management in Flutter (Google I/O'19)]で推奨されてたProvider使ってみた

Last updated at Posted at 2019-05-12

Pragmatic State Management in Flutter (Google I/O'19)の中でこれからFlutter始めるならproviderを推奨するって言われてたので少しだけ触ってみました。

今回はChangeNotifierProviderのみです。

Providerとは

A dependency injection system built with widgets for widgets. provider is mostly syntax sugar for InheritedWidget, to make common use-cases straightforward.

READMEに書かれている通り、ウィジェット用のDIするものです。

下記のように親で提供したものが子孫で使えるようになり、引数で渡し続けなくて良くなります。
reactのcontext APIのproviderとかreact-reduxのproviderとかと同じ感じだと思います。

void main() => runApp(
      Provider<String>.value(
        value: 'Hello World', // これが子孫で使えるようになる
        child: MaterialApp(
          home: Child(),
        ),
      ),
    );

class Child extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Grandchild();
  }
}

class Grandchild extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text(Provider.of<String>(context)); // 今回はここで使用している。下位ウィジェットでも同様に取得できる。
  }
}

ChangeNotifierProviderを利用した実装

下のgifのような動きをするアプリケーションをChangeNotifierProviderを利用して実装しただけのものです。
provider.gif

コードは下記になります。
流れとしては、「ChangeNotifierProviderがChangeNotifierをmixinしたCounterを子孫ウィジェットに提供する。Counterは状態変更したらnotifyListenersで変更通知を行う。変更通知により再レンダリングが行われる。」って感じです。

適宜ChangeNotifyProviderが関連する箇所にコメント入れてあります。

実装

lib/main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:first_flutter_provider_example/counter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChangeNotifierProvider<Counter>( // ChangeNotifierProviderを利用して子孫ウィジェットでCounterを使えるようにする
        builder: (_) => Counter(0),
        child: MyHomePage(title: 'First Flutter Provider Example'),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;
  MyHomePage({Key key, @required this.title}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    var counter = Provider.of<Counter>(context); // 上位のウィジェットから提供されているCounterを取得
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              '${counter.value}', // counterを表示
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: counter.increment, // counterをインクリメント
            child: Icon(Icons.add),
          ),
          SizedBox(height: 10),
          FloatingActionButton(
            onPressed: counter.decrement, // counterをデクリメント
            child: Icon(Icons.remove),
          )
        ],
      ),
    );
  }
}

lib/counter.dart
import 'package:flutter/material.dart';

class Counter with ChangeNotifier { // ChangeNotifierをmixin
  int _value;

  Counter(this._value);

  int get value => this._value;

  increment() {
    _value++;
    notifyListeners(); // 変更通知により再描画
  }

  decrement() {
    _value--;
    notifyListeners(); // 変更通知により再描画
  }
}

テスト

test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

import 'package:first_flutter_provider_example/main.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());

    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });

  testWidgets('Counter decrements smoke test', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());

    expect(find.text('0'), findsOneWidget);
    expect(find.text('-1'), findsNothing);

    await tester.tap(find.byIcon(Icons.remove));
    await tester.pump();

    expect(find.text('0'), findsNothing);
    expect(find.text('-1'), findsOneWidget);
  });
}

終わりに

次はprovider使ったBlocをやってみようかと思います。

今回のリポジトリはこちら

14
10
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
14
10