
Передача параметров во Flutter с помощью популярного пакета Provider и организация UserScope.
Большинство приложений использует пакет Provider. Этот пакет рекомендован разработчиками Flutter и насчитывает на момент написания статьи наибольшее число лайков.
Provider является оберткой надо InheritedWidget. Этот пакет позволяет с легкостью использовать InheritedWidget для передачи значений с возможностью подписаться на изменения или получить просто текущее значение.
Используя Provider вам доступны автоматическое создание и освобождение ресурсов, ленивая загрузка (ресурс будет создан только если понадобится), а так же удобные инструменты для упрощения написания кода ваших виджетов.
Виджет Provider имеет конструктор вида:
Provider( create: (context) => MyModel(), dispose: (context, value) => value.dispose(), lazy: false, child: ... )
Параметр lazy по умолчанию равен true. Это означает, что вызов функции create будет происходить лениво, то есть после того, как какой либо виджет запросит значение, а не в момент создания виджета Provider. Если передать значение false, то функция create вызовется в момент создания данного провайдера. Параметр lazy и dispose не обязательны. Вместо child можно использовать builder, об этом чуть ниже.
Данную конструкцию следует использовать только для передачи ресурсов которые будут созданы непосредственно в create.
Если вам необходимо получить ресурс непосредственно в пределах одного контекста, то есть что то вроде:
build(BuildContext context) => Provider<MyModel>( create: (context) => MyModel(), child: Text(context.watch<MyModel>()) );
Такая запись не сработает, так как в данном случае в 4-ой строке будет context из 1-ой строки, который ничего не знает о нашем провайдере. Для этого есть функция builder:
build(BuildContext context) => Provider<MyModel>( create: (context) => MyModel(), builder: (context) => Text(context.watch<MyModel>()) );
Теперь виджет Text уже создается в контексте из провайдера.
Если вам необходимо передать по контексту уже созданный ресурс, воспользуйтесь записью:
Provider.value( value: _model, child: ... )
Как и в случае с InheritedWidget, может понадобится создать Scope для какого либо ресурса.
Для получения доступа к ресурсу и подписаться на обновления, можно воспользоваться записью:
context.watch<MyModel>() //или Provider.of<MyModel>(context)
Данную запись следует использовать в методе build. Метод build будет перерисовываться, если метод updateShouldNotify вернет true. Данный метод имеет вид:
typedef UpdateShouldNotify<T> = bool Function(T previous, T current);
В функцию передается предыдущий ресурс и новый. По умолчанию сравнение имеет вид previous != current.
Если же вам необходимо получить значение в местах, не связаны с отрисовкой, и вам не нужно следить за изменением значения, например в initState или в обработчике нажатия, используйте способы ниже:
context.read<MyModel>() //или Provider.of<MyModel>(context, listen: false)
Пример из предыдущей статьи но с использованием Provider:
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; void main() { runApp(MyApp()); } class User { final int id; final String name; User({ @required this.id, @required this.name, }): assert(id != null), assert(name != null); } class UserScope extends StatefulWidget { const UserScope({Key key, this.child}): assert(child != null), super(key: key); final Widget child; @override UserScopeState createState() => UserScopeState(); } class UserScopeState extends State<UserScope> { User _user; set user(User user) { setState(() { _user = user; }); } User get user => _user; @override Widget build(BuildContext context) { return Provider.value( value: _user, child: Provider.value( value: this, child: widget.child, ), ); } } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter App', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: UserScope( child: HomePage() ), ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) => Scaffold( body: Container( child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ UserLogin(), UserBar() ], ), ) ); } class UserLogin extends StatelessWidget { @override Widget build(BuildContext context) { return context.watch<User>() == null ? RaisedButton( onPressed: () => context.read<UserScopeState>().user = User(id: 1, name: 'Александр'), child: Text('Войти') ) : RaisedButton( onPressed: () => context.read<UserScopeState>().user = null, child: Text('Выйти') ); } } class UserBar extends StatelessWidget { @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: EdgeInsets.all(20), color: Color(Random().nextInt(0xffffffff)).withAlpha(0xff), child: Center(child: UserView()) ); } } class UserView extends StatelessWidget { @override Widget build(BuildContext context) { final user = context.watch<User>(); return user == null ? Container() : Container( color: Colors.blueGrey, padding: EdgeInsets.all(12.0), child: Text( "Привет, ${user.name}!", style: TextStyle(color: Colors.white) ) ); } }
Продолжение статьи на примере создания корзины с ChangeNotifier.
Комментарии к статье: Используем Flutter Provider