Flutter Friendly – Telegram
Flutter Friendly
1.04K subscribers
174 photos
71 videos
1 file
147 links
Канал Friflex о разработке на Flutter. Обновления, плагины, полезные материалы — превращаем знания в реальный опыт, доступный каждому разработчику.

🔗 Наш канал для разработчиков: @friflex_dev
🔗 Канал о продуктовой разработке: @friflex_product
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
Привет, с вами Катя, Flutter Dev Friflex. В предыдущем посте я рассказывала о принципе подстановки Барбары Лисков (L). Сегодня разбираем четвертую букву — I (Interface Segregation Principle), принцип разделения интерфейса.

Что такое Interface Segregation Principle?
Принцип разделения интерфейса гласит: клиенты не должны зависеть от методов, которые они не используют. Другими словами, лучше иметь несколько узких специализированных интерфейсов, чем один универсальный «толстый» интерфейс, который заставляет классы реализовывать ненужные им методы.

Почему это важно?
Когда класс вынужден реализовывать методы, которые ему не нужны, это приводит к нескольким проблемам:​
▪️ Ненужные зависимости — класс зависит от функциональности, которую не использует
▪️ Пустые реализации — приходится писать заглушки или выбрасывать исключения
▪️ Сложность тестирования — нужно мокать методы, которые не используются
▪️ Нарушение SRP — класс получает ответственность, которая ему не нужна
▪️ Хрупкость кода — изменения в неиспользуемых методах могут сломать работу класса

Нарушение принципа
Рассмотрим классический пример:

// "Толстый" интерфейс с множеством методов
abstract class Worker {
  void work();
  void eat();
  void sleep();
}

// Человек использует все методы
class Human implements Worker {
  @override
  void work() {
    print('Человек работает');
  }
  
  @override
  void eat() {
    print('Человек ест');
  }
  
  @override
  void sleep() {
    print('Человек спит');
  }
}

// Робот вынужден реализовывать ненужные методы
class Robot implements Worker {
  @override
  void work() {
    print('Робот работает');
  }
  
  @override
  void eat() {
    throw Exception('Робот не ест!'); // Бесполезная реализация
  }
  
  @override
  void sleep() {
    throw Exception('Робот не спит!'); // Бесполезная реализация
  }
}


Проблема: интерфейс Worker слишком широкий. Робот вынужден реализовывать методы eat() и sleep(), которые ему не нужны, и либо бросает исключения, либо оставляет пустые реализации. Это нарушение ISP.

Правильное применение принципа
Разделим «толстый» интерфейс на несколько узких специализированных интерфейсов:

// Узкие интерфейсы с конкретными обязанностями
abstract class Workable {
  void work();
}

abstract class Eatable {
  void eat();
}

abstract class Sleepable {
  void sleep();
}

// Человек реализует все три интерфейса
class Human implements Workable, Eatable, Sleepable {
  @override
  void work() {
    print('Человек работает');
  }
  
  @override
  void eat() {
    print('Человек ест');
  }
  
  @override
  void sleep() {
    print('Человек спит');
  }
}

// Робот реализует только то, что ему нужно
class Robot implements Workable {
  @override
  void work() {
    print('Робот работает');
  }
}

// Использование
void makeWork(Workable worker) {
  worker.work();
}

void feedWorker(Eatable worker) {
  worker.eat();
}

void main() {
  final human = Human();
  final robot = Robot();
  
  makeWork(human); // Работает
  makeWork(robot); // Работает
  
  feedWorker(human); // Работает
  // feedWorker(robot); // Ошибка компиляции - правильно!
}


Теперь каждый класс реализует только те интерфейсы, которые ему нужны. Робот не притворяется, что умеет есть и спать. Принцип разделения интерфейса — это про создание чистых и понятных контрактов. Вместо универсальных «комбайнов» создавайте специализированные интерфейсы, каждый из которых отвечает за конкретную роль.

В следующем посте я расскажу о последнем принципе SOLID — принципе инверсии зависимостей (D).
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥97👍5
💎Привет! Это Анна, Flutter Team Lead Friflex

