Про Flutter написано уже много статей. С каждым месяцем он становится всё популярнее. Поэтому я решил интерпретировать официальную документацию Flutter в лаконичный формат «вопрос — ответ». Думаю, многие, как и я, не имеют достаточно свободного времени для подробного изучения документации фреймворка, с которым они ещё не работают.
Если вы хотите понять, чем хорош этот фреймворк, и оценить, сколько усилий придётся приложить, чтобы его использовать — добро пожаловать под кат.

Какой аналог у View во Flutter?
Widget
View — фактически то, что будет на экране. Для отображения изменений вызывается invalidate().
Widget — описание того, что будет на экране. Для изменения создаётся заново.
При запуске на самом Android под капотом Widget находится View. Flutter включает в себя библиотеку Material Components. В ней собраны виджеты, которые реализуют гайдлайны Material Design.
Как обновлять отображение виджетов?
Используя StatefulWidget и его State. Во Flutter есть 2 вида виджетов: StatelessWidget и StatefulWidget. Они работают одинаково, отличие только в состоянии при рендеринге.
StatelessWidget имеет неизменное состояние. Подойдёт для отображения текста, логотипа и т.д. Т.е. если элемент на экране не должен изменяться за всё время отображения, значит, он вам подходит. Его можно использовать и как контейнер для виджетов с изменяемым состоянием.
StatefulWidget имеет состояние State, в котором хранится информация о текущем состоянии. Если вы хотите изменить элемент на экране при выполнении какого-то действия (пришёл ответ с сервера, пользователь нажал на кнопку и т.д.) — это ваш вариант.
1) StatelessWidget — Text
2) StatefulWidget — при нажатии на кнопку (FloatingActionButton) текст в виджете Text меняется с «I Like Flutter» на «Flutter is Awesome!».
Как верстать экран с виджетами? Где файл XML layout?
Во Flutter нет XML-вёрстки экранов. Всё верстается в дереве виджетов прямо в коде.
Все дефолтные виджеты во Flutter можно посмотреть в widget catalog.
Как добавить или удалить компонент в вёрстку во время работы приложения?
Через функцию, которая будет возвращать нужный виджет в зависимости от состояния.
В Android можно сделать addView() или removeView() во ViewGroup. Во Flutter так нельзя, т.к. виджеты неизменны. Может изменяться только их состояние.
Как поменять Text на Button по нажатию на FloatingActionButton.
Как анимировать виджеты?
Используя класс AnimationController, который является наследником абстрактного класса Animation<T>. Кроме запуска анимации он может ставить её на паузу, перематывать, останавливать и проигрывать в обратную сторону. Работает с помощью Ticker, который сообщает о перерисовке экрана.
В Android можно создавать анимации в XML или анимировать View с помощью animate(). Во Flutter анимацию нужно писать в коде с помощью AnimationController.
Более подробно можно изучить в Animation & Motion widgets, Animations tutorial и Animations overview.
Fade-анимация лого Flutter.
Как использовать Canvas?
У Android и Flutter одинаковый API для Canvas, т.к. они используют одинаковый низкоуровневый движок Skia.
Нет.
У Flutter есть два класса для рисования на Canvas — CustomPaint и CustomPainter. Второй реализует ваш алгоритм отрисовки.
Подробнее тут: StackOverflow
Как создавать кастомные виджеты?
Компоновать виджеты внутри одного (вместо наследования).
В Android мы можем наследоваться от интересующей нас View и дописать свою логику. Во Flutter это похоже на ViewGroup, только виджет всегда наследуется от StatelessWidget или StatefulWidget. Т.е. нужно создать новый виджет и использовать в нём набор нужных вам виджетов в качестве параметров или полей.
Какой аналог Intent во Flutter?
Его нет. Для навигации между экранами используются классы Navigator и Route.
Для взаимодействия с внешними компонентами (например, камерой или файл-пикером) можно использовать плагины или нативную интеграцию на каждой платформе. Подробнее о нативной интеграции: Developing Packages and Plugins.
Во Flutter нет таких понятий, как Activity и Fragment. Есть Navigator (навигатор) и Routes (маршруты). Приложение на Flutter напоминает single-activity приложение, где разные экраны представляют собой разные фрагменты, а управляет ими FragmentManager. Navigator похож на FragmentManager по принципу работы. Он может сделать push() или pop() указанному вами маршруту. Route — это своего рода Fragment, но во Flutter его принято сравнивать с экраном или страницей.
В Android мы описываем все Activities, между которыми можем навигировать в AndroidManifest.xml.
Во Flutter есть два способа:
Как обрабатывать поступающие от других приложений интенты?
Взаимодействуя с Android-слоем приложения через MethodChannel.
Прописываем intent-filter в AndroidManifest.xml:
Обрабатываем Intent в MainActivity и из Flutter вызываем код через MethodChannel:
Запрашиваем данные, когда виджет начнёт отрисовываться:
Какой аналог у startActivityForResult()?
Ключевое слово await и результат Future-класса.
После вызова startActivityForResult() в Android нам нужно реализовывать обработку в onActivityResult(). Во Flutter ничего реализовывать не нужно, т.к. метод навигатора push() возвращает объект Future.
И когда на экране '/location' получили координаты, делаем pop():
Какой аналог у runOnUiThread() во Flutter?
В Dart реализована однопоточная модель исполнения, которая работает на изоляциях (Isolates). Для асинхронного выполнения используется async/await, с которым вы, возможно, знакомы из C#, JavaScript или Kotlin coroutines.
Выполнение запроса и возврата результата для обновления UI:
Когда ответ на запрос получен, нужно вызвать метод setState() для перерисовки дерева виджетов с новыми данными.
Загрузка и обновления данных в ListView:
Как выполнить код в фоновом потоке?
Как было сказано выше — с помощью async/await и изоляций (Isolate).
«Из коробки» в Android можно использовать AsyncTask. В нём нужно реализовать onPreExecute(), doInBackground(), onPostExecute(). Во Flutter «из коробки» вам просто нужно использовать async/await, об остальном позаботится Dart.
Здесь метод dataLoader() изолирован. В изоляциях вы можете запускать тяжелые операции, такие как парсинг больших JSON-ов, шифрование, обработка изображений и т.д.
Какой аналог у OkHttp во Flutter?
Во Flutter есть свой HTTP package.
Пока в HTTP Package реализованы не все фичи из OkHttp, поэтому многие недостающие из них вынесены в абстракции и вы можете реализовать их самостоятельно по мере необходимости.
Чтобы использовать HTTP package, добавьте его как зависимость в pubspec.yaml:
Для выполнения запроса вызовите await в async функции http.get():
Как показывать прогресс выполнения?
С помощью виджета ProgressIndicator.
Где хранить ресурсы разного разрешения?
В assets.
В Android у ресурсов есть папка res и есть assets. Во Flutter есть только assets. Папка assets может располагаться в любом месте проекта, главное, прописать путь к ней в файле pubspec.yaml.
Сопоставление размеров графических ресурсов в Android и Flutter.
Во Flutter для использования ресурсов в коде используется AssetManager или специализированные классы, начинающиеся с Asset.
AssetManager:
Расположение ресурсов:
Путь в pubspec.yaml файле:
Использование AssetImage:
Использование asset напрямую:
Где хранить строки? Как их локализовать?
Хранить в статичных полях. Локализовать с помощью intl package.
Какой аналог Gradle-файла? Как добавлять зависимости?
pubspec.yaml.
Flutter делегирует сборку нативным Android и iOS сборщикам. Посмотреть список всех популярных библиотек для Flutter можно в Pub.
Какой аналог у Activity и Fragment во Flutter?
Во Flutter всё — виджеты. Роль активити и фрагментов для работы с UI выполняют виджеты. А роль навигации, как было сказано в пункте про навигацию — Navigator и Route.
Flutter For Android Developers: How to design an Activity UI in Flutter.
Как обрабатывать события жизненного цикла?
С помощью WidgetsBinding и метода didChangeAppLifecycleState().
Во Flutter используется FlutterActivity в нативном коде, и движок Flutter делает обработку изменений состояния максимально незаметной. Но если вам всё же необходимо выполнить какую-либо работу в зависимости от состояния, то жизненный цикл немного отличается:
Более подробно это описано в AppLifecycleStatus documentation.
Какой аналог у LinearLayout?
Row — для горизонтального расположения, Column — для вертикального.
Flutter For Android Developers: How to design LinearLayout in Flutter?
Какой аналог у RelativeLayout?
Виджет Stack.
Какой аналог у ScrollView?
ListView с виджетами.
Как обрабатывать переходы между portrait и landscape?
FlutterView обрабатывает перевороты, если AndroidManifest.xml содержит
android:configChanges=«orientation|screenSize»
Как добавить слушатель onClick для виджета во Flutter?
Если виджет поддерживает клики, то в onPressed(). Если нет, то в onTap().
В onPressed():
В onTap():
Как обрабатывать другие жесты на виджетах?
Используя GestureDetector. Им можно обрабатывать следующие действия:
Обработка onDoubleTap:
Какой аналог у ListView во Flutter?
ListView.
Во Flutter не нужно думать об очистке и повторном использовании элементов (чем занимается ListView/RecyclerView в Android, используя паттерн ViewHolder).
Как узнать на каком элементе было нажатие?
Оборачивая элемент в GestureDetector.
Как динамически обновить ListView?
Если у вас небольшой набор данных, то это можно сделать через setState(). Если набор данных большой, то через ListView.Builder, который является аналогом RecyclerView.
Используя setState():
Используя ListView.Builder:
Как использовать кастомные шрифты?
Файл шрифтов нужно просто положить в папку (название придумайте сами) и указать к ней путь в pubspec.yaml.
Как стилизовать текстовые виджеты?
С помощью параметров:
Более подробно написано здесь: Retrieve the value of a text field.
Какой аналог у hint в TextInput?
Подсказку можно показать с помощью InputDecoration, передав его в качестве конструктора в виджет.
Как показать ошибки валидации?
Всё так же — с помощью InputDecoration и его состояния.
Как получить доступ к GPS?
С помощью плагина geolocator.
Как получить доступ к камере?
С помощью плагина image_picker.
Как авторизоваться через Facebook?
С помощью плагина flutter_facebook_login.
Как использовать Firebase?
Firebase поддерживает Flutter first party plugins.
Как делать нативные (платформенные) вставки кода?
Flutter использует EventBus для взаимодействия с платформенным кодом. Подробно тут: developing packages and plugins.
Как использовать NDK?
Написать свой плагин для взаимодействия вашего NDK-кода с Flutter. Пока Flutter не поддерживает прямое взаимодействие.
Как использовать тему (Theme) в приложении?
Используя виджет MaterialApp или WidgetApp как корневой в приложении.
Как получить доступ к Shared Preferences?
С помощью Shared_Preferences plugin (для NSUserDefaults в iOS тоже).
Как получить доступ к SQLite во Flutter?
С помощью плагина SQFlite.
Как показать push-уведомление?
С помощью плагина Firebase_Messaging.
Новые языки программирования и фреймворки появляются практически постоянно. И на старте трудно понять, что выстрелит и будет долго жить, а что забудут уже через год. Боб Мартин в своей книге «Идеальный программист» призывает нас изучать новые языки программирования и фреймворки. Чед Фаулер в книге «Программист-фанатик» советует всегда быть на острие технологий. Но как понять, что ты не ошибся с выбором? В 2016 году я обратил внимание на Kotlin, но из-за высокой загруженности не смог уделить ему достаточно времени до второй половины 2017. На старте многие относились к нему скептически, а сейчас это один из самых популярных языков программирования, и огромное количество разработчиков создают на нём свои продукты. Я чувствую, что за те полтора года мог бы получить более глубокое понимание тонкостей языка.
В том же 2016 году появился фреймворк Flutter на языке Dart. Но рост его популярности был не такой стремительный, и только в 2018 году о нём заговорили громко. Тогда мне тоже захотелось попробовать его в действии. И мне понравилось! Время покажет, какое будущее ждёт этот фреймворк, но кажется, он очень перспективный. (И если Google Fuchsia выстрелит, то, без сомнений, Flutter не останется позади). Изучать его или нет — решать вам! В любом случае, изучение нового — отличная разминка для мозга. На этом у меня всё. Да не сломает Google ваш Play!
Если вы хотите понять, чем хорош этот фреймворк, и оценить, сколько усилий придётся приложить, чтобы его использовать — добро пожаловать под кат.

