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

Новости Flutter-разработки, дайджесты мероприятий, личный опыт.
Download Telegram
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(),
)


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