Flutter 에서 카메라 및 사진 불러오기 기능 사용하는 방법.
1. 안드로이드 AndroidManifest.xml 권한 추가.
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
Android 14에서부터 ‘사진/동영상의 일부 접근 권한’
(READ_MEDIA_VISUAL_USER_SELECTED) 이라는 개념이 추가되었습니다.
https://developer.android.com/about/versions/14/changes/partial-photo-video-access?hl=ko
아래는 홈페이지에 나와있는 내용입니다.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" /> // 33이상.
<uses-permission android:name="android.permission.CAMERA"/>
2. IOS 권한 설정
2-1. Runner / info.plist 추가
<key>NSCameraUsageDescription</key>
<string>We need access to the camera to take photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need access to your photo library to upload images.</string>
2-2. Podfile 추가
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
// 이 부분 부터
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.calendar
#'PERMISSION_EVENTS=1',
## dart: PermissionGroup.calendarFullAccess
#'PERMISSION_EVENTS_FULL_ACCESS=1',
## dart: PermissionGroup.reminders
#'PERMISSION_REMINDERS=1',
## dart: PermissionGroup.contacts
#'PERMISSION_CONTACTS=1',
## dart: PermissionGroup.camera
'PERMISSION_CAMERA=1',
## dart: PermissionGroup.microphone
#'PERMISSION_MICROPHONE=1',
## dart: PermissionGroup.speech
#'PERMISSION_SPEECH_RECOGNIZER=1',
## dart: PermissionGroup.photos
'PERMISSION_PHOTOS=1',
## The 'PERMISSION_LOCATION' macro enables the `locationWhenInUse` and `locationAlways` permission. If
## the application only requires `locationWhenInUse`, only specify the `PERMISSION_LOCATION_WHENINUSE`
## macro.
##
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
'PERMISSION_LOCATION=1',
'PERMISSION_LOCATION_WHENINUSE=0',
## dart: PermissionGroup.notification
'PERMISSION_NOTIFICATIONS=1',
## dart: PermissionGroup.mediaLibrary
'PERMISSION_MEDIA_LIBRARY=1',
## dart: PermissionGroup.sensors
#'PERMISSION_SENSORS=1',
## dart: PermissionGroup.bluetooth
#'PERMISSION_BLUETOOTH=1',
## dart: PermissionGroup.appTrackingTransparency
#'PERMISSION_APP_TRACKING_TRANSPARENCY=1',
## dart: PermissionGroup.criticalAlerts
#'PERMISSION_CRITICAL_ALERTS=1',
## dart: PermissionGroup.criticalAlerts
#'PERMISSION_ASSISTANT=1',
]
end
// 여기까지
end
end
3. pubspec.yaml - dependencies 추가 .
image_picker: ^1.1.2
permission_handler: ^10.2.0
4. Flutter Code 추가
4-1.
class _MyHomePageState extends State<MyHomePage> {
String? imagePath;
Future<void> _pickImage() async {
// 이미지 선택
final ImagePicker _picker = ImagePicker();
final XFile? pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
imagePath = pickedFile.path;
});
}
}
Future<void> _takePhoto() async {
// 사진 촬영
final ImagePicker _picker = ImagePicker();
final XFile? takenFile =
await _picker.pickImage(source: ImageSource.camera);
if (takenFile != null) {
setState(() {
imagePath = takenFile.path;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Picker Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
imagePath != null
? Image.file(
File(imagePath!),
width: 300, // 너비
height: 300, // 높이
fit: BoxFit.cover, // 이미지 비율 유지
)
: const Text('이미지를 선택하거나 촬영하세요.'),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _pickImage,
child: const Text('앨범에서 이미지 선택하기'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: _takePhoto,
child: const Text('사진 찍기'),
),
],
),
],
),
),
);
}
}
4-2. 여기에 퍼미션 권한을 확인하고 Alert 를 띄워서 설정 페이지에 갔다오는 코드를 추가하겠습니다.
void _showPermissionDeniedAlert(bool type) { // 파라미터 타입 추가
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('권한 필요'),
content: Text(
type ? '사진 앨범에 접근하기 위한 권한이 필요합니다.' : "카메라에 접근하기 위한 권한이 필요합니다."),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
openAppSettings(); // 설정 화면으로 이동
},
child: Text('설정으로 이동'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('취소'),
),
],
);
},
);
}
4-3. _pickImage() , _takePhoto() 메서드에 코드 추가
Future<void> _pickImage() async {
// 권한 요청
var status = await Permission.photos.request();
if (!status.isGranted) {
_showPermissionDeniedAlert(true);
return;
}
// 이미지 선택
final ImagePicker _picker = ImagePicker();
final XFile? pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
imagePath = pickedFile.path;
});
}
}
Future<void> _takePhoto() async {
// 카메라 권한 요청
var status = await Permission.camera.request();
if (!status.isGranted) {
_showPermissionDeniedAlert(false);
return;
}
// 사진 촬영
final ImagePicker _picker = ImagePicker();
final XFile? takenFile =
await _picker.pickImage(source: ImageSource.camera);
if (takenFile != null) {
setState(() {
imagePath = takenFile.path;
});
}
}
5. Error 확인.
나 같은 경우는 셋팅화면을 갔다오면 화면이 날아가는 현상이 있었다. 생명주기 확인이 필요하였다.
5-1. class 에 WidgetsBindingObserver 을 상속.
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
5-2. 코드 추가
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
print("앱이 resumed .");
setState(() {
imagePath = imagePath;
});
} else if (state == AppLifecycleState.paused) {
} else if (state == AppLifecycleState.detached) {
} else if (state == AppLifecycleState.inactive) {
}
}
전체 코드
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {
String? imagePath;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
print("앱이 resumed .");
setState(() {
imagePath = imagePath;
});
} else if (state == AppLifecycleState.paused) {
} else if (state == AppLifecycleState.detached) {
} else if (state == AppLifecycleState.inactive) {
}
}
Future<void> _pickImage() async {
// 권한 요청
var status = await Permission.photos.status;
if (!status.isGranted) {
_showPermissionDeniedAlert(true);
return;
}
// 이미지 선택
final ImagePicker _picker = ImagePicker();
final XFile? pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
imagePath = pickedFile.path;
});
}
}
Future<void> _takePhoto() async {
// 카메라 권한 요청
var status = await Permission.camera.status;
if (!status.isGranted) {
_showPermissionDeniedAlert(false);
return;
}
// 사진 촬영
final ImagePicker _picker = ImagePicker();
final XFile? takenFile =
await _picker.pickImage(source: ImageSource.camera);
if (takenFile != null) {
setState(() {
imagePath = takenFile.path;
});
}
}
void _showPermissionDeniedAlert(bool type) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('권한 필요'),
content: Text(
type ? '사진 앨범에 접근하기 위한 권한이 필요합니다.' : "카메라에 접근하기 위한 권한이 필요합니다."),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
openAppSettings(); // 설정 화면으로 이동
},
child: Text('설정으로 이동'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('취소'),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Picker Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
imagePath != null
? Image.file(
File(imagePath!),
width: 300, // 너비
height: 300, // 높이
fit: BoxFit.cover, // 이미지 비율 유지
)
: const Text('이미지를 선택하거나 촬영하세요.'),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _pickImage,
child: const Text('앨범에서 이미지 선택하기'),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: _takePhoto,
child: const Text('사진 찍기'),
),
],
),
],
),
),
);
}
}
'Flutter' 카테고리의 다른 글
[Flutter] Firebase Analytics 추가하기. (ios) (0) | 2024.12.21 |
---|---|
[Flutter] 카메라 및 사진 불러오기 기능 사용하기 . Part. 2 (0) | 2024.12.07 |
[Flutter] Firebase FCM 사용하기 Part. 3 (Web) (0) | 2024.12.03 |
[Flutter] Firebase FCM 사용하기 Part. 2 (Android) (1) | 2024.11.30 |
[Flutter] Firebase FCM 사용하기 Part. 1 (0) | 2024.11.27 |