Динамічні теми оформлення мобільного застосунку
Чому користувачі кидають застосунки через проблеми з темою?
Користувач очікує, що застосунок підлаштовується під його вподобання: темна тема в метро, акцентний колір під шпалери телефону, мінімум два режими. Якщо перемикання теми відбувається із затримкою або миганням — користувач іде. Ми бачили проекти, де на реалізацію тем йшло 3 місяці, а результат — 5 екранів з багами. За 10 років ми виробили архітектуру, яка працює на всіх платформах і не прощає помилок. Одна з поширених проблем — flash of wrong theme: при першому запуску екран мигає світлою темою, а потім перемикається на збережену темну. Таке трапляється, якщо тема читається асинхронно. На iOS це вирішується синхронним читанням UserDefaults в AppDelegate, на Android — через SplashScreen API. Правильна реалізація виключає цей ефект і зберігає користувача. Зв'яжіться з нами для оцінки вашого проекту — ми проаналізуємо поточну архітектуру і запропонуємо оптимальне рішення.
Архітектура системи тем
Ключове рішення — як зберігати поточну тему і як передавати її в компоненти.
iOS (SwiftUI)
Використовуємо @Environment + кастомний EnvironmentKey. Створюємо AppTheme як ObservableObject, публікуємо через environmentObject, всі компоненти читають через @EnvironmentObject var theme: AppTheme. При зміні theme.colorScheme SwiftUI автоматично перемальовує все дерево. Перемикання миттєве, без UIApplication.shared.windows хаків.
iOS (UIKit)
Складніше: UIAppearance proxy для глобальних налаштувань + traitCollection override. Або кастомний ThemeManager через Notification Center: при зміні теми всі підписані компоненти викликають applyTheme(). Мінус: потрібно явно відписуватися, легко зловити retain cycle.
Android (Compose)
MaterialTheme(colorScheme = currentColorScheme) в корені composition. CompositionLocalProvider(LocalAppTheme provides theme). При зміні remember { mutableStateOf(lightColorScheme) } Compose перекомпоновує тільки ті піддерева, які читають тему. Дуже ефективно.
React Native
ThemeContext через React Context API + useContext. Або готове рішення через styled-components/native з ThemeProvider. При зміні теми всі компоненти, підписані на контекст, перерендерюються. Для запобігання зайвих рендерів — React.memo + useMemo для об'єкта теми.
Flutter
MaterialApp(theme: lightTheme, darkTheme: darkTheme, themeMode: themeMode). Кастомні теми — ThemeExtension<T>. ThemeMode.system / .light / .dark управляється через setState або Provider/Riverpod.
Як забезпечити персистентність теми без перезапуску?
Вибір теми потрібно зберігати. iOS: UserDefaults + @AppStorage в SwiftUI. Android: DataStore<Preferences> (рекомендується над SharedPreferences). React Native: AsyncStorage або MMKV для синхронного доступу. Flutter: SharedPreferences або Hive.
Критичний момент: при першому запуску тема повинна застосовуватися до того, як користувач побачить перший кадр. Інакше буде flash of wrong theme — екран мигає з дефолтної теми в збережену. На iOS вирішується синхронним читанням з UserDefaults в AppDelegate / @main до відмальовки вікна. На Android — через SplashScreen API з правильним кольором фону. Ми гарантуємо відсутність цього ефекту.
Як уникнути витоків пам'яті при зміні тем?
При активному перемиканні тем легко створити безліч підписок і слухачів, які не очищаються. На iOS використовуємо Combine з AnyCancellable і store(in: &cancellables), на Android — Flow з collect в lifecycleScope. Тестуємо на 10+ пристроях, перевіряючи витоки через Instruments і Android Profiler. Економія на налагодженні може сягати значних сум — запобігання багам на ранніх етапах окупає інвестиції.
Як реалізувати динамічну тему: покроковий план
- Аудит поточного UI: виявити всі місця, де використовується колір, шрифти, іконки. Визначити, які компоненти потребують адаптації.
- Проектування палітри: визначити набір кольорів (primary, secondary, background, surface, error) для світлої та темної схем. Якщо потрібен кастомний акцент — передбачити генерацію з seed-кольору.
- Вибір механізму зберігання: на основі платформи вибрати persistent storage (UserDefaults, DataStore, AsyncStorage, SharedPreferences). Переконатися, що читання відбувається синхронно до відмальовки першого кадру.
- Реалізація на платформі: впровадити систему тем згідно з best practices (Environment, Context, ThemeProvider). Написати тести для перевірки коректної зміни тем без мигання.
- Тестування на 10+ пристроях: перевірити на різних версіях ОС, з різними налаштуваннями (System theme, accessibility). Використовувати Instruments / Android Profiler для витоків пам'яті.
Як тестувати перемикання тем?
Тест, який виявляє більшість проблем: відкрити екран зі складним UI → перемкнути тему 5–10 разів швидко → переконатися, що немає мигання, витоку пам'яті (Instruments / Android Profiler), і всі кольори застосувалися коректно. Особливу увагу: UIAlertController, UIActivityViewController, системні компоненти на iOS — вони не завжди реагують на кастомні теми і потребують окремої обробки.
Чек-лист тестування динамічних тем:
- Перевірити перемикання світлої/темної теми на головному екрані та всіх дочірніх.
- Швидко перемикати тему 10 разів поспіль — немає витоків пам'яті, перемальовок.
- Переконатися, що системні компоненти (ActionSheet, ShareSheet) застосовують правильну тему.
- Перевірити збереження теми після перезапуску застосунку.
- Протестувати з Material You (Android 12+) — повинні підхопитися кольори з шпалер.
- На iOS — перевірити з активним Dark Mode в системі і без.
Користувацький акцентний колір
Android 12+ підтримує Material You — динамічна палітра генерується з шпалер користувача через DynamicColors.applyToActivitiesIfAvailable(this). Результат: застосунок автоматично адаптує кольори під персоналізацію телефону. Генерація палітри займає менше 5 мс, що в 10 разів швидше кастомної обробки кольорів.
Для кастомного colour picker всередині застосунку: потрібно генерувати повну ColorScheme з вибраного seed-кольору. На Android це dynamicDarkColorScheme / dynamicLightColorScheme (API 31+) або бібліотека material-color-utilities для API <31. На iOS — ручний розрахунок похідних кольорів через HSL.
Типові помилки при впровадженні тем
| Помилка | Наслідок | Рішення |
|---|---|---|
| Несинхронне читання теми | Flash of wrong theme | Синхронне читання до першого кадру |
| Ігнорування system theme | Відсутність автоматичної зміни | Використовувати ThemeMode.system |
| Жорстке кодування кольорів | Складність підтримки | Виносити всі кольори в палітру |
| Відсутність тестування на планшетах | Нерівномірна зміна кольорів | Додати в чек-лист |
Що входить у розробку динамічних тем?
- Аналіз поточної архітектури застосунку
- Проектування системи тем (палітри, способи зберігання та передачі)
- Реалізація на вибраному стеку (iOS/Android/Flutter/RN) з урахуванням best practices
- Інтеграція збереження та відновлення теми
- Тестування на 10+ фізичних пристроях та емуляторах
- Документація по підтримці та доопрацюванню
- 30 днів безкоштовної підтримки після здачі
| Вид підтримки | Термін |
|---|---|
| Тільки dark/light перемикання | 1–2 дні |
| Кілька готових тем на вибір | 2–3 дні |
| Динамічний акцентний колір | 3–5 днів |
| Material You (Android) + повна система | 4–6 днів |
Розробка динамічних тем — це вкладення, яке швидко окупається за рахунок підвищення утримання користувачів. Замовте впровадження динамічних тем — і ми забезпечимо безшовний перехід. Отримайте 30 днів безкоштовної підтримки та консультацій. Наша команда має 10+ років досвіду в мобільній розробці та сертифікати Apple і Google.
Додаткова інформація: Human Interface Guidelines — офіційні рекомендації Apple по роботі з кольором.