Перед публикацией любого приложения в Google Play и AppStore необходимо создать аккаунт разработчика (Developer Account). Этот аккаунт может быть двух видов — индивидуальный аккаунт и аккаунт юридического лица. Сегодня поговорим, чем оба аккаунта отличаются и какой стоит выбирать.

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

Преимущества:
✔️ Быстрый процесс регистрации: требуется минимум документов и проверок
✔️ Не требует наличия официально зарегистрированного юридического лица
✔️ Меньше бюрократии по сравнению с корпоративным аккаунтом
✔️ Дешевле в содержании

Недостатки:
Имеются существенные ограничения в доступах. Даже если вы получите роль администратора, многие важные функции вам останутся недоступны, например, выпуск идентификаторов, API-ключей и сертификатов
В опубликованной карточке приложения указывается имя владельца аккаунта, что влияет на репутацию продукта в целом. Больше доверия вызывают официальные организации
Доход поступает как доход физического лица
Требуется выполнение особых условий для публикации. Например, в Google Play при публикации новых приложений под аккаунтом физического лица требуется прохождение закрытого тестирования в течение 14 дней в составе группы не менее 12 тестировщиков.

Для pet-проектов, стартапов и экспериментальных продуктов индивидуальный аккаунт является оптимальным вариантом. Обычно в таких командах не возникает проблем с управлением аккаунтом из-за небольшого состава. А вот упрощенная система бюрократии делает общий процесс значительно быстрее и дешевле.

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

Преимущества:
✔️ Доверие клиентов выше, так как они видят перед собой целую компанию, а не одного человека. Особенно это важно для серьезных коммерческих продуктов, например, B2B и финансовой сферы
✔️ Огромный выбор различных ролевых моделей в управлении проектом. Вы можете четко разграничивать права и зону ответственности через выдачу конкретной роли
✔️ Нет проблем с делегированием ключевых прав в доступах. В отличие от индивидуального аккаунта, сертификаты и прочее может выпускать не только владелец
✔️ Удобное ведение бизнеса, отслеживание поступающих платежей и аналитика приложения

Недостатки:
Сложно зарегистрировать аккаунт. При регистрации требуется много различных документов, а сам процесс может затянуться на пару недель
У сторов более строгие требования к соответствию внутренним правилам. Нарушения воспринимаются жестче, чем у индивидуальных аккаунтов
Если компания зарегистрирована в РФ, в сторе могут быть ограничения для такого разработчика

Если ваше приложение является частью полноценного бизнеса, если у вас большая команда разработчиков, где важно грамотно делегировать права — лучший выбор для вас именно аккаунт юридического лица.

❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
17👍7🔥4🤩2🤝1
💭Привет! С вами Роза, Flutter Dev Friflex

Часто в десктоп- или веб-приложениях нужна возможность перетаскивания файлов для их загрузки в проект. Во Flutter это можно реализовать несколькими способами: написать функционал вручную или воспользоваться готовыми решениями. Сегодня я расскажу о пакете desktop_drop

desktop_drop
— это пакет для drag-and-drop на вебе и десктоп-платформах. Основной виджет пакета — DropTarget, который определяет область, на которую можно перетаскивать файлы. Сам по себе виджет невидим, он просто определяет область для перетаскивания вокруг своего child. Основные свойства DropTarget включают:
✔️ onDragEntered — вызывается, когда файл входит в область виджета
✔️ onDragExited — вызывается, когда файл покидает область виджета
✔️ onDragDone — вызывается после того, как файл был отпущен внутри области
✔️ onDragUpdated — вызывается при движении файла внутри области
✔️ child — виджет, который отображается внутри области DropTarget

DropTarget(
  onDragDone: (detail) {
    for (final file in detail.files) {
      print(file.path);
    }
  },
  onDragEntered: (detail) => print('Файл в области'),
  onDragExited: (detail) => print('Файл вне области'),
  child: // some child
)

