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


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


image


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


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


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


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



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


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


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


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



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


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


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


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

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


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



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


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


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



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


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


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



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


Начать работу с 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.



Первая — директория фреймворка. Там располагаются все 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


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


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


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



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


  • приложение
  • экран
  • 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<T>, который является состоянием виджета и отвечает за то, что пользователь увидит на экране смартфона.


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


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


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

  @override  
  Widget build(BuildContext context) => Center(  
    child: new Column(  
      mainAxisAlignment: MainAxisAlignment.center,  
      children: <Widget>[  
        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() {}. Без этого вызова виджет просто не будет перерисован.


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



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


_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 найдёт своё применение — покажет время.

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (43):

  1. glycol
    /#19250621

    Очередной фреймворк «убийца ххх»… а тем временем, все серьезные приложения все равно пишут нативно, под каждую платформу отдельно. Потому как то произодительности не хватает, то возможностей, и все равно часть кода приходится писать нативно, что очень усложняет «кроссплатформенную» разработку, и в итоге люди задаются вопросом, если половина нативна, то почему же уж все нативно не написать? P.S. Был опыт разработки на Xamarin, знаю о чем говорю. Для простеньких приложений аля «визитка» — самое то. Для серьезных проектов- лучше даже не начитать.

    • Neikist
      /#19250689

      С производительностью норм должно быть, ибо в нативный arm код компилится. В случае андроида даже накладных расходов на jni не должно быть.

      • Neikist
        /#19251025

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

    • agent10
      /#19251003

      А можете рассказать как раз о примерах, когда не хватает возможностей и частях, которые надо писать на нативе? Сам то я пишу только на нативе, но т.к. тема кроссплатформенных решений становится популярна, то интересуюсь этим чисто как инструментом для мелких проектиков/прототипов. Просто все вокруг говорят «не подходит для серьёзных проектов», а о конкретных моментах умалчивают.

      • glycol
        /#19251335

        Я тоже пишу на нативе под ios, одно время помогал проекту, написанному на xamarin'e, как раз писал нативные части, которых им не хватало. Ну, к примеру, когда вышел FaceID, работу с ним приходилось делать нативно, так как xamarin не поддерживал (это относится и ко многим другим новым фичам iOS, фреймворки начинают их поддерживать с сильным запозданием). Второй пример- работа с сервером через вебсокеты (передача real-time видео/audio), xamarin не тянул. Третий пример — компонент выбора фото/видео из библиотеки телефона (нужен был кастомный, со сложным поведением).

        • Newbilius
          /#19253903

          Эммм… FaceID из коробки в Xamarin работал в момент релиза нового айфона. Там же обратная совместимость от TouchID, тоже апи использовано.

          • glycol
            /#19254037

            Обратная совместимость была, да, но требовалось при использовании TouchID в дизайне это отобразить, а для этого нужно было отличать TouchID от FaceID.

            • stul5tul
              /#19255623

              Обратная совместимость была, да, но требовалось при использовании TouchID в дизайне это отобразить, а для этого нужно было отличать TouchID от FaceID.


              Из-за этого «одного бита» вы делаете вывод, что все кросс-платформенные системы — говно?

              Стоимость решения проблемы отличения TouchID/FaceID и рядом не стояла со стоимостью не кросс-платформенной разработки.

              • glycol
                /#19256163

                Вообще-то я не писал что «что все кросс-платформенные системы — говно», а наоборот, писал «Для простеньких приложений аля «визитка» — самое то». Если вы готовы постоянно идти на компромисы, мириться с производительностью и не использовать новейшие фичи платформ- то да, это неплохой выбор. Конкретная проблема отличить TouchID от FaceID на тот момент была не решаема через фреймворк, только натив, и не всегда заказчик хочет об этом слышать, ему нужен результат.

    • na9ort
      /#19254491

      Ой ну не знаю…
      Пока все поливают помоями очередных «убийц ххх», то эти самые убийцы медленно и верно завоёвывают рынок. Оглянуться не успеем как нативных разработчиков останется с гулькин нос, и основная численность будет вот таких flutter, kotlin и т.д. разработчиков.

      • glycol
        /#19256185

        Я пишу нативно и пока безработица мне не грозит. Наоборот, моя стоимость только повысится, когда кругом будут сплошные фреймворк-кодеры, не способные делать хоть что-то серьезное и быстро работающее. Кстати, тоже самое писали про Ruby on Rails, что он захватит всю веб разработку. И где он сейчас?

        • Neikist
          /#19256213

          Особенно весело станет когда флаттер будет нативным под фуксию и кроссплатформой для остальных)

      • arielf
        /#19258015

        20 лет слышу такие вещи! ;-)

        • Neikist
          /#19258419

          А разве не так?) За 20 лет (господи, я в первый класс тогда пошел и даже о существовании компьютеров еще не подозревал) по моему все заполонил веб с js и всякие высокоуровневые языки.

  2. EnChikiben
    /#19250625

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

  3. Neikist
    /#19250695

    Отличная статья, спасибо, жду фуксию и пока повысится популярность), а до тех пор параллельно буду нативный андроид копать + флаттер.

  4. TimeCoder
    /#19250811

    Ищу на чем сделать пресловутый кросс-платформенный MVP, ранее немного работал с Xamarin. Вопрос: чем все-таки Flutter лучше? При этом, он должен быть сильно лучше, ибо +время на изучение Dart, более сырое решение (даже нет релизной версии), Андроид студия послабее VS.

  5. Tsimur_S
    /#19250897 / +1

    Какие преимущества перед React Native? Из недостатков нужен новый язык — Dart который за пределами этого фреймворка нигде не нужен.

    • Neikist
      /#19250923

      Натив. Ну т.е. компилится в нормальный бинарь и никаких прослоек между UI и кодом. Ну и статическая типизация конечно же)

    • saturovv
      /#19250941

      Основной момент, в котором Flutter со старта имеет большое преимущество — производительность. Не нужно использовать JS Bridge при прямом обращении к платформе, а за счёт этого значительно проще выйти на 60 FPS при отрисовке UI.

  6. Mofas
    /#19251149

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

    И еще момент. Я вот смотрю на Hamilton (это приложение на iOS и Android на Flutter) и мне очень-очень больно пользоваться подобным интерфейсом. Все-таки нативные элементы на каждой платформе должны быть обязательно.

    P.S. На первой демо GIF вы поставили кнопку в Safe-зону. Так делать не стоит )

  7. vba
    /#19251983 / +2

    Flutter не выглядит ли как попытка воскресить Dart?

    • format1981
      /#19252769 / -1

      Очень на то похоже. На тайпскрипте было бы лучше.

      • Splo1ter
        /#19255421

        Согласен, непонимаю почему минус поставили.

        • Neikist
          /#19255499

          Минус ставил не я, но что я думаю:
          — typescript меньше типизирован да и не сказать что дико популярен, плюс хотят народ с нативной разработки переманивать, а на него с java и c# народ вроде как легко пересаживается.
          — На дарт гораздо больше влияния гугл может оказывать.
          — Возможно опасение повторения истории с java и oracle.
          Как мне кажется если и выбирать — то логично было выбирать между go и dart.

    • stul5tul
      /#19252945

      Flutter не выглядит ли как попытка воскресить Dart?

      Почему воскресить?
      На Dart вполне себе актуальные вещи пишутся. Например, веб-морда основного источника дохода Google — их рекламной системы.

    • androidovshchik
      /#19253881 / +1

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

      • Neikist
        /#19253957

        и трансляцией в JavaScript

        А это то тут причем?

        • androidovshchik
          /#19253993

          Скорее никакого в отношении к JavaScript (хотя он такой, что все может быть :)
          Но, в данном случае, сам факт возможности трансляции на другие языки очень важен

  8. Wriketeam
    /#19252123 / +1

    Кстати, поговорить о Dart и Flutter можно в русскоязычном сообществе в телеграм t.me/rudart

  9. Gorthauer87
    /#19254015

    Вообще по ходу дела тут подход в итоге подобен тому что есть в Qt qml.

  10. mSnus
    /#19254577

    А какие-то инструменты для той самой визуальной верстки есть?

    • saturovv
      /#19254951

      Никаких, как нет и визуальной вёрстки.

      Всё дерево виджетов создаётся в коде, и единственный способ визуализировать его — в виде древовидной структуры в одной из вкладок плагина. Но есть утилиты, позволяющие эмулятор использовать в качестве интерактивного макета. Открываешь «Flutter Inspector», кликаешь на «Toggle Select Widget Mode», далее жмёшь на любой UI-элемент в запущенном приложении и инспектор тебя перебрасывает к нужному элементу в дереве виджетов.

      • Neikist
        /#19255011

        Еще могу дополнить что часто hot reload спасает.

  11. dimka11
    /#19254993

    Есть какое-либо взаимодействие с нативной средой? Т.е. если в фреймворка, что-то не хватает это нужно самим на Dart дописывать?

    • stul5tul
      /#19255665

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

      Если расширение реализовано уже удобным способом, интегрированным с Dart и Flutter, то через plugin
      flutter.io/developing-packages/#plugin

      Нет, не на чистом Dart дописывать. А еще и на native под платформу.

  12. shuron
    /#19257985

    Естъ какие-то примеры реальных приложений на флатере?

  13. Wriketeam
    /#19259311

    Хочу пригласить всех, кому интересен Dart и Flutter, на конференцию DartUP в Питере 1 декабря. Конференция бесплатная, но регистрироваться нужно. Программу будем публиковать со следующей недели dartup.ru

    • stul5tul
      /#19259341

      А после конференции видео будут для живущих далеко-далеко, которые точно не смогут приехать?

      • Neikist
        /#19259411

        +1, очень хотелось бы видео.

      • Wriketeam
        /#19259441

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