LoginSignup
16
11

More than 1 year has passed since last update.

【Flutter】 Providerで全域状態を管理する

Last updated at Posted at 2023-04-24

Providerとは?

  • 全域で状態管理ができるようにしてくれるツール。

  • Flutterでは状態を管理する様々な方法があるが、多様なパッケージの中でユーザーが最も多いパッケージ。

  • Provider github repository

Reactで言えばRedux、Recoilのようなツール

なぜProviderを使うのか?

  • コンポーネント間で状態を共有するためには、2つのコンポーネントを1つの親コンポーネントに包むプロセスが必要だ。

  • 上記のような過程は、コンポーネントtreeのdepthが深まれば深まるほど不要なコンポーネントを増加させ、このような問題は性能イシューを呼び起こす恐れがある。

  • Providerは、depthに関係なくグローバルに状態を管理し、どこでもデータを簡単に使用できるようサポートしてくれる。

Provider を使ってみよう

  • Providerを使って簡単なcounterを実装してみよう。

Directory

├── lib
   ├── providers 
   │    ├── counts.dart
   ├── widgets 
   │    ├── buttons.dart
   │    ├── counter.dart
   └── main.dart

Provider install

flutter pub add provider

providers/counts.dart

  • Provider 生成コード。

  • countの状態を管理する。

  • Provider は ChangeNotifier相続(多重相続)して生成。

  • 画面に更新されたデータを反映させるために notifyListeners を使用。

// counts.dart
// Provider 生成
import 'package:flutter/material.dart';

class Counts with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void add() {
    _count++;
    notifyListeners();
  }

  void remove() {
    _count--;
    notifyListeners();
  }
}

main.dart

  • MultiProviderを使えば複数のProviderを追加して複数の状態を管理することができる。

  • 1つの状態だけを管理するときは ChangeNotifierProvider を使用する。

  • ChangeNotifierProvider を通じて counts.dart で作成した Counts を購読する。

  • buttons.dart counter.dart ファイルでもProviderを使用できるように ChangeNotifierProvider で包んだ。

// main.dart
// Provider パッケージと上で生成したProviderインポートする
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
import 'package:provider_example/widgets/buttons.dart';
import 'package:provider_example/widgets/counter.dart';

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => Counts()),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Provider Test',
      home: Home(),
    );
  }
}

class Home extends StatelessWidget {
  const Home({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Provider'),
      ),
      body: ChangeNotifierProvider(
        create: (BuildContext context) => Counts(),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Counter(),
              Buttons(),
            ],
          ),
        ),
      ),
    );
  }
}

widgets/button.dart

  • ボタンを画面に表示するコード

  • main.dartButtons コンポーネントを ChangeNotifierProvider で包み込んでいるため、contextを利用してProviderを使用することができる。

  • context.read<T>() ➔ Tのデータ値を変更するなどのイベント用に使用

  • onPressed: () { context.read<Counts>().add(); } ➔ 該当ボタンをクリックすると、countに+1をさせる.

  • onPressed: () { context.read<Counts>().remove(); } ➔ 該当ボタンをクリックすると、countに-1をさせる。

// button.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';

class Buttons extends StatelessWidget {
  const Buttons({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
            onPressed: () {
              context.read<Counts>().add();
            },
            child: const Icon(Icons.add)),
        const SizedBox(
          width: 40,
        ),
        ElevatedButton(
            onPressed: () {
              context.read<Counts>().remove();
            },
            child: const Icon(Icons.remove))
      ],
    );
  }
}

widgets/counter.dart

  • 更新されたデータを表示するコード

  • context.watch<T>() ➔ Tのデータ値を画面に表示する用途で使用

// counter.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_example/providers/counts.dart';
class Counter extends StatelessWidget {
  const Counter({super.key});
  @override
  Widget build(BuildContext context) {
    return Text(
      context.watch<Counts>().count.toString(),
      style: const TextStyle(
        fontSize: 20,
      ),
    );
  }
}

備考

  • Consumer を使ったらもっと効率的なコード作成ができそう。
  1. Consumerを使用すると、そのWidgetが依存するデータが変更された場合にのみ、そのWidgetをレンダリングする。 これにより、アプリ全体をレンダリングし直すのではなく、必要な部分だけを更新することでアプリの性能を向上させることができる。

  2. Consumerを使用するとコードが読みやすくなる。Consumerは該当Widgetで使用するデータへの依存性を明示的に表現できる。 これにより、コードの可読性を高め、debugを容易にする。

# Consumer example

// provider.dart
class Counter extends ChangeNotifier {
  int _count = 0;
  
  int get count => _count;
  
  void increment() {
    _count++;
    notifyListeners();
  }
}

// counter.dart
class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Counter>(
      builder: (context, counter, child) {
        return Text(
          'Count: ${counter.count}',
        );
      },
    );
  }
}
16
11
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
16
11