Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save kdmatrosov/2c7a8197fe5fbea5df88a33b14993c2a to your computer and use it in GitHub Desktop.

Select an option

Save kdmatrosov/2c7a8197fe5fbea5df88a33b14993c2a to your computer and use it in GitHub Desktop.
Перевод "How to improve the performance of your Flutter app" https://blog.codemagic.io/how-to-improve-the-performance-of-your-flutter-app./

Как улучшить производительность вашего Flutter приложения

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

Не выносите виджеты в методы класса

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

В следующем примере представлен виджет, содержащий заголовок, основной контент и "подвал" (footer).

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(),
          ],
        ),
      ),
    );
  }
}

То, что мы увидели выше, – это антипаттерн. Почему так? Всё потому, что, когда мы вносим изменения и обновляем MyHomePage виджет, то виджеты, которые у нас вынесены методах, также обновляются, даже если в этом нет никакой необходимости.

Чтобы не было лишних вычислений, мы можем переделать эти методы в StatelessWidget следующим образом.

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 виджетов есть специальный механизм "кэширования", учитывающий ключ, тип виджета и его атрибуты, который позволяет не перестраивать виджет без необходимости. Кроме того, это помогает нам инкапсулировать и рефакторировать наши виджеты. (Разделяй и властвуй)

И было бы неплохо добавить const в наши виджеты. Позже мы увидим, почему это важно.

Избегайте обновления всех виджетов

Это обычная ошибка, которую многие совершают, когда начинают использовать Flutter и впервые сталкиваются с необходимостью обновить StatefulWidget с помощью setState.

Следующий пример - это представление, содержащее квадрат в центре и FloatingActionButton кнопку, при каждом нажатии на которую вызывается изменение цвета. О, а еще на странице также есть виджет с фоновым изображением. Кроме того, мы добавим несколько print операторов внутри build метода каждого виджета, чтобы посмотреть, как он работает.

class _MyHomePageState extends State<MyHomePage> {
  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,
    );
  }
}

После клика по кнопке в консоли мы увидим два вывода

flutter: building `MyHomePage`
flutter: building `BackgroundWidget`

Каждый раз, когда мы нажимаем кнопку, мы обновляем весь экран: Scaffold, BackgroundWidget и, наконец, то, что и хотели обновить, – квадрат-Container.

Как мы уже выяснили выше, перестраивать виджеты без необходимости – нехорошая практика. Обновляем только то, что нам нужно. Многие знают, что это можно сделать с помощью различных пакетов управления состоянием: flutter_bloc, mobx, provider и т. д. Но мало кто знает, что также это можно сделать с помощью классов, которые Flutter уже предлагает из коробки, без каких-либо сторонных библиотек.

Давайте, рассмотрим тот же пример, но обновленный с помощью ValueNotifier.

class _MyHomePageState extends State<MyHomePage> {
  final _colorNotifier = ValueNotifier<Color>(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).

Но есть еще один интересный виджет, который мы также можем использовать в этом случае, если мы хотим дополнительно разделить бизнес-логику и представление (но, возможно, добавив немного логики в него) и обрабатывать больше данных в вне виджета (в уведомителе).

Снова тот же пример, но уже с ChangeNotifier.

//------ 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<MyHomePage> {
  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/)).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment