プロバイダーとフラッターで状態を管理する方法
序章
状態管理には、アプリケーション全体の状態変化の追跡が含まれます。
プロバイダーパッケージは、状態管理のニーズに対応する1つのソリューションです。
この記事では、provider
をサンプルのFlutterアプリケーションに適用して、ユーザーアカウント情報の状態を管理する方法を学習します。
前提条件
このチュートリアルを完了するには、次のものが必要です。
- Flutterをダウンロードしてインストールするには。
- Android StudioまたはVisual StudioCodeをダウンロードしてインストールするには。
- コードエディタ用のプラグインをインストールすることをお勧めします。
AndroidStudio用にインストールされたFlutterおよびDartプラグイン。 VisualStudioCode用にインストールされたFlutter拡張機能。 - ナビゲーションとルートに精通していることは有益ですが、必須ではありません。
- フォームの状態に精通していることも有益ですが、必須ではありません。
このチュートリアルは、Flutter v2.0.6、Android SDK v31.0.2、およびAndroidStudiov4.1で検証されました。
問題を理解する
名前などのユーザーのデータを使用して画面の一部をカスタマイズするアプリを作成する場合を考えてみます。 画面間でデータを渡すための通常の方法は、すぐにコールバック、未使用のデータ、および不必要に再構築されたウィジェットの絡み合った混乱になります。 Reactのようなフロントエンドライブラリでは、これはプロップドリルと呼ばれる一般的な問題です。
これらのウィジェットのいずれかからデータを渡したい場合は、未使用のコールバックを増やすことで、すべての中間ウィジェットをさらに膨らませる必要があります。 ほとんどの小さな機能の場合、これにより、ほとんど努力する価値がなくなる可能性があります。
幸いなことに、provider
パッケージを使用すると、MaterialApp
を初期化する場合と同様に、上位のウィジェットにデータを保存し、サブウィジェットから直接アクセスして変更することができます。ネストし、その間のすべてを再構築せずに。
ステップ1—プロジェクトの設定
Flutter用に環境を設定したら、次のコマンドを実行して新しいアプリケーションを作成できます。
- flutter create flutter_provider_example
新しいプロジェクトディレクトリに移動します。
- cd flutter_provider_example
flutter create
を使用すると、ボタンがクリックされた回数を表示するデモアプリケーションが作成されます。
ステップ2—provider
プラグインを追加する
次に、pubspec.yaml
内にprovider
プラグインを追加する必要があります。
dependencies:
flutter:
sdk: flutter
provider: ^3.1.0
次に、変更をファイルに保存します。
注: VS Codeを使用している場合は、依存関係をすばやく追加するために PubspecAssist拡張機能の使用を検討することをお勧めします。
これで、iOSまたはAndroidシミュレーター、または選択したデバイスでこれを実行できます。
ステップ3—プロジェクトの足場
2つの画面、ルーター、およびナビゲーションバーが必要になります。 アカウントデータを表示するページと、ルーターから保存、変更、受け継がれる状態自体で更新するページを設定しています。
コードエディタで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
ファイルを作成し、コードエディタで開きます。
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
サブディレクトリを作成します。
- mkdir 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
ファイルを作成します。 これは、アカウント情報を表示するために使用されます。
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
を設定するには、MaterialApp
をProvider
にデータの種類でラップする必要があります。
main.dart
に再度アクセスして、コードエディタで開きます。 このチュートリアルでは、データ型はMap
です。 最後に、create
を設定して、context
とdata
を使用する必要があります。
// ...
class _MyHomePageState extends State<MyHomePage> {
Map data = {
'name': 'Sammy Shark',
'email': '[email protected]',
'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.dart
がprovider
パッケージを呼び出してインポートする他のすべての画面とウィジェットで使用できるようになりました。
Provider
クリエーターに渡したものはすべて、Provider.of<Map>(context)
で利用できるようになりました。 渡すタイプは、Provider
が期待するデータのタイプと一致する必要があることに注意してください。
注: VS Codeを使用している場合は、provider
に頻繁にアクセスする可能性があるため、スニペットの使用を検討することをお勧めします。
"Provider": {
"prefix": "provider",
"body": [
"Provider.of<$1>(context).$2"
]
}
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
に再度アクセスして、コードエディタで開きます。
// ...
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': '[email protected]',
'age': 42
};
void updateAccount(input) {
data = input;
notifyListeners();
}
}
Provider
タイプを変更したため、そのタイプへの呼び出しを更新する必要があります。 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
に再度アクセスして、コードエディタで開きます。
TextButton(
onPressed: () {
formKey.currentState.save();
Provider.of<Data>(context, listen: false).updateAccount(data);
formKey.currentState.reset();
},
)
コードをコンパイルして、エミュレーターで実行します。
この時点で、[設定]画面でのユーザー情報の更新と[アカウント]画面での変更の表示をサポートするアプリケーションができました。
結論
この記事では、provider
をサンプルのFlutterアプリケーションに適用して、ユーザーアカウント情報の状態を管理する方法を学びました。
Flutterについて詳しく知りたい場合は、Flutterトピックページで演習とプログラミングプロジェクトを確認してください。