Содержание:
- Views
- Какой аналог у View во Flutter?
- Как обновлять отображение виджетов?
- Как верстать экран с виджетами? Где файл XML layout?
- Как добавить или удалить компонент в вёрстку во время работы приложения?
- Как анимировать виджеты?
- Как использовать Canvas?
- Как создавать кастомные виджеты?
- Intents
- Какой аналог Intent во Flutter?
- Как обрабатывать поступающие от других приложений интенты?
- Какой аналог у startActivityForResult()?
- Async UI
- Какой аналог у runOnUiThread() во Flutter?
- Как выполнить код в фоновом потоке?
- Какой аналог у OkHttp во Flutter?
- Как показывать прогресс выполнения?
- Структура проекта и ресурсы
- Где хранить ресурсы разного разрешения?
- Где хранить строки? Как их локализовать?
- Какой аналог Gradle-файла? Как добавлять зависимости?
- Activities & Fragments
- Layouts
- Какой аналог у LinearLayout?
- Какой аналог у RelativeLayout?
- Какой аналог у ScrollView?
- Как обрабатывать переходы между portrait и landscape?
- Жесты и обработка touch event.
- ListViews & Adapters
- Какой аналог у ListView во Flutter?
- Как узнать на каком элементе было нажатие?
- Как динамически обновить ListView?
- Работа с текстом
- Форма ввода
- Плагины Flutter
- Как получить доступ к GPS?
- Как получить доступ к камере?
- Как авторизоваться через Facebook?
- Как использовать Firebase?
- Как делать нативные (платформенные) вставки кода?
- Как использовать NDK?
- Themes
- Базы данных и локальное хранилище
- Уведомления
Views
Вопрос:
Какой аналог у View во Flutter?
Ответ:
Widget
Отличия:
View — фактически то, что будет на экране. Для отображения изменений вызывается invalidate().
Widget — описание того, что будет на экране. Для изменения создаётся заново.
Дополнительная информация:
При запуске на самом Android под капотом Widget находится View. Flutter включает в себя библиотеку Material Components. В ней собраны виджеты, которые реализуют гайдлайны Material Design.
Вопрос:
Как обновлять отображение виджетов?
Ответ:
Используя StatefulWidget и его State. Во Flutter есть 2 вида виджетов: StatelessWidget и StatefulWidget. Они работают одинаково, отличие только в состоянии при рендеринге.
Отличия:
StatelessWidget имеет неизменное состояние. Подойдёт для отображения текста, логотипа и т.д. Т.е. если элемент на экране не должен изменяться за всё время отображения, значит, он вам подходит. Его можно использовать и как контейнер для виджетов с изменяемым состоянием.
StatefulWidget имеет состояние State, в котором хранится информация о текущем состоянии. Если вы хотите изменить элемент на экране при выполнении какого-то действия (пришёл ответ с сервера, пользователь нажал на кнопку и т.д.) — это ваш вариант.
Пример:
1) StatelessWidget — Text
Text(
'I like Flutter!',
style: TextStyle(fontWeight: FontWeight.bold),
);
2) StatefulWidget — при нажатии на кнопку (FloatingActionButton) текст в виджете Text меняется с «I Like Flutter» на «Flutter is Awesome!».
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// Этот виджет корневой в приложении.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// дефолтный текст
String textToShow = "Мне нравится Flutter";
void _updateText() {
setState(() {
// обновление текста
textToShow = "Flutter крутой!";
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(child: Text(textToShow)),
floatingActionButton: FloatingActionButton(
onPressed: _updateText,
tooltip: 'Обновить текст',
child: Icon(Icons.update),
),
);
}
}
Вопрос:
Как верстать экран с виджетами? Где файл XML layout?
Ответ:
Во Flutter нет XML-вёрстки экранов. Всё верстается в дереве виджетов прямо в коде.
Пример:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: MaterialButton(
onPressed: () {},
child: Text('Hello'),
padding: EdgeInsets.only(left: 10.0, right: 10.0),
),
),
);
}
Все дефолтные виджеты во Flutter можно посмотреть в widget catalog.
Вопрос:
Как добавить или удалить компонент в вёрстку во время работы приложения?
Ответ:
Через функцию, которая будет возвращать нужный виджет в зависимости от состояния.
Отличия:
В Android можно сделать addView() или removeView() во ViewGroup. Во Flutter так нельзя, т.к. виджеты неизменны. Может изменяться только их состояние.
Пример:
Как поменять Text на Button по нажатию на FloatingActionButton.
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// Этот виджет корневой в приложении.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
// Дефолтное значение для флага
bool toggle = true;
void _toggle() {
setState(() {
toggle = !toggle;
});
}
_getToggleChild() {
if (toggle) {
return Text('Toggle One');
} else {
return MaterialButton(onPressed: () {}, child: Text('Toggle Two'));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: _getToggleChild(),
),
floatingActionButton: FloatingActionButton(
onPressed: _toggle,
tooltip: 'Update Text',
child: Icon(Icons.update),
),
);
}
}
Вопрос:
Как анимировать виджеты?
Ответ:
Используя класс AnimationController, который является наследником абстрактного класса Animation<T>. Кроме запуска анимации он может ставить её на паузу, перематывать, останавливать и проигрывать в обратную сторону. Работает с помощью Ticker, который сообщает о перерисовке экрана.
Отличия:
В Android можно создавать анимации в XML или анимировать View с помощью animate(). Во Flutter анимацию нужно писать в коде с помощью AnimationController.
Дополнительная информация:
Более подробно можно изучить в Animation & Motion widgets, Animations tutorial и Animations overview.
Пример:
Fade-анимация лого Flutter.
import 'package:flutter/material.dart';
void main() {
runApp(FadeAppTest());
}
class FadeAppTest extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fade Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyFadeTest(title: 'Fade Demo'),
);
}
}
class MyFadeTest extends StatefulWidget {
MyFadeTest({Key key, this.title}) : super(key: key);
final String title;
@override
_MyFadeTest createState() => _MyFadeTest();
}
class _MyFadeTest extends State<MyFadeTest> with TickerProviderStateMixin {
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
child: FadeTransition(
opacity: curve,
child: FlutterLogo(
size: 100.0,
)))),
floatingActionButton: FloatingActionButton(
tooltip: 'Fade',
child: Icon(Icons.brush),
onPressed: () {
controller.forward();
},
),
);
}
}
Вопрос:
Как использовать Canvas?
Ответ:
У Android и Flutter одинаковый API для Canvas, т.к. они используют одинаковый низкоуровневый движок Skia.
Отличия:
Нет.
Дополнительная информация:
У Flutter есть два класса для рисования на Canvas — CustomPaint и CustomPainter. Второй реализует ваш алгоритм отрисовки.
Подробнее тут: StackOverflow
Пример:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: DemoApp()));
class DemoApp extends StatelessWidget {
Widget build(BuildContext context) => Scaffold(body: Signature());
}
class Signature extends StatefulWidget {
SignatureState createState() => SignatureState();
}
class SignatureState extends State<Signature> {
List<Offset> _points = <Offset>[];
Widget build(BuildContext context) {
return GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox referenceBox = context.findRenderObject();
Offset localPosition =
referenceBox.globalToLocal(details.globalPosition);
_points = List.from(_points)..add(localPosition);
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
child: CustomPaint(painter: SignaturePainter(_points), size: Size.infinite),
);
}
}
class SignaturePainter extends CustomPainter {
SignaturePainter(this.points);
final List<Offset> points;
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.black
..strokeCap = StrokeCap.round
..strokeWidth = 5.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null)
canvas.drawLine(points[i], points[i + 1], paint);
}
}
bool shouldRepaint(SignaturePainter other) => other.points != points;
}
Вопрос:
Как создавать кастомные виджеты?
Ответ:
Компоновать виджеты внутри одного (вместо наследования).
Отличия:
В Android мы можем наследоваться от интересующей нас View и дописать свою логику. Во Flutter это похоже на ViewGroup, только виджет всегда наследуется от StatelessWidget или StatefulWidget. Т.е. нужно создать новый виджет и использовать в нём набор нужных вам виджетов в качестве параметров или полей.
Пример:
class CustomButton extends StatelessWidget {
final String label;
CustomButton(this.label);
@override
Widget build(BuildContext context) {
return RaisedButton(onPressed: () {}, child: Text(label));
}
}
@override
Widget build(BuildContext context) {
return Center(
child: CustomButton("Hello"),
);
}
Intents
Вопрос:
Какой аналог Intent во Flutter?
Ответ:
Его нет. Для навигации между экранами используются классы Navigator и Route.
Для взаимодействия с внешними компонентами (например, камерой или файл-пикером) можно использовать плагины или нативную интеграцию на каждой платформе. Подробнее о нативной интеграции: Developing Packages and Plugins.
Отличия:
Во Flutter нет таких понятий, как Activity и Fragment. Есть Navigator (навигатор) и Routes (маршруты). Приложение на Flutter напоминает single-activity приложение, где разные экраны представляют собой разные фрагменты, а управляет ими FragmentManager. Navigator похож на FragmentManager по принципу работы. Он может сделать push() или pop() указанному вами маршруту. Route — это своего рода Fragment, но во Flutter его принято сравнивать с экраном или страницей.
В Android мы описываем все Activities, между которыми можем навигировать в AndroidManifest.xml.
Во Flutter есть два способа:
- описать Map с именами Route (MaterialApp);
- напрямую навигировать к Route (WidgetApp).
Пример:
void main() {
runApp(MaterialApp(
home: MyAppHome(), // becomes the route named '/'
routes: <String, WidgetBuilder> {
'/a': (BuildContext context) => MyPage(title: 'page A'),
'/b': (BuildContext context) => MyPage(title: 'page B'),
'/c': (BuildContext context) => MyPage(title: 'page C'),
},
));
}
Navigator.of(context).pushNamed('/b');
Вопрос:
Как обрабатывать поступающие от других приложений интенты?
Ответ:
Взаимодействуя с Android-слоем приложения через MethodChannel.
Пример:
Прописываем intent-filter в AndroidManifest.xml:
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- ... -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Обрабатываем Intent в MainActivity и из Flutter вызываем код через MethodChannel:
package com.example.shared;
import android.content.Intent;
import android.os.Bundle;
import java.nio.ByteBuffer;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
private String sharedText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
Intent intent = getIntent();
String action = intent.getAction();
String type = intent.getType();
if (Intent.ACTION_SEND.equals(action) && type != null) {
if ("text/plain".equals(type)) {
handleSendText(intent); // Handle text being sent
}
}
new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(
new MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.Result result) {
if (call.method.contentEquals("getSharedText")) {
result.success(sharedText);
sharedText = null;
}
}
});
}
void handleSendText(Intent intent) {
sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
}
}
Запрашиваем данные, когда виджет начнёт отрисовываться:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample Shared App Handler',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
static const platform = const MethodChannel('app.channel.shared.data');
String dataShared = "No data";
@override
void initState() {
super.initState();
getSharedText();
}
@override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: Text(dataShared)));
}
getSharedText() async {
var sharedData = await platform.invokeMethod("getSharedText");
if (sharedData != null) {
setState(() {
dataShared = sharedData;
});
}
}
}
Вопрос:
Какой аналог у startActivityForResult()?
Ответ:
Ключевое слово await и результат Future-класса.
Отличия:
После вызова startActivityForResult() в Android нам нужно реализовывать обработку в onActivityResult(). Во Flutter ничего реализовывать не нужно, т.к. метод навигатора push() возвращает объект Future.
Пример:
Map coordinates = await Navigator.of(context).pushNamed('/location');
И когда на экране '/location' получили координаты, делаем pop():
Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});
Async UI
Вопрос:
Какой аналог у runOnUiThread() во Flutter?
Ответ:
В Dart реализована однопоточная модель исполнения, которая работает на изоляциях (Isolates). Для асинхронного выполнения используется async/await, с которым вы, возможно, знакомы из C#, JavaScript или Kotlin coroutines.
Пример:
Выполнение запроса и возврата результата для обновления UI:
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
Когда ответ на запрос получен, нужно вызвать метод setState() для перерисовки дерева виджетов с новыми данными.
Пример:
Загрузка и обновления данных в ListView:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row ${widgets[i]["title"]}")
);
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
Вопрос:
Как выполнить код в фоновом потоке?
Ответ:
Как было сказано выше — с помощью async/await и изоляций (Isolate).
Отличия:
«Из коробки» в Android можно использовать AsyncTask. В нём нужно реализовать onPreExecute(), doInBackground(), onPostExecute(). Во Flutter «из коробки» вам просто нужно использовать async/await, об остальном позаботится Dart.
Пример:
Здесь метод dataLoader() изолирован. В изоляциях вы можете запускать тяжелые операции, такие как парсинг больших JSON-ов, шифрование, обработка изображений и т.д.
loadData() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends its SendPort as the first message
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts");
setState(() {
widgets = msg;
});
}
// The entry point for the isolate
static dataLoader(SendPort sendPort) async {
// Open the ReceivePort for incoming messages.
ReceivePort port = ReceivePort();
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
await for (var msg in port) {
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// Lots of JSON to parse
replyTo.send(json.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
Полноценный запускаемый пример:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:isolate';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
if (widgets.length == 0) {
return true;
}
return false;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(dataLoader, receivePort.sendPort);
// The 'echo' isolate sends its SendPort as the first message
SendPort sendPort = await receivePort.first;
List msg = await sendReceive(sendPort, "https://jsonplaceholder.typicode.com/posts");
setState(() {
widgets = msg;
});
}
// the entry point for the isolate
static dataLoader(SendPort sendPort) async {
// Open the ReceivePort for incoming messages.
ReceivePort port = ReceivePort();
// Notify any other isolates what port this isolate listens to.
sendPort.send(port.sendPort);
await for (var msg in port) {
String data = msg[0];
SendPort replyTo = msg[1];
String dataURL = data;
http.Response response = await http.get(dataURL);
// Lots of JSON to parse
replyTo.send(json.decode(response.body));
}
}
Future sendReceive(SendPort port, msg) {
ReceivePort response = ReceivePort();
port.send([msg, response.sendPort]);
return response.first;
}
}
Вопрос:
Какой аналог у OkHttp во Flutter?
Ответ:
Во Flutter есть свой HTTP package.
Дополнительная информация:
Пока в HTTP Package реализованы не все фичи из OkHttp, поэтому многие недостающие из них вынесены в абстракции и вы можете реализовать их самостоятельно по мере необходимости.
Пример:
Чтобы использовать HTTP package, добавьте его как зависимость в pubspec.yaml:
dependencies:
...
http: ^0.11.3+16
Для выполнения запроса вызовите await в async функции http.get():
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
[...]
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
Вопрос:
Как показывать прогресс выполнения?
Ответ:
С помощью виджета ProgressIndicator.
Пример:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = [];
@override
void initState() {
super.initState();
loadData();
}
showLoadingDialog() {
return widgets.length == 0;
}
getBody() {
if (showLoadingDialog()) {
return getProgressDialog();
} else {
return getListView();
}
}
getProgressDialog() {
return Center(child: CircularProgressIndicator());
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: getBody());
}
ListView getListView() => ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
});
Widget getRow(int i) {
return Padding(padding: EdgeInsets.all(10.0), child: Text("Row ${widgets[i]["title"]}"));
}
loadData() async {
String dataURL = "https://jsonplaceholder.typicode.com/posts";
http.Response response = await http.get(dataURL);
setState(() {
widgets = json.decode(response.body);
});
}
}
Структура проекта и ресурсы
Вопрос:
Где хранить ресурсы разного разрешения?
Ответ:
В assets.
Отличия:
В Android у ресурсов есть папка res и есть assets. Во Flutter есть только assets. Папка assets может располагаться в любом месте проекта, главное, прописать путь к ней в файле pubspec.yaml.
Дополнительная информация:
Сопоставление размеров графических ресурсов в Android и Flutter.
Android density qualifier | Flutter pixel ratio |
ldpi | 0.75x |
mdpi | 1.0x |
hdpi | 1.5x |
xhdpi | 2.0x |
xxhdpi | 3.0x |
xxxhdpi | 4.0x |
Пример:
AssetManager:
val flutterAssetStream = assetManager.open("flutter_assets/assets/my_flutter_asset.png")
Расположение ресурсов:
images/my_icon.png // Base: 1.0x image
images/2.0x/my_icon.png // 2.0x image
images/3.0x/my_icon.png // 3.0x image
Путь в pubspec.yaml файле:
assets:
- images/my_icon.jpeg
Использование AssetImage:
return AssetImage("images/a_dot_burr.jpeg");
Использование asset напрямую:
@override
Widget build(BuildContext context) {
return Image.asset("images/my_image.png");
}
Вопрос:
Где хранить строки? Как их локализовать?
Ответ:
Хранить в статичных полях. Локализовать с помощью intl package.
Пример:
class Strings {
static String welcomeMessage = "Welcome To Flutter";
}
Text(Strings.welcomeMessage)
Вопрос:
Какой аналог Gradle-файла? Как добавлять зависимости?
Ответ:
pubspec.yaml.
Дополнительная информация:
Flutter делегирует сборку нативным Android и iOS сборщикам. Посмотреть список всех популярных библиотек для Flutter можно в Pub.
Activities & Fragments
Вопрос:
Какой аналог у Activity и Fragment во Flutter?
Ответ:
Во Flutter всё — виджеты. Роль активити и фрагментов для работы с UI выполняют виджеты. А роль навигации, как было сказано в пункте про навигацию — Navigator и Route.
Дополнительная информация:
Flutter For Android Developers: How to design an Activity UI in Flutter.
Вопрос:
Как обрабатывать события жизненного цикла?
Ответ:
С помощью WidgetsBinding и метода didChangeAppLifecycleState().
Дополнительная информация:
Во Flutter используется FlutterActivity в нативном коде, и движок Flutter делает обработку изменений состояния максимально незаметной. Но если вам всё же необходимо выполнить какую-либо работу в зависимости от состояния, то жизненный цикл немного отличается:
- inactive — этот метод есть только в iOS, в Android нет аналога;
- paused — аналогичен onPause() в Android;
- resumed — аналогичен onPostResume() в Android;
- suspending — аналогичен onStop в Android, в iOS нет аналога.
Более подробно это описано в AppLifecycleStatus documentation.
Пример:
import 'package:flutter/widgets.dart';
class LifecycleWatcher extends StatefulWidget {
@override
_LifecycleWatcherState createState() => _LifecycleWatcherState();
}
class _LifecycleWatcherState extends State<LifecycleWatcher> with WidgetsBindingObserver {
AppLifecycleState _lastLifecycleState;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_lastLifecycleState = state;
});
}
@override
Widget build(BuildContext context) {
if (_lastLifecycleState == null)
return Text('This widget has not observed any lifecycle changes.', textDirection: TextDirection.ltr);
return Text('The most recent lifecycle state this widget observed was: $_lastLifecycleState.',
textDirection: TextDirection.ltr);
}
}
void main() {
runApp(Center(child: LifecycleWatcher()));
}
Layouts
Вопрос:
Какой аналог у LinearLayout?
Ответ:
Row — для горизонтального расположения, Column — для вертикального.
Дополнительная информация:
Flutter For Android Developers: How to design LinearLayout in Flutter?
Пример:
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Column One'),
Text('Column Two'),
Text('Column Three'),
Text('Column Four'),
],
);
}
Вопрос:
Какой аналог у RelativeLayout?
Ответ:
Виджет Stack.
Подробнее:
StackOverflowВопрос:
Какой аналог у ScrollView?
Ответ:
ListView с виджетами.
Пример:
@override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
Text('Row One'),
Text('Row Two'),
Text('Row Three'),
Text('Row Four'),
],
);
}
Вопрос:
Как обрабатывать переходы между portrait и landscape?
Ответ:
FlutterView обрабатывает перевороты, если AndroidManifest.xml содержит
android:configChanges=«orientation|screenSize»
Жесты и обработка touch event
Вопрос:
Как добавить слушатель onClick для виджета во Flutter?
Ответ:
Если виджет поддерживает клики, то в onPressed(). Если нет, то в onTap().
Пример:
В onPressed():
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
print("click");
},
child: Text("Button"));
}
В onTap():
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
child: FlutterLogo(
size: 200.0,
),
onTap: () {
print("tap");
},
),
));
}
}
Вопрос:
Как обрабатывать другие жесты на виджетах?
Ответ:
Используя GestureDetector. Им можно обрабатывать следующие действия:
Tap
Double tap
Long press
Vertical drag
Horizontal drag
Пример:
Обработка onDoubleTap:
AnimationController controller;
CurvedAnimation curve;
@override
void initState() {
controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
curve = CurvedAnimation(parent: controller, curve: Curves.easeIn);
}
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: GestureDetector(
child: RotationTransition(
turns: curve,
child: FlutterLogo(
size: 200.0,
)),
onDoubleTap: () {
if (controller.isCompleted) {
controller.reverse();
} else {
controller.forward();
}
},
),
));
}
}
ListViews & Adapters
Вопрос:
Какой аналог у ListView во Flutter?
Ответ:
ListView.
Отличия:
Во Flutter не нужно думать об очистке и повторном использовании элементов (чем занимается ListView/RecyclerView в Android, используя паттерн ViewHolder).
Пример:
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: _getListData()),
);
}
_getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(Padding(padding: EdgeInsets.all(10.0), child: Text("Row $i")));
}
return widgets;
}
}
Вопрос:
Как узнать на каком элементе было нажатие?
Ответ:
Оборачивая элемент в GestureDetector.
Пример:
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: _getListData()),
);
}
_getListData() {
List<Widget> widgets = [];
for (int i = 0; i < 100; i++) {
widgets.add(GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i")),
onTap: () {
print('row tapped');
},
));
}
return widgets;
}
}
Вопрос:
Как динамически обновить ListView?
Ответ:
Если у вас небольшой набор данных, то это можно сделать через setState(). Если набор данных большой, то через ListView.Builder, который является аналогом RecyclerView.
Пример:
Используя setState():
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = <Widget>[];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView(children: widgets),
);
}
Widget getRow(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i")),
onTap: () {
setState(() {
widgets = List.from(widgets);
widgets.add(getRow(widgets.length + 1));
print('row $i');
});
},
);
}
}
Используя ListView.Builder:
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
List widgets = <Widget>[];
@override
void initState() {
super.initState();
for (int i = 0; i < 100; i++) {
widgets.add(getRow(i));
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: ListView.builder(
itemCount: widgets.length,
itemBuilder: (BuildContext context, int position) {
return getRow(position);
}));
}
Widget getRow(int i) {
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: Text("Row $i")),
onTap: () {
setState(() {
widgets.add(getRow(widgets.length + 1));
print('row $i');
});
},
);
}
}
Работа с текстом
Вопрос:
Как использовать кастомные шрифты?
Ответ:
Файл шрифтов нужно просто положить в папку (название придумайте сами) и указать к ней путь в pubspec.yaml.
Пример:
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
- style: italic
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: Text(
'This is a custom font text',
style: TextStyle(fontFamily: 'MyCustomFont'),
),
),
);
}
Вопрос:
Как стилизовать текстовые виджеты?
Ответ:
С помощью параметров:
- color;
- decoration;
- decorationColor;
- decorationStyle;
- fontFamily;
- fontSize;
- fontStyle;
- fontWeight;
- hashCode;
- height;
- inherit;
- letterSpacing;
- textBaseline;
- wordSpacing.
Форма ввода
Более подробно написано здесь: Retrieve the value of a text field.
Вопрос:
Какой аналог у hint в TextInput?
Ответ:
Подсказку можно показать с помощью InputDecoration, передав его в качестве конструктора в виджет.
Пример:
body: Center(
child: TextField(
decoration: InputDecoration(hintText: "This is a hint"),
)
)
Вопрос:
Как показать ошибки валидации?
Ответ:
Всё так же — с помощью InputDecoration и его состояния.
Пример:
import 'package:flutter/material.dart';
void main() {
runApp(SampleApp());
}
class SampleApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SampleAppPage(),
);
}
}
class SampleAppPage extends StatefulWidget {
SampleAppPage({Key key}) : super(key: key);
@override
_SampleAppPageState createState() => _SampleAppPageState();
}
class _SampleAppPageState extends State<SampleAppPage> {
String _errorText;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Sample App"),
),
body: Center(
child: TextField(
onSubmitted: (String text) {
setState(() {
if (!isEmail(text)) {
_errorText = 'Error: This is not an email';
} else {
_errorText = null;
}
});
},
decoration: InputDecoration(hintText: "This is a hint", errorText: _getErrorText()),
),
),
);
}
_getErrorText() {
return _errorText;
}
bool isEmail(String em) {
String emailRegexp =
r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';
RegExp regExp = RegExp(emailRegexp);
return regExp.hasMatch(em);
}
}
Плагины Flutter
Вопрос:
Как получить доступ к GPS?
Ответ:
С помощью плагина geolocator.
Вопрос:
Как получить доступ к камере?
Ответ:
С помощью плагина image_picker.
Вопрос:
Как авторизоваться через Facebook?
Ответ:
С помощью плагина flutter_facebook_login.
Вопрос:
Как использовать Firebase?
Ответ:
Firebase поддерживает Flutter first party plugins.
- firebase_admob для Firebase AdMob.
- firebase_analytics для Firebase Analytics.
- firebase_auth для Firebase Auth.
- firebase_database для Firebase RTDB.
- firebase_storage для Firebase Cloud Storage.
- firebase_messaging для Firebase Messaging (FCM).
- flutter_firebase_ui для быстрой интеграции Firebase Auth (Facebook, Google, Twitter and e-mail).
- cloud_firestore для Firebase Cloud Firestore.
Вопрос:
Как делать нативные (платформенные) вставки кода?
Ответ:
Flutter использует EventBus для взаимодействия с платформенным кодом. Подробно тут: developing packages and plugins.
Вопрос:
Как использовать NDK?
Ответ:
Написать свой плагин для взаимодействия вашего NDK-кода с Flutter. Пока Flutter не поддерживает прямое взаимодействие.
Themes
Вопрос:
Как использовать тему (Theme) в приложении?
Ответ:
Используя виджет MaterialApp или WidgetApp как корневой в приложении.
Пример:
class SampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sample App',
theme: ThemeData(
primarySwatch: Colors.blue,
textSelectionColor: Colors.red
),
home: SampleAppPage(),
);
}
}
Базы данных и локальное хранилище
Вопрос:
Как получить доступ к Shared Preferences?
Ответ:
С помощью Shared_Preferences plugin (для NSUserDefaults в iOS тоже).
Пример:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: RaisedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter'),
),
),
),
),
);
}
_incrementCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int counter = (prefs.getInt('counter') ?? 0) + 1;
print('Pressed $counter times.');
prefs.setInt('counter', counter);
}
Вопрос:
Как получить доступ к SQLite во Flutter?
Ответ:
С помощью плагина SQFlite.
Уведомления
Вопрос:
Как показать push-уведомление?
Ответ:
С помощью плагина Firebase_Messaging.
Заключение
Новые языки программирования и фреймворки появляются практически постоянно. И на старте трудно понять, что выстрелит и будет долго жить, а что забудут уже через год. Боб Мартин в своей книге «Идеальный программист» призывает нас изучать новые языки программирования и фреймворки. Чед Фаулер в книге «Программист-фанатик» советует всегда быть на острие технологий. Но как понять, что ты не ошибся с выбором? В 2016 году я обратил внимание на Kotlin, но из-за высокой загруженности не смог уделить ему достаточно времени до второй половины 2017. На старте многие относились к нему скептически, а сейчас это один из самых популярных языков программирования, и огромное количество разработчиков создают на нём свои продукты. Я чувствую, что за те полтора года мог бы получить более глубокое понимание тонкостей языка.
В том же 2016 году появился фреймворк Flutter на языке Dart. Но рост его популярности был не такой стремительный, и только в 2018 году о нём заговорили громко. Тогда мне тоже захотелось попробовать его в действии. И мне понравилось! Время покажет, какое будущее ждёт этот фреймворк, но кажется, он очень перспективный. (И если Google Fuchsia выстрелит, то, без сомнений, Flutter не останется позади). Изучать его или нет — решать вам! В любом случае, изучение нового — отличная разминка для мозга. На этом у меня всё. Да не сломает Google ваш Play!