Начинаем работать с Flutter

Изучаем фреймворк Flutter и создаем кроссплатформенное приложение под iOS и Android, используя единый исходный код, сформированный в редакторе VS Code.

Скачать файлы проекта

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

За последнее десятилетие было выпущено несколько средств кроссплатформенной разработки: компания Adobe представила веб-сервис PhoneGap, Microsoft – фреймворк Xamarin, Facebook – пакет React Native. У каждого из этих решений есть свои преимущества и недостатки, и они используются в мобильной индустрии с разной степенью успешности.

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

Введение в концепцию Flutter

Приложения, создаваемые с помощью фреймворка Flutter. используют язык программирования Dart, созданный и поддерживаемый корпорацией Google. Dart отвечает стандартам ECMA, во многом похож на языки Kotlin и Swift, а также может быть скомпилирован в JavaScript- код.

Будучи кроссплатформенным фреймворком, Flutter имеет очень много общего со средой разработки React Native и позволяет использовать реактивный и декларативный стиль программирования. Однако в отличие от React Native, Flutter не нуждается в мосте для взаимодействия с Javascript, что ускоряет запуск приложений и увеличивает их производительность. Язык программирования Dart вместо Javascript-моста использует технологию AOT – компиляцию перед исполнением.

Другая уникальная черта языка программирования Dart – использование JIT-компиляции, в ходе которой промежуточный код компилируется напрямую в бинарный во время его выполнения. Эта концепция позволяет выполнять горячую перезагрузку для мгновенного обновления интерфейса – без необходимости создания новой сборки.

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

Помимо разработки кроссплатформенных приложений для iOS и Android, изучение фреймворка Flutter позволит вам изучить особенности Fuchsia – экспериментальной операционной системы, разрабатываемой корпорацией Google.

В этом руководстве мы рассмотрим создание приложения, которое запрашивает имена участников платформы через API портала GitHub и отображает их в прокручиваемом списке.

Введение в концепцию Flutter

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

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

Также в ходе реализации проекта вы познакомитесь с основами языка программирования Dart.

Приступаем к работе

Фреймворк Flutter устанавливается на Windows, macOS и Linux. Писать код для Flutter можно в любом редакторе, однако специальные плагины для сред разработки IntelliJ IDEA, Android Studio и Visual Studio Code значительно упрощают работу. Мы воспользуемся редактором VS Code.

Настройка среды разработки

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

  1. Скачайте установочный пакет, соответствующий вашей операционной системе.
  2. Извлеките установочные файлы в нужную папку.
  3. Добавьте путь к директории Flutter в переменную среды PATH.
  4. Выполните команду flutter doctor, которая устанавливает фреймворк, интерпретатор Dart, а также уведомляет об отсутствии необходимых системных компонентов.
  5. Установите отсутствующие компоненты.
  6. Добавьте в свою интегрированную среду разработки плагин или расширение для Flutter.
  7. Проверьте работоспособность среды разработки запуском тестового приложения.

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

Если вы используете среду разработки Android Studio, у вас не должно возникнуть затруднений при прохождении этого руководства. Вам также понадобится запустить симулятор iOS, или эмулятор Android, либо использовать мобильное устройство с операционной системой iOS или Android.

Примечание: для создания сборки и запуска приложения на iOS-устройстве или на iOS-симуляторе вам потребуется операционная система macOS с установленной средой Xcode.

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

В редакторе VS Code с установленным расширением Flutter откройте командную палитру, выбрав «Обзор» — «Командная палитра», или использовав комбинацию клавиш Cmd-Shift-P на macOS, или Ctrl-Shift-P на Windows и Linux. Наберите в строке Flutter: New Project и нажмите Enter.

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

В качестве названия проекта введите ghflutter и нажмите Enter. Выберите директорию для сохранения проекта, и подождите, пока Flutter завершит создание рабочих файлов в VS Code. Когда все будет готово, файл main.dartоткроется в окне редактора.

Создание нового проекта - 2

В редакторе VS Code в левой части экрана отображается структура вашего проекта. Там есть папки для файлов iOS и Android, а также директория lib, в которой содержится файл main.dart с кодом, предназначенным для обеих платформ. В этом руководстве мы будем работать только с папкой lib.

Замените код в файле main.dart на приведенный ниже:

