Flutter

[Flutter] 폰에 저장된 이미지 가져오기 (Image Picker)

hminor 2023. 8. 11. 15:17

폰에 저장된 이미지 가져오기 (Image Picker)

  • 우선 플러그인을 하나 설치해야 하기에 pubspec.yaml에서 코드 기입
    • 이후 전구를 클릭 해서 Pub get 해주기
dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.4
  image_picker: ^0.8.4+4
  • 이후 ios/Runner/Info.plist 에서 아래의 코드를 기입 해주기
    • 해당 코드는 허락 받는 코드가 되고
      • 안드로이드는 필요 없는데
      • IOS에서는 필요해서 작성해줘야 함.
    <dict>
    
    	<key>NSPhotoLibraryUsageDescription</key>
    	<string>사진첩좀 써도 됩니까</string>
    	<key>NSCameraUsageDescription</key>
    	<string>카메라좀 써도 됩니까</string>
    	<key>NSMicrophoneUsageDescription</key>
    	<string>마이크 권한좀 제발</string>
    
  • 이제 main.dart 파일로 돌아가서 아래의 코드를 기입해 사용하기
  • import 'package:image_picker/image_picker.dart'; import 'dart:io';
  • 가져오는 코드 작성하기
    • picker 변수에 ImagePicker() 위젯을 담아주기
    • 이후 image 변수에 picker.pickImage() 위젯으로 source: ImageSource.gallery 를 작성
    • 여기서 처리하는 시간이 길기에 async, await을 사용해주기
    IconButton(
                onPressed: () async{
                  var picker = ImagePicker();
                  var image = await picker.pickImage(source: ImageSource.gallery);
    
                  Navigator.push(context,
                    MaterialPageRoute(builder: (c) => Upload()
                    )
                  );
                },
                icon: Icon(Icons.add_box_outlined)
              )
    
    • 이때 사진첩이 아닌 카메라를 켜고 싶다면 ImageSource.camera 를 작성하면 된다.
    • 비디오를 선택하고 싶다면
      • var image = await picker.pickVideo();
    • 한 장이 아닌 여러 사진을 가져오고 싶다면
      • pickMultiImage() 를 사용하기
  • 이제 선택한 사진을 보여주기
    • 상단에 변수를 만든 후 onPressed(){} 안에 담아주기
      • userImage = File(image.path);
    • 그런데 이렇게만 하면 에러가 발생할 수 있다.
      • 이유는 선택하지 않았을 경우 Null 이기에 걱정을 하는 것인데
      • 이럴 땐 조건문을 사용해주면 된다.
    IconButton(
                onPressed: () async{
                  var picker = ImagePicker();
                  var image = await picker.pickImage(source: ImageSource.gallery);
                  if (image != null) {
                    userImage = File(image.path);
                  }
                  Navigator.push(context,
                    MaterialPageRoute(builder: (c) => Upload()
                    )
                  );
                },
                icon: Icon(Icons.add_box_outlined)
              )
    
    • 다만 userImage 또한 State이기에 setState를 통해 변경해야 반영될 수 있다.
