이번 포스팅에서는 스토리지에 직접 이미지 파일을 저장하는 방법에 대해 알아보도록 하겠습니다.
Firebase Storage를 사용하기 위해선, Firebase_Storage 라이브러리를 프로젝트에 다운로드 받으셔야 합니다.
당연히, Firebase를 사용하기 위해선 core도 필요한거 아시죠? 필요한 패키지는 모두 다운로드 받을겁니다.
Image Picker는 손쉽게 디바이스의 이미지 파일을 다룰 수 있게 해주는 라이브러리입니다.
Pub.dev에서 가장 인기가 많은 라이브러리 중 하나입니다. 현재는 버전이 0.8.7+4네요. 버전마다 다루는 방법이 다를 수 있습니다. 문서를 보고 따라하시면 문제가 없을겁니다.
Image Picker얘기를 왜 갑자기 하냐면, 이 라이브러리를 통해서 갤러리의 이미지를 가져와서 Firebase Storage에 저장하는 앱을 만들거기 때문입니다. 일단 사용을 위해서 필요한 라이브러리인 Image_Picker, Firebase_Storage를 꼭 다운로드 받으셔야 합니다.
이미지를 불러오기 위해선 url을 사용합니다. url을 데이터베이스에 따로 저장할거기 때문에, cloud_firestore도 함께 받았습니다.
우선, 기본적인 UI를 디자인 하겠습니다. 상단 앱바에서 아이콘을 클릭하면 갤러리에서 이미지를 선택한 후, 선택이 완료되면, 앱 중앙에 선택한 이미지를 랜더시키고, 버튼을 이용해서 Firebase_storage에 이미지를 저장시키는 방식으로 화면을 구성하겠습니다.
import 'package:flutter/material.dart';
class ImagePickerApp extends StatefulWidget {
const ImagePickerApp({super.key});
State<ImagePickerApp> createState() => _ImagePickerAppState();
}
class _ImagePickerAppState extends State<ImagePickerApp> {
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}
가장 기본적인 화면입니다. 앱바를 만들어서 상단엔 갤러리 모양의 버튼, 저장 버튼, 중간에는 이미지가 삽입되는 부분, 그리고 저장된 이미지를 갤러리 형식으로 불러오겠습니다. 앱바부터 만들겠습니다.
...
class _ImagePickerAppState extends State<ImagePickerApp> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.image)),
IconButton(onPressed: () {}, icon: const Icon(Icons.check)),
],
),
...
이제 가장 위는 선택된 이미지를 보여주게끔 하겠습니다. 앱 화면을 정확히 두 부분으로 나누어서, _selectedImage(), _imageGallery() 두개로 분리시킬겁니다. 우선, 선택된이미지가 보이는 부분을 만들겠습니다.
...
body: SingleChildScrollView(
child: Column(
children: [
_selectedImage(), //이미지가 선택되면 이미지를 보여줌
],
),
),
....
Widget _selectedImage() {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width,
color: Colors.black,
child: const Center(
child: Icon( // 선택 안되면 보여줄 아이콘부분
Icons.image_not_supported,
color: Colors.white,
size: 100,
),
),
);
}
마지막으로, 저장된 모든 이미지를 불러오는 갤러리를 만들겁니다. gridView로 만들면 편하겠네요.
...
body: SingleChildScrollView(
child: Column(
children: [
_selectedImage(),
_imageGallery(),
],
),
),
...
Widget _imageGallery() {
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: 1.0, crossAxisSpacing: 1.0, crossAxisCount: 3),
itemCount: 10,
itemBuilder: (context, index) => Container(
color:
Colors.primaries[Random().nextInt(Colors.primaries.length)],
));
}
...
GridView는 무작위 랜덤의 색상이 나오게끔 일단 처리해놨지만, 실제로 마무리 단계에서는 저장된 url의 갯수를 전달하고 이미지를 보여줄 수 있게 바꿔줄겁니다. 그리고, GridVeiw와 SingleChildScrollView모두 스크롤이 가능하기 때문에 GridView는 스크롤링이 되지 않도록, physics 프로퍼티에 NeverScrollableScrollPhysics()를 아규먼트로 전달했습니다. 이렇게 하지 않으면 오류가 발생합니다.
앱바에서 이미지 모양의 아이콘 버튼을 클릭하면 이미지를 가져와야 합니다. 이를 위해서는 Image_Picker를 사용할건데, 항상 권한 설정을 해야 합니다.
1. iOS 설정
info.plist파일에서 하단을 추가합니다.
<key>NSPhotoLibraryUsageDescription</key>
<string>this app use gallery</string>
<key>NSCameraUsageDescription</key>
<string>this app use camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>this app use microphone</string>
저기서, 키에 대한 값으로 보통은 이 앱에서 이 기능이 왜 필요한지 설명해줘야 합니다. 이거 그냥 대충대충 적으면 앱스토어 심사에서 탈락합니다.
2. Android 설정
AndroidManifest.xml에 하단과 같은 권한 설정을 추가합니다.
android:requestLegacyExternalStorage="true"
자, 이제 시작해볼까요? 코드 상단부분에서 다음 부분을 추가해줍니다.
...
class _ImagePickerAppState extends State<ImagePickerApp> {
ImagePicker picker = ImagePicker();
File? selectedImage;
...
picker는 ImagePicker를 사용하기 위한 값입니다. 뭐 보통 사용하려면 이렇게 선언하죠? 그리고 ImagePicker를 이용해서 가져온 이미지는 selectedImage라는 변수에 전달할겁니다. 그래서 초기화는 일단 시키지 않습니다. 함수 하나를 만들게요. pickImageFromGallery입니다.
...
Future<void> pickImageFromGallery() async {
var image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
selectedImage = File(image.path);
});
}
}
...
이 함수를 통해서 갤러리의 이미지를 가져오고, 그 가져온 이미지를 토대로 selectedImage에 전달할 수 있습니다.
그리고, 기존에 작성한 _selecetedImage()에 변화를 주겠습니다.
Widget _selectedImage() {
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width,
color: Colors.black,
child: Center(
child: (selectedImage == null)
? const Icon(
Icons.image_not_supported,
color: Colors.white,
size: 100,
)
: Image.file(
selectedImage!,
fit: BoxFit.fill,
),
),
);
}
이제, 이미지를 선택하면 상단에 이미지가 없는 아이콘에서 이미지로 변할겁니다. 이미지를 선택해보겠습니다.
자, 이미지 선택까지 완료입니다. 이제 저장소로 저장해볼차례군요.
현재까지 상황은 이미지를 선택해서 변수에 저장해놨습니다. 그러면 이 이미지 파일을 그대로 업로드시키면 되겠네요? 업로드를 하기 위한 함수를 작성합시다.
Future<void> uploadImage() async {
final now = DateTime.now(); //현재 시간 저장
var ref = storage.ref().child('Images/$now.jpg'); //참조 생성
ref.putFile(selectedImage!); //참조에 파일 저장
}
현재 이 함수는 우리가 가져온 파일을 저장소에 저장하는 방식입니다. 가장 처음으로 이미지의 이름이 겹치는것을 원하지 않기 때문에 uid 대신, 현재 시간을 이름으로 지정해서, 참조를 생성할 때 이미지의 이름으로 사용합니다. 그러고 나서 putFile메소드를 통해서 선택한 이미지를 저장소에 저장할 수 있습니다. 결과를 한번 볼까요?
저 함수를 이용해서 check 버튼을 클릭해서 저장소에 이미지를 저장했습니다.
안드로이드 권한 설정은 따로 안하신거 같은데 이유가 있으실까요?
잘 보고 배우고 있어요^^