# Как улучшить производительность вашего Flutter приложения ![](https://habrastorage.org/webt/76/nr/ut/76nrutrkfl7c49c7ifdu9qgb134.png) Есть много вопросов, связанных с тем, как мы можем улучшить производительность нашего Flutter приложения. Необходимо сразу уточнить, что Flutter производительный по умолчанию, но мы должны избегать некоторых ошибок при написании кода, чтобы приложение работало хорошо и быстро. Ниже я подготовил для вас ряд советов и подсказок, как можно писать, чтобы не приходилось постоянно обращаться к инструментам профилирования. ## Не выносите виджеты в методы класса Когда у нас есть сложное представление, чтобы реализовать в один виджет, то обычно мы разделяем его на виджеты поменьше, которые помещаем в методы класса. В следующем примере представлен виджет, содержащий заголовок, основной контент и "подвал" *(footer)*. ```java class MyHomePage extends StatelessWidget { Widget _buildHeaderWidget() { final size = 40.0; return Padding( padding: const EdgeInsets.all(8.0), child: CircleAvatar( backgroundColor: Colors.grey[700], child: FlutterLogo( size: size, ), radius: size, ), ); } Widget _buildMainWidget(BuildContext context) { return Expanded( child: Container( color: Colors.grey[700], child: Center( child: Text( 'Hello Flutter', style: Theme.of(context).textTheme.display1, ), ), ), ); } Widget _buildFooterWidget() { return Padding( padding: const EdgeInsets.all(8.0), child: Text('This is the footer '), ); } @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.all(15.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ _buildHeaderWidget(), _buildMainWidget(context), _buildFooterWidget(), ], ), ), ); } } ``` ![](https://habrastorage.org/webt/e5/fo/gl/e5fogldinutl4jybj3cpd5m5wju.gif) То, что мы увидели выше, – это антипаттерн. Почему так? Всё потому, что, когда мы вносим изменения и обновляем `MyHomePage` виджет, то виджеты, которые у нас вынесены методах, также обновляются, даже если в этом нет никакой необходимости. Чтобы не было лишних вычислений, мы можем переделать эти методы в `StatelessWidget` следующим образом. ```java class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.all(15.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ HeaderWidget(), MainWidget(), FooterWidget(), ], ), ), ); } } class HeaderWidget extends StatelessWidget { @override Widget build(BuildContext context) { final size = 40.0; return Padding( padding: const EdgeInsets.all(8.0), child: CircleAvatar( backgroundColor: Colors.grey[700], child: FlutterLogo( size: size, ), radius: size, ), ); } } class MainWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Expanded( child: Container( color: Colors.grey[700], child: Center( child: Text( 'Hello Flutter', style: Theme.of(context).textTheme.display1, ), ), ), ); } } class FooterWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Text('This is the footer '), ); } } ``` У `Stateful`/`Stateless` виджетов есть специальный механизм "кэширования", учитывающий ключ, тип виджета и его атрибуты, который позволяет не перестраивать виджет без необходимости. Кроме того, это помогает нам инкапсулировать и рефакторировать наши виджеты. ([Разделяй и властвуй](https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm)) И было бы неплохо добавить `const` в наши виджеты. Позже мы увидим, почему это важно. ## Избегайте обновления всех виджетов Это обычная ошибка, которую многие совершают, когда начинают использовать Flutter и впервые сталкиваются с необходимостью обновить `StatefulWidget` с помощью `setState`. Следующий пример - это представление, содержащее квадрат в центре и `FloatingActionButton` кнопку, при каждом нажатии на которую вызывается изменение цвета. О, а еще на странице также есть виджет с фоновым изображением. Кроме того, мы добавим несколько `print` операторов внутри `build` метода каждого виджета, чтобы посмотреть, как он работает. ```java class _MyHomePageState extends State { Color _currentColor = Colors.grey; Random _random = new Random(); void _onPressed() { int randomNumber = _random.nextInt(30); setState(() { _currentColor = Colors.primaries[randomNumber % Colors.primaries.length]; }); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: BackgroundWidget(), ), Center( child: Container( height: 150, width: 150, color: _currentColor, ), ), ], ), ); } } class BackgroundWidget extends StatelessWidget { @override Widget build(BuildContext context) { print('building `BackgroundWidget`'); return Image.network( 'https://cdn.pixabay.com/photo/2017/08/30/01/05/milky-way-2695569_960_720.jpg', fit: BoxFit.cover, ); } } ``` ![](https://habrastorage.org/webt/yk/0k/-h/yk0k-heypf5gclu8em7abi1jp2y.gif) После клика по кнопке в консоли мы увидим два вывода ``` flutter: building `MyHomePage` flutter: building `BackgroundWidget` ``` Каждый раз, когда мы нажимаем кнопку, мы обновляем весь экран: `Scaffold`, `BackgroundWidget` и, наконец, то, что и хотели обновить, – квадрат-`Container`. Как мы уже выяснили выше, перестраивать виджеты без необходимости – нехорошая практика. Обновляем только то, что нам нужно. Многие знают, что это можно сделать с помощью различных пакетов управления состоянием: [flutter_bloc](https://pub.dev/packages/flutter_bloc), [mobx](https://pub.dev/packages/mobx), [provider](https://pub.dev/packages/provider) и т. д. Но мало кто знает, что также это можно сделать с помощью классов, которые Flutter уже предлагает из коробки, без каких-либо сторонных библиотек. Давайте, рассмотрим тот же пример, но обновленный с помощью `ValueNotifier`. ```java class _MyHomePageState extends State { final _colorNotifier = ValueNotifier(Colors.grey); Random _random = new Random(); void _onPressed() { int randomNumber = _random.nextInt(30); _colorNotifier.value = Colors.primaries[randomNumber % Colors.primaries.length]; } @override void dispose() { _colorNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: BackgroundWidget(), ), Center( child: ValueListenableBuilder( valueListenable: _colorNotifier, builder: (_, value, __) => Container( height: 150, width: 150, color: value, ), ), ), ], ), ); } } ``` Кликнем по кнопке и... ничего не увидим в консоли. Это работает, как и ожидалось, мы просто обновляем только тот виджет, который нам нужен *(прим. подробнее про [ValueListenableBuilder и ValueNotifier](https://www.youtube.com/watch?v=s-ZG-jS5QHQ))*. Но есть еще один интересный виджет, который мы также можем использовать в этом случае, если мы хотим дополнительно разделить бизнес-логику и представление (но, возможно, добавив немного логики в него) и обрабатывать больше данных в вне виджета *(в уведомителе)*. Снова тот же пример, но уже с `ChangeNotifier`. ```java //------ ChangeNotifier class ----// class MyColorNotifier extends ChangeNotifier { Color myColor = Colors.grey; Random _random = new Random(); void changeColor() { int randomNumber = _random.nextInt(30); myColor = Colors.primaries[randomNumber % Colors.primaries.length]; notifyListeners(); } } //------ State class ----// class _MyHomePageState extends State { final _colorNotifier = MyColorNotifier(); void _onPressed() { _colorNotifier.changeColor(); } @override void dispose() { _colorNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { print('building `MyHomePage`'); return Scaffold( floatingActionButton: FloatingActionButton( onPressed: _onPressed, child: Icon(Icons.colorize), ), body: Stack( children: [ Positioned.fill( child: BackgroundWidget(), ), Center( child: AnimatedBuilder( animation: _colorNotifier, builder: (_, __) => Container( height: 150, width: 150, color: _colorNotifier.myColor, ), ), ), ], ), ); } } ``` Самое прекрасное, что и в этом случае у нас нет ненужных обновлений. ## Используйте `const` Рекомендуется использовать ключевое слово `const` для значений, которые возможно инициализировать во время компиляции, а также при вызове конструктора виджета (если он поддерживает `const`, конечно), что позволяет работать с одним и тем же каноническим экземпляром, тем самым избегая повторных вычислений *(прим. подробнее про [работу const(https://habr.com/ru/post/501804/))*.