Я начал изучать Flutter некоторое время назад и дошел до того, что мне нужно было передавать состояние между разными компонентами. Я думаю, я мог бы использовать параметры конструктора или что-то подобное, но я полагал, что есть способ получше.

Хотя, как правило, хорошо понимать, как все работает внутри, иногда вам просто нужно быстрое решение вашей проблемы. Поэтому в этой статье я кратко опишу найденный мною простейший способ, а вам дам готовое решение, особо не вдаваясь в подробности.

Задача: я хочу создать приложение, которое будет давать обзор баланса банковского счета, с возможностью вносить или снимать деньги с увеличением/уменьшением 100,00 евро, а также изменять отображать валюту между EUR, USD и CHF.

Прежде всего, давайте добавим зависимость к pubspec.yaml, что позволит нам использовать библиотеку Provider. На момент написания этой статьи последней версией была 4.3.2:

...
dependencies:
  flutter:
    sdk: flutter
  provider: ^4.3.2
...

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

class AccountBalance extends ChangeNotifier {
  String _currency = 'EUR';
  double _exchangeRateUSD = 1.18;
  double _exchangeRateCHF = 1.08;
  double _currentBalanceEUR = 1000.00;

  String get getCurrency {
    return _currency;
  }

  double get getCurrentBalanceEUR {
    return _currentBalanceEUR;
  }

  double get getExchangeRateUSD {
    return _exchangeRateUSD;
  }

  double get getExchangeRateCHF {
    return _exchangeRateCHF;
  }

  set currency(String currency) {
    _currency = currency;
    notifyListeners();
  }

  set currentBalanceEUR(double balance) {
    _currentBalanceEUR = balance;
    notifyListeners();
  }

  void changeCurrentBalance(double delta) {
    _currentBalanceEUR += delta;
    notifyListeners();
  }
}

Обратите внимание, что все мутаторы вызывают notifyListeners(). Таким образом, мы можем обновлять отображаемые значения в другом месте всякий раз, когда изменяется баланс или отображаемая валюта. Наш AccountBalance теперь готов к использованию.

Мы обернем наше основное приложение MaterialApp виджетом MultiProvider из библиотеки provider. Прямо сейчас нам не нужно использовать этот конкретный провайдер, так как у нас будет только один источник данных, но давайте подготовим наше приложение к будущему и ко многим провайдерам, которые могут появиться. MultiProvider позволяет нам объединять множество поставщиков, сокращая при этом шаблонный код. Что нам нужно сделать, так это указать поле providers и добавить наш класс, который будет отправлять уведомления о произошедших изменениях:

...
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider.value(
          value: AccountBalance(),
        ),
      ],
      child: MaterialApp(
        ...
        home: MyHomePage(),
        ...
      ),
    ); // MultiProvider
  }
}
...

В виджете MyHomePage теперь мы можем подписываться на разные поля и вызывать разные методы из AccountBalance, используя синтаксис Provider.of‹AccountBalance›(context). . Если мы хотим вызвать метод, который увеличит баланс на 100 евро (например, внутри метода onPressed кнопки), мы сделаем следующее:

Provider.of<AccountBalance>(context, listen: false).changeCurrentBalance(100.00);

Мы устанавливаем для параметра listen значение false, поскольку мы только вызываем метод и нам не нужно ничего прослушивать.

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

double _currentBalanceEUR = Provider.of<AccountBalance>(context).getCurrentBalanceEUR;

В этом месте мы опустили listen: false, что означает, что по умолчанию оно будет иметь значение true. Это необходимо, так как мы хотим прослушивать любое обновление значения нашей переменной. Теперь мы можем отображать _currentBalanceEUR в любом месте нашего виджета, и его значение будет автоматически обновляться при каждом вызове одного из мутаторов currentBalance в AccountBalance. >

Аналогичным образом мы реализуем кнопки, которые изменят отображаемую валюту, и мы добавим поле для отображаемого значения, которое будет рассчитываться на основе выбранной валюты, текущей суммы в евро и соответствующего обменного курса.

Вот полный код:

import 'package:flutter/material.dart';
import 'package:flutter_provider_demo/account_balance.dart';
import 'package:provider/provider.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider.value(
          value: AccountBalance(),
        ),
      ],
      child: MaterialApp(
        title: 'Flutter Provider Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    double _currentBalanceEUR =
        Provider.of<AccountBalance>(context).getCurrentBalanceEUR;
    double _exchangeRateUSD =
        Provider.of<AccountBalance>(context).getExchangeRateUSD;
    double _exchangeRateCHF =
        Provider.of<AccountBalance>(context).getExchangeRateCHF;
    String _currentCurrency = Provider.of<AccountBalance>(context).getCurrency;

    double _currentBalanceToDisplay = (_currentCurrency == 'EUR'
        ? _currentBalanceEUR
        : (_currentCurrency == 'USD'
            ? _currentBalanceEUR * _exchangeRateUSD
            : _currentBalanceEUR * _exchangeRateCHF));

    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Provider Demo'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(10.0),
          child: Column(
            children: [
              Text('Your current balance:'),
              Text(
                _currentBalanceToDisplay.toStringAsFixed(2) +
                    ' ' +
                    _currentCurrency,
                style: TextStyle(
                  fontSize: 24,
                  fontWeight: FontWeight.bold,
                  color:
                      _currentBalanceToDisplay >= 0 ? Colors.green : Colors.red,
                ),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  FlatButton(
                    child: Text('Deposit 100 EUR'),
                    color: Color.fromRGBO(100, 250, 255, 1),
                    splashColor: Colors.blue,
                    onPressed: () {
                      Provider.of<AccountBalance>(context, listen: false)
                          .changeCurrentBalance(100.00);
                    },
                  ),
                  FlatButton(
                    child: Text('Withdraw 100 EUR'),
                    color: Color.fromRGBO(100, 250, 255, 1),
                    splashColor: Colors.blue,
                    onPressed: () {
                      Provider.of<AccountBalance>(context, listen: false)
                          .changeCurrentBalance(-100.00);
                    },
                  ),
                ],
              ),
              Text('Select the display currency:'),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  FlatButton(
                    child: Text('EUR'),
                    color: Color.fromRGBO(100, 250, 255, 1),
                    splashColor: Colors.blue,
                    onPressed: () {
                      Provider.of<AccountBalance>(context, listen: false)
                          .currency = 'EUR';
                    },
                  ),
                  FlatButton(
                    child: Text('USD'),
                    color: Color.fromRGBO(100, 250, 255, 1),
                    splashColor: Colors.blue,
                    onPressed: () {
                      Provider.of<AccountBalance>(context, listen: false)
                          .currency = 'USD';
                    },
                  ),
                  FlatButton(
                    child: Text('CHF'),
                    color: Color.fromRGBO(100, 250, 255, 1),
                    splashColor: Colors.blue,
                    onPressed: () {
                      Provider.of<AccountBalance>(context, listen: false)
                          .currency = 'CHF';
                    },
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

Увидимся позже :)