Flutter — новый взгляд на кроссплатформенную разработку

В августе 2018 года Flutter стал самой запрашиваемой кроссплатформенной технологией на Stack Overflow.

image

В нашем блоге Артем Зайцев, Android-разработчик Surf, и Евгений Сатуров, Android-тимлид Surf, расскажут, почему и как так получилось:

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

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

Совсем скоро ожидается финальный релиз нового фреймворка для мобильной разработки от Google — Flutter, который стал самой запрашиваемой кроссплатформенной технологией на Stack Overflow. Подчеркну, что он предназначен именно для мобильных приложений и охватывает две платформы: Android и iOS. На данный момент представлена Release Preview 2 версия. Новые проекты на Flutter попадают в специальную подборку, цель которой — показать возможности фреймворка. Сейчас фреймворк активно пополняется компонентами и архитектурными надстройками благодаря сообществу разработчиков (например, реализация Redux).

64bbb3dc8e369d9defb1b3aac7c206cc.jpg


Почему нужно поверить во Flutter?


Отличный тулинг для быстрой разработки

Вам не придётся вылезать из привычной Android Studio. При помощи плагина она отлично приспосабливается под разработку Flutter-приложений.

Hot Reload — киллер-фича, позволяющая моментально переносить все изменения из кода на запущенный эмулятор или подключенное устройство.

6d8b83e4e063dbccf279adfe2b66dd1d.gif


Простота и выразительность вёрстки

Если вы когда-либо разрабатывали приложения под Android, уверен, вёрстка — это не то, от чего вы получали удовольствие.

С Flutter всё иначе. Во-первых, никаких XML-файлов с вёрсткой — виджеты создаются и настраиваются прямо в коде (чем-то напоминает Anko Layouts). Вместо View используются Widget.

new Column(  
  mainAxisAlignment: MainAxisAlignment.center,  
  children: [  
    Padding(  
      padding: EdgeInsets.all(16.0),  
      child: Text(  
        '$_name',  
      ),  
    ),
    //...

Так выглядит код Flutter-проекта. Поначалу огромное количество скобок пугает, но такие «деревья» довольно наглядны. Flutter пропагандирует композицию: из готовых виджетов можно составить новый, как из конструктора. Почти у любого компонента есть свойство child или children, которое принимает другой элемент или массив элементов соответственно. Все просто и понятно. Сделать красивый пользовательский интерфейс можно достаточно быстро.

Во-вторых, прямо со старта создатели платформы предлагают разработчикам каталог готовых виджетов. Он содержит два набора элементов, Material Components и Cupertino, которые выглядят нативно для каждой из платформ. Кроме того, доступны кроссплатформенные виджеты. Их внешний вид и поведение идентичны на iOS и на Android-устройствах.

f9e31a6110ad50567cc69420dedc8489.png


Реактивный фреймворк

Создать красивый и приятный UI в сжатые сроки можно не только благодаря большому количеству готовых виджетов, но и языку, на котором вам придётся писать. Dart отдалённо напоминает Java, JavaScript, C#. Он выразителен и отлично заточен под нужды фреймворка, хотя, после Kotlin некоторые художественные излишества синтаксиса могут ввести в легкий ступор.

Flutter компилируется в нативный код под каждую из платформ. «Под капотом» он использует Skia в качестве графического движка.

ccfe682c7711e28775b59d6e5e85bec2.png

Ключевой особенностью архитектуры системы является то, что все виджеты, а также компоненты, ответственные за отрисовку виджетов на канве, являются частью приложения, а не платформы. Именно отсутствие необходимости в переключении контекста и использования «мостов» даёт прирост производительности, который, способствует достижению заветного показателя в 60 FPS при отрисовке UI.


Вся мощь платформы по-прежнему в ваших руках

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

2326714a314e40d563bb7594bbbe7c06.png


Настройка окружения

Начать работу с Flutter очень просто.

При разработке официальная документация советует использовать Android Studio, IntelliJ или VSCode c соответствующими плагинами, но подойдет любой текстовый редактор.


Шаг первый

Скачать архив с Flutter SDK с официального сайта под свою ОС. Распаковать в желаемую директорию и запустить команду flutter doctor. Данная команда проверит установлено ли все необходимое, а также — наличие плагинов под установленные IDE (например, при установленной Android Studio утилита проверит плагины под нее).


Шаг второй

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


Шаг третий

Для использования Flutter в Android Studio необходимо поставить два плагина: Flutter и Dart. Установить их можно стандартным образом, открыв настройки и выбрав нужные плагины в поиске.

Подробно о настройке окружения написано в официальной документации.


Dart

import  'package:flutter/material.dart';  

void main() => runApp(MyApp());

Первое что бросается в глаза при виде Flutter-приложений — непривычный код. В мире Android-разработки используется Java, а с недавнего времени и Kotlin.

Теперь в одном ряду с ними стоит Dart. Google позиционирует его как альтернативу JavaScript со строгой типизацией, высокой производительностью и гибкостью.

Синтаксис Dart прост в освоении, хотя и не так красив как у Kotlin. Возможно, это дело вкуса и привычки.


Создание проекта

Проект можно создать, выполнив в консоли команду flutter create name или используя IDE (в Android Studio → New Flutter Project).


Структура приложения

После создания проекта вы увидите следующую структуру. В корневой директории приложения есть четыре пакета — lib, ios, android и test.

c84587b1ba1bf0cda65135f085a6dfb2.png

Первая — директория фреймворка. Там располагаются все dart-файлы и основной код приложения. Несмотря на то, что Flutter компилируется в нативный код, для каждой из платформ приходится писать некоторые нативные взаимодействия. Кроме того, можно встроить Flutter в уже существующее приложение. Для нативного кода предусмотрены два пакета — ios/android, в которых есть возможность писать на привычном для конкретной платформы языке — Obj-C/Swift или Java/Kotlin. В пакете test найдете тесты.

В корневой директории есть файл конфигурации pubspec.yaml — там подключаются библиотеки и т.д. Если говорить об аналогии, то для Flutter«а это, как build.gradle (он тоже есть, но уже в нативной Android части).

Пакет lib можно разбивать на дополнительные пакеты — тут уже дело вкуса и желания использовать ту или иную архитектуру. К слову, для создания приложения на Flutter используются различные приемы, которые можно посмотреть здесь.

В вашем проекте сразу будет файл main.dart, содержащий код с примером. У приложения единая точка входа — метод main. Он отвечает за создание корневого виджета.


Everything is a Widget

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

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

Для примера построим приложение с таким экраном:

0fa0c918ceaab3394a3d254101977a5d.jpge9438436e25250d2665cc064426a2442.jpg

Здесь присутствуют следующие виджеты:


  • приложение
  • экран
  • AppBar
  • текст
  • кнопка с лоадером

Часть из них реализована в фреймворке, часть необходимо составить самим из готовых деталей.

Виджеты бывают двух типов Stateless и Stateful. Первые статичны (например, текст), вторые поддерживают изменение состояния (например, экран).


Stateless

Примером такого виджета в приложении может быть MyApp. Это корень приложения. Внутри разместим все, что требуется для отрисовки с помощью метода build.

class  MyApp  extends  StatelessWidget {  
  // This widget is the root of your application.  
  @override  
  Widget build(BuildContext context) {  
    return  new MaterialApp(  
      title: 'Flutter Demo',  
      theme: new ThemeData(  
        primarySwatch: Colors.blue,  
      ),  
      home: Scaffold(  
        appBar: new AppBar(  
          title: new Text("Flutter Random Name"),  
          ),  
        body: new MyHomePage(),  
       ),  
    );  
  }  
}

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

В примере данный виджет строится из MaterialApp (корень приложения, основанного на компонентах из MaterialDesign), внутри которого лежит Scaffold — это экран.

MyHomePage — тоже виджет, который отрисовывает тело экрана, исключая AppBar. В данном случае он имеет состояние. О нем поговорим ниже.

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

Замечание: слово new в Dart, начиная со второй версии, опционально.

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


Statefull

Виджеты с состоянием поддерживают перерисовку при изменении их состояния (State). Чтобы сделать такой виджет необходимо наследоваться от StatefullWidgetи создать класс-наследник State, который является состоянием виджета и отвечает за то, что пользователь увидит на экране смартфона.

Изменение состояния виджета происходит через вызов метода setState() {}. Внутри можно, например, задать другой цвет фона кнопки, и фреймворк сам определит минимально необходимую перерисовку UI.

В моем случае главная страница MyHomePage будет виджетом с состоянием. Оно хранит _name — имя, которое будет выводится в текстовом блоке, и флаг загрузки _isLoading (отвечает за то, как будет отрисован внутренний виджет LoadingButton; пример управления состоянием через родителя).

class  _MyHomePageState  extends  State {  
  String _name = "";  
  bool _isLoading = false;  

  @override  
  Widget build(BuildContext context) => Center(  
    child: new Column(  
      mainAxisAlignment: MainAxisAlignment.center,  
      children: [  
        Padding(  
          padding: EdgeInsets.all(16.0),  
          child: Text(  
            '$_name',  
          ),  
        ),  
        LoadingButton(  
          isLoading: _isLoading,  
          action: _generateName,  
        )  
      ],  
    ),  
  );  
}

У кнопки есть коллбек, action, куда подается метод _generateName(). Замечу, что в Dart нет модификаторов доступа, типа private и public. Но если необходимо сделать приватным что-либо внутри модуля, то наименование должно начинаться с префикса »_».

Метод _generateName асинхронный. Он отвечает за загрузку имени, изменяет флаг isLoading и устанавливает значение name, что приводит к перерисовке кнопки и текста.

final snack = SnackBar(content: Text("Handle error!"));  

_generateName() async {  
  toggleLoading();  

  try {  
    //загрузка данных через await  
    //парсинг Json  
    setName(map["name"]);  
    toggleLoading();  
  } catch (e) {  
    setName("oops!");  
    Scaffold.of(context).showSnackBar(snack);  
    toggleLoading();  
  }  
}  

void toggleLoading() {  
  setState(() {  
    _isLoading = !_isLoading;  
  });  
}

Для изменения состояния обязательно нужно вызвать setState() {}. Без этого вызова виджет просто не будет перерисован.

Реализация лоадера оказалась довольно простой. В момент перерисовки подставляется либо текст, либо индикатор в кнопку.

f78aa3fd52cb8168a1d95740e932a484.jpg

Реализация занимает всего несколько строк

_buildButtonChild() {  
  if (isLoading) {  
    return Transform.scale(  
      scale: 0.5,  
      child: CircularProgressIndicator(),  
    );  
  } else {  
    return Text("Click for name");  
  }  
}

Transform.scale нужен, чтобы уменьшить размер.

Асинхронное взаимодействие в Flutter, собственно как и в Dart, основано на async-await. Подробнее можно посмотреть здесь, здесь и здесь.


С чего начать?

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

Хороший пример базового курса по Flutter — курс на Udacity. Уроки разбиты на две главы, вдумчивое прохождение каждой из которых займёт 3–4 часа.

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


Заключение

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

© Habrahabr.ru