開発者ドキュメント

プロバイダーとフラッターで状態を管理する方法

序章

状態管理には、アプリケーション全体の状態変化の追跡が含まれます。

プロバイダーパッケージは、状態管理のニーズに対応する1つのソリューションです。

この記事では、providerをサンプルのFlutterアプリケーションに適用して、ユーザーアカウント情報の状態を管理する方法を学習します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

このチュートリアルは、Flutter v2.0.6、Android SDK v31.0.2、およびAndroidStudiov4.1で検証されました。

問題を理解する

名前などのユーザーのデータを使用して画面の一部をカスタマイズするアプリを作成する場合を考えてみます。 画面間でデータを渡すための通常の方法は、すぐにコールバック、未使用のデータ、および不必要に再構築されたウィジェットの絡み合った混乱になります。 Reactのようなフロントエンドライブラリでは、これはプロップドリルと呼ばれる一般的な問題です。

これらのウィジェットのいずれかからデータを渡したい場合は、未使用のコールバックを増やすことで、すべての中間ウィジェットをさらに膨らませる必要があります。 ほとんどの小さな機能の場合、これにより、ほとんど努力する価値がなくなる可能性があります。

幸いなことに、providerパッケージを使用すると、MaterialAppを初期化する場合と同様に、上位のウィジェットにデータを保存し、サブウィジェットから直接アクセスして変更することができます。ネストし、その間のすべてを再構築せずに。

ステップ1—プロジェクトの設定

Flutter用に環境を設定したら、次のコマンドを実行して新しいアプリケーションを作成できます。

  1. flutter create flutter_provider_example

新しいプロジェクトディレクトリに移動します。

  1. cd flutter_provider_example

flutter createを使用すると、ボタンがクリックされた回数を表示するデモアプリケーションが作成されます。

ステップ2—providerプラグインを追加する

次に、pubspec.yaml内にproviderプラグインを追加する必要があります。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  provider: ^3.1.0

次に、変更をファイルに保存します。

注: VS Codeを使用している場合は、依存関係をすばやく追加するために PubspecAssist拡張機能の使用を検討することをお勧めします。

これで、iOSまたはAndroidシミュレーター、または選択したデバイスでこれを実行できます。

ステップ3—プロジェクトの足場

2つの画面、ルーター、およびナビゲーションバーが必要になります。 アカウントデータを表示するページと、ルーターから保存、変更、受け継がれる状態自体で更新するページを設定しています。

コードエディタでmain.dartを開き、次のコード行を変更します。

lib / main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/account.dart';
import './screens/settings.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Provider Demo',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return MaterialApp(home: AccountScreen(), routes: {
      'account_screen': (context) => AccountScreen(),
      'settings_screen': (context) => SettingsScreen(),
    });
  }
}

navbar.dartファイルを作成し、コードエディタで開きます。

lib / navbar.dart
import 'package:flutter/material.dart';
import './screens/account.dart';
import './screens/settings.dart';

class Navbar extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: <Widget>[
          TextButton(
            onPressed: () =>
              Navigator.pushReplacementNamed(context, AccountScreen.id),
            child: Icon(Icons.account_circle, color: Colors.white)
          ),
          TextButton(
            onPressed: () =>
              Navigator.pushReplacementNamed(context, SettingsScreen.id),
            child: Icon(Icons.settings, color: Colors.white)
          ),
        ],
      ),
    );
  }
}

libディレクトリに、新しいscreensサブディレクトリを作成します。

  1. mkdir lib/screens

このサブディレクトリに、settings.dartファイルを作成します。 これは、フォームの状態を作成し、入力を保存するためのマップを設定し、後で使用する送信ボタンを追加するために使用されます。

lib /screens/settings.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../navbar.dart';

class SettingsScreen extends StatelessWidget {
  static const String id = 'settings_screen';

  final formKey = GlobalKey<FormState>();

  final Map data = {'name': String, 'email': String, 'age': int};

  
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Navbar(),
      appBar: AppBar(title: Text('Change Account Details')),
      body: Center(
        child: Container(
        padding: EdgeInsets.symmetric(vertical: 20, horizontal: 30),
        child: Form(
          key: formKey,
          child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                TextFormField(
                  decoration: InputDecoration(labelText: 'Name'),
                  onSaved: (input) => data['name'] = input,
                ),
                TextFormField(
                  decoration: InputDecoration(labelText: 'Email'),
                  onSaved: (input) => data['email'] = input,
                ),
                TextFormField(
                  decoration: InputDecoration(labelText: 'Age'),
                  onSaved: (input) => data['age'] = input,
                ),
                TextButton(
                  onPressed: () => formKey.currentState.save(),
                  child: Text('Submit'),
                  style: TextButton.styleFrom(
                    primary: Colors.white,
                    backgroundColor: Colors.blue,
                  ),
                )
              ]
            ),
          ),
        ),
      ),
    );
  }
}

また、このサブディレクトリに、account.dartファイルを作成します。 これは、アカウント情報を表示するために使用されます。

lib /screens/account.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import '../navbar.dart';