Также можно визуально показывать, что область готова принять файл. Для этого необходимо использовать состояния, отслеживаемые через onDragEntered и onDragExited.

class _FileDropAreaState extends State<FileDropArea> {
  bool _isDragging = false;

  @override
  Widget build(BuildContext context) {
    return DropTarget(
      onDragEntered: (detail) {
        setState(() => _isDragging = true);
      },
      onDragExited: (detail) {
        setState(() => _isDragging = false);
      },
      onDragDone: (detail) {
       // onDragDone logic
      },
      child: //some child
    );
  }
}


При работе с desktop_drop есть несколько нюансов, о которых стоит помнить:
▪️Разные платформы могут по-разному обрабатывать события перетаскивания
▪️С помощью DropDoneDetails можно получать пути к файлам и обрабатывать их без необходимости загружать весь файл в память
▪️Для безопасной работы всегда проверяйте тип, размер и количество файлов

Для полноценного функционала работы с файлами desktop_drop можно комбинировать с file_picker и другими пакетами.

❤️ — если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍3🔥1
❄️Привет, с вами Катя, Flutter Dev Friflex

В предыдущем посте я рассказывала о принципе разделения интерфейса (I). Сегодня разбираем последнюю букву — D (Dependency Inversion Principle), принцип инверсии зависимостей.

Что такое Dependency Inversion Principle?
Принцип инверсии зависимостей говорит о двух вещах:
✔️высокоуровневые модули не должны зависеть от низкоуровневых — и те, и другие должны зависеть от абстракций
✔️абстракции не должны зависеть от деталей, детали должны зависеть от абстракций.
По-простому: «верх» приложения (экран, бизнес-логика) не должен быть привязан к конкретным реализациям «низа» (HTTP‑клиент, база данных, SharedPreferences и другим), он должен зависеть только от интерфейсов.

Почему это важно?
Когда высокоуровневый код напрямую знает о конкретных классах нижнего уровня, это приводит к проблемам:

◾️Любое изменение реализации «внизу» (REST → gRPC, другая БД, кэш) требует правок в бизнес-логике
◾️Код сложно тестировать — приходится тянуть реальные репозитории/сети вместо заглушек
◾️Система становится хрупкой: одна деталь «внизу» ломает много кода «наверху»
◾️Нарушаются другие принципы SOLID — растет связанность, падает переиспользуемость.

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

Нарушение принципа
Рассмотрим типичный пример с авторизацией:

class AuthRepository {
  Future<void> login(String email, String password) async {
    // Здесь конкретная реализация:
    // HTTP-запрос, парсинг ответа, сохранение токена и т.д.
  }
}

class LoginViewModel {
  Future<void> login(String email, String password) async {
    final repo = AuthRepository(); // Жёсткая зависимость
    await repo.login(email, password);
  }
}


Проблемы:
✖️LoginViewModel сам создает AuthRepository и жестко на него завязан
✖️Нельзя легко подменить репозиторий в тестах (например, на фейковый, который не ходит в сеть)
✖️Любое изменение механизма авторизации требует лезть в LoginViewModel
✖️Высокоуровневый модуль (view model) зависит от конкретной детали (репозитория), а не от абстракции

Правильное применение принципа
Введем абстракцию и будем передавать зависимость извне (через конструктор):

// Абстракция (контракт), от которой зависит верхний уровень
abstract class IAuthRepository {
  Future<void> login(String email, String password);
}

// Низкоуровневая реализация для реального API
class NetworkAuthRepository implements IAuthRepository {
  @override
  Future<void> login(String email, String password) async {
    // HTTP, обработка ошибок, сохранение токена и т.д.
  }
}

// Другая реализация, например, фейковая для тестов
class FakeAuthRepository implements IAuthRepository {
  @override
  Future<void> login(String email, String password) async {
    // Ничего не делает или имитирует успешный логин
  }
}

// Высокоуровневый модуль зависит только от интерфейса
class LoginViewModel {
  final IAuthRepository _authRepository;

  LoginViewModel(this._authRepository);

