글 작성자: 취업중인 피터팬
728x90

0 - 앞에 글과 이어집니다. add.dart는 backPage.dart 위에 bottomNavigationBar중 2번째 인덱스에 속한 body입니다. 옷의 사진과 정보를 올리는 역할을 합니다.

1. 목표


아이콘을 통해 핸드폰의 겔러리 혹은 카메라에 접근할 수 있도록 합니다.

사진을 압축하고 화면에 보여질 수 있도록 합니다.

사진의 관련된 정보를 입력받습니다.

사진을 storage에 저장합니다.

사진 정보를 datebase에 저장합니다.


2. 구현 내용

 

add.dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:graduationproject/provider.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path; //중요!!!! 임시 저장소에 압충하기 위해 입시 저장소 path을 알아내기 위한 라이브러리
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

여러가지 라이브러리를 임프로 해줍니다. 이미지를 압축해주는 라이브러리, provider 라이브러리, firebase 라이브러리 모두 add.dart에서 필요합니다.

class _AddState extends State<Add> {


  int Image_q = 0;
  String Image_Url;

  File _image;
  final picker = ImagePicker();
  final _valueList = [
    'Top', //1
    'Outer', //2
    'Pants', //3
    'Bag', //4
    'Shoes' //5
  ];
  var _selectedValue = 'Top';
  var selectCheck = int.parse('1');

Image_q 변수는 나중에 설명하도록 하겠습니다. Image_Url은 이미지를 저장하게 되면 고유 Url이 생성되게 되는데 이 Url을 통해서 이미지를 불러와야 하기 때문에 필요한 변수입니다. _image는 이미지 파일을 저장하는 변수입니다.  _valueList[] 배열은 DropdownButton을 위한 배열 변수입니다. DropdownButton의 초기값을 설정하기 위해 _selectedValue = 'Top'을 초기화 해줍니다. selectCheck은 번호가 변할 때마다 provider을 통해 그림을 새로 그리기 위해 선어해줍니다. seletCheck은 _valueList에 주석 처럼 값이 변합니다.

 

 final GlobalKey<FormState> _titlefk = GlobalKey<FormState>();
  final GlobalKey<FormState> _topfk = GlobalKey<FormState>();
  final GlobalKey<FormState> _bottomfk = GlobalKey<FormState>();
  final GlobalKey<FormState> _shoesfk = GlobalKey<FormState>();
  final GlobalKey<FormState> _sellerfk = GlobalKey<FormState>();

  TextEditingController title = TextEditingController(); // 제목
  TextEditingController top_total_length = TextEditingController(); // 총장
  TextEditingController shoulder_extend = TextEditingController(); //어깨 너비
  TextEditingController chest_size = TextEditingController(); // 가슴 단면
  TextEditingController retail_length = TextEditingController(); // 소매 길이
  TextEditingController bottom_total_length = TextEditingController(); // 총장
  TextEditingController waist_size = TextEditingController(); //허리 단면
  TextEditingController cortch_size = TextEditingController(); // 밑위 길이
  TextEditingController tail_edge = TextEditingController(); // 밑단 길이
  TextEditingController shoes_size = TextEditingController(); //신발 사이즈
  TextEditingController phone_number = TextEditingController(); //전화번호
  TextEditingController count_number = TextEditingController(); //계좌 번호
  TextEditingController bank_name = TextEditingController(); //계좌 번호

  FirebaseStorage _firebaseStorage = FirebaseStorage.instance;

  //컬랙션 명
  final String colName = 'Image_collection';

  // 필드명
  final String Image_Title = "title";
  final String Image_URL = "imgae_url";
  final String Image_top_total_length = "top_total_length";// 총장
  final String Image_shoulder_extend = "shoulder_extend"; //어깨 너비
  final String Image_chest_size = "chest_size"; // 가슴 단면
  final String Image_retail_length = 'retail_length'; // 소매 길이
  final String Image_bottom_total_length = 'bottom_total_length'; // 총장
  final String Image_waist_size = 'waist_size'; //허리 단면
  final String Image_cortch_size = 'cortch_size'; // 밑위 길이
  final String Image_tail_edge = 'tail_edge'; // 밑단 길이
  final String Image_shoes_size = 'shoes_size'; //신발 사이즈
  final String Image_phone_number = 'phone_number'; //전화번호
  final String Image_count_number = 'count_number'; //계좌 번호
  final String Image_bank_name = 'bank_name'; //계좌 번호

 

옷에 대한 정보를 받을 controller와 데이터 베이스에 지정할 필드명을 선언합니다.

 

@override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ChangeSign>(
      create: (_) => ChangeSign(),
      child: SingleChildScrollView(
        child: ListBody(children: <Widget>[
          Stack(
            alignment: Alignment.bottomCenter,
        children: <Widget>[
            Container(
                child: _image == null ? Image.asset(
              'assets/add_image.jpg',
              fit: BoxFit.cover,
              ): Image.file(_image, fit: BoxFit.cover,)
            ),
          Container(
            color: Colors.black45,
            height: 70,
          ),
          ButtonBar(
            mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                _BuildCameraButton(),
                SizedBox(width: 250,),
                _BuildPhotoAlbumeButton()
              ]
          )
      ]
          ),
          Center(
            child: Consumer<ChangeSign>(
              builder: (context, pro_value, child) => DropdownButton(
                value: _selectedValue,
                items: _valueList.map(
                  (value) {
                    return DropdownMenuItem(
                      value: value,
                      child: Text(value),
                    );
                  },
                ).toList(),
                onChanged: (value) {
                  setState(() {
                    _selectedValue = value;
                    if (value == 'Top') selectCheck = 1;
                    if (value == 'Outer') selectCheck = 2;
                    if (value == 'Pants') selectCheck = 3;
                    if (value == 'Bag') selectCheck = 4;
                    if (value == 'Shoes') selectCheck = 5;
                    pro_value.ChangSelectedVale(selectCheck);
                  });
                },
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextFormField(
              key: _titlefk,
              decoration: InputDecoration(
                  labelText: '글의 제목을 입력해주세요',
                  hintText: "title",
                  border: OutlineInputBorder()),
              controller: title,
            ),
          ),
          if (selectCheck == 1) Top_information(),
          if (selectCheck == 2) Top_information(),
          if (selectCheck == 3) Bottom_information(),
          if (selectCheck == 5) Shose_informtion(),
          Seller_information(),
          SizedBox(
            height: 10,
          ),
          Stack(children: <Widget>[
            Container(
                child: Image.asset(
              'assets/add_image.jpg',
                  fit: BoxFit.cover,
            )),
          ]),
          Row(
            children: <Widget>[
              Expanded(
                child: RaisedButton(
                  child: Text('올리기'),
                  onPressed: () {
                    UpdateCustomDialog(context);
                  },
                ),
              ),
              SizedBox(
                width: 10,
              ),
              Expanded(
                child: RaisedButton(
                  child: Text('취소'),
                  onPressed: () {
                    CancelCustomDialog(context);
                  },
                ),
              )
            ],
          )
        ]),
      ),
    );
  }

먼저 이 코드를 설명하기 전에 옷에 대한 약간의 정보를 알아야합니다. 옷은 대략 top, outer, pants, shoes, bag 정도를 나눌수 있는데 이것마다 필요한 사이즈가 다릅니다. 예를 들어 top은 총장, 어깨 너비, 가슴 단면, 소매길이 가 필요하다면 pants은 허리 단면, 밑위 길이, 밑단 길이 등이 필요합니다. 이를 위해 사용자가 어떤 제품을 올리는지에 따라 입력받아야하는 값과 데이터 베이스에 저장해야 하는 필드 값이 달라집니다. 이를 구현하기 위해 provider을 설정하고 맨위 사진에 미리 지정한 사진을 넣어주고 DropdownButton을 구현하면서 사용자가 다른 값을 구하게 되면 pro_value.ChangSelectedVale(selectCheck)을 이용해 provider을 호출하고 그림을 다시 그릴 수 있도록 합니다. 그림을 다시 그리면서 밑에 if (selectCheck == 1) Top_information() 과 같이 selectCheck의 변수값에 따라 보이는 함수가 달라집니다. 함수에 그려지는 폼에 따라 정보를 입력하고 올리기를 누르면 UpdateCustomDialog(context) 함수가 호출됩니다. 다음 함수를 설명하도록 하겠습니다. 그 전에 이해를 쉽게 하기 위해 구현 화면을 먼저 첨부하도록 하겠습니다.

DropdownButton에서 top을 선택했을 때

 

DropdownButton에서 Pants을 선택했을 때

 

void UpdateCustomDialog(BuildContext context) {
    Dialog simpleDialog = Dialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.0),
      ),
      child: Container(
        height: 200.0,
        width: 300.0,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: EdgeInsets.only(left: 15.0, top: 20.0, right: 15.0, bottom: 0.0),
              child: Text(
                '정말 올리시겠습니까?',
                style: TextStyle(
                    color: Color(0xFFFF1744),
                    fontFamily: 'Multilingual',
                    fontStyle: FontStyle.normal,
                    fontSize: 25
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 10, right: 10, top: 30),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  RaisedButton(
                    color: Color(0xFF0277BD),
                    onPressed: () async {
                      Image_Url = _Update();
                      print(Image_Url);

                    },
                    child: Text(
                      '올리기',
                      style: TextStyle(fontSize: 18.0, color: Colors.white,fontFamily: 'Multilingual',),
                    ),
                  ),
                  SizedBox(
                    width: 20,
                  ),
                  RaisedButton(
                    color: Colors.red,
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                    child: Text(
                      '취소',
                      style: TextStyle(fontSize: 18.0, color: Colors.white),
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
    showDialog(
        context: context, builder: (BuildContext context) => simpleDialog);
  }

올리기를 누르면 나오는 Dialog입니다. 크게 특별한건 없습니다. 올리기를 클릭하면 Image_Url = _Update(); 의해 _Update() 함수가 호출됩니다. 

 //firebase에 이미지를 업로드 하는 함수
    _Update() async {
    //저장소 images 패키지에 title.txt라는 제목으로 저장한다.
    StorageReference storageReference = _firebaseStorage.ref().child("profile/${title.text}");

    // 파일 업로드
    StorageUploadTask storageUploadTask = storageReference.putFile(_image);

    // 파일 업로드 완료까지 대기
    await storageUploadTask.onComplete;

    String downloadURL = await storageReference.getDownloadURL();

    Image_q++;

    createDoc(title.text, downloadURL, top_total_length.text, shoulder_extend.text, chest_size.text,
        retail_length.text, bottom_total_length.text, waist_size.text, cortch_size.text,
        tail_edge.text, shoes_size.text, phone_number.text, count_number.text, bank_name.text);

    Navigator.of(context).pop();

    return downloadURL;

  }

firebase에 storage를 업로드 하는 함수입니다. title.txt라는 제목으로 제목을 지정하고 파일을 업로드 하고 createDoc로 이미지 정보를 데이터 베이스에 만들어 줍니다. null값인 변수는 자동으로 ""값으로 저장됩니다. 밑에서 이미지로 보여드리겠습니다. 

void createDoc(String name, String URL, String top_total_length, String shoulder_extend,
      String chest_size, String retail_length, String bottom_total_length, String waist_size, String cortch_size,
      String tail_edge, String shoes_size, String phone_number, String count_number, String bank_name)
  {
    Firestore.instance.collection(colName).document(name).setData({
      Image_Title : name, //이미지 제목
      Image_URL: URL, // 이미지 URL
      Image_top_total_length: top_total_length,// 총장
      Image_shoulder_extend: shoulder_extend, //어깨 너비
      Image_chest_size: chest_size, // 가슴 단면
      Image_retail_length: retail_length, // 소매 길이
      Image_bottom_total_length: bottom_total_length, // 총장
      Image_waist_size: waist_size, //허리 단면
      Image_cortch_size: cortch_size, // 밑위 길이
      Image_tail_edge: tail_edge, // 밑단 길이
      Image_shoes_size: shoes_size, //신발 사이즈
      Image_phone_number: phone_number, //전화번호
      Image_count_number: count_number, //계좌 번호
      Image_bank_name: bank_name, //계좌 번호
    });
  }

createDoc는 데이터를 firebase에 만드는 함수입니다.  필드와 값으로 이루어져 있습니다.

 void CancelCustomDialog(BuildContext context) {
    Dialog simpleDialog = Dialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.0),
      ),
      child: Container(
        height: 200.0,
        width: 300.0,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: EdgeInsets.only(left: 15.0, top: 20.0, right: 15.0, bottom: 0.0),
              child: Text(
                '정말 취소할래요?',
                style: TextStyle(
                    color: Color(0xFFFF1744),
                    fontFamily: 'Multilingual',
                    fontStyle: FontStyle.normal,
                    fontSize: 25
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 10, right: 10, top: 30),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  RaisedButton(
                    color: Colors.red,
                    onPressed: () {
                      Navigator.of(context).pop();
                      reset();
                    },
                    child: Text(
                      '리셋',
                      style: TextStyle(fontSize: 18.0, color: Colors.white),
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
    showDialog(
        context: context, builder: (BuildContext context) => simpleDialog);
  }

 취소를 클릭하면 실행되는 Dialog입니다. 리셋을 클릭하면 reset() 함수가 호출되면서 작성한 필드가 모두 지워지도록 구현했는데 지워지지 않습니다. 실행되지 않은 함수임으로 전체 소스코드에서 reset 함수를 보여드리겠습니다.

 

    Widget _BuildCameraButton(){
    return InkWell(
      child: Icon(Icons.camera_alt, color: Colors.white, size: 50,),
      onTap: () async {
        await getImage(ImageSource.camera);
      },
    );
  }

  Widget _BuildPhotoAlbumeButton(){
    return InkWell(
      child: Icon(Icons.image, color: Colors.white, size:  50),
      onTap: () async {
        await getImage(ImageSource.gallery);
      },
    );
  }

카메라 아이콘과 포토 앨범 아이콘을 클릭하면 getImage을 통해서 핸드폰의 카메라와 사진에 접근합니다.

//사진 파일 압축
  Future<File> _compressImage(File file, String targetPath) async {
    var result = await FlutterImageCompress.compressAndGetFile(
      file.absolute.path,
      targetPath,
      quality: 88,
      minWidth: 500,
      minHeight: 500
      //rotate: 180,
    );

    //단위 바이트 확률 높음
    print(file.lengthSync());
    print(result.lengthSync());

    return result;
  }

사진을 압축하는 함수입니다. 다음 그림을 보면 거의 30배 이상 압축된 것을 보실 수 있습니다.

 

 //image을 가져오는 함수
  Future getImage(ImageSource source) async {
    // ignore: deprecated_member_use
    var image = await ImagePicker.pickImage(source: source);// sourece을 통해 이미지를 가져온다.
    var tempDir = await getTemporaryDirectory(); // 디렉토리경로를 가져온다.
    String tempPath = path.join(tempDir.path, path.basename(image.path));

    File tempImage = await _compressImage(image, tempPath);// 이미지 파일 압축

    //이미지에 이미지를 가져온 파일 경로에 있는 파일을 대입한다.
    setState(() {
      _image = File(tempImage.path);
    });
  }

접근한 이미지를 가져오는 함수입니다. 압축하는 함수도 포함되어 있습니다. 주석에서 설명되어 있듯 ImagePicker을 통해 source에서 이미지를 가져오고 디렉토리 경로를 가져오고 파일을 대입하는 방식으로 작동합니다.

 

 Widget Top_information() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key:_topfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Text(
                    'Top의 정보를 입력합니다',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '총장',
                        hintText: '총장 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: top_total_length,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '어깨 너비',
                        hintText: '어깨 너비를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: shoulder_extend,
                    ),
                  ),
                ],
              ),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '가슴 단면',
                        hintText: '가슴 단면의 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: chest_size,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                          labelText: '소매 길이',
                          hintText: "소매 길이를 입력해주세요",
                          border: OutlineInputBorder() // 비밀번호를 가리게 해줌
                          ),
                      controller: retail_length,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }

  Widget Bottom_information() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key: _bottomfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Text(
                    'Bottom의 정보를 입력합니다',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '총장',
                        hintText: '총장 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: bottom_total_length,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '허리 단면',
                        hintText: '허리 단면 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: waist_size,
                    ),
                  ),
                ],
              ),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '허벅지 단면',
                        hintText: '허벅지 단면 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                          labelText: '밑위',
                          hintText: "밑위 길이를 입력해주세요",
                          border: OutlineInputBorder() // 비밀번호를 가리게 해줌
                          ),
                      controller: cortch_size,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                          labelText: '밑단 단면',
                          hintText: "밑단 단면 길이를 입력해주세요",
                          border: OutlineInputBorder() // 비밀번호를 가리게 해줌
                          ),
                      controller: tail_edge,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }

  Widget Shose_informtion() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key: _shoesfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: Text(
                    'Shose 의 정보를 입력합니다',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '신발 사이즈',
                        hintText: '신발 사이즈를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: shoes_size,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }

  Widget Seller_information() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key: _sellerfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Text(
                    '판매자의 정보를 입력합니다.',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '전화번호',
                        hintText: '-를 제외한 전화번호를 적어주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: phone_number,
                    ),
                  ),
                ],
              ),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '은행 이름',
                        hintText: '은행 이름을 적어주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: bank_name,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '계좌 번호',
                        hintText: '계좌 번호를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: count_number,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }
}

사용자에게서 사진 이미지에 대한 정보를 받는 위젯 폼입니다. 사지을 통해 어떤게 구현했는지 보여드리겠습니다.

 

3. 전제 소스 코드 - add.dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:graduationproject/provider.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as path; //중요!!!! 임시 저장소에 압충하기 위해 입시 저장소 path을 알아내기 위한 라이브러리
import 'package:flutter_image_compress/flutter_image_compress.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class Add extends StatefulWidget {
  Add({Key key}) : super(key : key);
  static const routeName = "/Add";

  @override
  _AddState createState() => _AddState();
}

class _AddState extends State<Add> {


  int Image_q = 0;
  String Image_Url;

  String downloadURL;
  File _image;
  final picker = ImagePicker();
  final _valueList = [
    'Top', //1
    'Outer', //2
    'Pants', //3
    'Bag', //4
    'Shoes' //5
  ];
  var _selectedValue = 'Top';
  var selectCheck = int.parse('1');

  final GlobalKey<FormState> _titlefk = GlobalKey<FormState>();
  final GlobalKey<FormState> _topfk = GlobalKey<FormState>();
  final GlobalKey<FormState> _bottomfk = GlobalKey<FormState>();
  final GlobalKey<FormState> _shoesfk = GlobalKey<FormState>();
  final GlobalKey<FormState> _sellerfk = GlobalKey<FormState>();

  TextEditingController title = TextEditingController(); // 제목
  TextEditingController top_total_length = TextEditingController(); // 총장
  TextEditingController shoulder_extend = TextEditingController(); //어깨 너비
  TextEditingController chest_size = TextEditingController(); // 가슴 단면
  TextEditingController retail_length = TextEditingController(); // 소매 길이
  TextEditingController bottom_total_length = TextEditingController(); // 총장
  TextEditingController waist_size = TextEditingController(); //허리 단면
  TextEditingController cortch_size = TextEditingController(); // 밑위 길이
  TextEditingController tail_edge = TextEditingController(); // 밑단 길이
  TextEditingController shoes_size = TextEditingController(); //신발 사이즈
  TextEditingController phone_number = TextEditingController(); //전화번호
  TextEditingController count_number = TextEditingController(); //계좌 번호
  TextEditingController bank_name = TextEditingController(); //계좌 번호

  FirebaseStorage _firebaseStorage = FirebaseStorage.instance;

  //컬랙션 명
  final String colName = 'Image_collection';

  // 필드명
  final String Image_Title = "title";
  final String Image_URL = "imgae_url";
  final String Image_top_total_length = "top_total_length";// 총장
  final String Image_shoulder_extend = "shoulder_extend"; //어깨 너비
  final String Image_chest_size = "chest_size"; // 가슴 단면
  final String Image_retail_length = 'retail_length'; // 소매 길이
  final String Image_bottom_total_length = 'bottom_total_length'; // 총장
  final String Image_waist_size = 'waist_size'; //허리 단면
  final String Image_cortch_size = 'cortch_size'; // 밑위 길이
  final String Image_tail_edge = 'tail_edge'; // 밑단 길이
  final String Image_shoes_size = 'shoes_size'; //신발 사이즈
  final String Image_phone_number = 'phone_number'; //전화번호
  final String Image_count_number = 'count_number'; //계좌 번호
  final String Image_bank_name = 'bank_name'; //계좌 번호


  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<ChangeSign>(
      create: (_) => ChangeSign(),
      child: SingleChildScrollView(
        child: ListBody(children: <Widget>[
          Stack(
            alignment: Alignment.bottomCenter,
        children: <Widget>[
            Container(
                child: _image == null ? Image.asset(
              'assets/add_image.jpg',
              fit: BoxFit.cover,
              ): Image.file(_image, fit: BoxFit.cover,)
            ),
          Container(
            color: Colors.black45,
            height: 70,
          ),
          ButtonBar(
            mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                _BuildCameraButton(),
                SizedBox(width: 250,),
                _BuildPhotoAlbumeButton()
              ]
          )
      ]
          ),
          Center(
            child: Consumer<ChangeSign>(
              builder: (context, pro_value, child) => DropdownButton(
                value: _selectedValue,
                items: _valueList.map(
                  (value) {
                    return DropdownMenuItem(
                      value: value,
                      child: Text(value),
                    );
                  },
                ).toList(),
                onChanged: (value) {
                  setState(() {
                    _selectedValue = value;
                    if (value == 'Top') selectCheck = 1;
                    if (value == 'Outer') selectCheck = 2;
                    if (value == 'Pants') selectCheck = 3;
                    if (value == 'Bag') selectCheck = 4;
                    if (value == 'Shoes') selectCheck = 5;
                    pro_value.ChangSelectedVale(selectCheck);
                  });
                },
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: TextFormField(
              key: _titlefk,
              decoration: InputDecoration(
                  labelText: '글의 제목을 입력해주세요',
                  hintText: "title",
                  border: OutlineInputBorder()),
              controller: title,
            ),
          ),
          if (selectCheck == 1) Top_information(),
          if (selectCheck == 2) Top_information(),
          if (selectCheck == 3) Bottom_information(),
          if (selectCheck == 5) Shose_informtion(),
          Seller_information(),
          SizedBox(
            height: 10,
          ),
          Stack(children: <Widget>[
            Container(
                child: Image.asset(
              'assets/add_image.jpg',
                  fit: BoxFit.cover,
            )),
          ]),
          Row(
            children: <Widget>[
              Expanded(
                child: RaisedButton(
                  child: Text('올리기'),
                  onPressed: () {
                    UpdateCustomDialog(context);
                  },
                ),
              ),
              SizedBox(
                width: 10,
              ),
              Expanded(
                child: RaisedButton(
                  child: Text('취소'),
                  onPressed: () {
                    CancelCustomDialog(context);
                  },
                ),
              )
            ],
          )
        ]),
      ),
    );
  }

  void createDoc(String name, String URL, String top_total_length, String shoulder_extend,
      String chest_size, String retail_length, String bottom_total_length, String waist_size, String cortch_size,
      String tail_edge, String shoes_size, String phone_number, String count_number, String bank_name)
  {
    Firestore.instance.collection(colName).document(name).setData({
      Image_Title : name, //이미지 제목
      Image_URL: URL, // 이미지 URL
      Image_top_total_length: top_total_length,// 총장
      Image_shoulder_extend: shoulder_extend, //어깨 너비
      Image_chest_size: chest_size, // 가슴 단면
      Image_retail_length: retail_length, // 소매 길이
      Image_bottom_total_length: bottom_total_length, // 총장
      Image_waist_size: waist_size, //허리 단면
      Image_cortch_size: cortch_size, // 밑위 길이
      Image_tail_edge: tail_edge, // 밑단 길이
      Image_shoes_size: shoes_size, //신발 사이즈
      Image_phone_number: phone_number, //전화번호
      Image_count_number: count_number, //계좌 번호
      Image_bank_name: bank_name, //계좌 번호
    });
  }

  //firebase에 이미지를 업로드 하는 함수
    _Update() async {
    //저장소 images 패키지에 title.txt라는 제목으로 저장한다.
    StorageReference storageReference = _firebaseStorage.ref().child("profile/${title.text}");

    // 파일 업로드
    StorageUploadTask storageUploadTask = storageReference.putFile(_image);

    // 파일 업로드 완료까지 대기
    await storageUploadTask.onComplete;

    String downloadURL = await storageReference.getDownloadURL();

    Image_q++;

    createDoc(title.text, downloadURL, top_total_length.text, shoulder_extend.text, chest_size.text,
        retail_length.text, bottom_total_length.text, waist_size.text, cortch_size.text,
        tail_edge.text, shoes_size.text, phone_number.text, count_number.text, bank_name.text);

    Navigator.of(context).pop();

    return downloadURL;

  }

