5 Основных шаблонов Проектирования, которые должен знать каждый Flutter инженер

5 Essential Design Patterns Every Flutter Engineer Should Master

Шаблоны проектирования важны для разработчиков, так как они предлагают решения для распространённых проблем, встречающихся в разработке ПО. Для Flutter-разработчиков понимание и использование этих шаблонов может значительно повысить эффективность, масштабируемость и поддержку их приложений. В этой статье рассматриваются пять ключевых шаблонов проектирования, которые должен знать каждый Flutter-разработчик: Singleton, Provider, Builder, Observer и MVC (Model-View-Controller).

1. Паттерн Singleton

Паттерн Singleton гарантирует, что у класса будет только один экземпляр и обеспечивает глобальную точку доступа к нему. Это особенно полезно в Flutter для управления общими ресурсами, такими как настройки конфигурации, базы данных или сетевые соединения.

Во Flutter паттерн Singleton можно реализовать с помощью приватного конструктора и статического экземпляра. Вот пример:

class MySingleton {
  static final MySingleton _instance = MySingleton._internal();

  // Приватный конструктор
  MySingleton._internal();

  // Публичный фабричный метод для возврата одного и того же экземпляра
  factory MySingleton() {
    return _instance;
  }

  void someMethod() {
    print("Вызван метод экземпляра Singleton");
  }
}

При вызове MySingleton() вы всегда получаете один и тот же экземпляр, что гарантирует единообразный доступ к общим ресурсам.

Случаи использования

  • Подключение к базе данных: Обеспечивает единственный экземпляр подключения, чтобы избежать множественных подключений.
  • Настройки конфигурации: Поддерживает настройки для всего приложения.

2. Паттерн Provider

Паттерн Provider, являющийся основным в сообществе Flutter, упрощает управление состоянием, предоставляя простой способ доступа к данным и бизнес-логике из дерева виджетов. Он придерживается принципов инверсии управления (IoC) и внедрения зависимостей (DI).

Пакет Provider предлагает простой способ реализации этого паттерна. Вот базовый пример:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Пример использования Provider')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Вы нажали кнопку столько раз:',
              ),
              Consumer<Counter>(
                builder: (context, counter, child) => Text(
                  '${counter.count}',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => context.read<Counter>().increment(),
          tooltip: 'Увеличить',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Случаи использования

  • Управление состоянием: Управление и передача состояния в разных частях приложения.
  • Внедрение зависимостей: Легкое внедрение зависимостей в дерево виджетов.

3. Паттерн Builder

Паттерн Builder помогает пошагово создавать сложные объекты. Он отделяет конструирование объекта от его представления, что позволяет использовать один и тот же процесс создания для различных представлений.


Во Flutter виджет Builder является классическим примером и часто используется для создания виджетов, которые зависят от BuildContext, недоступного на момент создания виджета.

import 'package:flutter/material.dart';

class MyCustomWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Builder(
      builder: (BuildContext context) {
        return Text(
          'Привет, Flutter!',
          style: Theme.of(context).textTheme.headline4,
        );
      },
    );
  }
}

Случаи использования

  • Сложные элементы пользовательского интерфейса: Создание сложных виджетов, зависящих от контекста.
  • Условный рендеринг: Отображение виджетов в зависимости от условий выполнения.

4. Паттерн Observer

Паттерн Observer определяет зависимость «один ко многим» между объектами, так что при изменении состояния одного объекта все его зависимые объекты уведомляются и автоматически обновляются. Этот паттерн важен для реализации систем обработки событий.

В Flutter этот паттерн широко используется в решениях для управления состоянием, таких как ChangeNotifier и ValueNotifier.

import 'package:flutter/material.dart';

class Counter extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

void main() {
  final counter = Counter();

  counter.addListener(() {
    print('Счетчик изменился: ${counter.count}');
  });

  counter.increment();
}

Случаи использования

  • Обработка событий: Уведомление и обновление слушателей при изменении состояния.
  • Реактивное программирование: Реализация реактивных потоков данных.

5. Паттерн MVC (Model-View-Controller)

Паттерн MVC разделяет приложение на три взаимосвязанных компонента: Model (логика данных), View (логика интерфейса) и Controller (бизнес-логика). Такое разделение помогает управлять сложностью приложения путем декомпозиции кода на управляемые части.
Хотя Flutter не навязывает определенный архитектурный шаблон, MVC можно реализовать вручную. Вот простой пример:

Model: Определяет структуру данных и бизнес-логику.

class CounterModel {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
  }
}

View: Представляет пользовательский интерфейс.

import 'package:flutter/material.dart';

class CounterView extends StatelessWidget {
  final int count;
  final VoidCallback onIncrement;

  CounterView({required this.count, required this.onIncrement});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Пример MVC')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Вы нажали кнопку столько раз:'),
            Text(
              '$count',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: onIncrement,
        tooltip: 'Увеличить',
        child: Icon(Icons.add),
      ),
    );
  }
}

Controller: Связывает Model и View, обрабатывает ввод пользователя и обновляет Model.

import 'package:flutter/material.dart';

class CounterController {
  final CounterModel _model;

  CounterController(this._model);

  int get count => _model.count;

  void increment() {
    _model.increment();
  }
}

void main() {
  final model = CounterModel();
  final controller = CounterController(model);

  runApp(MyApp(controller: controller));
}

class MyApp extends StatelessWidget {
  final CounterController controller;

  MyApp({required this.controller});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CounterView(
        count: controller.count,
        onIncrement: controller.increment,
      ),
    );
  }
}

Случаи использования

  • Сложные приложения: Разделение задач для управления сложностью.
  • Масштабируемость: Легкое добавление новых функций без изменения других частей.

Заключение
Шаблоны проектирования — это важные инструменты в арсенале Flutter-разработчика. Освоив паттерны Singleton, Provider, Builder, Observer и MVC, вы сможете создавать надёжные, масштабируемые и поддерживаемые приложения. Эти паттерны не только решают распространённые проблемы, но и способствуют применению лучших практик и принципов проектирования в разработке ПО. Независимо от того, управляете ли вы состоянием, создаете ли сложные объекты, обрабатываете ли события или организуете архитектуру приложения, эти паттерны помогут вам найти эффективные и действенные решения.

Источник: https://medium.com/@ayhamxv12instagram/5-essential-design-patterns-every-flutter-engineer-should-master-09206ec077ee

Переход на Flutter 3.22 для Web

Flutter 3.22 представляет значительные улучшения для веб-разработки, сосредоточенные на настройке инициализации приложения. Если вы переходите с более старой версии или начинаете с нуля, это руководство проведет вас через весь процесс. Мы рассмотрим интеграцию платформы, инициализацию веб-приложения и настройку процесса инициализации с использованием файла flutter_bootstrap.js.

Инициализация веб-приложения Flutter
Интеграция Flutter в вашу веб-платформу включает настройку правильного процесса инициализации для обеспечения плавной работы и функциональности. Flutter 3.22 предоставляет более гибкий способ настройки этого процесса, что позволяет удовлетворить различные требования приложения.