class AccountScreen extends StatelessWidget {
  static const String id = 'account_screen';

  
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: Navbar(),
      appBar: AppBar(
        title: Text('Account Details'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Name: '),
            Text('Email: '),
            Text('Age: '),
          ],
        ),
      ),
    );
  }
}

コードをコンパイルして、エミュレーターで実行します。

この時点で、アカウント画面と設定画面を備えたアプリケーションができました。

ステップ4—Providerを使用する

providerを設定するには、MaterialAppProviderにデータの種類でラップする必要があります。

main.dartに再度アクセスして、コードエディタで開きます。 このチュートリアルでは、データ型はMapです。 最後に、createを設定して、contextdataを使用する必要があります。

lib / main.dart
// ...

class _MyHomePageState extends State<MyHomePage> {
  Map data = {
    'name': 'Sammy Shark',
    'email': 'example@example.com',
    'age': 42
  };

  
  Widget build(BuildContext context) {
    return Provider<Map>(
      create: (context) => data,
      child: MaterialApp(home: AccountScreen(), routes: {
        'account_screen': (context) => AccountScreen(),
        'settings_screen': (context) => SettingsScreen(),
      }),
    );
  }
}

dataマップは、main.dartproviderパッケージを呼び出してインポートする他のすべての画面とウィジェットで使用できるようになりました。

Providerクリエーターに渡したものはすべて、Provider.of<Map>(context)で利用できるようになりました。 渡すタイプは、Providerが期待するデータのタイプと一致する必要があることに注意してください。

注: VS Codeを使用している場合は、providerに頻繁にアクセスする可能性があるため、スニペットの使用を検討することをお勧めします。

dart.json
"Provider": {
  "prefix": "provider",
  "body": [
    "Provider.of<$1>(context).$2"
  ]
}

account.dartに再度アクセスして、コードエディタで開きます。 次のコード行を追加します。

lib /screens/account.dart
// ...

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Name: ' + Provider.of<Map>(context)['name'].toString()),
      Text('Email: ' + Provider.of<Map>(context)['email'].toString()),
      Text('Age: ' + Provider.of<Map>(context)['age'].toString()),
    ]),
  ),
)

// ...

コードをコンパイルして、エミュレーターで実行します。

この時点で、アカウント画面に表示されるハードコードされたユーザーデータを含むアプリケーションがあります。

ステップ5—ChangeNotifierを使用する

Providerをこのように使用すると、非常にトップダウンのように見えます。データを渡し、マップを変更したい場合はどうでしょうか。 Providerだけでは十分ではありません。 まず、データをChangeNotifierを拡張する独自のクラスに分割する必要があります。 Providerはそれでは機能しないため、ChangeNotifierProviderに変更し、代わりにDataクラスのインスタンスを渡す必要があります。

これで、単一の変数だけでなく、クラス全体を渡すことになります。これは、データを操作できるメソッドの作成を開始できることを意味します。このメソッドは、Providerにアクセスするすべてのユーザーが利用できます。

グローバルデータのいずれかを変更した後、notifyListenersを使用します。これにより、それに依存するすべてのウィジェットが再構築されます。

main.dartに再度アクセスして、コードエディタで開きます。

lib / main.dart
// ...

class _MyHomePageState extends State<MyHomePage> {
  
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Data>(
      create: (context) => Data(),
      child: MaterialApp(home: AccountScreen(), routes: {
        'account_screen': (context) => AccountScreen(),
        'settings_screen': (context) => SettingsScreen(),
      }),
    );
  }
}

class Data extends ChangeNotifier {
  Map data = {
    'name': 'Sammy Shark',
    'email': 'example@example.com',
    'age': 42
  };

  void updateAccount(input) {
    data = input;
    notifyListeners();
  }
}

Providerタイプを変更したため、そのタイプへの呼び出しを更新する必要があります。 account.dartに再度アクセスして、コードエディタで開きます。

lib /screens/account.dart
// ...

body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Text('Name: ' + Provider.of<Data>(context).data['name'].toString()),
      Text('Email: ' + Provider.of<Data>(context).data['email'].toString()),
      Text('Age: ' + Provider.of<Data>(context).data['age'].toString()),
    ]),
  ),
)

// ...

データを渡すには、Dataクラスで渡されたメソッドを使用してProviderにアクセスする必要があります。 settings.dartに再度アクセスして、コードエディタで開きます。

lib /screens/settings.dart
TextButton(
  onPressed: () {
    formKey.currentState.save();
    Provider.of<Data>(context, listen: false).updateAccount(data);
    formKey.currentState.reset();
  },
)

コードをコンパイルして、エミュレーターで実行します。

この時点で、[設定]画面でのユーザー情報の更新と[アカウント]画面での変更の表示をサポートするアプリケーションができました。

結論

この記事では、providerをサンプルのFlutterアプリケーションに適用して、ユーザーアカウント情報の状態を管理する方法を学びました。

Flutterについて詳しく知りたい場合は、Flutterトピックページで演習とプログラミングプロジェクトを確認してください。

モバイルバージョンを終了