  Future _download() async{

  }

  void UpdateCustomDialog(BuildContext context) {
    Dialog simpleDialog = Dialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.0),
      ),
      child: Container(
        height: 200.0,
        width: 300.0,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: EdgeInsets.only(left: 15.0, top: 20.0, right: 15.0, bottom: 0.0),
              child: Text(
                '정말 올리시겠습니까?',
                style: TextStyle(
                    color: Color(0xFFFF1744),
                    fontFamily: 'Multilingual',
                    fontStyle: FontStyle.normal,
                    fontSize: 25
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 10, right: 10, top: 30),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  RaisedButton(
                    color: Color(0xFF0277BD),
                    onPressed: () async {
                      Image_Url = _Update();
                      print(Image_Url);

                    },
                    child: Text(
                      '올리기',
                      style: TextStyle(fontSize: 18.0, color: Colors.white,fontFamily: 'Multilingual',),
                    ),
                  ),
                  SizedBox(
                    width: 20,
                  ),
                  RaisedButton(
                    color: Colors.red,
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                    child: Text(
                      '취소',
                      style: TextStyle(fontSize: 18.0, color: Colors.white),
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
    showDialog(
        context: context, builder: (BuildContext context) => simpleDialog);
  }

  void reset(){
    _titlefk.currentState.reset();
    _topfk.currentState.reset();
    _bottomfk.currentState.reset();
    _shoesfk.currentState.reset();
    _sellerfk.currentState.reset();
  }

  void CancelCustomDialog(BuildContext context) {
    Dialog simpleDialog = Dialog(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12.0),
      ),
      child: Container(
        height: 200.0,
        width: 300.0,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: EdgeInsets.only(left: 15.0, top: 20.0, right: 15.0, bottom: 0.0),
              child: Text(
                '정말 취소할래요?',
                style: TextStyle(
                    color: Color(0xFFFF1744),
                    fontFamily: 'Multilingual',
                    fontStyle: FontStyle.normal,
                    fontSize: 25
                ),
              ),
            ),
            Padding(
              padding: const EdgeInsets.only(left: 10, right: 10, top: 30),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  RaisedButton(
                    color: Colors.red,
                    onPressed: () {
                      Navigator.of(context).pop();
                      reset();
                    },
                    child: Text(
                      '리셋',
                      style: TextStyle(fontSize: 18.0, color: Colors.white),
                    ),
                  )
                ],
              ),
            ),
          ],
        ),
      ),
    );
    showDialog(
        context: context, builder: (BuildContext context) => simpleDialog);
  }

    Widget _BuildCameraButton(){
    return InkWell(
      child: Icon(Icons.camera_alt, color: Colors.white, size: 50,),
      onTap: () async {
        await getImage(ImageSource.camera);
      },
    );
  }

  Widget _BuildPhotoAlbumeButton(){
    return InkWell(
      child: Icon(Icons.image, color: Colors.white, size:  50),
      onTap: () async {
        await getImage(ImageSource.gallery);
      },
    );
  }

  //사진 파일 압축
  Future<File> _compressImage(File file, String targetPath) async {
    var result = await FlutterImageCompress.compressAndGetFile(
      file.absolute.path,
      targetPath,
      quality: 88,
      minWidth: 500,
      minHeight: 500
      //rotate: 180,
    );

    //단위 바이트 확률 높음
    print(file.lengthSync());
    print(result.lengthSync());

    return result;
  }

  //image을 가져오는 함수
  Future getImage(ImageSource source) async {
    // ignore: deprecated_member_use
    var image = await ImagePicker.pickImage(source: source);// sourece을 통해 이미지를 가져온다.
    var tempDir = await getTemporaryDirectory(); // 디렉토리경로를 가져온다.
    String tempPath = path.join(tempDir.path, path.basename(image.path));

    File tempImage = await _compressImage(image, tempPath);// 이미지 파일 압축

    //이미지에 이미지를 가져온 파일 경로에 있는 파일을 대입한다.
    setState(() {
      _image = File(tempImage.path);
    });
  }


    Widget Top_information() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key:_topfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Text(
                    'Top의 정보를 입력합니다',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '총장',
                        hintText: '총장 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: top_total_length,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '어깨 너비',
                        hintText: '어깨 너비를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: shoulder_extend,
                    ),
                  ),
                ],
              ),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '가슴 단면',
                        hintText: '가슴 단면의 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: chest_size,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                          labelText: '소매 길이',
                          hintText: "소매 길이를 입력해주세요",
                          border: OutlineInputBorder() // 비밀번호를 가리게 해줌
                          ),
                      controller: retail_length,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }

  Widget Bottom_information() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key: _bottomfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Text(
                    'Bottom의 정보를 입력합니다',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '총장',
                        hintText: '총장 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: bottom_total_length,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '허리 단면',
                        hintText: '허리 단면 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: waist_size,
                    ),
                  ),
                ],
              ),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '허벅지 단면',
                        hintText: '허벅지 단면 길이를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                          labelText: '밑위',
                          hintText: "밑위 길이를 입력해주세요",
                          border: OutlineInputBorder() // 비밀번호를 가리게 해줌
                          ),
                      controller: cortch_size,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                          labelText: '밑단 단면',
                          hintText: "밑단 단면 길이를 입력해주세요",
                          border: OutlineInputBorder() // 비밀번호를 가리게 해줌
                          ),
                      controller: tail_edge,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }

  Widget Shose_informtion() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key: _shoesfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(20.0),
                  child: Text(
                    'Shose 의 정보를 입력합니다',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '신발 사이즈',
                        hintText: '신발 사이즈를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: shoes_size,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }

  Widget Seller_information() {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Card(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
        elevation: 6,
        child: Padding(
          padding: const EdgeInsets.only(
              left: 12.0, right: 12.0, top: 12.0, bottom: 12.0),
          child: Form(
            key: _sellerfk,
            child: Column(children: <Widget>[
              Padding(
                  padding: const EdgeInsets.all(30.0),
                  child: Text(
                    '판매자의 정보를 입력합니다.',
                    style: TextStyle(
                      fontFamily: 'Multilingual',
                      fontStyle: FontStyle.normal,
                      fontSize: 20,
                    ),
                  )),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '전화번호',
                        hintText: '-를 제외한 전화번호를 적어주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: phone_number,
                    ),
                  ),
                ],
              ),
              SizedBox(
                height: 10,
              ),
              Row(
                children: <Widget>[
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '은행 이름',
                        hintText: '은행 이름을 적어주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: bank_name,
                    ),
                  ),
                  SizedBox(
                    width: 10,
                  ),
                  Expanded(
                    child: TextFormField(
                      decoration: InputDecoration(
                        labelText: '계좌 번호',
                        hintText: '계좌 번호를 입력해주세요',
                        border: OutlineInputBorder(),
                      ),
                      controller: count_number,
                    ),
                  ),
                ],
              ),
            ]),
          ),
        ),
      ),
    );
  }
}

 

4. 구현된 화면

shoes을 선택한 경우

 

 

pants을 선택한 경우

 

 

판매자 정보

 

 

firebaseStorage

 

 

firebase

 

출처 :

https://flutter.dev/docs

 

Flutter Documentation

The landing page for Flutter documentation.

flutter.dev

https://fkkmemi.github.io/

 

memi’s startup

Development logs

fkkmemi.github.io