Процесс инициализации веб-приложения
Процесс инициализации веб-приложения Flutter упрощен, но поддается настройке. Он вращается вокруг сценария flutter_bootstrap.js, который генерируется в процессе сборки и управляет последовательностью запуска приложения.

Сценарий flutter_bootstrap.js
Когда вы собираете свое веб-приложение Flutter с помощью команды flutter build web, в каталоге build/web создается сценарий flutter_bootstrap.js. Этот сценарий необходим для инициализации и запуска вашего приложения.

Чтобы включить этот сценарий в ваш index.html, добавьте следующий код:

<html>
  <body>
    <script src="flutter_bootstrap.js" async></script>
  </body>
</html>

Кроме того, вы можете встроить сценарий, вставив токен {{flutter_bootstrap_js}}:

<html>
  <body>
    <script>
      {{flutter_bootstrap_js}}
    </script>
  </body>
</html>

Настройка инициализации
По умолчанию команда flutter build web генерирует базовый сценарий flutter_bootstrap.js. Однако вам может понадобиться настроить эту инициализацию по различным причинам, таким как установка пользовательской конфигурации или изменение настроек сервис-воркера.

Для настройки процесса инициализации создайте собственный файл flutter_bootstrap.js в каталоге web вашего проекта. Этот пользовательский сценарий заменит стандартный, сгенерированный в процессе сборки.

Токены для настройки
В своем пользовательском файле flutter_bootstrap.js вы можете использовать несколько токенов:

  • {{flutter_js}}: Делает объект _flutter.loader доступным.
  • {{flutter_build_config}}: Устанавливает метаданные для FlutterLoader.
  • {{flutter_service_worker_version}}: Представляет версию сборки сервис-воркера.

Написание пользовательского flutter_bootstrap.js
Пользовательский сценарий flutter_bootstrap.js должен содержать три основных компонента:

  • Токен {{flutter_js}}.
  • Токен {{flutter_build_config}}.
  • Вызов _flutter.loader.load() для запуска приложения.

Базовый пример выглядит так:

{{flutter_js}}
{{flutter_build_config}}

_flutter.loader.load();

API _flutter.loader.load()
Функция _flutter.loader.load() может принимать необязательные аргументы для более индивидуализированной инициализации:

  • config: Объект конфигурации для вашего приложения.
  • onEntrypointLoaded: Функция обратного вызова, вызываемая при готовности движка к инициализации.
  • serviceWorkerSettings: Конфигурация для сервис-воркера.

Настройки конфигурации
Объект config может включать различные поля:

  • assetBase: Базовый URL для каталога с ресурсами.
  • canvasKitBaseUrl: Базовый URL для загрузки canvaskit.wasm.
  • canvasKitVariant: Указывает вариант CanvasKit (auto, full, или chromium).
  • canvasKitForceCpuOnly: Принудительное использование только CPU-рендеринга, если установлено значение true.
  • canvasKitMaximumSurfaces: Максимальное количество наложенных поверхностей.
  • debugShowSemanticNodes: Отображение дерева семантики на экране для отладки.
  • hostElement: HTML-элемент, в котором Flutter отрисовывает приложение.
  • renderer: Указывает веб-рендерер (canvaskit или html).

Настройка конфигурации Flutter на основе параметров URL-запроса
Вот пример, который настраивает конфигурацию на основе параметров URL-запроса:

{{flutter_js}}
{{flutter_build_config}}

const searchParams = new URLSearchParams(window.location.search);
const forceCanvaskit = searchParams.get('force_canvaskit') === 'true';
const userConfig = forceCanvaskit ? {'renderer': 'canvaskit'} : {};
_flutter.loader.load({
  config: userConfig,
  serviceWorkerSettings: {
    serviceWorkerVersion: {{flutter_service_worker_version}},
  },
});

Использование обратного вызова onEntrypointLoaded
Функция onEntrypointLoaded позволяет вам выполнять пользовательскую логику на различных этапах инициализации:

{{flutter_js}}
{{flutter_build_config}}

const loading = document.createElement('div');
document.body.appendChild(loading);
loading.textContent = "Загрузка точки входа...";
_flutter.loader.load({
  onEntrypointLoaded: async function(engineInitializer) {
    loading.textContent = "Инициализация движка...";
    const appRunner = await engineInitializer.initializeEngine();
    loading.textContent = "Запуск приложения...";
    await appRunner.runApp();
  }
});

Обновление старого проекта
Если вы обновляете проект с версии Flutter 3.21 или более ранней, выполните следующие шаги для создания нового файла index.html:

  1. Удалите существующие файлы из вашего каталога /web.
  2. Выполните следующую команду в каталоге вашего проекта:
   flutter create . --platforms=web

Заключение
Переход на Flutter 3.22 для веб-разработки предлагает улучшенную гибкость и возможность настройки инициализации приложений. Понимая и используя сценарий flutter_bootstrap.js, вы можете точно настроить процесс запуска в соответствии с конкретными потребностями вашего приложения. Независимо от того, настраиваете ли вы конфигурацию или обновляете старый проект, это руководство предоставляет подробные шаги для начала работы.

Источник: https://yawarosman.medium.com/switching-to-flutter-3-22-for-web-7e405e9b56f6

Как хранить ключи API во Flutter: —dart-define vs .env

Если ваше приложение на Flutter использует API стороннего сервиса, для которого требуется API-ключ, то где его нужно хранить?

Согласно различным источникам, для приложений в продакшене, которым необходима максимальная безопасность:

  • API-ключ должен храниться на вашем защищенном сервере (и никогда на клиенте).
  • Он никогда не должен передаваться обратно на клиент (чтобы предотвратить атаки типа «человек посередине»).
  • Клиент должен общаться только с вашим сервером, который выступает в качестве прокси для стороннего API, который вы планируете использовать.

Это связано с тем, что хранение API-ключей на клиенте небезопасно и может вызвать различные проблемы, если они будут скомпрометированы.

Однако не все ключи одинаковы: некоторые ключи могут быть доступны клиенту, тогда как другие должны храниться в секрете на сервере (документация Stripe хорошо объясняет это).

На самом деле, один из ответов на StackOverflow хорошо подытоживает:

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

Без сомнения, безопасность мобильного приложения — это обширная тема (на эту тему можно написать целые книги).

Поэтому давайте я дам контекст того, что мы здесь будем рассматривать. 👇

Контекст

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

И когда дело доходит до API-ключей и безопасности, вам следует избегать двух основных ошибок:

  1. Загрузка секретного ключа в систему контроля версий, делая его видимым для всех в Интернете 🤯.
  2. Отказ от шифрования API-ключей, что упрощает задачу злоумышленникам по обратной разработке вашего приложения и извлечению ключей 🛠.

В этом руководстве мы узнаем, как избежать этих ошибок.

Что рассматривается в этом руководстве

