Developing Image Post Publishing in a Mobile App
Publishing a photo post appears simple at first glance. In practice it includes image selection, cropping, compression, server upload, and database record creation. Each step can be implemented correctly or become a source of bugs.
Selection and Cropping
PHPickerViewController (iOS 14+) — standard choice. selectionLimit controls number of photos: for posts usually 1–10. PHPickerFilter.images filters only photos, excluding video and LivePhoto by default.
After photo selection, cropping is often needed. On iOS — TOCropViewController (open-source) or custom UIScrollView with UIImageView inside and pan/zoom gestures. To preserve aspect ratio during cropping: compute crop area CGRect in original image coordinates through CGAffineTransform — not in UIView coordinates, otherwise zoom causes misalignment.
On Android — uCrop (Yalantis) widely used in production: supports aspect ratio, rotation, custom toolbar. Alternative — android-image-cropper.
Compression Before Upload
Strategy: maximum dimension 1440px for posts (vs 1280px for chat — posts are usually viewed larger), JPEG quality 80%. This gives 200–400 KB file with acceptable visual quality.
On iOS UIGraphicsImageRenderer with CGSize target and jpegData(compressionQuality: 0.80). Operation runs in DispatchQueue.global(qos: .userInitiated). For multi-select (5+ photos) — sequential processing in background with progress on UI.
On Android — Bitmap.createScaledBitmap() with true filtering for better scaling quality, then compress(JPEG, 80, stream). Compute BitmapFactory.Options.inSampleSize beforehand by metadata to avoid loading original full size into memory.
Upload and Post Synchronization
Images upload before post creation. Algorithm:
- Upload all photos in parallel (not sequentially) — each in separate
URLSessionUploadTask. - Collect array of URLs/keys from server responses.
- Create post with this array via separate API request.
Parallel upload on iOS through withTaskGroup (Swift Concurrency) or DispatchGroup. On Android — coroutineScope { launch { ... } } for each file, awaitAll().
Display progress total: "uploaded X of N photos". Don't lock publish button strictly — if one of five photos failed, show retry only for it, rest are ready.
Optimistic post: create record in local DB (Core Data, Room) immediately with uploading status, show in feed with indicator. On successful upload update status to published. On failure — failed with retry button. User doesn't wait — they already see their post.
Display in Feed
Photo grid in post (multiple images) — UICollectionView with flow layout on iOS, LazyVerticalGrid (Compose) or RecyclerView with GridLayoutManager on Android. First photo large, rest — in 2×N grid — classic Instagram pattern.
Lazy loading via Kingfisher (iOS) or Coil (Android) with disk caching. Placeholder before load — blur-hash from metadata (if server returns it) or solid color from dominant image color.
Timeline
Photo selection + compression + upload + display in post — 1–3 days. Cropping + multi-select + optimistic UI — another 1–2 days. Cost calculated individually.







