Разрабатывая приложения на Flutter с нативным дизайном под iOS и Android, я столкнулся с тем, что мне пришлось писать кучу условий для проверки платформы на которой исполняется код, при этом делая две схожие реализации UI. Мне это не понравилось и я рад, что мне попалась статья, которая помогла мне решить мою проблему.
Об авторе: Swav Kulinski — Lead Android Developer в The App Business, Flutter GDE.
Далее речь пойдет от лица автора.
Flutter — это решение для кроссплатформенной разработки мобильных приложений, которое обещает абсолютную свободу в создании пользовательского интерфейса независимо от платформы. Это достигается тем, что фреймворк использует собственный механизм рендеринга для рисования виджетов.
Проблема многих кроссплатформенных решений в том, что они выглядят одинаково на iPhone и Android. Но что делать компаниям, которым нужно поддерживать внешний вид платформы? Которым нужно использовать Material Design для Android и Human Interface для iOS? Для таких компаний подходит Flutter, оснащенный пакетами, которые содержат набор собственных виджетов для iOS и Android, под названием Cupertino и Material.
Flutter является кроссплатформенным по своей сути, но когда дело доходит до верстки UI, который должен выглядеть нативно для каждой из платформ, это не совсем так. Приходится делать две схожих реализации верстки. Это происходит из-за того что, например, для IOS, CupertinoNavigationBar должен находиться в CupertinoPageScaffold, а в Android, AppBar внутри Scaffold. Данная особенность уменьшает преимущество кроссплатформенности во Flutter, так как для каждой платформы приходится писать свой код для верстки.
Я хотел бы предложить подход, который позволяет создавать абстрактные интерфейсы и корректировать внешний вид и поведение приложения в зависимости от того, на какой платформе оно работает.
Рассмотрим следующие конструкторы для двух виджетов, которые предоставляют верхнюю панель приложений:
CupertinoNavigationBar ({
this.leading,
this.middle,
})
и
AppBar ({
this.leading,
this.title
})
Оба вышеупомянутых виджета играют одну и ту же роль, являясь верхней панелью в приложении в стиле Cupertino и Material. Но все же они требуют входные данные в разном виде. Необходимо решение, которое будет абстрагироваться от того, как мы создаем конкретные виджеты, и при этом обеспечивать реализацию в зависимости от платформы. Воспользуемся старым добрым фабричным методом.
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
abstract class PlatformWidget<I extends Widget, A extends Widget>
extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return createAndroidWidget(context);
} else if (Platform.isIOS) {
return createIosWidget(context);
}
// platform not supported returns an empty widget
return Container();
}
I createIosWidget(BuildContext context);
A createAndroidWidget(BuildContext context);
}
По сути, вышеуказанный класс — это фабрика платформозависимых виджетов, которая при реализации может предоставить пользовательский конструктор (или несколько именованных конструкторов), поддерживающих потребности обоих конкретных классов.
Я выбрал generics для возврата конкретных классов, потому что иногда родительскому виджету нужен определенный тип, который должен быть возвращен из дочернего виджета.
Теперь мы можем реализовать наш первый виджет.
class PlatformAppBar extends PlatformWidget<CupertinoNavigationBar, AppBar> {
final Widget leading;
final Widget title;
PlatformAppBar({
this.leading,
this.title,
});
@override
AppBar createAndroidWidget(BuildContext context) {
return AppBar(
leading: leading,
title: title,
);
}
@override
CupertinoNavigationBar createIosWidget(BuildContext context) {
return CupertinoNavigationBar(
leading: leading,
middle: title,
);
}
}
Довольно просто, правда? Обратите внимание, что мы полностью контролируем содержимое виджетов панели приложений.
Предположим, что мы реализовали Scaffold и Button.
class PlatformScaffoldWidget extends PlatformWidget<CupertinoPageScaffold,Scaffold> {
...
}
class PlatformButton extends PlatformWidget<CupertinoButton,FlatButton> {
...
}
Теперь мы готовы использовать и переиспользовать наши виджеты, ориентированные на платформу.
Widget build(BuildContext context) {
return PlatformScaffoldWidget(
appBar: PlatformAppBarWidget(
leading: PlatformButton(
child: Icon(Icons.ic_arrow_back),
onClick: () => _handleBack()
),
title: Text("I love my Platform"),
),
content: ...
);
}
Готово! Вышеприведенный код отобразит на обеих платформах панель приложений, ориентированную на платформу, и наш PlatformScaffoldWidget готов к повторному использованию в остальной части приложения без каких-либо проблем.
Код можно посмотреть на гитхабе.
Комментарии (5)
Prosium
19.10.2018 12:09Очередной надмозговый перевод.
Неужели вообще никто уже книг не читает?
Снятся ли приложениям виджеты.ivanlardis Автор
19.10.2018 12:13Я пытался сослаться на роман. В одном из переводов все-таки Мечтают, а не Снятся
stu5002
19.10.2018 12:18Спасибо большой за статью!
Только вчера интересовался, может ли флаттер выглядеть нативно без проверки платформы. Оказывается, что не может.
Вообще, очень интересный инструмент, но пока что смущают подобные мелочи.
Есть еще пара вопросов по флаттеру:
1. Где можно искать библиотеки, созданные сообществом? В смысле есть какой-то аналог js.coach для Реакта и mvnrepository.com для Java?
2. Существует ли какой-то способ отправки пуш-уведомлений во Флаттер-приложение без использования Firebase? Имею ввиду из коробки, без написания своей нативной библиотеки.ivanlardis Автор
19.10.2018 12:251) Искать библиотеки можно тут
2) Не могу компетентно ответить на этот вопрос. Но есть русскоязычный чат о Dart в telegram, думаю там вам с удовольствием ответят.
Neikist
Прямо праздник статей про флаттер в последнее время на хабре, не может не радовать)