Мы рассмотрим три различных метода хранения API-ключей на клиенте (вашем приложении на Flutter), а также их компромиссы:

  1. Жесткое кодирование ключей в файле .dart.
  2. Передача ключей в качестве аргументов командной строки с использованием --dart-define или --dart-define-from-file.
  3. Загрузка ключей из файла .env с помощью пакета ENVied.

По ходу дела мы будем помнить о следующих правилах:

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

К концу руководства вы лучше поймете, как безопасно хранить API-ключи. Также я включу чеклист безопасности, который вы можете использовать в своих проектах на Flutter.

⚠️ Эти методы не являются непогрешимыми. Если у вас есть API-ключ, который вы не можете позволить себе потерять, храните его на сервере (а если вы используете Firebase, посмотрите мой гайд о защите API-ключей с помощью функций второго поколения в облаке). Безопасная клиент-серверная коммуникация включает в себя множество аспектов, выходящих за рамки данной статьи (см. ссылки внизу для получения дополнительной информации).

Готовы? Давайте начнем! 👇

1. Жесткое кодирование ключа в файле Dart

Простой и эффективный способ сохранить наш API-ключ — сохранить его в Dart-файле, например, так:

// api_key.dart
final tmdbApiKey = 'a1b2c33d4e5f6g7h8i9jakblc';

Чтобы убедиться, что ключ не добавлен в git, мы можем добавить файл .gitignore в ту же папку с таким содержимым:

# Скрыть ключ от контроля версий
api_key.dart

Если мы сделали все правильно, файл должен выглядеть так в проводнике:

Пример кода, использующего пакет dio для получения данных из API TMDB:

import 'api_key.dart'; // импортируем здесь
import 'package:dio/dio.dart';

Future<TMDBMoviesResponse> fetchMovies() async {
  final url = Uri(
    scheme: 'https',
    host: 'api.themoviedb.org',
    path: '3/movie/now_playing',
    queryParameters: {
      'api_key': tmdbApiKey, // читаем здесь
      'include_adult': 'false',
      'page': '$page',
    },
  ).toString();
  final response = await Dio().get(url);
  return TMDBMoviesResponse.fromJson(response.data);
}

Этот подход очень прост, но у него есть несколько недостатков:

  • Трудно управлять различными API-ключами для разных сред/конфигураций.
  • Ключ хранится в виде открытого текста в файле api_key.dart, что облегчает работу злоумышленнику.

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

Давайте рассмотрим второй вариант. 👇

2. Передача ключа с помощью —dart-define

Альтернативный подход — передать API-ключ с помощью флага --dart-define во время компиляции.

Это означает, что мы можем запустить приложение вот так:

flutter run --dart-define TMDB_KEY=a1b2c33d4e5f6g7h8i9jakblc

Затем в коде Dart можно сделать:

const tmdbApiKey = String.fromEnvironment('TMDB_KEY');
if (tmdbApiKey.isEmpty) {
  throw AssertionError('TMDB_KEY is not set');
}
// TODO: использовать API-ключ

Основное преимущество использования --dart-define заключается в том, что мы больше не храним чувствительные ключи в исходном коде.

Однако при компиляции нашего приложения ключи все равно будут включены в бинарный файл релиза. Чтобы уменьшить риски, мы можем зашифровать наш Dart-код при создании сборки (подробнее об этом ниже).

Также становится неудобно запускать приложение, если у нас много ключей:

flutter run \
  --dart-define TMDB_KEY=a1b2c33d4e5f6g7h8i9jakblc \
  --dart-define STRIPE_PUBLISHABLE_KEY=pk_test_aposdjpa309u2n230ibt23908g \
  --dart-define SENTRY_KEY=https://aoifhboisd934y2fhfe@a093qhq4.ingest.sentry.io/2130923

Есть ли лучший способ?

Новинка в Flutter 3.7: использование —dart-define-from-file

С Flutter 3.7 мы можем хранить все API-ключи в JSON-файле и передавать его в новый флаг --dart-define-from-file из командной строки.

Таким образом, мы можем сделать:

flutter run --dart-define-from-file=api-keys.json

Затем мы можем добавить все ключи в файл api-keys.json (который должен быть .gitignored):

{
  "TMDB_KEY": "a1b2c33d4e5f6g7h8i9jakblc",
  "STRIPE_PUBLISHABLE_KEY": "pk_test_aposdjpa309u2n230ibt23908g",
  "SENTRY_KEY": "https://aoifhboisd934y2fhfe@a093qhq4.ingest.sentry.io/2130923"
}

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

Если вы используете IntelliJ или Android Studio, вы можете использовать конфигурации запуска/отладки для достижения того же результата.

3. Загрузка ключа из .env файла

.env — популярный формат файлов, который был создан для того, чтобы дать разработчикам одно безопасное место для хранения конфиденциальных данных приложений, таких как API-ключи.

Чтобы использовать это с Flutter, мы можем добавить .env файл в корень проекта.

Пример .env файла:

TMDB_KEY=a1b2c33d4e5f6g7h8i9jakblc
# добавьте другие ключи при необходимости

Заключение

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

Источник: https://codewithandrea.com/articles/flutter-api-keys-dart-define-env-files/

Отложенная загрузка во Flutter: преимущества, недостатки и реализация.

Отложенная загрузка в контексте Flutter — это техника, при которой определённые части приложения загружаются или выполняются только при необходимости. Это особенно полезно для уменьшения времени начальной загрузки приложения и снижения общего объема использования памяти.

Зачем существует отложенная загрузка?

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

Преимущества отложенной загрузки

  • Улучшенное время начальной загрузки: Отложенная загрузка сокращает время начальной загрузки приложения, так как загружается меньше ресурсов сразу.
  • Оптимизированное использование памяти: Помогает более эффективно управлять памятью, загружая ресурсы только при их необходимости.
  • Улучшенное взаимодействие с пользователем: Пользователи получают более быстрое и отзывчивое приложение, особенно при запуске.
  • Модульная структура кода: Поощряет более модульную структуру кода, так как разработчики разделяют функции и компоненты в зависимости от их использования.

Недостатки отложенной загрузки

  • Сложность: Реализация отложенной загрузки может добавить сложности в кодовую базу.
  • Потенциальные задержки: При неправильном управлении может привести к заметным задержкам при загрузке отложенных ресурсов во время выполнения.
  • Управление зависимостями: Требует тщательного управления зависимостями и понимания того, как они загружаются.

Реализация отложенной загрузки в Flutter

Отложенная загрузка в Flutter обычно реализуется с помощью ключевого слова deferred as в операторе импорта Dart. Это позволяет загружать библиотеки лениво.

Основная реализация

Предположим, у вас есть проект Flutter с несколькими библиотеками, и вы хотите отложить загрузку конкретной библиотеки:

  1. Определение отложенной библиотеки:
// В отдельном файле, например, heavy_library.dart
class HeavyLibrary {
  void performHeavyTask() {
    // Реализация тяжелой задачи
  }
}
  1. Импорт библиотеки с отложенной загрузкой:
