Используем Flutter Provider

Используем Flutter Provider

Передача параметров во 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.

При копировании материалов ссылка на https://terraideas.ru/ обязательна

Комментарии к статье: Используем Flutter Provider

Нет ни одного комментария. Будьте первым!