DevBoi

[Flutter] Firebase Storage, Database 본문

[Mobile]/[Flutter]

[Flutter] Firebase Storage, Database

HiSmith 2023. 10. 22. 19:23
반응형

요새 앱개발로 정신이 없다.

그래서 정리할 시간도 포스팅도 없다.

 

하지만, 최근 요청건으로 개발한 부분에 대해서, 포스팅을 하려고한다.

대부분 앱은 backend server로 보내고 3rd db를 쓰지만,

간단하거나 백엔드 구현이 어려운 경우 파이어베이스를 많이 쓴다.

 

1) Firebase storage

이미지를 핸드폰에서 집어와서 파이어베이스 storage에 올리는 방법이다.

별건 없다.


import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;
import 'package:path/path.dart';

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

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

class _ImageUploadsState extends State<ImageUploads> {
  firebase_storage.FirebaseStorage storage =
      firebase_storage.FirebaseStorage.instance;

  File? _photo;
  final ImagePicker _picker = ImagePicker();

  Future imgFromGallery() async {
    final pickedFile = await _picker.pickImage(source: ImageSource.gallery);

    setState(() {
      if (pickedFile != null) {
        _photo = File(pickedFile.path);
        uploadFile();
      } else {
        print('No image selected.');
      }
    });
  }

  Future imgFromCamera() async {
    final pickedFile = await _picker.pickImage(source: ImageSource.camera);

    setState(() {
      if (pickedFile != null) {
        _photo = File(pickedFile.path);
        uploadFile();
      } else {
        print('No image selected.');
      }
    });
  }

  Future uploadFile() async {
    if (_photo == null) return;
    final fileName = basename(_photo!.path);
    final destination = 'files/$fileName';
    //gs://finder-310f0.appspot.com/files/image_picker_A2C6C0E4-A9D4-4950-BCBD-02267D79E7F7-23512-0000028123B23F68.jpg
    try {
      final ref = firebase_storage.FirebaseStorage.instance
          .ref(destination)
          .child('file/');
      print(_photo);
      await ref.putFile(_photo!);
    } catch (e) {
      print('error occured');
    }
  }
  // 1. "https://firebasestorage.googleapis.com/v0/b/"
  // 2. bucket object
  // 3. "/o/"
  // 4. correctly encoded path object.
  // 5. "?alt=media"
  //
  // 6. token="..."
  //

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          SizedBox(
            height: 32,
          ),
          Center(
            child: GestureDetector(
              onTap: () {
                _showPicker(context);
              },
              child: CircleAvatar(
                radius: 55,
                backgroundColor: Color(0xffFDCF09),
                child: _photo != null
                    ? ClipRRect(
                  borderRadius: BorderRadius.circular(50),
                  child: Image.file(
                    _photo!,
                    width: 100,
                    height: 100,
                    fit: BoxFit.fitHeight,
                  ),
                )
                    : Container(
                  decoration: BoxDecoration(
                      color: Colors.grey[200],
                      borderRadius: BorderRadius.circular(50)),
                  width: 100,
                  height: 100,
                  child: Icon(
                    Icons.camera_alt,
                    color: Colors.grey[800],
                  ),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }

  void _showPicker(context) {
    showModalBottomSheet(
        context: context,
        builder: (BuildContext bc) {
          return SafeArea(
            child: Container(
              child: new Wrap(
                children: <Widget>[
                  new ListTile(
                      leading: new Icon(Icons.photo_library),
                      title: new Text('Gallery'),
                      onTap: () {
                        imgFromGallery();
                        Navigator.of(context).pop();
                      }),
                  new ListTile(
                    leading: new Icon(Icons.photo_camera),
                    title: new Text('Camera'),
                    onTap: () {
                      imgFromCamera();
                      Navigator.of(context).pop();
                    },
                  ),
                ],
              ),
            ),
          );
        });
  }
}

 

 

1) Firebase storage + form data

 

아래와 같이 엮어서 쓸수있다.

우선 Imageview 데이터를 바인딩하고

저장 시점에 Form과 file을 같이 save하고 뒤로 가는것이다.

db에는 이미지 Url이 저장되고 해당 주소를 호출하여 뿌려준다 (물론 느린건 어쩔수없다, 파이어베이스 자체가 느리다)

import 'dart:io';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:finder/firebase_options.dart';
import 'package:finder/view/file-upload.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:firebase_storage/firebase_storage.dart' as firebase_storage;
import 'package:path/path.dart';

class FindIt extends StatefulWidget {
  final schoolCode;
  final category;
  const FindIt({required this.schoolCode,required this.category});

  @override
  State<StatefulWidget> createState() {
    return _FindItState(schoolCode: schoolCode,category:category);
  }
}

class _FindItState extends State<FindIt>{
  _FindItState({required this.schoolCode,required this.category});
  final schoolCode;
  final category;
  File? _photo;
  String uploadName = '';
  @override
  void initState() {
    super.initState();

  }

  @override
  Widget build(BuildContext context) {

    final nameController = TextEditingController();
    final descController = TextEditingController();
    final placeController = TextEditingController();


    String name='';
    String description='';
    String place='';
    final screenWidth = MediaQuery.of(context).size.width;

    firebase_storage.FirebaseStorage storage =
        firebase_storage.FirebaseStorage.instance;

    final ImagePicker _picker = ImagePicker();


    Future uploadFile() async {
      if (_photo == null) return;
      final fileName = basename(_photo!.path);
      final destination = 'image/$fileName';
      //gs://finder-310f0.appspot.com/files/image_picker_A2C6C0E4-A9D4-4950-BCBD-02267D79E7F7-23512-0000028123B23F68.jpg
      try {
        final ref = firebase_storage.FirebaseStorage.instance
            .ref(destination)
            .child('file/');

        await ref.putFile(_photo!);
      } catch (e) {
        print('error occured');
      }

    }
    void save(String name,String place, String description){
      uploadFile().then((value) => {
      FirebaseFirestore.instance.collection(category).add({
        "name":name,
        "place":place,
        "imgUrl":uploadName,
        "description":description,
        "time":Timestamp.now()
      })
      })..then((value) => {
        Navigator.pop(context)
      });
    }
    Future<String> imgFromGallery() async {
      final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
        setState(() {
          if (pickedFile != null) {
            _photo = File(pickedFile.path);
          } else {
            print('No image selected.');

          }
        });
        return pickedFile!.name;
    }

    Future imgFromCamera() async {
      final pickedFile = await _picker.pickImage(source: ImageSource.camera);

      setState(() {
        if (pickedFile != null) {
          _photo = File(pickedFile.path);
          //uploadFile();
        } else {
          print('No image selected.');
        }
      });
    }
    void _showPicker(context) {
      showModalBottomSheet(
          context: context,
          builder: (BuildContext bc) {
            return SafeArea(
              child: Container(
                child: new Wrap(
                  children: <Widget>[
                    new ListTile(
                        leading: new Icon(Icons.photo_library),
                        title: new Text('Gallery'),
                        onTap: () {
                          imgFromGallery().then((value) => {
                          setState((){
                            uploadName = value;
                          })
                          });
                          Navigator.of(context).pop();
                        }),
                    new ListTile(
                      leading: new Icon(Icons.photo_camera),
                      title: new Text('Camera'),
                      onTap: () {
                        imgFromCamera();
                        Navigator.of(context).pop();
                      },
                    ),
                  ],
                ),
              ),
            );
          });
    }

    return Scaffold(
      appBar: AppBar(
          title: Text('Find it'),
          leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
            Navigator.pop(context);
          },),
          backgroundColor: Color.fromARGB(255, 50, 64, 182)),
      body: Container(
        child: Padding(
          padding: EdgeInsets.all(80),
          child: Center(
            
            child: Column(
              children: [
                TextField(
                  controller: nameController,
                  keyboardType: TextInputType.text,
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: '이름을 입력해주세요',
                  ),
                  onChanged: ((value){
                    name = nameController.text;
                  }),
                ),
                SizedBox(height: 10,),
                TextField(
                  controller: descController,
                  keyboardType: TextInputType.text,
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: '설명을 입력해주세요',
                  ),
                  onChanged: ((value){
                    description = descController.text;
                  }),
                ),
                SizedBox(height: 10,),
                TextField(
                  controller: placeController,
                  keyboardType: TextInputType.text,
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: '장소를 입력해주세요',
                  ),
                  onChanged: ((value){
                    place = placeController.text;
                  }),
                ),
                //SizedBox(height: 40,),
                //ImageUploads(),
                SizedBox(height: 30,),
                Center(
                  child: GestureDetector(
                    onTap: () {
                      _showPicker(context);
                    },
                    child: CircleAvatar(
                      radius: 55,
                      backgroundColor: Color(0xffFDCF09),
                      child: _photo != null
                          ? ClipRRect(
                        borderRadius: BorderRadius.circular(50),
                        child: Image.file(
                          _photo!,
                          width: 100,
                          height: 100,
                          fit: BoxFit.fitHeight,
                        ),
                      )
                          : Container(
                        decoration: BoxDecoration(
                            color: Colors.grey[200],
                            borderRadius: BorderRadius.circular(50)),
                        width: 100,
                        height: 100,
                        child: Icon(
                          Icons.camera_alt,
                          color: Colors.grey[800],
                        ),
                      ),
                    ),
                  ),
                ),
                SizedBox(height: 30,),
                ElevatedButton(
                    onPressed: ((){
                  save(name,place,description);
                }),
                child: Text('저장')
                ),
              ],
            ),
          ),
        ),
      )
    );

  }

}

 

 