import 'heavy_library.dart' deferred as heavyLib;

Future<void> loadHeavyLibrary() async {
  await heavyLib.loadLibrary();
  heavyLib.HeavyLibrary().performHeavyTask();
}

Сценарии использования

  • Большие ассеты: Отложенная загрузка для изображений высокого разрешения или видео, которые не требуются немедленно.
  • Модули функций: В приложениях с несколькими функциями загружайте функции по требованию. Например, в приложении для электронной коммерции можно отложить загрузку модуля отзывов о товаре.
  • Код, специфичный для платформы: Откладывайте загрузку кода, специфичного для определённой платформы, который не требуется на других платформах.

Управление зависимостями

При отложенной загрузке библиотеки убедитесь, что все зависимости этой библиотеки также доступны во время её загрузки. Это может потребовать структурирования вашего кода и зависимостей, чтобы избежать ошибок времени выполнения.

Лучшие практики

  • Предзагрузка незадолго до необходимости: Начинайте загрузку отложенных ресурсов немного раньше, чтобы минимизировать задержку, воспринимаемую пользователем.
  • Тщательно тестируйте: Обеспечьте тщательное тестирование, чтобы выявить любые проблемы с загрузкой из-за отложенной загрузки.
  • Используйте с осторожностью: Откладывайте загрузку только тех ресурсов, которые действительно выигрывают от этого. Чрезмерное использование отложенной загрузки может привести к фрагментации и сложному в управлении коду.

Источник: https://yawarosman.medium.com/understanding-deferred-loading-in-flutter-benefits-drawbacks-and-implementation-07d15e91c65e

Как использовать flavor во Flutter и почему это так важно

flautter flavors

В современной разработке приложений управление различными версиями приложения необходимо для их целевой направленности на разные среды, группы пользователей или рыночные сегменты. Эти версии, называемые «флейворами» (flavors), позволяют разработчикам легко настраивать поведение и внешний вид приложения, поддерживать его, включать или отключать функции для различных версий и конфигураций без необходимости поддерживать несколько кодовых баз.

Флейворы позволяют эффективно управлять различными конфигурациями для таких сред, как разработка, промежуточное тестирование (staging) и производство, гарантируя, что каждая среда имеет свои уникальные настройки, API-эндпоинты и учетные данные. Это снижает риск ошибок и повышает эффективность разработки и тестирования.

Например, версия вашего приложения для разработки может использовать dev.api.myapp.com, тогда как производственная версия использует api.myapp.com. Вместо того чтобы жестко прописывать эти значения и вручную собирать приложение для каждой среды, вы можете использовать флейворы, чтобы задавать эти значения в качестве конфигураций во время сборки, ускоряя процесс разработки.

Использование флейворов во Flutter с помощью flutter_flavorizr

flutter_flavorizr упрощает создание и управление множественными флейворами в приложениях на Flutter. Он автоматизирует процесс конфигурации как для Android, так и для iOS, помогая разработчикам поддерживать единообразие конфигурации и минимизировать время, затрачиваемое на ручную настройку. Как это сделать? Итак…

Шаг 1 — Установка flutter_flavorizr

Добавьте flutter_flavorizr в ваш файл pubspec.yaml в разделе dev_dependencies.

Шаг 2 — Настройка флейворов

Создайте новый файл с именем flavorizr.yaml в корне проекта и определите имена флейворов, затем запустите команду

flutter pub run flutter_flavorizr

для генерации необходимых файлов и конфигураций.

flavors:
  development:
    app:
      name: "MyApp DEV"
    android:
      applicationId: "com.example.myapp.dev"
    ios:
      bundleId: "com.example.myapp.dev"
    macos:
      bundleId: "com.example.myapp.dev"
  production:
    app:
      name: "MyApp"
    android:
      applicationId: "com.example.myapp"
    ios:
      bundleId: "com.example.myapp"
    macos:
      bundleId: "com.example.myapp"

Эта команда создаёт соответствующие файлы и каталоги для каждого флейвора, включая отдельные файлы main.dart и конфигурационные файлы.

Шаг 3 — Доступ к конфигурациям флейворов в коде

Используйте класс FlavorConfig для доступа к конфигурациям, специфичным для каждого флейвора, в вашем коде на Flutter.

import 'package: flutter/material.dart';
import 'package:flutter_flavorizr/flutter_flavorizr.dart';

void main {
    runApp (MyApp) ;
}

class MyApp extends StatelessWidget {
  @override
  Widget build (BuildContext context) {
    final flavor = FlavorConfig.instance.name;

    return MaterialApp( 
      title: 'Flutter Flavors - $flavor',
      home: HomeScreen,
    );
  }
}

Шаг 4 — Сборка и запуск флейворов

Используйте следующие команды для сборки и запуска конкретного флейвора:

flutter run --flavor development -t lib/main_development.dart
flutter run --flavor production -t lib/main_production.dart

И всё готово!

Почему это важно?

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

  • Упрощение процесса разработки: Флейворы позволяют поддерживать одну кодовую базу, адаптируя приложение под разные среды (разработка, промежуточное тестирование, производство) или рыночные сегменты (бесплатная, премиум версия). Это уменьшает сложность и трудозатраты, связанные с управлением отдельными кодовыми базами для каждой версии вашего приложения.
  • Повышение качества тестирования и контроля качества (QA): Флейворы позволяют создавать специфические конфигурации для различных сценариев тестирования. Вы можете иметь флейвор, который имитирует производственную среду, но использует промежуточный API. Это гарантирует, что ваша команда QA сможет тестировать приложение в условиях, максимально приближенных к реальной производственной среде.
  • Упрощение кастомизации: Флейворы позволяют легко включать или отключать функции в зависимости от целевой аудитории или рынка. Например, можно иметь флейвор с дополнительными функциями для премиум-пользователей, сохраняя упрощённую версию для бесплатных пользователей.
  • Упрощенная развертка и конфигурация: Управление различными конфигурациями, такими как API-эндпоинты, настройки темы или учетные данные сторонних сервисов, становится более эффективным с флейворами. Определяя эти настройки в централизованном конфигурационном файле, вы снижаете риск ошибок и обеспечиваете согласованность между средами.
  • Ускорение разработки: Инструменты, такие как flutter_flavorizr, значительно сокращают ручные усилия, необходимые для настройки и управления несколькими флейворами. Это позволяет разработчикам сосредоточиться больше на кодировании.
  • Подготовка к будущему: По мере роста приложения появляется необходимость поддержки дополнительных сред или рыночных сегментов. Использование флейворов с самого начала делает архитектуру вашего приложения более масштабируемой и адаптируемой в долгосрочной перспективе.

Спасибо за чтение! Теперь вы успешно настроили флейворы в своём проекте Flutter всего за несколько простых шагов. Для более глубоких вариантов настройки вы также можете адаптировать флейворы под свои сценарии. Удачного кодинга!