  Future<void> login(String email, String password) async {
    await _authRepository.login(email, password);
  }
}


Теперь:
✔️LoginViewModel не знает, как именно реализован репозиторий — он видит только интерфейс
✔️В проде можно передать NetworkAuthRepository, в тестах — FakeAuthRepository
✔️Изменения в реализации репозитория не требуют правок в бизнес-логике
✔️Мы перевернули зависимость: конкретные реализации зависят от абстракции IAuthRepository, а не наоборот

Если вы видите в коде new/() внутри бизнес‑логики или ViewModel, создающий конкретные репозитории, сервисы и клиенты, — это хороший сигнал задуматься: не пора ли ввести интерфейс и развернуть зависимость?

✍️Повторим все принципы SOLID:
Single Responsibility Principle
Open/Closed Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
Please open Telegram to view this post
VIEW IN TELEGRAM
6👍4🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
Привет! Это Анна, Flutter Team Lead Friflex

Сегодня поговорим о той самой лотерее, которой опасаются все разработчики — отправке приложения на проверку в AppStore.

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

Первая проверка нового приложения — процесс небыстрый. Официально никакого временного регламента нет, но как показывает практика, лучше закладывать на это около 7 дней. Перед отправкой оцените обстановку — если впереди праздники, например, как сейчас близится Новый Год и Рождество, будьте готовы к увеличению срока. Обычно об этом в App Store Connect заранее появляется сообщение.

Повторные проверки и проверки обновлений уже опубликованного приложения проходят значительно быстрее — обычно в течение 1-2 суток. Но опять же на праздники срок может растянуться.

Теперь поговорим о том, на что стоит обратить внимание при подготовке:

Совместимость
Приложение обязательно должно быть совместимо с последними версиями iOS. Если на новых версиях продукт падает или не запускается совсем, это весомый повод получить отказ.

Стабильность
Приложение не должно вылетать — это важно не только для положительного пользовательского опыта, но и для проверки. Если какие-то сценарии вызовут краш вашего приложения при проверке, сборка точно будет отклонена.

Отсутствие вывода отладочной информации
Перед публикацией стоит почистить все вызовы методов вывода отладочной информации — print, debugPrint.

Разрешения
Особое внимание уделяется всем разрешениям, которые запрашивает приложение. Во-первых, приложение не должно запрашивать неиспользуемые разрешения. А во-вторых, каждое разрешение обязательно должно иметь четкое обоснование, прописанное в Info.plist. Это описание будет отображено пользователю в нативном окне. Если App Store посчитает формулировку недостаточно информативной, сборка может быть отклонена.

Нативные интеграции
Если вы добавляете в свой проект платформенные функции, требующие специфичной нативной настройки, обязательно обратите особое внимание на соблюдение всех этапов подключения, а также досконально проверьте работоспособность. Это относится, например, к push-уведомлениям и in-app покупкам.

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

Описание и оформление
Особое внимание стоит уделить заполнению карточки приложения. Описание обязательно должно быть достоверным и не должно содержать оценивающих суждений, например, «самый лучший калькулятор в мире» точно не подойдет. Преувеличения лучше оставить при себе. Пусть описание будет продающее, завлекающее, но без фанатизма.

Сюда же стоит отнести и скриншоты. Они должны быть в высоком качестве, определенного размера (нормы можно найти во вкладке загрузки медиаданных). На изображениях должен отражаться только реальный функционал и интерфейс. Кроме этого, картинки не могут содержать альфа-каналы или прозрачные области.

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

Тестовые данные и контактная информация
Если ваше приложение содержит какое-то ограничение в доступе, вам обязательно необходимо приложить инструкцию, контактные данные и тестовый аккаунт, чтобы проверяющие могли полноценно проверить все функции. Важно, что тестовая учетная запись должна быть доступна всегда. Если при проверке что-то случится с доступом, вы получите отказ.

