Flutter. Много
2.72K subscribers
362 photos
23 videos
266 links
Заказать мобильную разработку: https://amiga.ru//?utm_source=tg
Заказать рекламу в канале @amiga_agency_bot

Новости Flutter-разработки, дайджесты мероприятий, личный опыт.
Download Telegram
Hola, Amigos! Вот и прошла IT-конференция BOOST😍 Мы много общались с другими компаниями, выступали с лекциями, делились факапами, веселились на афтепати, гуляли по Сколково и знакомились с крутыми специалистами. Можем смело заявить, что закрыли все галочки в бинго участника конференции.

Показываем в карточках, как прошли для нас эти 2 дня!
5🔥3😍3
Hola, Amigos! На связи Павел Гершевич, Mobile Team Lead в Amiga. При настройке диплинков в наше приложение иногда требуется сделать так, чтобы только определенные ссылки вели в приложение и наоборот. В этой небольшой серии постов мы разберем, как поступать в таких ситуациях.

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

Как и для любых других диплинков, настройка будет внутри проекта для Android и в файле apple-app-site-association для iOS.

Для Android нужно открыть файл AndroidManifest.xml вашего проекта и там добавить код (или изменить его, если вы уже настраивали диплинки до этого):

<intent-filter android:autoVerify=”true” tools:targetApi=”m”>
<action android:name=”android.intent.action.VIEW” />
<category android:name=”android.intent.category.DEFAULT />
<category android:name=”android.intent.category.BROWSABLE />

<data
android:scheme=”https”
android:host=”example.com”
android:pathPrefix=”/sample”
/>
</intent-filter>


Такой код позволит нам открывать только страницы, чей URL начинается с https://example.com/sample.

Используйте path, чтобы указать только 1 ссылку:

<data android:path="/sample" />


Используйте pathSuffix, если нужно указать только окончание URL:

<data android:pathSuffix="ample" />


Если вы только настраиваете диплинки в ваше приложение, то не забудьте подготовить и загрузить на ваш сайт файл assetlinks.json. Как он выглядит и создается рассказано в этой статье, которую мы переводили для вас.

Давайте сделаем то же самое и для iOS. Откроем или создадим файл apple-app-site-association. Тут нас интересует массив components внутри объекта details. Существует несколько вариантов реализации:

Если нужно сделать переход именно на эту страницу:
{
“/”: “/sample”,
“comment”: “Откроет только https://example.com/sample”
}


