[Flutter/Riverpod] ImagePicker로 갤러리 이미지 불러오기(MultiImage)

찌니·2023년 3월 24일
1

Flutter

목록 보기
8/13
post-thumbnail

본인은 게시판에 게시할 이미지를 불러오는 코드를 구현할 예정이다.
이미지는 5장으로 한정하며 완성된 UI는 위 사진과 같다

먼저 본인은 게시판, 채팅, 유저 정보 등 여러 화면에서 사용할 예정이므로 Service를 싱글톤으로 구현했다

ImagePickerService


pickImage() 메소드는 여러장의 사진을 불러오는 메소드이며, picksingleImage()는 한장의 사진을 불러오는 메소드이다.
다음은 게시판에서 서비스를 사용하는 Provider이다

ImagePickProvider


copilot이 작성해준 주석...
참고로 위 코드에서 리스트를 복사하는 부분이 있는데

list = super.state
list.remove()
state = list

이런식으로 하면 안된다. state가 변화를 감지할 때 rebuild를 하게 되는데 list = super.state를 하게되면 list가 state를 받게 된다.
그래서 state = list가 결국은 state = state가 되어버려 rebuild가 일어나지 않는다... 다들 알고 있을 수도 있지만 본인은 값을 받는다고 생각해 꽤나 헤맸다 실제로 list type도 List<XFil'>로 나오니 헷갈릴 수밖에!!!

ImageWidget


이렇게 깔끔한 UI가 완성되었다!

<복붙용 코드>

ImageService

class ImagePickerService {
  static final ImagePickerService _imagePickerService =
      ImagePickerService._internal();
  factory ImagePickerService() {
    return _imagePickerService;
  }
  ImagePickerService._internal();

  final ImagePicker _picker = ImagePicker();
  Future<List<XFile>> pickImage() async {
    try {
      final pickedFile = await _picker.pickMultiImage();
      return pickedFile;
    } catch (e) {
      print('ImagePickerService: $e');
      return [];
    }
  }

  Future<XFile?> pickSingleImage() async {
    try {
      final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
      return pickedFile;
    } catch (e) {
      print('ImagePickerService: $e');
      return null;
    }
  }
}

ImageProvider

class ImageState extends StateNotifier<List<XFile>> {
  ImageState() : super(<XFile>[]);
  final ImagePickerService picker = ImagePickerService();

  
  set state(List<XFile> value) {
    super.state = value;
  }

  delImage(XFile image) {
    var list = [...super.state];
    list.remove(image);
    state = list;
  }

  void addImage(List<XFile> value) {
    var list = [...super.state];
    if (list.isEmpty) {
      state = value;
    } else {
      list.addAll(value);
      list.toSet().toList();
      state = list;
    }
    if (super.state.length > 5) {
      state = super.state.sublist(0, 5);
      Fluttertoast.showToast(msg: 'You can only upload 5 images');
    }
  }

  Future getImage() async {
    picker.pickImage().then((value) {
      addImage(value);
    }).catchError((onError) {
      Fluttertoast.showToast(msg: 'failed to get image');
    });
  }
}

ImageWidget

final imagePickerProvider =
    StateNotifierProvider<ImageState, List<XFile>>((ref) {
  return ImageState();
});

class ImageWidget extends ConsumerWidget {
  const ImageWidget({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    double imgBoxSize = ((MediaQuery.of(context).size.width - 32) / 5) - 4;
    final images = ref.watch(imagePickerProvider);

    Widget imageBox(XFile img) => GestureDetector(
          onTap: () => ref.read(imagePickerProvider.notifier).delImage(img),
          child: Container(
              margin: const EdgeInsets.symmetric(horizontal: 2),
              width: imgBoxSize,
              height: imgBoxSize,
              child: Stack(children: [
                Center(
                    child: Container(
                        decoration: BoxDecoration(
                            image: DecorationImage(
                                fit: BoxFit.cover,
                                image: Image.file(File(img.path)).image),
                            borderRadius: BorderRadius.circular(10)),
                        width: imgBoxSize,
                        height: imgBoxSize)),
                Positioned(
                    top: 0,
                    right: 0,
                    child: Container(
                        width: 20,
                        height: 20,
                        decoration: BoxDecoration(
                            color: Colors.grey[200],
                            borderRadius: BorderRadius.circular(10)),
                        child: Icon(Icons.close,
                            size: 15, color: Colors.grey[400])))
              ])));
    

    return Row(children: [
      if (images.length == 5) ...[
        ...images.map((e) => imageBox(e)).toList(),
      ] else ...[
        ...images.map((e) => imageBox(e)).toList(),
        InkWell(
            onTap: () => ref.read(imagePickerProvider.notifier).getImage(),
            child: Container(
              margin: const EdgeInsets.symmetric(horizontal: 2),
              width: MediaQuery.of(context).size.width * 0.17,
              height: MediaQuery.of(context).size.width * 0.17,
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey[300]!, width: 1),
                borderRadius: BorderRadius.circular(10),
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.image, color: Colors.grey[400]!),
                  const SizedBox(
                    height: 5,
                  ),
                  Text(
                    'image',
                    style: TextStyle(
                        color: Colors.grey[400],
                        fontWeight: FontWeight.w500,
                        fontSize: 12.0),
                  )
                ],
              ),
            ))
      ]
    ]);
  }
}
profile
찌니's develog

0개의 댓글