Як використовувати шаблон постачальника в Flutter

У цій публікації ми розглянемо шаблон постачальника у Flutter. Деякі інші шаблони, такі як архітектура BLoC, використовують шаблон постачальника внутрішньо. Але шаблон постачальника набагато простіший у вивченні і має набагато менше шаблонного коду.

У цій публікації ми візьмемо програму Counter за замовчуванням, яку надає Flutter, та рефакторинг її, щоб використовувати шаблон постачальника.

Якщо ви хочете знати, що команда Flutter в Google говорить про шаблон постачальника, перегляньте цю бесіду у 2019 році.

Якщо ви хочете дізнатись більше про архітектуру BLoC, перегляньте її тут.

Починаємо

Створіть новий проект Flutter і дайте йому назву, що завгодно.

По-перше, нам потрібно видалити всі коментарі, щоб у нас було чисте рішення:

import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Тепер додайте залежність для шаблону постачальника у pubspec.yamlфайл. На момент написання останньої версії 4.1.2.

Ось як ваш pubspec.yamlфайл буде виглядати зараз:

name: provider_pattern_explained description: A new Flutter project. publish_to: 'none' version: 1.0.0+1 environment: sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter provider: ^4.1.2 cupertino_icons: ^0.1.3 dev_dependencies: flutter_test: sdk: flutter flutter: uses-material-design: true 

Додаток за замовчуванням - це в основному віджет, що переглядає стан, який відновлюється кожного разу, коли ви натискаєте FloatingActionButton(який викликає setState()).

Але зараз ми перетворимо його на віджет без громадянства.

Створення постачальника

Давайте створимо нашого провайдера. Це буде єдиним джерелом істини для нашого додатка. Тут ми будемо зберігати наш стан, який у цьому випадку є поточним підрахунком.

Створіть клас з іменем Counterта додайте countзмінну:

import 'package:flutter/material.dart'; class Counter { var _count = 0; } 

Щоб перетворити його на клас провайдера, вийдіть ChangeNotifierіз material.dartпакету. Це надає нам notifyListeners()метод і повідомляє всіх слухачів, коли ми змінюємо значення.

Тепер додайте метод збільшення лічильника:

import 'package:flutter/material.dart'; class Counter extends ChangeNotifier { var _count = 0; void incrementCounter() { _count += 1; } } 

В кінці цього методу ми зателефонуємо notifyListeners(). Це спричинить зміну в усій програмі будь-який віджет, який її слухає.

У цьому і полягає краса шаблону провайдера у Flutter - вам не потрібно дбати про вручну відправлення до потоків.

Нарешті, створіть геттер для повернення значення лічильника. Ми використаємо це для відображення останнього значення:

import 'package:flutter/material.dart'; class Counter extends ChangeNotifier { var _count = 0; int get getCounter { return _count; } void incrementCounter() { _count += 1; notifyListeners(); } } 

Прослуховування натискань кнопок

Тепер, коли у нас налаштовано постачальника, ми можемо продовжувати використовувати його в основному віджеті.

По-перше, давайте перетворимо MyHomePageна віджет без стану без заміни. Нам доведеться видалити setState()дзвінок, оскільки він доступний лише в StatefulWidget:

import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatelessWidget { int _counter = 0; final String title; MyHomePage({this.title}); void _incrementCounter() {} @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Після цього ми тепер можемо використовувати шаблон постачальника у Flutter для встановлення та отримання значення лічильника. При кожному натисканні кнопки нам потрібно збільшити значення лічильника на 1.

Отже, у _incrementCounterметод (який викликається при натисканні кнопки) додайте цей рядок:

Provider.of(context, listen: false).incrementCounter();

Тут відбувається те, що ви попросили Флаттера зайти в дерево віджетів і знайти перше місце, де Counterце передбачено. (Я розповім вам, як це надати в наступному розділі.) Ось що Provider.of()робить.

Загальні засоби (значення всередині дужки) повідомте Flutter, якого типу постачальника шукати. Потім Flutter проходить вгору по дереву віджетів, поки не знайде надане значення. Якщо значення ніде не вказано, виникає виняток.

Нарешті, як тільки ви отримали провайдера, ви можете викликати будь-який метод на ньому. Тут ми називаємо наш incrementCounterметод.

Але нам також потрібен контекст, тому ми приймаємо контекст як аргумент і змінюємо onPressedметод, щоб також передавати контекст:

void _incrementCounter(BuildContext context) { Provider.of(context, listen: false).incrementCounter(); } 

Примітка: Ми встановили прослуховування false, оскільки нам не потрібно слухати будь-які значення тут. Ми просто відправляємо дію, яку потрібно виконати.

Надання Постачальнику

Шаблон постачальника у Flutter шукатиме останнє надане значення. Діаграма нижче допоможе вам краще зрозуміти.

На цій діаграмі ЗЕЛЕНИЙ об'єкт A буде доступний для решти елементів під ним, тобто B, C, D, E та F.

Тепер припустимо, що ми хочемо додати деяку функціональність до програми, і ми створимо іншого постачальника, Z. Z потрібен E та F.

То де найкраще це додати?

Ми можемо додати його в корінь вище A . Це буде працювати:

Але цей метод не дуже ефективний.

Flutter пройде через усі віджети вище, а потім нарешті перейде до кореня. Якщо у вас є дуже довгі дерева віджетів - що ви точно знайдете у виробничому додатку, - це не гарна ідея ставити все в корені.

Натомість ми можемо розглянути спільний знаменник E та F. Це C. Це означає, що якщо ми поставимо Z трохи вище E та F, це буде працювати.

But what if we want to add another object X that'srequired by E and F? We'll do the same thing. But notice how the tree keeps growing.

There’s a better way to manage that. What if we provide all the objects at one level?

This is perfect, and is how we’ll eventually implement our provider pattern in Flutter. We’ll make use of something called MultiProviderwhich lets us declare multiple providers at one level.

We'll get MultiProvider to wrap the MaterialApp widget:

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

With this, we’ve provided the provider to our widget tree and can use it anywhere below this level in the tree.

There’s just one more thing left: we need to update the value that's displayed.

Updating the text

To update the text, get the provider in the build function of your MyHomePage widget. We’ll use the getter we created to get the latest value.

Then just add this value to the text widget below.

And we’re done! This is how your final main.dart file should look:

import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:provider_pattern_explained/counter.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider.value( value: Counter(), ), ], child: MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: "AndroidVille Provider Pattern"), ), ); } } class MyHomePage extends StatelessWidget { final String title; MyHomePage({this.title}); void _incrementCounter(BuildContext context) { Provider.of(context, listen: false).incrementCounter(); } @override Widget build(BuildContext context) { var counter = Provider.of(context).getCounter; return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( 'You have pushed the button this many times:', ), Text( '$counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: () => _incrementCounter(context), tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Note: We haven’t set listen:false in this case because we want to listen to any updates in the count value.

Here's the source code on GitHub if you want to have a look: //github.com/Ayusch/Flutter-Provider-Pattern.

Let me know if you have any issues.

Welcome to AndroidVille :)

AndroidVille is a community of Mobile Developers where we share knowledge related to Android Development, Flutter Development, React Native Tutorials, Java, Kotlin and much more.

Click on this link to join the AndroidVille SLACK workspace. It’s absolutely free!

If you liked this article, feel free to share it on Facebook or LinkedIn. You can follow me on LinkedIn, Twitter, Quora, and Medium where I answer questions related to mobile development, Android, and Flutter.