Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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をやってみようかと思います。

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

hiko1129
サーバサイド寄りの人
https://note.hiko1129.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away