Розробка системи репостів і шерінгу в мобільному додатку
Клієнт просить кнопку «Поділитися», а ми витрачаємо тиждень на дебаг пуша та deep link. Знайома ситуація: на Android зображення не відправляється через FileProvider, на iOS Universal Links не працюють — посилання відкриває Safari. Наприклад, проєкт соцмережі для фотографів: вимагався репост чужих робіт у стрічку та відправлення посилань у месенджери. На iOS Universal Links не налаштовувалися через помилку в apple-app-site-association — рішення полягало в перевірці MIME-типу та правильній структурі JSON. На Android — налаштування intent-filter для App Links та fallback на Chrome. Результат — повна система репостів і шерінгу за 2-3 дні з нуля. Ми розберемо обидві задачі, покажемо робочі рішення та порівняємо підходи.
Внутрішній репост
Яку модель даних обрати?
Два підходи:
- Копіювання контенту — новий пост з полем
reposted_from_id. Простота відображення, але при редагуванні оригіналу копія застаріває. - Посилання на оригінал — пост з
repost_of_id, тіло не копіюється, береться при запиті. При видаленні оригіналу репост показує «Оригінал видалено». Цей підхід використовують Telegram і Twitter/X. Він у 3 рази скорочує дублювання даних та автоматично синхронізується з оригіналом. Використовується лише один додатковий SQL-запит при завантаженні стрічки.
У стрічці репост рендериться як embedded-картка оригіналу всередині комірки репостера.
// iOS — ячейка поста с вложенной карточкой if let repostOf = post.repostOf { // Рисуем RepostCardView внутри PostCell let repostCard = RepostCardView(post: repostOf) contentStack.addArrangedSubview(repostCard) } Картка — UIView із заокругленими кутами, обводкою CALayer.borderColor, аватаром та ім'ям автора оригіналу. Репост репосту відображає лише один рівень вкладеності.
На Compose: if (post.repostOf != null) EmbeddedPostCard(post = post.repostOf).
Як уникнути дублювання репостів?
Таблиця reposts (user_id, original_post_id, UNIQUE) — користувач може репостити оригінал лише один раз. Кнопка після натискання підсвічується, повторне натискання скасовує репост (un-repost). Лічильник reposts_count у таблиці оригіналу інкрементується за <10 мс.
Зовнішній шерінг
Чому він складніший, ніж здається?
iOS
let items: [Any] = [postText, URL(string: deeplink)!] let vc = UIActivityViewController(activityItems: items, applicationActivities: nil) // На iPad нужен popoverPresentationController vc.popoverPresentationController?.sourceView = shareButton present(vc, animated: true) Для зображення — рендеримо UIView в UIImage через UIGraphicsImageRenderer. Android
val intent = Intent(Intent.ACTION_SEND).apply { type = "text/plain" putExtra(Intent.EXTRA_TEXT, "$postText\n$deeplinkUrl") } startActivity(Intent.createChooser(intent, "Поділитися")) Для зображень — Intent.ACTION_SEND з type = "image/*" та URI через FileProvider. Прямий file:// URI не працює з Android 7+, лише content://.
Flutter
await Share.shareXFiles([XFile(imagePath)], text: '$postText\n$deeplinkUrl'); Як налаштувати deep link для шерінгу?
Зовнішній шерінг без deep link втрачає сенс. Посилання має відкривати конкретний пост у додатку. На iOS — Universal Links (apple-app-site-association на сервері + NSUserActivityTypes). На Android — App Links (assetlinks.json + intent-filter). Якщо додаток не встановлено — fallback на веб-версію. Детальніше про deep linking на Wikipedia.
Apple App Store Review Guidelines (Section 4.2) та Google Play Console вимагають коректної реалізації deep link для обходу reject.
Порівняння
Внутрішній репост vs зовнішній шерінг
| Аспект | Внутрішній репост | Зовнішній шерінг |
|---|---|---|
| Мета | Публікація у своїй стрічці | Відправлення в інший додаток |
| Deep link | Не потрібен | Обов'язковий |
| Лічильник | Так, інкремент/декремент | Не потрібен |
| iOS | Core Data + SwiftUI | UIActivityViewController |
| Android | Room + Jetpack Compose | Intent.ACTION_SEND |
Власний продукт vs готова SDK
Якщо використовувати готову SDK (наприклад, Branch.io), ви отримуєте швидкий старт, але зав'язуєтесь на зовнішнього провайдера, що збільшує вартість ліцензії та ризики блокувань. Кастомна реалізація дає повний контроль та економію до 50% (у 2 рази) порівняно з Branch.io на масштабі понад 10К користувачів.
Обсяг робіт та терміни
- Проєктування моделі даних репосту (схема БД, API).
- Розробка embedded-картки для стрічки (iOS/Android/Flutter).
- Інтеграція із системним share sheet та deep linking.
- Налаштування push-сповіщень при репості.
- Документація та передача вихідного коду.
Етапи роботи: аналітика, проєктування, реалізація (backend + клієнт), інтеграція, тестування, деплой.
Строки та вартість
Внутрішній репост з UI — 1-2 дні. Зовнішній шерінг з deep link — ще 1-2 дні. Повна система з обома режимами — 2-3 дні при паралельній розробці платформ. Вартість базової версії починається від $500. Повна система під ключ — від $1200. Пишіть нам — оцінимо ваш проєкт безкоштовно. Замовте розробку під ключ за 2-3 дні!
Типові помилки при реалізації
- Відсутність обробки видалення оригіналу — репост посилається на неіснуючий об'єкт.
- Ігнорування Android
FileProvider— краш на Android 7+ при шерінгу зображень. - Забутий
popoverPresentationControllerна iPad — додаток зависає.
Досвід нашої команди — 10+ років у мобільній розробці, понад 50 реалізованих проєктів. Гарантія якості на кожен етап.