Источник: https://bilalrehman08.medium.com/how-to-use-flavor-in-flutter-and-why-its-important-for-you-0f53c71b59ff

Реализация изолятов: spawn() и run()

В сегодняшней статье мы будем реализовывать изоляты. Но перед реализацией важно понять, что такое изоляты, чем они отличаются от async-await и когда их стоит использовать. Я уже подробно объяснил все эти моменты в моем предыдущем блоге, который вы можете прочитать здесь.

Также я создал репозиторий на GitHub с кодом этого проекта, с которым вы можете ознакомиться здесь.

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

Прежде всего, важно помнить, что функция для изолята должна быть объявлена вне всех классов. Это одна из самых распространенных ошибок, которую легко забыть и сложно отладить, так что обязательно обратите на это внимание.

Короткое напоминание о ReceivePort. ReceivePort — это двусторонний канал связи, где одна сторона может отправлять сообщения, а другая — их принимать и отвечать на них. Это обеспечивает двунаправленную связь между разными частями программы или между разными программами. Мы будем использовать его в нашем коде.

В этой статье мы будем вычислять сумму первых 1 000 000 чисел — это довольно ресурсоемкая задача. Если вы попробуете сделать это напрямую или с использованием async-await, то заметите, что интерфейс приложения начинает «лагать». Здесь же мы рассмотрим две разные реализации изолятов. Вы можете выбрать ту, которая лучше подходит под ваши требования.

Метод Isolate.run() [Короткоживущие изоляты]

Самый простой способ перенести процесс в изолят в Flutter — использовать метод Isolate.run. Этот метод создает изолят, передает ему функцию обратного вызова для начала вычислений, возвращает значение вычислений и затем завершает изолят после окончания вычислений. Все это происходит параллельно с основным изолятом и не блокирует его.

Метод Isolate.run требует одного аргумента — функции обратного вызова, которая будет запущена в новом изоляте. Подпись функции обратного вызова должна содержать ровно один обязательный неименованный аргумент. Когда вычисление завершается, функция возвращает значение обратно в основной изолят и завершает работу созданного изолята.

Пример: мы вычисляем сумму первых 10 000 000 чисел и возвращаем её. Не забудьте, что функция должна быть объявлена вне всех классов.

Future<double> complexTask3() async {
  const double iteration = 1000000000;
  final double result = await Isolate.run<double>(() {
    double total = 0.0;
    for (var i = 0; i < iteration; i++) {
      total += i;
    }
    return total;
  });
  return result;
}

Метод spawn() [Долгоживущие изоляты]

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

Когда вы используете метод Isolate.run, новый изолят завершает работу сразу после возвращения единственного сообщения в основной изолят. Иногда вам нужны изоляты, которые будут работать долго и смогут передавать друг другу несколько сообщений с течением времени. В Dart вы можете достичь этого с помощью API изолятов и портов. Такие долгоживущие изоляты называют фоновые рабочие процессы (background workers).

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

Ниже приведен код задачи, которую мы будем выполнять. И не забудьте поместить эту функцию вне всех классов, чтобы изолят смог ее запустить.

complexTask(SendPort sendPort) {
  var total = 0.0;
  for (var i = 0; i < 1000000000; i++) {
    total += i;
  }
  sendPort.send(total);
}

Здесь мы будем использовать функцию complexTask при нажатии на кнопку.

ElevatedButton(
  onPressed: () async {
    final receivePort = ReceivePort();
    await Isolate.spawn(complexTask, receivePort.sendPort);
    receivePort.listen((total) {
      debugPrint('Result 2: $total');
    });
  },
  child: const Text('Task 1'),
)

Теперь, каждый раз, когда вы нажимаете на эту кнопку, будет выполняться complexTask() без блокировки каких-либо операций интерфейса, и когда функция завершит свою задачу в другом изоляте, она вернет результат в основной изолят с использованием receivePort, так как вы слушаете его с помощью listen().

Предположим, вы хотите передать некоторые данные в изолят, что тогда? Это тоже можно сделать.

В этом примере предположим, что мы хотим передать конечное значение для цикла for. Тогда код будет выглядеть следующим образом:

ElevatedButton(
  onPressed: () async {
    final receivePort = ReceivePort();
    await Isolate.spawn(complexTask3, (iteration: 1000000000, sendPort: receivePort.sendPort));
    receivePort.listen((total) {
      debugPrint('Result 2: $total');
    });
  },
  child: const Text('Task 2'),
),
complexTask3(({int iteration, SendPort sendPort}) data) {
  var total = 0.0;
  for (var i = 0; i < data.iteration; i++) {
    total += i;
  }
  data.sendPort.send(total);
}

Таким образом, вы можете передавать столько переменных данных, сколько вам нужно.

У изолятов есть некоторые ограничения, о которых можно прочитать здесь.

Надеюсь, эта статья помогла вам лучше понять тему. Подписывайтесь на меня, чтобы не пропустить новые статьи.

Источник: https://medium.com/codex/isolate-implementation-explained-spawn-and-run-2cd8462c91ba


Flutter: async или isolate. Параллелизм

Многие не понимают разницу между async и isolate (параллелизмом и конкурентностью). В этой статье мы разберём эти понятия, а в следующей покажу, как реализовать изоляты в Dart.

ASYNC
Сначала разберём async, так как большинство из нас уже использовало этот подход хотя бы раз.

void readData() async {
  var url = "www.example.com";
  var content = await http.get(url); // Асинхронный вызов
  var data = parsingData(content); // Здесь будем использовать изолят, так как операция тяжелая
}

Этот код выполняется в обычном режиме до вызова загрузки файла, после чего программа ждёт, пока файл будет загружен. Пока она ждёт, другие части программы могут продолжать работать. Однако, обратите внимание, что в любой момент времени работает только одна часть приложения — ведь у нас всего один поток, один процессор. Асинхронность не делает ваше приложение многопоточным автоматически. После загрузки файла метод продолжит выполнение. Это и есть async. Когда задача (future) завершается, выполнение продолжается с следующей строки кода. В большинстве случаев async достаточно.

В примере выше наш код будет ждать на второй строке (await http.get(url)) до тех пор, пока данные не будут получены с интернета, и после этого мы начнем их обработку.

Теперь представьте, что файл очень большой, и его обработка требует много ресурсов процессора. Это может вызвать задержки в работе приложения? Многие могут подумать, что нет, так как вычисления происходят в асинхронном методе. Но это не так. Почему? Даже если метод асинхронный, в нём всё равно может происходить много синхронных операций. Эта обработка данных (parsingData) выполняется синхронно. Она занимает процессор, и другие части вашего приложения не смогут выполняться. Интерфейс пользователя не обновится, пока тяжелая работа не будет завершена, и пользователь увидит задержки и «подтормаживания».

Решение? Многопоточность с помощью изолятов!

