Camera (Photo) Integration into a Mobile Application
At first glance, the task is simple — open camera, take shot, get file. In practice, it's a chain of permissions, delegates, orientation handling, and server upload, where each step can fail differently.
Where Problems Usually Arise
Permissions. iOS requires Info.plist descriptions (NSCameraUsageDescription, NSPhotoLibraryUsageDescription), otherwise crash without clear log. Android before API 23 had static permissions, from API 23+ — runtime request via ActivityResultContracts.RequestPermission. If you don't handle shouldShowRequestPermissionRationale, user clicks "Deny forever" — and app silently gets no camera access without explanation.
EXIF Orientation. Device physically rotates sensor, but JPEG gets recorded "sideways," with correct orientation stored in EXIF Orientation tag. UIImageView on iOS shows picture correctly due to automatic correction, but once photo goes to server, Node.js or Python reads bytes without EXIF — and avatar is rotated 90°. Android has same issue with ExifInterface from androidx.exifinterface.
Gallery Selection. PHPickerViewController with PHPickerFilter.videos on iOS 14+ works without Photos library access request. For older versions — UIImagePickerController with .photoLibrary. On Android — ActivityResultContracts.PickVisualMedia (Photo Picker API, available from Android 13, backported via androidx.activity:activity:1.7+).
How We Implement
iOS (Swift, native). Open UIImagePickerController or PHPickerViewController. In delegate picker(_:didFinishPicking:), get NSItemProvider, call loadObject(ofClass: UIImage.self). Before saving or sending — normalize orientation via imageFixedOrientation() (draw in UIGraphicsImageRenderer with correct CGAffineTransform by EXIF). Compression: jpegData(compressionQuality: 0.82) — empirically chosen balance between size and quality for avatars/previews.
Android (Kotlin). CameraX — standard for photo capture via ImageCapture.takePicture(). Get ImageProxy, convert via toBitmap(), apply ExifInterface to read orientation and Matrix.postRotate() to correct. For gallery selection — PickVisualMedia with ActivityResultContracts.
Flutter. image_picker (pub.dev) — de facto standard. Under the hood calls platform pickers. Additionally: flutter_image_compress for resize before upload. On iOS need to add keys to Info.plist even using plugin.
React Native. react-native-image-picker or expo-image-picker (for Expo projects). Returns uri, width, height, fileSize. Orientation not always auto-corrected — react-native-image-resizer helps.
Upload to Server
Multipart upload via URLSession.shared.uploadTask (iOS) or OkHttp MultipartBody (Android). For large files implement progress via delegate/listener. On unstable connection — background upload via URLSessionConfiguration.background on iOS or WorkManager + CoroutineWorker on Android.
Custom Camera Interface
UIImagePickerController looks like system camera and doesn't support arbitrary UI. For custom interface — use AVCaptureSession directly: AVCaptureDeviceInput → AVCapturePhotoOutput, preview via AVCaptureVideoPreviewLayer. This allows adding document scan frame, camera switch button with animation, rule of thirds grid.
On Android custom camera — CameraX Preview + CameraX ImageCapture in one ProcessCameraProvider. PreviewView renders camera stream, on top — own view elements.
Timeline
Basic integration (camera + gallery + upload) — 1–2 days. If custom camera UI with document frame overlay or EXIF normalization on all devices needed — 3 days.