🐱А вы сталкивались с проблемами при прохождении проверки в AppStore? Делитесь своим опытом в комментариях.
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥5👍2🤩1
Обновление дизайна от Apple с активным использованием glass effect вызвало у многих споры: кому-то понравилось, а кто-то был просто в ужасе. Чуть позже к этому тренду подтянулись и другие продукты, и в итоге мы внезапно оказались в мире стеклянных интерфейсов🐈

А теперь давайте представим, что мы ТОЖЕ ХОТИМ ТАК ЖЕ! Как же быть?
Есть 2 решения: воспользоваться готовыми библиотеками или реализовать все самостоятельно.

Начнем со второго варианта. Для этого нам понадобится виджет BackdropFilter. В отличие от обычных эффектов, он работает не с самим виджетом, а с тем, что находится под ним. BackdropFilter берет уже отрисованный фон и применяет к нему фильтр, в нашем случае — размытие.

Именно поэтому BackdropFilter почти всегда используют вместе с ClipRRect или ClipPath. Без него фильтр применится ко всей области экрана, а не только к нужному участку.

Также важно помнить, что сам BackdropFilter ничего не рисует и не имеет размера, он лишь изменяет фон. Поэтому поверх него всегда добавляют полупрозрачный контейнер с цветом, границей и тенью. Без этого слоя эффект будет почти незаметен.

Простейшая реализация может выглядеть так:

class GlassContainer extends StatelessWidget {
final double width;
final double height;
final Widget child;

const GlassContainer({
Key? key,
required this.width,
required this.height,
required this.child,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(25),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18),
child: Container(
width: width,
height: height,
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
borderRadius: BorderRadius.circular(25),
border: Border.all(
color: Colors.white.withOpacity(0.2),
width: 1.2,
),
),
child: child,
),
),
);
}
}


Если хочется воспользоваться готовыми решениями, можно присмотреться, например, к пакету glass_kit — внутри он более глубоко работает с BackdropFilter и помогает добиться аккуратного эффекта размытия.

При работе с glassmorphic-дизайном важно помнить о нескольких нюансах. Размытие стоит использовать умеренно – его избыток быстро утомляет, а мы этого не хотим! Кроме того, BackdropFilter не самый дешевый в отрисовке виджет, поэтому лучше избегать большого количества перекрывающихся размытых элементов.

В следующей статье можем разобрать liquid glass эффект. Его часто считают разновидностью glassmorphism, но это не совсем так. В отличие от классического glass effect с размытием фона, liquid glass добавляет ощущение движения, текучести и визуальных искажений.

А какие библиотеки для работы с glassmorphism или готовые реализации вы используете?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7👎32🔥1
Подойдут и для Android, и для iOS

Сделали новогодние заставки для телефонов с вайбом разработки на Flutter. Можно поставить себе, закинуть коллеге или в рабочий чат 🎄

Скачать — в комментариях ⬇️
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍129🔥5😁3
Привет, я Катя, Friflex Flutter Dev. Сегодня расскажу о GenUI SDK для Flutter — инструменте, который помогает собирать динамические пользовательские интерфейсы с помощью генеративного ИИ.

Что такое GenUI SDK
GenUI SDK для Flutter — это альфа-версия SDK на pub.dev. Она предлагает другой подход к взаимодействию с пользователем: вместо текстового ответа чат-бота вы можете показывать адаптивные визуальные компоненты.

Например, выпадающие списки, слайдеры, карусели товаров и формы с выбором даты. Компоненты меняются в реальном времени, в зависимости от намерений пользователя.

⭐️ Вопрос к размышлению: как часто вы сталкивались с тем, что чат-бот перечисляет товары текстом, хотя удобнее было бы сразу увидеть карточки с кнопками?

Как работает GenUI
Процесс устроен как интерактивный цикл. Пользователь вводит запрос (например: «помоги спланировать поездку в Токио»), а приложение отправляет его ИИ-агенту вместе с описанием доступных виджетов.

Дальше ИИ-агент генерирует не только текст, но и описание интерфейса с помощью инструментов GenUI SDK. Обычно это структура в формате JSON (JavaScript Object Notation), которую приложение десериализует и превращает в соответствующие Flutter-виджеты.