ИЗОЛЯТЫ
Dart позволяет выполнять код параллельно с помощью изолятов. Изолят запускает другой процесс Dart, фактически, на другом потоке, на другом процессоре. Эти два изолята полностью изолированы друг от друга. Один не блокирует другой. Таким образом, ваше приложение может выполнять тяжелые вычисления и одновременно оставаться отзывчивым для пользователя. В этой статье я не буду углубляться в реализацию изолятов, так как это немного сложнее, и цель статьи — разъяснить разницу между async и изолятами.

Изолят можно представить как отдельное пространство на машине со своим собственным участком памяти и одним потоком, выполняющим события. В отличие от языков, таких как C++, где несколько потоков могут разделять одну память и выполнять произвольный код, в Dart каждый поток работает в своем собственном изоляте, с собственной памятью и обрабатывает только события.

Когда использовать изоляты?

Большинство приложений Dart работают в одном изоляте, но можно использовать больше, если это необходимо. Например, если вам нужно выполнить вычисление, которое может занять слишком много времени в основном изоляте и привести к «подтормаживанию», вы можете использовать Isolate.spawn() или функцию compute в Flutter, чтобы создать отдельный изолят для выполнения тяжелых вычислений, оставляя основной изолят свободным для перерисовки интерфейса.

Этот новый изолят получит свой собственный цикл событий и свою память, к которой родительский изолят не имеет доступа. Изоляты называются так потому, что они изолированы друг от друга. Они могут взаимодействовать только посредством передачи сообщений. Один изолят отправляет сообщение другому, и тот обрабатывает его в своем цикле событий.

Подведем итоги:
Async позволяет ждать выполнения других операций без блокировки текущего потока. Async не создаёт отдельный поток; Dart просто переключается между разными частями программы по мере необходимости. Но если необходимо выполнение параллельных задач, вам нужны изоляты. Они сложнее в настройке, но позволяют решать задачи, требующие интенсивных вычислений, без задержек в работе пользовательского интерфейса.

Изоляты играют важную роль в обеспечении конкурентной обработки. Выполнение ресурсоемких операций в основном потоке может негативно сказаться на производительности приложения. Использование изолятов позволяет значительно повысить производительность и эффективность приложений Flutter.

Источник: https://medium.com/@AryanBeast/async-vs-isolate-in-flutter-parallelism-in-flutter-ae3954fb5d4c

Освоение циклов в Dart: while и for

Циклы — это фундаментальные конструкции в программировании, которые позволяют выполнять блок кода многократно, основываясь на условии или наборе значений. В Dart основными конструкциями для организации циклов являются while и for. В этой статье мы подробно рассмотрим эти конструкции и дадим вам знания, необходимые для их эффективного использования в программах на Dart.

Циклы while

Цикл while выполняет блок кода до тех пор, пока определённое условие истинно. Это полезно, когда количество итераций заранее неизвестно и зависит от условия.

Синтаксис

while (condition) {
// Код выполняется пока condition истинно
}

Пример использования

void main() {
    int counter = 0;
    
    while (counter < 5) {
      print( 'Counter: $counter');
      counter++;
  }
}

В этом примере программа выводит значение переменной counter от 0 до 4. Цикл продолжает выполняться, пока counter меньше 5.

Бесконечный цикл

Будьте осторожны с циклами while, чтобы избежать создания бесконечных циклов, где условие никогда не становится ложным.

void main() {
    int counter = 0;
    
    while (true) {
      print( 'Counter: $counter');
      counter++;
      if (counter >= 5) {
       break;
      }
   }
}

В этом примере цикл while (true) управляется оператором break, который завершает выполнение цикла, когда counter достигает 5.

Циклы do-while

Цикл do-while похож на while, но гарантирует, что блок кода будет выполнен хотя бы один раз, так как условие проверяется после выполнения блока.

Синтаксис

do {
// Исполняемый код
} while (condition);
void main() {
  int counter = 0;

  do {
    print( 'Counter: $counter');
    counter++;
  } while (counter < 5);
}

В этом примере программа выводит значение переменной counter от 0 до 4. Цикл гарантирует, что блок кода выполнится хотя бы один раз перед проверкой условия.

Циклы for

Цикл for обычно используется, когда количество итераций известно заранее. Он состоит из инициализации, условия и операции инкремента/декремента.

for (initialization; condition; increment/decrement) {
  // Исполняемый код
}
void main () {
  for (int i = 0; i < 5; i++) {
    print('i: $i');
  }
}

В этом примере программа выводит значение переменной i от 0 до 4. Цикл инициализирует i значением 0, проверяет условие i < 5 и увеличивает i на 1 в каждой итерации.

Вложенные циклы for

Вы можете вкладывать циклы for друг в друга для обработки более сложных итераций, например, для обхода матрицы.

void main) {
  for (int i = 0; i < 3; i++) {
    for(intj=0; j<3; j++) {
      print('i: $i, j: $j');
    }
  }
}

В этом примере внешний цикл выполняется 3 раза, и для каждой итерации внешнего цикла внутренний цикл выполняется 3 раза, что в сумме дает 9 итераций и выводит значения i и j.

Циклы for-in

Цикл for-in используется для обхода элементов коллекции, такой как список или множество.

void main ( ) {
  List<String> fruits = ['Apple', 'Banana', 'Cherry'];
    
  for (var fruit in fruits) {
    print(fruit);
  }
}

В этом примере программа перебирает список fruits и выводит в консоль каждый фрукт.

Метод forEach

Коллекции в Dart также предоставляют метод forEach, который является функциональным подходом для обхода элементов.

void main ( ) {
  List<String> fruits = ['Apple', 'Banana', 'Cherry'];
  fruits. forEach((fruit) => print(fruit));
}

В этом примере метод forEach перебирает список fruits и выполняет заданную функцию для каждого элемента.

Лучшие практики при использовании циклов

  1. Избегайте бесконечных циклов: Убедитесь, что в ваших циклах есть условие завершения, чтобы предотвратить бесконечные циклы.
  2. Используйте описательные имена переменных: Применяйте понятные имена для переменных-счётчиков и элементов, чтобы улучшить читаемость кода.
  3. Сохраняйте циклы простыми: Избегайте сложной логики внутри циклов. При необходимости рефакторьте код в функции.
  4. Используйте break и continue разумно: Используйте break для досрочного выхода из цикла и continue для пропуска текущей итерации. Применяйте их с осторожностью, чтобы избежать путаницы в коде.
  5. Предпочитайте for-in и forEach для коллекций: При обходе коллекций используйте for-in или forEach для более чистого и читаемого кода.

Заключение

Понимание и эффективное использование циклов, таких как while, do-while, for и for-in, имеет решающее значение для управления потоком выполнения программ на Dart. Выбирая подходящую конструкцию цикла для ваших нужд и следуя лучшим практикам, вы сможете писать чистый, эффективный и удобный для поддержки код. Независимо от того, обрабатываете ли вы простые итерации или сложные вложенные циклы, Dart предоставляет все необходимые инструменты для управления повторяющимися задачами в ваших программах.

