Hola, Amigos! На связи Саша Чаплыгин, Flutter-dev. Нашел интересную статью от автора Deep Bhavsar, которая помогла мне детальнее разобраться с Flutter 3, а еще я сам теперь применяю советы из нее в разработке. Будет полезно почитать разработчикам всех мастей, эта информация окажется весьма полезной для решения ваших повседневных рабочих задач.
Я и мои коллеги пишем о том, что такое быть Flutter-разработчиками и рассказываем о внутренней кухне проектов в телеграм-канале Flutter.Много. Заходите, будем вас ждать :)
Передовые методики разработки приложений с помощью Flutter
В этой публикации вы познакомитесь с передовыми методиками, предназначенными для разработчиков Flutter, которые помогут вам улучшить качество программного кода, его читаемость, простоту сопровождения и продуктивность.
1. Поддержание чистоты функции build.
Для эффективной работы функции build необходимо обеспечить ее максимальную «чистоту», т.е. отсутствие ненужных элементов программного кода. Это связано с тем, что существуют определенные внешние факторы, которые могут спровоцировать создание нового виджета (Widget build), ниже приведены некоторые примеры:
навигация в приложении;
изменение размера экрана, обычно из-за показа/скрытия клавиатуры или изменения ориентации экрана;
родительский виджет воссоздал свой дочерний виджет;
виджет Inherited Widget зависит от изменения значений (Class. of(context) pattern).
Пример неправильного программного кода выглядит так:
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: httpCall(),
builder: (context, snapshot) {
// create some layout here
},
);
}
Пример правильного программного кода должен выглядеть так:
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
Future<int> future;
@override
void initState() {
future = repository.httpCall();
super.initState();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) {
// create some layout here
},
);
}
}
2. Понимание принципов работы концепции constrains Flutter.
Существует одно широко распространенное практическое правило в отношении макетов Flutter, которое должен соблюдать каждый разработчик приложений Flutter: ограничения уменьшаются, размеры увеличиваются, а родительский элемент определяет положение. Давайте разберемся в этом вопросе подробнее.
Виджет имеет свои собственные ограничения, которые определяются его родительским виджетом. Ограничение – это набор из четырех парных значений: минимальной и максимальной ширины, а также минимальной и максимальной высоты.
Далее виджет просматривает свой собственный список дочерних элементов. Виджет по очереди задает своим дочерним элементам ограничения (которые могут быть разными для каждого элемента), а затем запрашивает у каждого такого элемента информацию о желаемом размере.
Далее виджет размещает один за другим свои дочерние элементы (горизонтально по оси x и вертикально по оси y). Затем виджет передает своему родительскому элементу информацию о своем собственном размере (в рамках исходных ограничений).
Во Flutter все виджеты определяют себя на основе ограничений своего родительского элемента или ограничений своего бокса. Однако здесь имеется ряд ограничений.
Например, если у вас есть дочерний виджет внутри родительского виджета, и вы хотите задать его размер. Виджет не может иметь какой-либо размер сам по себе. Размер виджета должен находиться в пределах ограничений, установленных его родительским элементом.
3. Рациональное использование операторов для сокращения количества выполняемых строк.
Используйте оператор Cascades
Если необходимо выполнить последовательность операций для одного и того же объекта, то следует выбрать оператор Cascades(...).
//Do
var path = Path()
..lineTo(0, size.height)
..lineTo(size.width, size.height)
..lineTo(size.width, 0)
..close();
//Do not
var path = Path();
path.lineTo(0, size.height);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0);
path.close();
Используйте коллекции spread
Вы можете использовать коллекции spread, когда существующие элементы уже хранятся в другой коллекции. Синтаксис коллекций spread обеспечивает более простой программный код.
//Do
var y = [4,5,6];
var x = [1,2,...y];
//Do not
var y = [4,5,6];
var x = [1,2];
x.addAll(y);
Используйте операторы Null safe (??) и Null aware (?.)
В условных выражениях всегда используйте операторы ?? (if null) и ?. (null aware) вместо проверки на неопределенное значение (null).
//Do
v = a ?? b;
//Do not
v = a == null ? b : a;
//Do
v = a?.b;
//Do not
v = a == null ? null : a.b;
Избегайте использования оператора as, вместо него необходимо использовать оператор is.
Как правило, оператор приведения as создает исключение, если приведение невозможно. В таких случаях можно использовать оператор is.
//Do
if (item is Animal)
item.name = 'Lion';
//Do not
(item as Animal).name = 'Lion';
4. Использование функции потоков данных Streams только в случае необходимости.
Хотя функция Streams представляет собой достаточно мощный и эффективный инструмент, она создает большую нагрузку на аппаратные ресурсы.
Неправильная реализация может привести к увеличению потребления ресурсов памяти и процессора. Но дело не только в этом. Если вы забудете остановить выполнение функции Streams, это приведет к утечке памяти.
Поэтому в таких случаях для реактивного пользовательского интерфейса можно использовать другой инструмент, который потребляет меньше ресурсов памяти, например, ChangeNotifier. Для более продвинутых функций мы можем выбрать библиотеку Bloc, которая обеспечивает эффективное использование аппаратных ресурсов и предлагает набор простых инструментов для создания реактивного пользовательского интерфейса.
Если функция Streams не используется, ее данные будут эффективно удаляться. Дело в том, что если вы просто удалите переменную, этого будет недостаточно, чтобы убедиться, что она не используется. Она может продолжать работать в фоновом режиме.
Вам нужно вызвать Sink.close(), чтобы остановить соответствующий StreamController, чтобы убедиться, что ресурсы памяти могут быть позже освобождены с помощью функции очистки GC.
Для этого необходимо использовать метод StatefulWidget.dispose:
abstract class MyBloc {
Sink foo;
Sink bar;
}
class MyWiget extends StatefulWidget {
@override
_MyWigetState createState() => _MyWigetState();
}
class _MyWigetState extends State<MyWiget> {
MyBloc bloc;
@override
void dispose() {
bloc.bar.close();
bloc.foo.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
// ...
}
}
5. Написание тестов проверки критически важных функций.
Вы не сможете застраховать себя от ошибок, связанных с тестированием, выполняемом в ручном режиме, однако наличие автоматизированного набора тестов поможет вам сэкономить значительное количество времени и усилий. Поскольку комплект средств разработки Flutter предназначен для большого количества платформ, проверка каждой функции после внесения каждого изменения будет отнимать много времени и потребует большого объема постоянно повторяющейся работы.
Проверка всего программного кода целиком гарантирует наилучшие результаты, однако ее выполнение не всегда возможно в силу ограниченного времени и средств. Тем не менее, необходимо иметь тесты хотя бы для проверки основных критически важных функций приложения.
Тесты для проверки отдельных юнитов и виджетов будут оптимальными решениями, которые следует использовать с самого начала, и их выполнение не будет настолько сложной задачей по сравнению с тестами проверки интеграции.
6. Использование необработанных строк.
Необработанные строки можно использовать не только для того, чтобы изолировать символы «\»и «$».
//Do
var s = r'This is demo string \ and $';
//Do not
var s = 'This is demo string \ and $';
В обычной строке «$» предшествует идентификатору, который подставит значение. А «\» позволит вставить символ (\\ = \ в консоле). В случае var s = r'$ \'; все ок, оно так же отобразится в консоле.
Антон Мартышков, Flutter-dev Amiga.
7. Использование функции относительного импорта вместо абсолютного импорта.
При одновременном использовании функции относительного и абсолютного импорта может возникнуть путаница, когда один и тот же класс импортируется двумя разными способами. Чтобы избежать этого, необходимо использовать относительный путь в папке lib/.
8. Использование виджета SizedBox вместо Container.
Существует множество случаев, когда необходимо использовать заполнитель. Ниже приведен наглядный пример:
return _isNotLoaded ? Container() : YourAppropriateWidget();
Container – это отличный виджет, который вы будете активно использовать во Flutter. Container() адаптируется, чтобы соответствовать ограничениям, заданным родительским элементом, и не является const-конструктором.
В то время как виджет SizedBox является const-конструктором и создает бокс фиксированного размера. Значения ширины и высоты могут быть равны нулю, чтобы указать, что размер бокса не должен быть ограничен в соответствующем измерении.
Таким образом, когда нам нужно использовать заполнитель, нужно сделать выбор в пользу виджета SizedBox, а не Container.
return _isNotLoaded ? SizedBox() : YourAppropriateWidget();
9. Использование функции log вместо print
Функции print() и debugPrint() всегда применяются для входа в консоль. Если вы используете функцию print() и получаете слишком большой объем выводимых данных, то Android иногда отбрасывает некоторые строки функции log.
Чтобы избежать возникновения подобного рода ситуаций используйте функцию debugPrint(). Если данных функции log более чем достаточно, используйте функцию dart: developer log(). Это позволяет вам получить более детальную информацию.
//Do
log('data: $data');
//Do not
print('data: $data');
Для вариантов с одной строкой используйте условный оператор.
String alert = isReturningCustomer ? 'Welcome back to our site!' : 'Welcome, please sign up.';
Для вариантов, как те, что показаны ниже, используйте условие if вместо условного оператора.
Widget getText(BuildContext context) {
return Row(
children:
[
Text("Hello"),
if (Platform.isAndroid) Text("Android") (here if you use ternary then that is wrong)
]
);
}
Всегда старайтесь использовать виджеты Const. Виджет не будет меняться при вызове setState, мы должны определить его как константу. Таким образом виджет не будет перестраиваться и сохранит высокую скорость работы.
//Do
const SizedBox(height: Dimens.space_normal)
//Do not
SizedBox(height: Dimens.space_normal)
10. Объявление переменной как null избыточно, автоматически уже null.
В Dart переменная интуитивно инициализируется в null, если ее значение не указано, поэтому добавление null является избыточным и необязательным.
//Do
int _item;
//Do not
int _item = null;
Всегда выделяйте тип члена, если тип его значения известен. Не используйте var, когда это не требуется. Поскольку var является динамическим типом, для его выполнения требуется больше места и времени.
//Do
int item = 10;
final Car bar = Car();
String name = 'john';
const int timeOut = 20;
//Do not
var item = 10;
final car = Car();
const timeOut = 2000;
11. Использование ключевого слова const всегда, когда это возможно
Использование const-конструктора для виджетов позволяет уменьшить объем мусора и упростить работу функции очистки GC. Возможно, вначале скорость работы покажется вам не слишком высокой, но на самом деле она увеличивается и играет важную роль, когда приложение становится достаточно большим, или существует представление, которое часто перестраивается.
Декларации const также лучше подходят для горячей перезагрузки. Более того, мы не должны использовать ненужное ключевое слово const. Посмотрите на следующий код:
const Container(
width: 100,
child: const Text('Hello World')
);
Нам не нужно использовать const для виджета Text, поскольку const уже применяется к родительскому виджету.
Dart поддерживает следующие правила Linter для const:
prefer_const_constructors
prefer_const_declarations
prefer_const_literals_to_create_immutables
Unnecessary_const
12. Соблюдение отдельных формальных требований
Не забывайте убирать корневые виджеты в безопасное место.
Вы можете объявить несколько переменных с помощью сокращения- (int mark =10, total = 20, amount = 30;)
Убедитесь, что используете переменные класса final/const, когда есть такая возможность.
Старайтесь не использовать необязательный закомментированный код.
Создавайте частные переменные и методы, когда это возможно.
Создавайте различные классы для цветов, стилей текста, размеров, постоянных строк, продолжительности и так далее.
Разработайте константы API для ключей API.
Старайтесь не использовать ключевые слова await внутри блока
Старайтесь не использовать глобальные переменные и функции. Они должны быть связаны со своим классом.
Следуйте рекомендациям Dart Analysis
Проверьте строки с подчеркиванием, которое указывает на опечатку или дает советы по оптимизации
Используйте _ (нижнее подчеркивание), если значение не используется внутри блока кода.
//Do
someFuture.then((_) => someFunc());
//Do not
someFuture.then((DATA_TYPE VARIABLE) => someFunc());
Магические числа всегда имеют правильные названия для удобочитаемости.
//Do
final _frameIconSize = 13.0;
SvgPicture.asset(
Images.frameWhite,
height: _frameIconSize,
width: _frameIconSize,
);
//Do not
SvgPicture.asset(
Images.frameWhite,
height: 13.0,
width: 13.0,
);
На сегодня все.
Комментарии (6)
ookami_kb
31.01.2023 14:00+7Снова побрюзжу. Плохой перевод не очень хорошей статьи.
По переводу. Кроме просто криво построенных фраз, местами вы будто вообще не понимаете, о чем идет речь.
ограничения уменьшаются, размеры увеличиваются, а родительский элемент определяет положение.
Нет. Ограничения идут вниз (по дереву), размеры идут вверх (по дереву).
Не забывайте убирать корневые виджеты в безопасное место.
Нет. Не забывайте оборачивать корневые виджеты в SafeArea.
Возможно, вначале скорость работы покажется вам не слишком высокой,
Нет. Возможно, поначалу увеличение производительности может показаться незначительным...
И т.д.
По статье. Это какая-то смесь из личных предпочтений автора (а ля "используйте всегда относительные импорты"), подсказок линтера (don't explicitly initialize variables to null), неправильных примеров (первый же пример не скомпилируется, там пропущен late у `Future<int> future;` – если статья рассчитана на новичков, то это важно), откровенной ерунды (Try not to use of await keywords inside the bloc).
Серьезно, читайте официальную документацию. Она у флаттера превосходная, и там есть лучшие практики, объяснения, примеры, советы по оформлению кода – и всё это из первых рук.
undersunich
31.01.2023 15:40Это все личные предпочтения автора статьи.Плохо что подобный субьективизм потом берется за основу
PackRuble
31.01.2023 23:45+18 - Если уж зудим, то почему бы не воспользоваться SizedBox.shrink();
10 - Разве здесь тип не выводится автоматически?
//Do not var item = 10; final car = Car(); const timeOut = 2000;
И если "var является динамическим типом", то чем тогда является
dynamic
?Статья, как и сам перевод, является спорным. Солидарен с @ookami_kb. Попахивает субъективными рассуждениями. Хотя, стоит заметить, половина пунктов действительно полезные.
starik-2005
После таких статей я начинаю думать, что "в консоле" правильно, а "в консоли" - нет.