플러터로 이미지 업로더 만들기 (with Riverpod)

2023. 4. 12. 22:23Flutter

반응형

플러터로 이미지 업로더를 만들어보자

 

https://pub.dev/packages/image_picker

 

image_picker | Flutter Package

Flutter plugin for selecting images from the Android and iOS image library, and taking new pictures with the camera.

pub.dev

먼저, pub.dev의 image_picker을 pubspec.yaml에 넣은 뒤

안드로이드는 따로 설정이 필요 없고 iOS는 문서를 따라 Info.plist파일에 설정을 추가해 준다.

 

 

riverpod을 사용해 선택된 이미지를 담을 리스트를 선언해 준다.

class SelectedImgNotifier extends StateNotifier<List<XFile>> {
  SelectedImgNotifier() : super([]);

  void insertImg(List<XFile> list) {
    state = list;
  }

  removeImg(int idx) {
    var temp = [...state];
    temp.removeAt(idx);
    state = temp;
  }
}

final selectedImgProvider = StateNotifierProvider<SelectedImgNotifier, List<XFile>>((ref) {
  return SelectedImgNotifier();
});

초기값은 빈배열이며 insertImg를 통해 이미지를 추가하고 removeImg를 통해 원하는 이미지를 제거할 수 있다.

 

 

그리고 이미지를 선택하기 위한 함수를 작성한다.

 Future pickImages() async {
    final List<XFile> selectedImages = await _picker.pickMultiImage();
    if (selectedImages.isNotEmpty) {
      ref.watch(selectedImgProvider.notifier).insertImg(selectedImages);
    }
  }

다중 이미지 선택을 위해 pickMultiImage()를 사용하였다

단일 이미지 선택을 원한다면 pickImage()를 사용하면 된다.

pickMultiImage()를 통해 이미지 리스트를 받아온 뒤, 이미지 리스트가 비어있지 않다면 insertImg함수를 통해 selectedImgProvider에 저장한다.

 

이미지를 등록하고 출력하기 위한 간단한 레이아웃을 작성했다

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        InkWell(
          onTap: () => pickImages(),
          child: Icon(Icons.add_a_photo_outlined),
        ),
        SizedBox(width: 5.w),
        Expanded(
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              children: [
                for (var i = 0; i < ref.watch(selectedImgProvider).length; i++)
                  Stack(
                    children: [
                      Container(
                        margin: EdgeInsets.symmetric(horizontal: 2.w),
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8.w),
                          image: DecorationImage(
                            image: FileImage(File(ref.watch(selectedImgProvider)[i].path)),
                            fit: BoxFit.cover,
                          ),
                        ),
                        width: 80.w,
                        height: 80.w,
                      ),
                      Positioned(
                        top: 0,
                        right: 0,
                        child: InkWell(
                          onTap: () => ref.watch(selectedImgProvider.notifier).removeImg(i),
                          child: Icon(Icons.close),
                        ),
                      ),
                    ],
                  )
              ],
            ),
          ),
        )
      ],
    );
  }

아이콘을 누르면 pickImages() 함수가 실행되며 갤러리가 표시된다.

에뮬레이터 기준으로 짧게 사진을 누르면 단일 선택이 되고 길게 누르면 다중 선택이 가능해진다.

이미지를 모두 고른 후 선택을 누르면 다음과 같이 이미지 미리 보기가 출력된다. SingleChildScrollView를 통해 이미지가 많이 선택되어도 오버플로우 없이 스크롤되도록 적용하였다.

이미지 우측 상단 x버튼을 누르면 해당 이미지가 리스트에서 삭제되고 이미지 첨부 아이콘을 다시 누를 시 이미지 리스트는 clear 된다.

 

 

pick_img 전체코드

class PickImg extends ConsumerStatefulWidget {
  const PickImg({super.key});

  @override
  ConsumerState<ConsumerStatefulWidget> createState() => _PickImgState();
}

class _PickImgState extends ConsumerState<PickImg> {
  final ImagePicker _picker = ImagePicker();

  Future pickImages() async {
    final List<XFile> selectedImages = await _picker.pickMultiImage();
    if (selectedImages.isNotEmpty) {
      ref.watch(selectedImgProvider.notifier).insertImg(selectedImages);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        InkWell(
          onTap: () => pickImages(),
          child: Icon(Icons.add_a_photo_outlined),
        ),
        SizedBox(width: 5.w),
        Expanded(
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Row(
              children: [
                for (var i = 0; i < ref.watch(selectedImgProvider).length; i++)
                  Stack(
                    children: [
                      Container(
                        margin: EdgeInsets.symmetric(horizontal: 2.w),
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(8.w),
                          image: DecorationImage(
                            image: FileImage(File(ref.watch(selectedImgProvider)[i].path)),
                            fit: BoxFit.cover,
                          ),
                        ),
                        width: 80.w,
                        height: 80.w,
                      ),
                      Positioned(
                        top: 0,
                        right: 0,
                        child: InkWell(
                          onTap: () => ref.watch(selectedImgProvider.notifier).removeImg(i),
                          child: Icon(Icons.close),
                        ),
                      ),
                    ],
                  )
              ],
            ),
          ),
        )
      ],
    );
  }
}

 

 

후에 이미지를 서버로 전송하는 코드를 작성하면 추가 예정,,,

반응형