Источник: https://azimdev.medium.com/mastering-loops-in-dart-while-and-for-4d5e48aa9a03

Flutter: Unable to load asset: /assets/images/logo.png

Обожаю flutter. Нет, правда это лучший, на мой скромный взгляд, мультиплатформенный фреймворк. С помощью которого можно написать нативное приложение с гуём под любую платформу. И вот пилю я значится как всегда приложение и ничего не предвещало беды. На одном из этапов потребовалось, значится, добавить картиночку локально в приложение.

Прописал я как положено изображение в файле pubspec.yaml со всеми yaml подобающими отступами в виде табов:

flutter:
  assets:
    - assets/images/logo.png
    - assets/loginPage.svg
  uses-material-design: true

а на страничке непосредственно пытаюсь вот так вот вывести картинку:

Image.asset(
              '/assets/images/logo.png',
              height: 148,
            ),

и понять не понимаю какого буя приложение постоянно в эксепшн выпадает с такой вот ошибкой:

════════ Exception caught by image resource service ════════════════════════════
The following assertion was thrown resolving an image codec:
Unable to load asset: /assets/img/logo.png

When the exception was thrown, this was the stack
#0      PlatformAssetBundle.load
package:flutter/…/services/asset_bundle.dart:224
<asynchronous suspension>
#1      AssetBundleImageProvider._loadAsync
package:flutter/…/painting/image_provider.dart:675
<asynchronous suspension>
Image provider: AssetImage(bundle: null, name: "/assets/img/logo.png")
Image key: AssetBundleImageKey(bundle: PlatformAssetBundle#9dc2c(), name: "/assets/img/logo.png", scale: 1.0)
════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by image resource service ════════════════════════════
Unable to load asset: /assets/images/logo.png
════════════════════════════════════════════════════════════════════════════════
Flutter: Unable to load asset: /assets/images/logo.png

Час мучаний и гуглопоиска ни к чему хорошему не привёл. Я уже было начал подумывать, что всему виной обновление Flutter до последней, на текущий момент, версии:

$ flutter --version
Flutter 2.5.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision ffb2ecea52 (13 days ago) • 2021-09-17 15:26:33 -0400
Engine • revision b3af521a05
Tools • Dart 2.14.2

Оказалось всё так глупо и прозаично, что решил в назидание таким же затупкам как и я оставить сей топик-высер. Итак внимание, барабанная дробь…

Оказывается не надо в виджете перед assets ставить слеш. <FACEPALM>. Да-да, я там фиганул слеш и потратил уйму времени на поиски решения, посыпаю голову пеплом, не будь таким как я. Делай так:

Image.asset(
              'assets/images/logo.png',
              height: 148,
            ),

Flutter или Xamarin: сравнение инструментов кросс-платформенной мобильной разработки

flutter против xamarin

За последнее десятилетие мы наблюдали огромный рост мобильной индустрии, особенно в отношении разработки приложений. По данным Statista Reports, в мире тогда насчитывалось более 2 миллиардов пользователей смартфонов, а к концу 2022 года их число увеличится до более чем 5 миллиардов. Из этих смартфонов почти 100 процентов работают на трех популярных платформах: Android от Google, iOS от Apple и Windows Mobile, разработанной Microsoft. Долгое время разработчики мобильных приложений зависели от инструментов, привязанных к одной платформе. Например, Kotlin и Java в основном используются для разработки нативных мобильных приложений под Android, в то время как разработчики iOS используют Objective-C, а с недавних пор и Swift.

Недостатки нативной разработки  мобильных приложений

Традиционная разработка мобильных приложений была медленной и дорогой, поскольку компаниям приходилось разрабатывать отдельные приложения для каждой платформы, нанимать разные команды разработчиков для своего инструмента разработки. Сегодня у нас появились кросс-платформенные средства мобильной разработки, которые помогают компаниям сократить время на разработку, затраты на обслуживание, привлечь больше пользователей. Упрощённо: кросс-платформенная мобильная разработка это создание мобильных приложений, которые могут работать на нескольких платформах. Сегодня разработчикам доступно несколько кросс-платформенных средств, в том числе Intel XDK, Xamarin, Cordova и Flutter. В этой статье мы сфокусируемся на двух — Xamarin и Flutter — путем сравнения характеристик, сильных сторон и недостатков каждой, чтобы помочь разработчикам выбрать лучший инструмент.

Если вы только начинаете разрабатывать на Flutter, обратите внимание на статью Создание первого приложения Flutter и продолжение.

Обзор фреймворков Xamarin и Flutter 

Xamarin от Microsoft, возможно, является одной из ведущих технологий кросс-платформенной разработки с открытым исходным кодом. Он использует язык C# для разработки мобильных приложений под Android, iOS и Windows Mobile. Xamarin позволяет разработчикам получать доступ к нативным API под Android и iOS, предоставляет общую кодовую базу на C#, а также тестирование приложений на разных устройствах с Xamarin Testing Cloud. Xamarin был создан в 2011 году разработчиками Mono, которые использовали CLI (Common Language Infrastructure) и Common Language Specification, также известную как Microsoft .NET. Microsoft приобрела Xamarin в 2016 году, а затем сделала Xamarin SDK платформой с открытым исходным кодом, которая стала неотъемлемой частью Xamarin Visual Studio IDE. Чтобы использовать весь потенциал Xamarin, разработчикам понадобятся знания iOS и Android, кроме, собственно, языка C#.

Flutter также является бесплатным кросс-платформенным инструментом с открытым исходным кодом, разработанным Google, и позволяет разработчикам создавать высокопроизводительные мобильные приложения для Android и iOS. Фреймворк использует язык программирования Dart от Google и легкий движок C++. Подобно Xamarin, он позволяет использовать единую кодовую базу для разных платформ. Фреймворк предлагает API и SDK для 2D-рендеринга, моделирования, жестов и рисования, а также позволяет использовать существующий код Swift, Objective C и Java. Он поставляется с виджетами Material Design, также продуктом Google.

Сравнение Flutter и Xamarin

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

В настоящее время Xamarin более популярен среди разработчиков: 7,2 процента опрошенных в Stack Overflow 2018 заявили, что они используют Xamarin, а Microsoft утверждает, что у них 1,4 миллиона инженеров Xamarin, в то время как Flutter не попал в список вообще. Сообщество Flutter еще недостаточно велико. Тем не менее, некоторые инженеры признают Flutter в качестве подающей надежды альтернативы Xamarin.

Ниже мы рассмотрим характеристики и основные функции обоих фреймворков.

Flutter или Xamarin: сравнение инструментов кросс-платформенной мобильной разработки

Переносимость

Flutter нацелен на разработку под Android и iOS, в то время как  Xamarin поддерживает разработку под Android, iOS, Windows (Windows 10 [UWP], устаревшие Windows-приложения [WPF]), а также MacOS. Бóльшая экосистема дает Xamarin преимущество перед Flutter. Но тот факт, что приложения Flutter не портируются на мобильную платформу Windows, не является недостатком, как утверждает недавний опрос Statista, так как на текущий момент около 98,5% смартфонов работают на Android или iOS. Но Xamarin в этом плане может стать лучшим выбором, если вы всё же хотите разрабатывать приложения и под Windows Mobile.

Flutter несовместим со старыми устройствами с 32-разрядными ОС. Поэтому, если вы планируете разрабатывать под старые телефоны, такие как, например, iPhone 5, Xamarin будет лучшим выбором.

Языки программирования

Xamarin использует C#, который популярен и широко используется разработчиками. Если у вас уже есть навыки в C# и .NET, вы можете сразу использовать Xamarin. И вы можете повторно использовать до 96 процентов своего кода на C# в Xamarin, если вы работаете с Xamarin.Forms.

Flutter использует язык Dart, который является относительно новым и непопулярным. Однако, если у вас есть опыт работы на языках ООП, таких как Java, JavaScript и C++, изучение Dart не будет проблемой, так как использует тот же подход и парадигмы.

Поддержка сообщества

Xamarin существует немного дольше, поэтому имеет большое комьюнити, члены которого имеют опыт и готовы делиться своими знаниями с другими разработчиками. На форумах Xamarin многие разработчики помогают друг другу, они делятся своим кодом и опытом. Платформа поддерживается Microsoft, что является большим плюсом, когда дело касается решения каких-либо проблем. Microsoft предоставляет достойную и актуальную документацию для всех своих продуктов, связанных с Xamarin и .NET.

Хотя Flutter и поддерживается Google, он относительно новый и не имеет пока широкой поддержки сообщества. Платформа также довольно молода, официального релиза пока не было, а это значит, что разработчикам еще предстоит обнаружить основные недостатки и сильные стороны платформы.

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

Доступ к нативному уровню ОС

Код Dart во Flutter компилируется в нативный код, используя компиляцию AoT (Ahead of Time), но по-прежнему требует Dart VM (виртуальная машина). Причина компиляции AoT заключается в том, что платформа iOS не поддерживает JIT или динамическую компиляцию. Flutter также позволяет создавать пользовательские плагины, которые поддерживают код, специфичный для платформы.

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

Flutter или Xamarin: сравнение инструментов кросс-платформенной мобильной разработки

В Xamarin код C# скомпилирован в машинный код, а затем упакован в .app. Генератор кода mono использует компиляцию JIT для приложений Xamarin.Android и компиляцию AoT для приложений iOS для компиляции промежуточного машинного кода (также известного как управляемый код) в нативный код платформы.

Xamarin использует .NET API и библиотеки, специфичные для платформы, посредством привязок (bindings) для доступа к нативным функциям.

Flutter или Xamarin: сравнение инструментов кросс-платформенной мобильной разработки

Дизайн UI

Использование в Xamarin нативных компонентов пользовательского интерфейса (UI) в целом неплохой механизм, однако это сопряжено с определенными затратами — платформы регулярно обновляются, и для фреймворка может потребоваться много времени на адаптацию к новой версии. Поэтому в Xamarin лучше держать общий код только для логики приложения, но не UI. Кроме того, мы рекомендуем использовать нативные модули для обработки тяжелой графики, такой как игры и анимации.

Flutter использует встроенные виджеты и не использует нативные  компоненты пользовательского интерфейса. Виджеты, как ожидается, будут оптимально настроены к платформе, для которой вы создаете пользовательский интерфейс. В настоящее время Flutter предлагает множество макетов, виджетов, платформ для создания графики и поддержки 2D-API, жестов, эффектов, анимации и других функций. Хотя функции интерфейса Flutter все еще находятся в разработке, они могут превратиться в мощную среду пользовательского интерфейса. Кроме того, Flutter поставляется с компонентами Material Design. В конце концов, это Google.

Средства разработки

Функция горячей перезагрузки в Flutter помогает разработчикам создавать пользовательские интерфейсы, экспериментировать и добавлять различные функции, а также быстро обнаруживать и исправлять ошибки без потери состояния на эмуляторах. Разработчики также могут обращаться к нативным функциям, таким как SDK сторонних разработчиков и библиотекам, повторно использовать существующий нативный код (Swift, Objective C, Java и Kotlin).

Одним из преимуществ Xamarin является то, что он позволяет разработчикам тестировать приложения на разных устройствах в Microsoft Xamarin Cloud. Однако вы должны платить абонентскую плату за доступ к этой функции. Xamarin также предоставляет функцию живой перезагрузки, такую же почти, как во Flutter, которая помогает разработчикам изменять XAML и видеть результат «вживую» без компиляции или установки приложения.

Одной из проблем Xamarin является интеграция с сторонними библиотеками, которая, по-видимому, лучше реализована во Flutter.

Опыт разработки

Xamarin позволяет переиспользовать около 96 процентов кода C#, но если вам понадобится полностью нативная производительность, вам придется использовать платформозависимый код. Таким образом, Xamarin выигрывает тогда, когда разработчики имеют опыт работы на C#, а также могут работать с такими платформами, как Java, Kotlin, Swift и Objective-C.

Для разработки приложений в Flutter вам необходимо освоить Dart, что может отнять у вас немного времени, однако, если вы знакомы с Java и C ++, это не будет проблемой. Если вы новичок в разработке мобильных приложений и не имеете опыта разработки приложений для Android или iOS, вам нужно вначале хорошо освоить Dart.

Размер приложения и APK

Согласно испытаниям Корхана Бикарна, инженера Capital One, простое приложение Flutter имеет размер бинарника 40,2 МБ, а такое же на Xamarin — 25,1 МБ. Размер APK для такого приложения Flutter занимает почти 8 Мбайт, а на Xamarin — около 7 МБ. Здесь вы можете увидеть более подробные результаты.

Использование памяти, процессора и графического процессора

Согласно тому же сравнению, инициализация адресного пространства приложения и динамическое связывание заняло 1,05 секунды в Flutter. Приложение запускается за ~220 мс и работает на 58 FPS. В Xamarin инициализация адресного пространства приложения и динамическое связывание отняли 3,2 секунды. Приложение запускается примерно за 345 мс на 53 FPS. Вы также можете узнать больше о производительности Xamarin в специальной статье.

Выводы

Хотя разработчики сейчас и выбирают чаще Flutter, это не означает, что Xamarin устарел. Имеет смысл выбрать Flutter, если вы новичок в кросс-платформенной мобильной разработке, так как эта платформа, вероятно, станет очень популярной в ближайшем будущем.

Тем не менее, платформа Xamarin в настоящее время более зрелая во многих отношениях, включая сообщество, набор инструментов и стабильность. Для сложных и долгосрочных проектов мы рекомендуем остановиться на Xamarin, особенно если у вас есть команда разработчиков на C# и .NET, и вы работаете с экосистемой Microsoft.

Перевод: «Flutter vs Xamarin Cross-Platform Mobile Development Compared»

Подписывайтесь на новости Flutter! https://t.me/flutterdaily