Если можно позволить переход на любого потомка этой страницы:
{
“/”: “/sample/*”,
“comment”: “Откроет также https://example.com/sample/first”
}


И если в пути содержится часть, но мы не знаем ее точного места:
{
“#”: “ample”,
“comment”: “Откроет также https://example.com/mySample”
}


После этого нужно загрузить этот файл на сервер и подождать, пока AASA-Bot заберет его на CDN компании Apple. Обычно это занимает до суток.
👍64🔥4
🙂 Hola, Amigos! На связи команда Amiga. У нас оооочень крутая новость — наш Mobile Team Lead, Павел Гершевич, стал соавтором книги «Основы Flutter».

Книга — настоящий must-have для тех, кто хочет разобраться во Flutter с нуля или прокачать свои навыки. Авторы — сильнейшие практики из индустрии: Станислав Ильин (Поток), Юрий Петров (Friflex), Станислав Чернышев (СПбГУАП) и наш Павел Гершевич.

⚙️ Что внутри:
— Пошаговое изучение Flutter: от Dart и верстки до сборки проекта, готового к публикации;
— Сквозной проект — Тетрис, который развивается от главы к главе;
— Два лабораторных практикума от Surf и MadBrains;
— Отдельный курс на Stepik с тестами для закрепления материала.

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

А приобрести книгу можно на официальном сайте издательства и на маркетплейсах: Ozon, Wildberries.

❤️ — поддержим Павла с долгожданным выходом!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥299👏5👍2🥰1
Hola, Amigos! Очень нужна ваша поддержка на Tagline Awards! Сейчас идет народное голосование, в котором у нас участвует 4 проекта:

⚙️ Сайт-путеводитель для самой северной точки Евразии — если Териберка надоела, можно заглянуть на Таймыр к оленям :) Сделали все, чтобы выбрать экскурсию и подобрать маршрут было удобно

⚙️ Travelpayouts: перезапустили инструмент для поиска авиабилетов — освежили дизайн и внутреннюю логику, осуществили поддержку 61 языка, включая арабский, чтобы справа налево тоже читалось

⚙️ Мобильное приложение CMstore — запустили приложение, где можно купить (или просто посмотреть) дайсоны, последние айфоны и другую технику

⚙️ Народный рейтинг «Люди ИТ-индустрии» — наш дебют в мероприятиях. Совместно с Alto и Телеграмошной запустили рейтинг, где можно голосовать не за кейсы или компании, а за людей, которые раскачивают индустрию

Будем рады и благодарны вашим голосам❤️
Please open Telegram to view this post
VIEW IN TELEGRAM
4🔥2👏1
Hola, Amigos! Заканчиваем год, но продолжаем выступать на мероприятиях!

11 декабря наш Mobile Team Lead Павел Гершевич выступит с докладом «Как не стоит делать корзину для мобильного e-com приложения». В своем выступлении он поделится:

⚙️ почему лучше вести разработку с одной командой, а не с несколькими разными;

⚙️ почему проектировать API должен тот, кто будет его использовать, а не разработчик бэкенда;

⚙️ опыт исправления ошибкок на проекте, где API диктовалось не командой МП, а командой Backend.

А для самых внимательных слушателей Павел подготовил подарок! За лучший вопрос он подарит экземпляр своей книги «Основы Flutter», о презентации которой мы недавно писали.

Митап пройдет в Красноярске, регистрируйтесь на сайте!
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍3🔥3
Hola, Amigos! Хотим поделиться с вами классной новостью. 5 декабря прошла церемония награждения премии Tagline Awards 2025 — главной награды за достижения в digital-сфере.

В премии участвует 500+ известных брендов и агентств, а награды вручаются за уровень работ, качество их исполнения и эффективность решения коммерческих задач🔥

Делимся победами! В этом году мы заняли:

🏆 2 место в номинации «Лучший сайт об услугах» с кейсом разработки сайта для федеральной сети клиник

🏆 3 место в номинации «Лучшее ритейл-приложение» с кейсом мобильного приложения CMstore

🏆 А также мы попали в шорт-лист номинации «Лучшая кампания для искусства/культуры/развлечений» с нашим первый мероприятием — народным рейтингом «Люди ИТ-индустрии»

Спасибо команде за работу и заказчикам за доверие ❤️ Особенно радует, что наш рейтинг, который мы вместе с Alto и Телеграмошной реализовали за пару месяцев, попал в шорт-лист! Замотивированы развиваться и дальше предлагать рынку свежие идеи ⚙️

Пишите свое мнение в комментариях, мы будем рады вашему фидбеку)
Please open Telegram to view this post
VIEW IN TELEGRAM
7👍5🔥5
Hola, Amigos! Сегодня начнем серию постов о банковских приложениях на Flutter. В таких проеках цена ошибок в безопасности намного выше, чем в обычных mobile-проектах. В нескольких частях разберем практические подходы к защите Flutter-приложений в банковском контексте.

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

1. Не храните данные в открытом виде

Сохранение токенов в SharedPreferences — прямой путь к компрометации:


final prefs = await SharedPreferences.getInstance();
prefs.setString("token", token);


Используйте зашифрованное хранилище:


final secureStorage = FlutterSecureStorage();

await secureStorage.write(
key: "access_token",
value: token,
);

final token = await secureStorage.read(key: "access_token");


2. Только token-based аутентификация

Cookies и сессии не подходят для банковских приложений. Токен должен добавляться ко всем запросам централизованно:


class AuthInterceptor extends Interceptor {
final FlutterSecureStorage storage;

AuthInterceptor(this.storage);

@override
void onRequest(RequestOptions options, handler) async {
final token = await storage.read(key: "access_token");
if (token != null) {
options.headers["Authorization"] = "Bearer $token";
}
handler.next(options);
}
}


3. Никогда не хардкодьте личные данные

API-ключи и токены не должны попадать в репозиторий:


const apiKey = "sk_test_123456";


Правильно:


const apiKey = String.fromEnvironment("API_KEY");


Передача через CI:


--dart-define=API_KEY=your_key_here



А что происходит, когда аутентификация уже выстроена, но трафик можно перехватить, а OTP проверяется на клиенте?

В следующей части разберем сетевую безопасность, SSL pinning и работу с OTP⚙️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19🔥52👎1👀1
Hola, Amigos! Продолжаем разговор о безопасности приложений на Flutter.

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

1. SSL pinning — обязательное требование

Без pinning HTTPS не спасает от атаки MITM (Man In The Middle), когда злоумышленники перехватывают трафик между приложением и сервером, а потом читают или подменяют запросы через прокси или wi-fi:


final context = SecurityContext(withTrustedRoots: false);
context.setTrustedCertificatesBytes(certBytes);

final httpClient = HttpClient(context: context);

final dio = Dio()
..httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () => httpClient,
);


Это блокирует:

- proxy
- fake Wi-Fi
- подмену сертификатов

2. OTP всегда проверяется на сервере

Проверка OTP на клиенте — критическая уязвимость:


if (enteredOtp == "123456") success();


Только сервер:


await api.verifyOtp(
mobile: mobile,
otp: enteredOtp,
);


3. Клиентская валидация — не финальная. Данные всегда проверяются на сервере:


bool isValidAmount(String value) {
final amount = double.tryParse(value);
return amount != null && amount > 0 && amount < 100000;
}


Эти меры закрывают большую часть рисков, но сами по себе они не решают все. Даже при защищенном трафике и корректной логике на бэке приложение остается уязвимым на уровне устройства и интерфейса.

В следующей части разберем защиту UI, работу с сессиями и ограничения на уровне устройства.
🔥154😍4🙊1
Hola, Amigos! Даже идеально защищенный backend не поможет, если приложение уязвимо на уровне устройства. В финальной части посмотрим на безопасность со стороны устройства и пользовательского интерфейса.

1. Запрещайте скриншоты на чувствительных экранах

Некоторые чувствительные данные не должны попадать в скриншоты:


@override
void initState() {
super.initState();
if (Platform.isAndroid) {
FlutterWindowManager.addFlags(
FlutterWindowManager.FLAG_SECURE,
);
}
}


2. Используйте биометрию

Биометрия нужна не только для логина.


final auth = LocalAuthentication();

await auth.authenticate(
localizedReason: "Verify to access your bank account",
options: const AuthenticationOptions(
biometricOnly: true,
),
);


Подходит для различных сценариев:

- входа
- платежей
- доступа к профилю

3. Автоматический логаут и контроль сессии


class SessionManager {
Timer? _timer;

void start(void Function() onExpire) {
_timer?.cancel();
_timer = Timer(
const Duration(minutes: 5),
onExpire,
);
}

void refresh() {
_timer?.cancel();
}
}


Выход при:

- уходе в фон
- бездействии
- истечении токена

4. Root / Jailbreak — блокировать


final isRooted = await RootChecker.isRooted();
if (isRooted) {
exit(0);
}


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

5. Код и логи в продакшене


flutter build apk --obfuscate --split-debug-info=./symbols
flutter build ios --obfuscate

if (kDebugMode) {
print("Token: $token");
}


Надеюсь, советы были полезны и помогут избежать типовых ошибок при разработке приложений на Flutter. Если тема откликнулась — пишите в комментарии!
🔥1310👍4❤‍🔥2
Hola, Amigos! Сегодня делимся простыми, но рабочими лайфхаками, которые помогут сделать ваше Flutter-приложение быстрее, легче и стабильнее.

Делитесь в комментариях, пользовались чем-то?
👍10🔥95
Hola, Amigos! Сегодня разбираем полезные и часто недооцененные виджеты во Flutter, которые реально спасают в реальных проектах 🙂

1. AbsorbPointer. Он блокирует взаимодействия (тапания, жесты) для своего потомка полностью, когда absorbing is true.

Use case: запретить двойные тапы на кнопке во время состояния загрузки.


AbsorbbPointer(
absorbing: isLoading,
child: isLoadingButton(
child: ElevatedButton(
onPressed: () { /* submit */ },
child: Text("Submit"),
),
),
)


Позволяет UI оставаться видимым и интерактивным для анимаций, но останавливает ввод пользователя.

2. IgnorePointer. Отключает жесты, но не блокирует layout hit testing.

Use case: чтобы UI выглядел «активным», но не реагировал на пользователя.


IgnorePointer(
ignoring: isReadOnly,
child: Slider(
value: sliderValue,
onChanged: (_) {},
),
)


Подойдет, чтобы временно отключить взаимодействие без изменения визуального layout и footprint виджета.

AbsorbPointer не пропускает какие-либо действия под собой, а IgnorePointer блокирует только взаимодействие именно с ним.

3. Offstage. Убирает виджет из рендеринга/layout'а, но оставляет его в дереве виджетов.

Use case: пре-билдить сложные или дорогие части UI (табы, экраны), но пока не показывать их.


Offstage(
offstage: !showSettings,
child: SettingsPanel(),
)


Почему помогает: скрытый виджет остается живым (state, controllers, animations) без влияния на видимый layout и без перестроек каждый раз.
Please open Telegram to view this post
VIEW IN TELEGRAM
11🔥11👍8
Hola, Amigos! Продолжаем разбор полезных Flutter-виджетов для адаптивной верстки. Во второй части еще несколько инструментов, которые помогут сделать UI стабильным на разных экранах.

1. FractionallySizedBox. Позволяет задать размер дочернего виджета как долю от размера родителя (по ширине и/или высоте).

Use case: когда нужно, чтобы кнопка или контейнер занимали, например, 80% ширины родителя без hardcoded значений.


FractionallySizedBox(
widthFactor: 0.8,
child: ElevatedButton(
onPressed: () {},
child: Text("Continue"),
),
)


Помогает сохранять пропорции на разных экранах и устройствах → UI выглядит консистентно и адаптивно.

2. FittedBox. Масштабирует дочерний виджет так, чтобы он вписался в доступное пространство, сохраняя пропорции (aspect ratio).

Use case: когда нужно вписать крупный текст или иконку в небольшую карточку или контейнер без overflow.


FittedBox(
child: Text(
"Responsive Title",
style: TextStyle(fontSize: 40),
),
)


Предотвращает ошибки переполнения (overflow) и гарантирует корректное масштабирование UI.

Что возьмете в работу?
👍11🔥6👌41
Hola, amigos! Сегодня рассмотрим четыре инструмента, которые помогают писать чище код, избавляться от хардкода и не жертвовать производительностью.

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


LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 700) {
return Row(
children: [
Sidebar(),
Expanded(child: Content()),
],
);
}
return Content();
},
);


2. AnimatedSwitcher позволяет аккуратно анимировать переходы через fade, slide или scale и сразу делает интерфейс «дороже»


AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Text('$count', key: ValueKey(count)),
);


3. TweenAnimationBuilder закрывает до 80% простых анимаций


TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: progress),
duration: Duration(milliseconds: 500),
builder: (context, value, child) {
return LinearProgressIndicator(value: value);
},
);


4. RepaintBoundary изолирует часть UI и не дает ей перерисовываться без необходимости


RepaintBoundary(
child: ExpensiveWidget(),
)


А что для в работе используете вы?
👍132🔥1