import 'package:flutter/material.dart';
void main() => runApp(GHFlutterApp());
class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'GHFlutter',
      home: Scaffold(
        appBar: AppBar(
          title: Text('GHFlutter'),
        ),
        body: Center(
          child: Text('GHFlutter'),
        ),
      ),
    );
  }
}

Функция main() в начале программы использует оператор => для запуска приложения GHFlutterApp. Так же называется класс GHFlutterApp, используемый программой.

Из приведенного выше кода видно, что приложение является виджетом StatelessWidget – без сохранения состояния. Почти все элементы в приложениях, созданных с помощью фреймворка Flutter, представляют собой виджеты и относятся к двум типам – с сохранением состояния и без сохранения состояния. Для создания виджета нашего приложения мы переопределяем метод build(). Мы используем виджет MaterialApp, содержащий набор компонентов, используемых для оформления интерфейса приложений в стиле Material Design.

В нашем проекте тестовый файл widget_test.dart, расположенный в папке test, не потребуется – кликните по нему правой кнопкой мыши, выберите команду «Удалить» из контекстного меню и подтвердите удаление.

Если вы работаете в macOS, запустите iOS-симулятор. Также на macOS, Linux и Windows можно использовать эмулятор Android. Можно запустить и симулятор, и эмулятор сразу и переключаться между ними, используя меню, доступное в правом нижнем углу окна редактора VS Code.

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

Обратите внимание на то, что конфигурация не определена. Нажмите на «Нет конфигурации» в выберите в выпадающем меню «Добавить конфигурацию».

При этом редактор VS Code создаст файл launch.json следующего вида:

Примечание: этот файл создается автоматически после нажатия кнопки «Добавить конфигурацию». В этом руководстве вносить изменения в код этого файла не требуется.

Теперь все готово – запустите проект нажатием клавиши F5 на клавиатуре или выбором пункта «Запуск отладки» в меню «Отладка», или нажатием зеленой кнопки «Пуск». После этого откроется консоль отладки, либо, при использовании iOS, запустится среда Xcode. В случае использования Android запустится Gradle.

При использовании симулятора iOS приложение будет выглядеть следующим образом:

А в случае использования эмулятора Android окно выглядит, так как показано ниже:

Ярлык slow mode («медленный режим») означает, что приложение выполняется в режиме отладки.

Выполнение приложения можно остановить нажатием кнопки «Стоп», расположенной с правой стороны верхней панели инструментов в окне редактора VS Code.

Вернуться к проекту можно нажатием иконки, расположенной в верхнем левом углу окна редактора VS Code, или выбором опции «Обозреватель» в меню «Обзор».

Горячая перезагрузка

Одно из главных преимуществ работы с фреймворком Flutter – возможность горячей перезагрузки после внесения каких-либо изменений в код. Эта опция напоминает мгновенный запуск с применением внесенных изменений в среде разработки Android Studio.

Запустите сборку и выполнение приложения в симуляторе или эмуляторе.

Горячая перезагрузка

Теперь, не останавливая программу, измените строку AppBar, например, так:

appBar: (
  title: Text('GHFlutter App'),
),

После этого нажмите кнопку горячей перезагрузки на панели инструментов или просто сохраните файл main.dart:

Горячая перезагрузка - 2

В течение 1-2 секунд вы увидите изменения в создаваемом приложении:

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

Импорт файла

Чтобы не хранить весь код в одном файле main.dart, нужно реализовать импорт кода из других пользовательских классов. Рассмотрим пример импорта, который поможет локализовать строки, обращенные к пользователю.

Создайте файл с именем strings.dart в папке lib, кликнув правой кнопкой мыши и выбрав в контекстном меню опцию «Новый файл»:

Импорт файла

Добавьте приведенный ниже класс в только что созданный файл:

class Strings {
  static String appTitle = "GHFlutter";
}

Теперь добавьте следующую строку в начало файла main.dart:

import 'strings.dart';

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

class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      home: Scaffold(
        appBar: AppBar(
          title: Text(Strings.appTitle),
        ),
        body: Center(
          child: Text(Strings.appTitle),
        ),
      ),
    );
  }
}

Запустите приложение нажатием клавиши F5 на клавиатуре – внешних изменений не видно, но теперь программа использует строки из файла.

Виджеты

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