3.관련 데이터를 불러와서 리스트 뷰로 뿌려줌

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:finder/firebase_options.dart';
import 'package:finder/view/message.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';


class FindMe extends StatefulWidget {
  final schoolCode;
  final category;
  const FindMe({required this.schoolCode,required this.category});

  @override
  State<StatefulWidget> createState() {
    return _FindMeState(schoolCode: schoolCode,category: category);
  }

}

class _FindMeState extends State<FindMe>{

  final schoolCode;
  final category;
  _FindMeState({required this.schoolCode,required this.category});


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

  }

  @override
  Widget build(BuildContext context) {

    final screenWidth = MediaQuery.of(context).size.width;
    return Scaffold(
      appBar: AppBar(
          title: Text('Find me'),
          leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: () {
            Navigator.pop(context);
          },),
          backgroundColor: Color.fromARGB(255, 50, 64, 182)),
      body: Container(
    padding: EdgeInsets.all(50),
    child: Column(
    children: [
      Expanded(child: Messages(schoolCode: schoolCode,category: category,)),
    ],),));
  }
}

 

실제 서비스 앱은 이런 구조가될일은 드물긴하지만.

모듈화 요청으로 인해 간단하게 한 내용을 기록한다.

반응형

'[Mobile] > [Flutter]' 카테고리의 다른 글

[Flutter] Firebase auth + Google login 구현  (0) 2023.11.12
[Flutter] Auto Serializable  (0) 2023.11.12
[Flutter] ios 빌드 구성 차이  (0) 2023.10.12
[Flutter] Chatting 모듈 정리  (0) 2023.10.08
[Flutter] IOS관련 리빌드  (0) 2023.10.07