Управление состоянием Flutter с помощью Provider

Процесс управления состоянием включает в себя отслеживание изменений состояния приложения.

Пакет provider – одно из средств управления состоянием.

В этом мануале вы узнаете, как использовать provider в простом приложении Flutter и управлять состоянием информации учетной записи пользователя.

Требования

  • Установка Flutter.
  • Установка Android Studio или Visual Studio Code.
  • Рекомендуется также установить плагины для вашего редактора кода:
    • Flutter и Dart для Android Studio.
    • расширение Flutter для Visual Studio Code.
  • Базовое знакомство с состоянием форм, навигацией и маршрутизацией (полезно, но не обязательно).

Это руководство было проверено с помощью версий Flutter v2.0.6, Android SDK v31.0.2 и Android Studio v4.1.

В чем проблема?

Давайте предположим, что мы хотим создать приложение, которое поддерживает пользовательскую настройку некоторых своих экранов и принимает некоторые пользовательские данные (например, имя). Обычные методы передачи данных между экранами быстро превратят всё в запутанный клубок обратных вызовов, неиспользуемых данных и излишне пересобранных виджетов. В контексте React это обычная проблема, которая называется пробрасыванием (prop drilling).

Если мы захотим передавать данные из любого из наших виджетов, нам придется еще сильнее раздуть каждый промежуточный виджет большим количеством неиспользуемых обратных вызовов. В таком случае, реализация большинства мелких функций нерациональна – они просто не стоят затраченных усилий.

К счастью для нас, пакет provider позволяет хранить данные в виджете, который находится выше (например, там, где мы инициализируем MaterialApp). Затем можно получить доступ к данным и изменить их непосредственно из суб-виджетов, независимо от вложенности и без пересборки всего того, что находится между ними.

1: Создание проекта

Итак, вы самостоятельно настроили среду Flutter. Теперь вы можете запустить следующую команду, чтобы создать новое приложение:

flutter create flutter_provider_example

Перейдите в каталог нового проекта:

cd flutter_provider_example

Команда flutter create создаст простое тестовое приложение, в котором будет отображаться количество нажатий кнопки.

2: Добавление плагина provider

Затем нам нужно добавить плагин provider в pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter

  provider: ^3.1.0

Сохраните изменения.

Примечание: Если вы используете VS Code, вы можете рассмотреть возможность использования расширения Pubspec Assist, оно предназначено для быстрого добавления зависимостей.

Теперь мы можем запустить приложение на симуляторе iOS или Android (или на другом устройстве по вашему выбору).

3: Скаффолд проекта

В данном случае нам понадобятся два экрана, маршрутизатор и панель навигации. Мы настроим одну страницу для отображения данных нашей учетной записи, а другую – для ее обновления с сохранением, изменением и передачей самого состояния из маршрутизатора.

Откройте файл 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 {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Provider Demo',
      home: MyHomePage(),
    );
  }
}

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  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 {
  @override
  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};

  @override
  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';

  @override
  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: '),
          ],
        ),
      ),
    );
  }
}

Скомпилируйте свой код и запустите его в эмуляторе.

Итак, на данный момент у нас есть приложение с экранами Account и Settings.

4: Использование пакета provider

Чтобы настроить provider, потребуется завернуть наш MaterialApp в Provider с указанием типа данных.

Снова посетите main.dart, откройте его в редакторе кода. В этом руководстве мы используем тип данных Map. Наконец, нам нужно настроить create, чтобы затем использовать context и data:

// ...

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

  @override
  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, вы можете рассмотреть возможность использования сниппетов в файле dart.json, так как вы, вероятно, будете часто обращаться к провайдеру:

"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()),
    ]),
  ),
)

// ...

Скомпилируйте код и запустите его в эмуляторе.

На этом этапе у нас есть приложение с жестко заданными пользовательскими данными, которые отображаются на экране Account.

5: Использование ChangeNotifier

Такой подход к использованию Provider работает по принципу «сверху вниз»; а что, если мы хотим передать данные вверх и изменить карту? Одного Provider для этого недостаточно. Во-первых, нам нужно разбить данные и создать собственный класс, расширяющий ChangeNotifier. Provider не умеет делать такое, поэтому нам нужно изменить его на ChangeNotifierProvider и передать ему экземпляр нашего класса Data.

Теперь мы передаем не одну переменную, а весь класс; это означает, что мы можем создавать методы, которые будут управлять нашими данными, в следствие чего эти данные будут доступны для всех, кто обращается к Provider.

После изменения каких-либо глобальных данных мы хотим использовать notifyListeners, которые перестроят каждый зависимый виджет.

Снова откройте main.dart в редакторе кода:

// ...

class _MyHomePageState extends State<MyHomePage> {
  @override
  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': '8host Blog',
    'email': 'example@example.com',
    '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()),
    ]),
  ),
)

// ...

Чтобы передать данные, нам нужно получить доступ к Provider, используя метод, который был передан в классе Data. Еще раз посетите settings.dart в редакторе кода измените следующее:

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

Скомпилируйте свой код и запустите его в эмуляторе.

Теперь наше приложение поддерживает обновление информации о пользователе на экране Settings и отображает эти изменения на экране Account.

Заключение

В этом руководстве вы узнали, как использовать пакет provider для управления состоянием информации учетной записи пользователя на примере простого приложения Flutter.

Читайте также: Основы навигации в Flutter

Tags:

Добавить комментарий