Все виджеты делятся на два типа:

  1. Без состояния: виджеты, которые зависят только от собственной конфигурации, например статические изображения.
  2. С сохранением состояния: виджеты, которые должны поддерживать динамическую передачу информации в ходе взаимодействия с объектом состояния State.

Оба типа виджетов обновляются в Flutter- приложениях в процессе рендеринга каждого фрейма. Различие заключается в том, что виджеты с сохранением состояния передают свою конфигурацию объекту State.

Чтобы создать собственный виджет, начните с определения нового класса внизу файла main.dart:

class GHFlutter extends StatefulWidget {
  @override
  createState() => GHFlutterState();
}

Приведенный выше код создает подкласс виджета StatefulWidget и переопределяет метод createState() для создания объекта состояния. Теперь разместите класс GHFlutterState перед GHFlutter:

class GHFlutterState extends State<GHFlutter> {
}

Теперь класс GHFlutterState расширяет объект State параметром GHFlutter.

Главная задача при создании нового виджета – переопределение метода build(), который вызывается при рендеринге компонента на экране. Добавьте переопределение build() в класс GHFlutterState:

@override
Widget build(BuildContext context) {
}
Заполните метод build(), как показано ниже:
@override
Widget build(BuildContext context) {
  return Scaffold (
    appBar: AppBar(
      title: Text(Strings.appTitle),
    ),
    body: Text(Strings.appTitle),
  );
}

Scaffold – контейнер, содержащий все виджеты стиля Material Design. Данный контейнер выполняет роль корневого каталога в иерархии виджетов. Приведенный выше код добавляет в контейнер панель AppBar и область body – они оба включают в себя виджеты для отображения текста.

Внесите изменения в класс GHFlutterApp для использования вашего собственного виджета GHFlutter вместо стандартного:

class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      home: GHFlutter(),
    );
  }
}

Запустите приложение, чтобы оценить новый виджет в действии.

Виджеты

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

Выполнение сетевых вызовов

Ранее мы импортировали strings.dart в проект. Точно также можно импортировать другие пакеты, входящие в состав фреймворка Flutter и языка программирования Dart. Например, теперь мы будем использовать пакет для сетевого вызова по протоколу HTTP и передачи полученных JSON данных объектам Dart. Добавьте два новых импорта в начале файла main.dart:

import 'dart:convert';
import 'package:http/http.dart' as http;

После этого вы получите сообщение о том, что пакет для HTTP вызова отсутствует – потому что он еще не добавлен в проект. Для добавления откройте файл pubspec.yaml и сразу под строкой cupertino_icons: ^0.1.2 добавьте строки, приведенные ниже:

  cupertino_icons: ^0.1.2
  # HTTP package
  http: ^0.12.0+2

Примечание: обратите внимание на отступы – они должны быть такими же, как и перед строкой cupertino_icons.

После сохранения файла pubspec.yaml расширение Flutter, установленное в редакторе VS Code, запустит команду flutter pub get, после чего пакет для работы с HTTP будет добавлен и во Flutter, и в наш проект main.dart. Вы увидите индикатор импорта в main.dart, который до этого был неактивным.

Приложения Dart – однопоточные, однако этот язык программирования поддерживает выполнение кода в других потоках, также как и исполнение асинхронного кода, который использует режим async/await и не прерывает выполнение основного потока.

Мы собираемся реализовать асинхронный сетевой вызов для получения списка участников платформы GitHub. Добавьте пустой список в качестве свойства в верхнюю часть GHFlutterState. Также добавьте свойство для сохранения стиля текста:

var _members = [];
final _biggerFont = const TextStyle(fontSize: 18.0);

Подчеркивания в начале имен делают классы закрытыми. Для выполнения асинхронного HTTP-вызова, добавьте метод _loadData() в GHFlutterState:

_loadData() async {
  String dataURL = "https://api.github.com/orgs/raywenderlich/members";
  http.Response response = await http.get(dataURL);
  setState(() {
    _members = json.decode(response.body);
  });
}

Мы добавили ключ async в метод _loadData(), чтобы указать Dart на асинхронность; ключ await в http.get() выполняет блокирующую функцию. Значение dataUrl указывает на конечную точку API портала GitHub для извлечения списка участников платформы.

Когда HTTP вызов завершается, обратный вызов передается функции setState(), которая выполняется синхронно в потоке пользовательского интерфейса. В этом случае, данные JSON преобразуются в список _members.

Добавьте переопределение initState() в GHFlutterState, которое вызывает _loadData() при инициализации состояния:

@override
void initState() {
  super.initState();

  _loadData();
}

Отображение элементов списка

После того, как Dart получил список участников, нам нужен способ его отображения в пользовательском интерфейсе. В Dart входит виджет ListView, который отображает пользовательские данные в виде списка. Функциональность этого виджета аналогична RecyclerView в Android и UITableView в iOS – он точно так же обеспечивает плавную прокрутку списка.

Добавьте метод _buildRow() в GHFlutterState:

Widget _buildRow(int i) {
  return ListTile(
    title: Text("${_members[i]["login"]}", style: _biggerFont)
  );
}

Код, приведенный выше, возвращает содержимое виджета ListTile – логины участников платформы, извлеченные из JSON данных, и оформленные с применением текстового стиля, который мы создали ранее.

Отредактируйте метод build в GHFlutterState для использования в формировании списка:

body: ListView.builder(
  padding: const EdgeInsets.all(16.0),
  itemCount: _members.length,
  itemBuilder: (BuildContext context, int position) {
    return _buildRow(position);
  }),

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

Отображение элементов списка

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

Отображение элементов списка - 2

Добавление разделителей

Для добавления разделителей в список нам нужно удвоить количество позиций, а затем вставить виджет Divider в каждую нечетную позицию. Отредактируйте метод build в GHFlutterState следующим образом:

body: ListView.builder(
  itemCount: _members.length * 2,
  itemBuilder: (BuildContext context, int position) {
    if (position.isOdd) return Divider();

    final index = position ~/ 2;
    
    return _buildRow(index);
  }),

Убедитесь, что добавили * 2 в количество позиций itemCount. При использовании разделителей определение межстрочных интервалов не требуется. В itemBuilder вы либо используете виджет-разделитель Divider (), либо вместо этого вычисляете новый индекс путем целочисленного деления и используете _buildRow() для создания элемента строки.

Используйте «горячую перезагрузку» и убедитесь, что теперь в списке есть разделители.

Добавление разделителей

Для добавления межстрочных интервалов к каждой строке используйте виджет Padding внутри _buildRow():

Widget _buildRow(int i) {
  return Padding(
    padding: const EdgeInsets.all(16.0),
    child: ListTile(
      title: Text("${_members[i]["login"]}", style: _biggerFont)
    )
  );
}

Теперь ListTile является дочерним виджетом родительского виджета Padding. Выполните горячую перезагрузку – после этого интервалы будут применены к строкам списка, но не к разделителям.

Использование пользовательских классов

На предыдущем этапе мы использовали обработчик данных, который извлекал логины участников платформы Github из документа JSON и вносил имена в список _members в качестве класса Map («схема»), аналогичного типу Map в Kotlin или Dictionary («словарю») в Swift.

Теперь мы будем использовать наш собственный тип данных. Добавьте новый класс Member («участник») в файл main.dart:

class Member {
  final String login;

  Member(this.login) {
    if (login == null) {
      throw ArgumentError("Логин или участник не могут быть пустыми. "
          "Received: '$login'");
    }
  }
}

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

Отредактируйте объявление _members в GHFlutterState для преобразования в список объектов класса Member:

var _members = <Member>[];

Обновите _buildRow() для использования свойства login вместо использования ключа login в схеме JSON данных:

title: Text("${_members[i].login}", style: _biggerFont)

Теперь отредактируйте функцию обратного вызова setState() в _loadData() для преобразования схемы данных в объект Member и добавления в список участников GitHub:

setState(() {
  final membersJSON = json.decode(response.body);

  for (var memberJSON in membersJSON) {
    final member = Member(memberJSON["login"]);
    _members.add(member);
  }
});

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

Загрузка изображений с использованием NetworkImage

Каждый участник платформы GitHub имеет аватар, а каждый аватар имеет свой URL-адрес. Теперь мы добавим ссылки на аватары в класс Member, чтобы изображения участников отображались в нашем приложении.

Отредактируйте класс Member и добавьте свойство avatarUrl с предупреждением, что оно не может быть незаполненным:

class Member {
  final String login;
  final String avatarUrl;

  Member(this.login, this.avatarUrl) {
    if (login == null) {
      throw ArgumentError("Логин или участник не могут быть пустыми. "
          "Received: '$login'");
    }
    if (avatarUrl == null) {
      throw ArgumentError("Ссылка на аватар не может быть пустой. "
          "Received: '$avatarUrl'");
    }
  }
}