conButton(
            onPressed: () async{
              var picker = ImagePicker();
              var image = await picker.pickImage(source: ImageSource.gallery);
              if (image != null) {
                setState(() {
                  userImage = File(image.path);
                });
              }
              Navigator.push(context,
                MaterialPageRoute(builder: (c) => Upload()
                )
              );
            },
            icon: Icon(Icons.add_box_outlined)
          )

 

  • 이후 파일 경로로 이미지를 띄우는 방법은
    • Image.file(userImage); 로 작성하면 된다.
    import 'package:flutter/material.dart';
    import './style.dart' as style;
    import 'package:http/http.dart' as http;
    import 'dart:convert';
    // 이미지 볼러오기
    import 'package:image_picker/image_picker.dart';
    import 'dart:io';
    
    void main() {
      runApp(
        MaterialApp(
          theme: style.theme,
          initialRoute: '/',
          routes: {
            '/': (c) => MyApp(),
            '/mypage': (c) => Text('마이 페이지')
          },
          // home: MyApp()
        )
      );
    }
    
    class MyApp extends StatefulWidget {
      MyApp({super.key});
      
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
    
      var homeData = [];
    
      appendData(value){
        setState(() {
          homeData.add(value);
        });
      }
      
      getData() async{
        var result = await http.get(Uri.parse('경로'));
        if (result.statusCode == 200) {
          setState(() {
            homeData = jsonDecode(result.body);
          });
        } else {
          print('실패');
        }
      }
    
      @override
      void initState() {
        super.initState();
        getData();
      }
    
      // 0: home, 1: shop
      var tab = 0;
      var userImage;
    
      changeTab(tabNumber){
        setState(() {
          tab = tabNumber;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text('Instagram'),
              actions: [
                IconButton(
                  onPressed: () async{
                    var picker = ImagePicker();
                    var image = await picker.pickImage(source: ImageSource.gallery);
                    if (image != null) {
                      setState(() {
                        userImage = File(image.path);
                      });
                    }
                    Navigator.push(context,
                      MaterialPageRoute(builder: (c) => Upload(userImage:userImage)
                      )
                    );
                  },
                  icon: Icon(Icons.add_box_outlined)
                )
              ],
            ),
          body: [Home(homeData:homeData, appendData:appendData),Text('shop')][tab],
          bottomNavigationBar: BottomNavigationBar(
            onTap: (i){
              changeTab(i);
            },
            items: [
              BottomNavigationBarItem(
                icon: Icon(Icons.home_outlined),
                label: 'Home',
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.shopping_bag_outlined),
                label: 'Shopping',
              ),
            ],
            showSelectedLabels: false, // 선택된 아이템의 레이블 숨김
            showUnselectedLabels: false, // 선택되지 않은 아이템의 레이블 숨김
          ),
        );
      }
    }
    
    // Home
    class Home extends StatefulWidget {
      Home({super.key, this.homeData, this.appendData});
      final homeData;
      final appendData;
    
      @override
      State<Home> createState() => _HomeState();
    }
    class _HomeState extends State<Home> {
    
      // ScrollController() 는 자료를 저장해주는 것으로, 클래스가 된다. 그래서 스크롤 정보를 저장해주는 것을 하는데 도움을 준다
      var scroll = ScrollController();
      var cnt = 0;
      getAddData() async{
        cnt ++;
        print(cnt);
        var data = await http.get(Uri.parse('경로'));
        if (data.statusCode == 200){
          widget.appendData(jsonDecode(data.body));
        } else {
          print('더 이상 없어');
        }
      }
      
      @override
      void initState() {
        super.initState();
        scroll.addListener(() {
          if (scroll.position.pixels == scroll.position.maxScrollExtent) {
            getAddData();
          }
        });
      }
    
      @override
      Widget build(BuildContext context) {
        if (widget.homeData.isNotEmpty){
          return ListView.builder(
            controller: scroll,
            itemCount: widget.homeData.length,
            itemBuilder: (context, idx){
              return Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Image.network(widget.homeData[idx]['image']),
                  Container(
                    margin: EdgeInsets.fromLTRB(10, 20, 10, 20),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            Text('좋아요 ${widget.homeData[idx]['likes'].toString()}', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700),),
                          ],
                        ),
                        Text(widget.homeData[idx]['date'].toString()),
                        Text(widget.homeData[idx]['content'].toString()),
                      ],
                    ),
                  )
                ],
              );
            },
          );
        } else {
          return Text('빈');
        }
      }
    }
    
    // Upload
    class Upload extends StatelessWidget {
      Upload({Key? key, this.userImage}) : super(key: key);
      @override
      final userImage;
    
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(),
            body: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('선택한 이미지'),
                Image.file(userImage),
                IconButton(
                    onPressed: (){
                      Navigator.pop(context);
                    },
                    icon: Icon(Icons.close)
                ),
              ],
            )
        );
    
      }
    }
    
  • 다만 Don't use 'BuildContext's across async gaps. 이런 경고가 발생하는데
  • 이유는 위젯이 마운트되지 않으면 async뒤에 context를 썻을 때 그 안에 아무런 값도 들어있지 않을 수 있어서라고 한다.
  • 즉 IconButton에서 await 코드를 아래로 빼니까 경고가 사라졌다.
conButton(
            onPressed: () async{
              var picker = ImagePicker();
              var image = await picker.pickImage(source: ImageSource.gallery);
              if (image != null) {
                setState(() {
                  userImage = File(image.path);
                });
              }
              Navigator.push(context,
                MaterialPageRoute(builder: (c) => Upload()
                )
              );
            },
            icon: Icon(Icons.add_box_outlined)
          )

숙제

발행 버튼 누르면 글 발행하기 흠 우선 휴대폰에서 선택한 사진은
Image.network가 아닌 Image.file로 해줘야 하기에
 우선 에러 문구를 보면 String 타입의 경우엔 Image.network() 를 사용하라고 하니까
 해당 데이터의 타입을 확인해보고 조건 분기 처리를 하면 될 듯하다.
 해당 데이터의 타입을 확인해보고 싶다면 .runtimeType을 붙여주면 된다.

// Home
class Home extends StatefulWidget {
  Home({super.key, this.homeData, this.appendData});
  final homeData;
  final appendData;

  @override
  State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {

  // ScrollController() 는 자료를 저장해주는 것으로, 클래스가 된다. 그래서 스크롤 정보를 저장해주는 것을 하는데 도움을 준다
  var scroll = ScrollController();
  var cnt = 0;
  getAddData() async{
    cnt ++;
    print(cnt);
    var data = await http.get(Uri.parse('주소'));
    if (data.statusCode == 200){
      widget.appendData(jsonDecode(data.body));
    } else {
      print('더 이상 없어');
    }
  }
  
  @override
  void initState() {
    super.initState();
    scroll.addListener(() {
      if (scroll.position.pixels == scroll.position.maxScrollExtent) {
        getAddData();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    if (widget.homeData.isNotEmpty){
      return ListView.builder(
        controller: scroll,
        itemCount: widget.homeData.length,
        itemBuilder: (context, idx){
          print(idx);
          return Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              widget.homeData[idx]['image'].runtimeType == String?Image.network(widget.homeData[idx]['image']):Image.file(widget.homeData[idx]['image']),
              Container(
                margin: EdgeInsets.fromLTRB(10, 20, 10, 20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Text('좋아요 ${widget.homeData[idx]['likes'].toString()}', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700),),
                      ],
                    ),
                    Text(widget.homeData[idx]['date'].toString()),
                    Text(widget.homeData[idx]['content'].toString()),
                  ],
                ),
              )
            ],
          );
        },
      );
    } else {
      return Text('빈');
    }
  }
}

// Upload
class Upload extends StatefulWidget {
  Upload({Key? key, this.userImage, this.appendData, this.homeData}) : super(key: key);
  @override
  final userImage;
  final appendData;
  final homeData;

  @override
  State<Upload> createState() => _UploadState();
}

class _UploadState extends State<Upload> {
  var inputValue = '';

  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('선택한 이미지'),
            Image.file(widget.userImage),
            TextField(onChanged: (val){setState(() {
              inputValue = val;
            });}),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                IconButton(
                    onPressed: (){
                      Navigator.pop(context);
                    },
                    icon: Icon(Icons.close)
                ),
                IconButton(
                    onPressed: (){
                      // print(widget.userImage);
                      var val = {'id': 3, 'image': widget.userImage, 'likes': 0, 'date': 'Aug 11', 'content': inputValue, 'liked': false, 'user': 'Hminor'};
                      widget.appendData(val);
                      Navigator.pop(context);
                    },
                    icon: Icon(Icons.add)
                ),
              ],
            )
          ],
        )
    );

  }
}

 

그리고 추가한 게시글이 가장 앞에 위치하고자 한다면 add가 아닌 
insert(idx, value) 로 추가해주면 idx 번째에 추가해주는 것을 의미한다.