SDK может отрисовывать стандартные компоненты постепенно, по мере генерации ответа большой языковой моделью (LLM). Так интерфейс появляется быстрее, и пользователю не нужно ждать, пока придет ответ целиком.

Что GenUI SDK умеет
🔴Генерировать UI из структурированных данных
🔴Работать с настраиваемым каталогом виджетов
🔴Обрабатывать события и поддерживать интерактивный диалог

Каталог виджетов задает словарь Flutter-компонентов, которые ИИ может использовать. Каждый CatalogItem содержит имя виджета, JSON-схему его свойств и builder-функцию для рендеринга.

Реактивная система автоматически перестраивает виджеты при изменении данных в клиентской модели. А обработка событий позволяет фиксировать действия пользователя (клики, ввод текста) и отправлять обновленное состояние обратно ИИ для следующего шага.

⭐️Попробуйте представить: какие компоненты вашего приложения могли бы генерироваться на основе запросов пользователей?

Практическое применение
GenUI SDK подходит для AI-нативных приложений. Например, вместо текстового списка продуктов можно показать кликабельную карусель, а при планировании поездки — сгенерировать форму с полями ввода и слайдерами.

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

Подробнее — в блоге Flutter: Rich and dynamic user interfaces with Flutter and generative UI.

P.S. SDK пока в альфа-версии, но уже доступен на pub.dev для экспериментов. Делитесь опытом в комментариях!
Please open Telegram to view this post
VIEW IN TELEGRAM
6🔥1
Какой сценарий вы бы протестировали первым?
Anonymous Poll
41%
e-commerce
24%
бронирование
35%
что-то совсем другое
💠Привет! Это Анна, Flutter Team Lead.

Сегодня поговорим про версионирование Futter-приложений. Тема простая, но часто вызывает вопросы у начинающих разработчиков.

Наверняка вы знаете, что во Flutter-приложениях версия указывается в pubspec.yaml с ключом vesion. Выглядит это так:

version: 1.2.3+4


Для удобства восприятия возьмем буквенное представление:

version: A.B.C+D


Сочетание A.B.C отвечает за версию приложения. Значение под буквой D — за версию сборки.

Когда повышать каждый уровень версии приложения?

A — отвечает за мажорные изменения.
Повышать этот уровень точно нужно, если ваше приложение сильно менялось. Например, если вы:
✔️Полностью изменили дизайн
✔️Переработали пользовательские пути
✔️Радикально изменили или удалили ключевые функции
✔️Загрузили полностью новое приложение

Если приложение уже не похоже на себя и может восприниматься пользователем как другой продукт, смело повышайте уровень А.

B — минорные изменения.
Сюда обычно относятся:
✔️Новый функционал и масштабирование старого
✔️Новые пользовательские пути без критичного изменения старых

Если приложение выросло, получило новые фичи, но при этом сохранило свой предыдущий вид — нужно увеличивать уровень B.

C — мелкие фиксы и доработки.
Стоит повысить уровень С, когда вы:
✔️Вносите правки по багам или верстке
✔️Повышаете производительность приложения
✔️Выполняете рефакторинг или обновление зависимостей без влияния на ранее реализованные функции

При повышении версии важно помнить: оно выполняется по уровню наиболее значимых изменений. Например, сейчас версия вашего приложения — 3.9.14. В новом релизе вы исправили баги, добавили новую фичу и полностью изменили весь UX/UI приложения. В этом случае самым критичным изменением считается последнее. Версия — 4.0.0.

А что с версией сборки?

D — версия конкретного билда.
Стандартно это значение инкрементируется в каждый новый билд вашего приложения. Независимо от изменений внутри.

Без повышения этой версии сторы не смогут принять вашу новую сборку. Возможно, в коде вообще не было никаких изменений — поднять D нужно обязательно.

❤️— если было полезно
Please open Telegram to view this post
VIEW IN TELEGRAM
18🔥3❤‍🔥1🤩1