В этой статье мы познакомимся с Flutter — популярным фреймворком для кроссплатформенной разработки. Мы шаг за шагом пройдем через установку инструментов в Visual Studio Code, создадим простое мобильное приложение и запустим его как на эмуляторе, так и на реальном устройстве.
Что такое Flutter и Dart
Flutter — популярный фреймворк для кроссплатформенной разработки. С его помощью можно создавать приложения для компьютеров, мобильных устройств и веба. Основной язык программирования для Flutter — Dart.
Dart — это альтернатива JavaScript. Разработчики хотели сделать язык гибким, структурированным и простым для изучения. Им это удалось.
Теперь перейдем к практике и начнем с установки инструментов.
Как установить плагин Flutter
Android Studio является официальной IDE (интегрированной средой разработки) от Google. В ней есть возможность установить специальный плагин для разработки на Flutter. Но, видимо, не для России. При попытке установки плагина вы можете столкнуться с таким сообщением:
Перевод:
Плагин Flutter не был установлен: к сожалению, в настоящее время мы не можем предоставить вам нашу продукцию или услуги из-за правил экспортного контроля.
Конечно, можно решить эту проблему через специальные сервисы, но мы не будем этого делать, а попробуем установить редактор Visual Studio Code и уже через его менеджер расширений поставить все необходимое.
Visual Studio Code — это мощнейший редактор кода от Microsoft, который при помощи различных расширений можно легко превратить в практически полноценную IDE. Скачать его можно здесь. После установки редактора запускаем его и заходим в раздел расширений. В строке поиска вводим слово flutter и выбираем расширение под названием Flutter от разработчика Dart Code. Нажимаем на кнопку установки. Вместе с этим расширением автоматически установится расширение Dart от того же разработчика.
Дополнительно можно еще русифицировать редактор. Для этого нам понадобится расширение Russian Language Pack for Visual Studio Code от Microsoft. Находим его и устанавливаем. В общем, наш редактор после всех манипуляций должен выглядеть примерно так:
Теперь попробуем создать наш первый проект на Flutter.
Как написать простое приложение-счетчик на Flutter
Для создания приложения нужно перейти в меню «Вид» и выбрать там пункт «Палитра команд». В строке поиска пишем flutter и выбираем пункт Flutter: New Project. Далее выбираем в качестве типа проекта Application и выбираем его место расположения. Остается только ввести название проекта (counter) и нажать на клавишу Enter. У нас должно получиться примерно вот это:
Шаблон проекта успешно создан. Но это еще не все. В правом нижнем углу должно появиться уведомление о том, что не найден Flutter SDK. Это набор инструментов разработки, который необходим для сборки и запуска проекта. Через это же уведомление можно загрузить SDK или если он уже загружен, то указать место его расположения. Загрузить SDK можно отсюда.
Для запуска приложения нам понадобится эмулятор устройства на Android. Такой эмулятор входит в состав Android SDK. Можно установить Android Studio и вместе с ней установятся все необходимые компоненты. Скачать ее можно здесь.
Для проверки правильности установки всех компонентов воспользуемся инструментом под названием Flutter Doctor. Найдем его в палитре команд и запустим. Вывод его будет примерно таким:
Если доктор показывает какие-то проблемы, то скорее всего не хватает каких-то компонентов или их не удалось найти. В таком случае нужно внимательно прочитать вывод доктора и доустановить необходимые компоненты или указать их местоположение в файловой системе.
Чтобы запустить приложение в эмуляторе, сначала нужно запустить сам эмулятор. Для этого нам снова понадобится палитра команд. Найдем в ней команду Flutter: Launch Emulator и запустим ее. Далее нам будет предоставлен список доступных эмуляторов. Если список пустой, то придется создать эмулятор с помощью Android Studio. Запускаем студию:
Нажимаем на More Actions и выбираем Virtual Device Manager. Откроется такое окно:
Чтобы создать новый эмулятор просто нажимаем на значок плюса и выбираем любой понравившийся телефон или планшет. У нас в списке один уже есть. Итак, если в списке палитры команд отображается хотя бы один эмулятор, то выбираем его и дожидаемся его запуска. Вот эмулятор в запущенном виде:
Чтобы запустить проект нажмем на кнопку запуска, расположенную в правой верхней части редактора, прямо над обзорной картой. После успешной сборки приложение запустится в эмуляторе:
Нажмем на кнопку со знаком плюс несколько раз, чтобы проверить работу приложения:
Все работает! Теперь давайте немного доработаем приложение.
Доработка приложения
В шаблоне содержится какой-то урезанный проект. И в самом деле: при нажатии на плюс число будет постоянно увеличиваться. Давайте добавим еще одну кнопку, которая будет уменьшать число. Но сначала изучим исходник поподробнее:
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
Что мы здесь видим? В самом начале исходника импортируется пакет material.dart. Из названия должно быть понятно, что он нужен для создания дизайна приложения в стиле Material Design. Далее идет точка входа в приложение — метод main. В его теле содержится вызов одного единственного метода runApp. На вход этого метода подается класс MyApp. В этом классе происходит установка заголовков, тем и домашней страницы. Дальше следует, собственно, сам класс домашней страницы MyHomePage, а за ним класс _MyHomePageState, в котором содержатся описание интерфейса и логика приложения.
Кнопка floatingActionButton (плавающая кнопка) описывается в самом конце класса _MyHomePageState. При нажатии на кнопку срабатывает метод _incrementCounter, который увеличивает число в компоненте Text на единицу. Нам нужно добавить еще одну кнопку, навесив на нее метод _decrementCounter, который будет уменьшать число на единицу.
Кнопки можно разместить в контейнере Row. Он расположит их горизонтально. Таким образом код для кнопок будет выглядеть так:
Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FloatingActionButton( onPressed: _decrementCounter, tooltip: 'Decrement', child: const Icon(Icons.remove), ), FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ], )
Метод _decrementCounter:
void _decrementCounter() { setState(() { _counter--; }); }
Соответственно, весь код получится такой:
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } void _decrementCounter() { setState(() { _counter--; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FloatingActionButton( onPressed: _decrementCounter, tooltip: 'Decrement', child: const Icon(Icons.remove), ), FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ], ) ], ), ), ); } }
Контейнер Row с кнопками мы расположили сразу после компонента Text. И теперь интерфейс приложения должен выглядеть более компактно, так как все компоненты собрались в центральной области экрана. Теперь попробуем запустить наше приложение в эмуляторе. Выглядеть оно будет следующим образом:
Вот теперь это похоже на Counter. Далее поговорим о том, как запустить наше приложение на физическом девайсе.
Как запустить приложение на Flutter на смартфоне
Для запуска нашего приложения на физическом смартфоне сначала необходимо активировать на нем режим разработчика. Мы будем использовать старенький Samsung Galaxy A6. В разделе сведений о ПО (в настройках) находим пункт «Номер сборки» и тапаем по нему несколько раз. После появления уведомления о том, что режим разработчика активирован, возвращаемся в список настроек и видим, что там появился новый пункт под названием «Параметры разработчика»:
Заходим в него и ищем пункт «Отладка по USB». Активируем этот пункт:
Далее подключаем телефон к компьютеру. Разрешаем передачу данных и отладку по USB в появившихся диалоговых окнах. Наконец, запускаем Visual Studio Code с нашим проектом. В боковой панели заходим во вкладку Flutter и видим там наш девайс (SM A600FN):
Нажимаем на кнопку запуска и через некоторое время видим наше приложение запущенное на телефоне:
А сейчас предлагаем выполнить небольшое задание.
Задание для самостоятельного выполнения
Добавьте в интерфейс приложения еще одну кнопку для сброса значений счетчика. Расположите ее между двумя имеющимися. Также поменяйте надпись в заголовке страницы на «Counter».
Решение
Код для контейнера с добавленной кнопкой сброса:
Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FloatingActionButton( onPressed: _decrementCounter, tooltip: 'Decrement', child: const Icon(Icons.remove), ), FloatingActionButton( onPressed: _resetCounter, tooltip: 'Reset', child: const Icon(Icons.restart_alt), ), FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ], )
Метод _resetCounter:
void _resetCounter() { setState(() { _counter = 0; }); }
Заголовок меняем в возврате MaterialApp, что прописан в классе MyApp:
home: const MyHomePage(title: 'Counter')
Весь код будет такой:
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const MyHomePage(title: 'Counter'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } void _resetCounter() { setState(() { _counter = 0; }); } void _decrementCounter() { setState(() { _counter--; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headlineMedium, ), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ FloatingActionButton( onPressed: _decrementCounter, tooltip: 'Decrement', child: const Icon(Icons.remove), ), FloatingActionButton( onPressed: _resetCounter, tooltip: 'Reset', child: const Icon(Icons.restart_alt), ), FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ], ) ], ), ), ); } }
Запущенное приложение: