Jin's Dev Story

[Flutter] 폰에 저장된 이미지 가져오기 본문

Web & Android/Flutter

[Flutter] 폰에 저장된 이미지 가져오기

woojin._. 2023. 9. 22. 22:40

image_picker 설치와 셋팅

  • pubspec.yaml 파일에 코드 추가 후 pub get
  dependencies:
    image_picker: ^0.8.4+4
  • ios/Runner/info.plist 파일에 코드 추가
    • 하단에 추가
    • 사용자에게 허락 팝업 띄울 때 보이는 글자들
  <key>NSPhotoLibraryUsageDescription</key>
  <string>사진첩좀 써도 됩니까</string>
  <key>NSCameraUsageDescription</key>
  <string>카메라좀 써도 됩니까</string>
  <key>NSMicrophoneUsageDescription</key>
  <string>마이크 권한좀 제발</string>
  • dart 파일 맨 위에 import 추가
import 'package:image_picker/image_picker.dart';
import 'dart:io';

 

image_picker 사용법

onPressed: () async {
  var picker = ImagePicker();
  var image = await picker.pickImage(source: ImageSource.gallery);
}

⇒ 이미지 선택 화면 뜨는 코드

  • picker.pickImage(source: ImageSource.camera);
    • 사진 선택하는 갤러리 말고 카메라를 띄워줌
  • picker.pickVideo(source: ImageSource.gallery);
    • 비디오 선택 화면이 뜸
  • picker.pickMultiImage(source: ImageSource.gallery);
    • 여러 이미지 선택이 가능

→ 고른 이미지 사이즈, 화질 조정도 가능

→ photofilters 패키지 설치하면 이미지에 필터 씌울 수 있음

 

선택한 이미지 다루기

  • 이미지는 용량이 크므로 변수에 저장하기보단 이미지의 경로만 가져와서 변수에 저장하고 사용하는게 일반적임
onPressed: () async {
  var picker = ImagePicker();
  var image = await picker.pickImage(source: ImageSource.gallery);
   if (image != null) {  // 선택 안하면 null이므로 체크
    setState((){
      userImage = File(image.path);
    });
  }
}

 

선택한 이미지를 위젯으로 보여주기

  • Image.file(userImage) 사용하면 됨
    - 이미지 파일 경로를 넣으면 이미지로 보여주는 위젯
    - userImage 변수는 MyApp() 위젯 안에 있으므로 Upload로 변수를 보내서 사용

전체 코드

import 'package:flutter/material.dart';
import './style.dart' as style;
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter/rendering.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';

void main() {
  runApp(
      MaterialApp(
      theme: style.theme,
      home: MyApp()
  ));
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  var tab = 0;  // 1. 현재 상태 저장
  var list = [1, 2, 3];
  var map = {'name':'john', 'age':20};
  var data = [];
  var userImage;

  addData(a) {
    setState(() {
      data.add(a);
    });
  }

  getData() async {
    var result = await http.get(Uri.parse('https://codingapple1.github.io/app/data.json'));

    var result2 = jsonDecode(result.body);
    setState(() {
        data = result2;
    });

  }

  @override
  void initState() {
    super.initState();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
          title: Text('Instagram'),
          actions: [
            IconButton(
              icon: Icon(Icons.add_box_outlined),
              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) )
                );
              },
              iconSize: 30,
            )
          ]),
      body: [Home(data : data, addData : addData), Text('샵페이지')][tab],  // 2. state에 따라 tab 보이는 상태 변경
      bottomNavigationBar: BottomNavigationBar(
        showSelectedLabels: false,
        showUnselectedLabels: false,
        onTap: (i){
          setState(() {
            tab = i;
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home_outlined), label: '홈'),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_bag_outlined), label: '샵')
        ],
      ),
    );
  }
}

class Home extends StatefulWidget {
  const Home({Key? key, this.data, this.addData}) : super(key: key);
  final data, addData;

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

class _HomeState extends State<Home> {

  var scroll = ScrollController();

