새로운 강의는 이제 https://memi.dev 에서 진행합니다.
memi가 Vue & Firebase로 직접 만든 새로운 사이트를 소개합니다.

바로가기


Flutter와 Firebase로 Android iOS 둘 다 만들기 24 fire storage에 저장하기

2 분 소요

파이어베이스 스토리지에 저장하고 링크를 받아 프로필을 표시해봅니다.

개요

파이어베이스 스토리지는 파일을 저장할 수 있고 http링크로 다양한 플랫폼에서 액세스가 가능합니다.

AWS S3와 비슷합니다.

무료 요금도

파이어베이스 설정

파이어베이스 콘솔에가서 스토리지를 활성화시킵니다.

alt init

service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;
    }
  }
}

보안규칙은 기본으로 해두면 로그인한 사람만 액세스가 가능합니다.

이미 로그인 프로세스를 구축해두었기 때문에 간편하게 사용이 가능합니다.

설치

참고: https://pub.dev/packages/firebase_storage

pubspec.yaml

  firebase_storage: ^3.1.5

업로드 버튼 만들기

상단의 저장 버튼을 클릭할 때 Image 파일이 있을 경우에만 활성화 되도록 만듭니다.

pages/profile_edit.dart

_save () async {
  print('save');
}
Widget _buildSaveButton () {
  return IconButton(
    icon: Icon(Icons.save),
    onPressed: _image == null ? null : () async => _save(),
  );
}

onPressed가 null 일 경우 버튼은 자동으로 disabled 상태가 되며 동작하지 않게 됩니다.

업로드하기

import 'package:firebase_storage/firebase_storage.dart';

  _save () {
    final StorageReference ref = FirebaseStorage().ref().child('images').child('users').child(widget.user.uid);
    final StorageUploadTask uploadTask = ref.putFile(_image,);
    uploadTask.events.listen((event) {
      print(event);
      print('EVENT ${event.type}');
      if (event.type == StorageTaskEventType.success) {
        ref.getDownloadURL()
          .then((url) async {
            _image.deleteSync();
            print(url);
            final UserUpdateInfo userUpdateInfo = UserUpdateInfo();
            userUpdateInfo.photoUrl = url;
            await widget.user.updateProfile(userUpdateInfo);
            await widget.user.reload();
            Navigator.pushNamedAndRemoveUntil(context, '/auth', (r) => false);
          });
      }
    });
  }

스토리지나 스토어 모두 참조 위치, 즉 레퍼런스가 가장 중요합니다.

참조는 images/users/uid 로 지정했습니다.

레퍼런스의 putFile로 파일이 전송됩니다.

putFile 뒤에 옵션으로 파일 형식등의 메타 정보를 지정할 수도 있습니다.

파일이 전송 중에 스트림 구독으로 상태를 알아 낼 수 있습니다.

사실 플러터답게 꾸미려면 스트림빌더로 사진위젯을 구현하는 것이 좋습니다.

그 중 이벤트 타입이 성공일 경우 사본 이미지를 지웁니다.

그리고 프로필 업데이트 후에 /auth로 보내버립니다.

보내는 것이 맘에 안들면 FirebaseAuth.instance.currentUser()로 치환해주는 방법이 있습니다.

이름 저장하기

빈 이름등을 방지하기 위해 지난번에 했던 폼관련 작업을 해둡니다.


  TextEditingController firstNameInputController;
  TextEditingController lastNameInputController;
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  bool _nameChanged = false;
  bool _loading = false;

  @override
  void initState() { 
    super.initState();
    final names = widget.user.displayName.split(' ');
    if (names.length < 2) {
      firstNameInputController = TextEditingController();
      lastNameInputController = TextEditingController();
      return;
    }
    firstNameInputController = TextEditingController(text: names[0]);
    lastNameInputController = TextEditingController(text: names[1]);

    firstNameInputController.addListener(() {
      if (firstNameInputController.text == names[0] && lastNameInputController.text == names[1]) setState(() => _nameChanged = false);
      else setState(() => _nameChanged = true);
    });

    lastNameInputController.addListener(() {
      if (firstNameInputController.text == names[0] && lastNameInputController.text == names[1]) setState(() => _nameChanged = false);
      else setState(() => _nameChanged = true);
    });
  }

  @override
  void dispose() {
    firstNameInputController.dispose();
    lastNameInputController.dispose();
    super.dispose();
  }

_nameChanged를 사용해서 저장버튼 상태를 표시합니다.

Widget _buildSaveButton () {
  if (_loading) {
    return Center(
      child: Container(
        width: 40,
        height: 40,
        padding: EdgeInsets.all(8),
        child: CircularProgressIndicator(backgroundColor: Colors.white,),
      )
    );
  }
  return IconButton(
    icon: Icon(Icons.save),
    onPressed: _image != null || _nameChanged ? () => _save() : null,
  );
}

이미지가 있거나 변한 것이 있을 경우만 저장버튼이 활성화됩니다.

최종 저장

_save () async {
  setState(() => _loading = true);
  if (_image != null) {
    final StorageReference ref = FirebaseStorage().ref().child('images').child('users').child(widget.user.uid);
    final StorageUploadTask uploadTask = ref.putFile(_image,);
    uploadTask.events.listen((event) {
      print(event);
      print('EVENT ${event.type}');
      if (event.type == StorageTaskEventType.success) {
        ref.getDownloadURL()
          .then((url) async {
            _image.deleteSync();
            final UserUpdateInfo userUpdateInfo = UserUpdateInfo();
            userUpdateInfo.photoUrl = url;
            if (_nameChanged) userUpdateInfo.displayName = firstNameInputController.text + ' ' + lastNameInputController.text;
            await widget.user.updateProfile(userUpdateInfo);
            await widget.user.reload();
            setState(() => _loading = false);
            Navigator.pushNamedAndRemoveUntil(context, '/auth', (r) => false);
          });
      }
    });
  } else {
    final UserUpdateInfo userUpdateInfo = UserUpdateInfo();
    userUpdateInfo.displayName = firstNameInputController.text + ' ' + lastNameInputController.text;
    await widget.user.updateProfile(userUpdateInfo);
    await widget.user.reload();
    setState(() => _loading = false);
    Navigator.pushNamedAndRemoveUntil(context, '/auth', (r) => false);
  }
}

이미지가 있을 때는 전송이 끝난 후 프로필 업데이트를 하고 없을 경우 이름만 변경하여 프로필 업데이트합니다.

마치며

이것으로 파이어베이스를 이용한 기초적인 운영은 끝입니다.

예제와는 별도로 Fire auth, store, storage를 직접 콘트롤 해보시기 바랍니다.

다음에는 응용편으로 진행합니다.

소스

https://github.com/fkkmemi/ff2

영상

댓글남기기