Обновите _buildRow() для выведения аватара с помощью NetworkImage и виджета CircleAvatar:

Widget _buildRow(int i) {
  return Padding(
    padding: const EdgeInsets.all(16.0),
    child: ListTile(
      title: Text("${_members[i].login}", style: _biggerFont),
      leading: CircleAvatar(
        backgroundColor: Colors.green,
        backgroundImage: NetworkImage(_members[i].avatarUrl)
      ),
    )
  );
}

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

Теперь обновите _loadData() для использования значения avatar_url в схеме во время создания записи для нового участника:

final member = Member(memberJSON["login"], memberJSON["avatar_url"]);

Остановите и перезапустите приложение нажатием клавиши F5 на клавиатуре. После этого вы увидите аватары рядом с каждым участником, представленным в списке:

Загрузка изображений с использованием NetworkImage

Оптимизация кода

Большая часть нашего кода расположена в файле main.dart. Чтобы сделать код немного опрятнее, можно провести рефакторинг виджета и переместить созданные нами классы в отдельные файлы.

Создайте файлы member.dart и ghflutter.dart в папке lib. Сохраните код класса Member в файле member.dart, а код классов GHFlutterState и GHFlutter – в файле ghflutter.dart. Файл member.dart не требует добавления команд импорта, однако импорт в ghflutter.dart должен быть определен следующим образом:

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'member.dart';
import 'strings.dart'; 

Также необходимо обновить команды импорта в файле main.dart, так как показано ниже:

import 'package:flutter/material.dart';
import 'ghflutter.dart';
import 'strings.dart';
void main() => runApp(GHFlutterApp());
class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      home: GHFlutter(),
    );
  }
}  

Перезапустите приложение нажатием клавиши F5 на клавиатуре – никаких внешних изменений вы не заметите, но код теперь выглядит гораздо чище.

Добавление темы оформления

Изменить тему оформления в создаваемом приложении можно добавлением атрибута theme в MaterialApp, которую мы создали в файле main.dart:

class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: Strings.appTitle,
      theme: ThemeData(primaryColor: Colors.green.shade800), 
      home: GHFlutter(),
    );
  }
}  

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

Добавление темы оформления

Большинство скриншотов, приведенных в этом руководстве, сделано в эмуляторе Android. Запустим приложение в новом оформлении в симуляторе iOS и увидим следующее:

Добавление темы оформления - 2

Вот это и есть настоящая кроссплатформенность!

Что делать дальше

Вы можете скачать все файлы проекта, кликнув по ссылке, приведенной в конце статьи. Файлы должны с одинаковым успехом открываться и в VS Code, и в Android Studio. В VS Code проект открывается из корневого каталога. Загрузить пакеты надо до запуска проекта – для этого откройте командную палитру с помощью комбинации клавиш Cmd+Shift+P и выполните команду Flutter: Get Packages.

Для открытия проекта в Android Studio выберите опцию «Открыть существующий проект» на экране «Добро пожаловать в Android Studio» и нажмите на корневую директорию готового проекта. Затем вам надо выбрать пункт «Открыть настройки Flutter» (выбрать Flutter также можно в «Настройках»), и указать путь к среде Flutter SDK, которую необходимо предварительно скачать из репозитория GitHub. Если понадобится, выберите пункт «Установить необходимые компоненты» в строке «Загрузка пакетов не запускалась». Выберите «Обзор проекта» на панели для просмотра файлов.

Если вы используйте macOS, запустите приложение в Android Studio, используя либо Android эмулятор, либо iOS симулятор. Попробуйте, это действительно здорово работает.

Вам еще многое предстоит узнать о Flutter и Dart. Лучшие ресурсы для обучения:

  1. Официальная документация Flutter на dev.
  2. Все доступные виджеты есть здесь.
  3. Здесь находится отличная инструкция для Android-разработчиков, переходящих на Flutter.
  4. Такая же инструкция для разработчиков, использующих React Native, расположена здесь.

Оставляйте отзывы и вопросы в комментариях. Надеюсь, это руководство по Flutter вам понравилось.

Скачать файлы проекта

Наталья Кайдаавтор-переводчик статьи «Getting Started With Flutter»