  getMore() async {
    var result = await http.get(Uri.parse('https://codingapple1.github.io/app/more1.json'));
    var result2 = jsonDecode(result.body);

    widget.addData(result2);
  }

  @override
  void initState() {
    super.initState();
    scroll.addListener(() {   // 스크롤바 높이 측정 코드
      if(scroll.position.pixels == scroll.position.maxScrollExtent) {
        getMore();
      }
    });
  }

  @override
  Widget build(BuildContext context) {

    if (widget.data.isNotEmpty) {
      return ListView.builder(
          itemCount: widget.data.length,
          controller: scroll,
          itemBuilder: (c, i) {
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Image.network(widget.data[i]['image']),
                Container(
                  constraints: BoxConstraints(maxWidth: 600),
                  padding: EdgeInsets.all(20),
                  width: double.infinity,
                  child: Column(
                    children: [
                      Text('좋아요 ${widget.data[i]['likes'].toString()}'),
                      Text(widget.data[i]['date']),
                      Text(widget.data[i]['content'])
                    ],
                  ),
                )
              ],
            );
          }
      );
    } else {
      return Text('로딩중임');
    }
  }
}

class Upload extends StatelessWidget {
  const Upload({Key? key, this.userImage}) : super(key: key);
  final userImage;

  @override

  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Image.file(userImage),
            Text('이미지업로드화면'),
            TextField(),
            IconButton(
                onPressed: (){
                  Navigator.pop(context);
                },
                icon: Icon(Icons.close)
            ),
          ],
        )
    );
  }
}

숙제

→ 발행 버튼 누르면 유저가 입력한 글과 사진 게시물로 보여주기

  1. 우선 발행 버튼 하나 만들고
  2. Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( actions: [ IconButton(onPressed: (){}, icon: Icon(Icons.send)) ]), (생략)
  3. 유저가 입력한 글과 사진을 MyApp 안에 state에 저장해둠
    • 유저가 글 입력하면 userContent에 저장되게 만들기
      (Upload페이지)
      
      TextField(onChanged: (text){ setUserContent(text); })
  4. class _MyAppState extends State<MyApp> { var tab = 0; var data = []; var userImage; var userContent; setUserContent(a){ setState(() { userContent = a; }); } (생략)
  5. 발행 버튼 누르면 var data = [] 에 게시물 데이터 하나 추가
    • 게시물 데이터엔 유저가 선택한 사진과 글이 들어가면 됨
    • list 맨 앞부분에 자료 추가하고 싶으면 data.insert(0, 자료) 사용
    class _MyAppState extends State<MyApp> {
    
      var tab = 0;
      var data = [];
      var userImage;
      var userContent;
    
      addMyData(){
        var myData = {
          'id': data.length,
          'image': userImage,
          'likes': 5,
          'date': 'July 25',
          'content': userContent,
          'liked': false,
          'user': 'John Kim'
        };
        setState(() {
          data.insert(0, myData);
        });
      }
    
    (생략)
    • Upload 페이지의 발행 버튼 누르면 저 함수 실행
      Scaffold(
            resizeToAvoidBottomInset: false,
            appBar: AppBar( actions: [
              IconButton(onPressed: (){ addMyData(); }, icon: Icon(Icons.send))
            ]),
      (생략)
  6. 이상한 건 if문으로 예외 처리
    - 3번까지 하고 돌리면 콘솔창에 에러남
    - type '_File' is not a subtype of type 'String’
    - 이미지 보여줄 때 Image.network() 여기에 ‘https://어쩌구’ 이런 거 넣어서 보여주고 있는데 사용자가 선택한 건 File 타입이기 때문에 타입 에러 발생
    - 그래서 삼중 연산자를 통해 이미지 경로가 문자이면 무엇을, 그에 아니라면 이것을 보여달라고 코드 구현
  7. Image.network(widget.data[i]['image']) // -> 이렇게 변경 widget.data[i]['image'].runtimeType == String ? Image.network(widget.data[i]['image']) : Image.file(widget.data[i]['image']),