Picking an image and uploading it are separate operations. This earlier example presented camera/gallery choices before passing the chosen file into the application’s avatar flow.
Platform and dependency context
image_picker: ^0.6.7+14
<key>NSCameraUsageDescription</key>
<string>App requires access to the camera for avatar upload</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App requires access to the photo library for avatar upload</string>
android:requestLegacyExternalStorage="true"
The Android legacy-storage flag and picker API belong to the earlier toolchain; they are not a complete permission/storage setup for every later Android release.
The selection interaction
Future<void> _selectImageSource(BuildContext context) async {
// Close keyboard
FocusScope.of(context).requestFocus(FocusNode());
// Define max size of image
const double maxWidth = 350;
const double maxHeight = 500;
// Show choose image source
final List<String> source = <String>['Camera', 'Gallery'];
final List<CupertinoActionSheetAction> actions = source
.map((String it) => CupertinoActionSheetAction(
isDefaultAction: true,
onPressed: () {
// pop value
Navigator.pop<int>(context, source.indexOf(it));
},
child: Text(
it,
style: boldTextStyle(14, Colors.black),
),
))
.toList(growable: false);
final int index = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) => CupertinoActionSheet(
actions: actions,
cancelButton: CupertinoActionSheetAction(
isDefaultAction: true,
isDestructiveAction: true,
onPressed: () {
// pop null
Navigator.pop(context);
},
child: Text(
'Close',
style: boldTextStyle(14, Colors.red),
),
),
),
);
if (index != null) {
final ImagePicker _imagePicker = ImagePicker();
final PickedFile image = await _imagePicker.getImage(
source: index == 0 ? ImageSource.camera : ImageSource.gallery,
maxWidth: maxWidth,
maxHeight: maxHeight,
);
if (image != null) {
final File file = File(image.path);
// Upload media image
firebaseCallSafety(() {
return CloudStorage.I.uploadFile(file);
}, onStart: () async {
AppLoadingProvider.show(context);
}, onCompleted: (bool status, void _) async {
AppLoadingProvider.hide(context);
if (status) {
AppHelper.showToast('success');
}
});
}
}
}
IconButton(
icon: const Icon(Icons.photo_library),
onPressed: () {
selectImageSource(context);
},
),
A canceled selection, denied permission, disposed screen, and failed upload need distinct outcomes. The selected file still needs validation and deliberate storage lifetime. No image was captured, selected, or uploaded for this revision.