Search
▪️

Firebase, Image Upload, Push Notifications

Firebase의 Cloud Firestore를 이용할 경우 데이터를 Document 형태로 갖고 올 수도 있지만, Stream 형태로도 가져올 수 있다.
Document로 갖고 오게 되면 한 번 Fetch하고 끝이지만, Stream 형태로 데이터를 갖고 올 경우에는 Listener를 달아서 데이터 변화에 대해서 감지할 수 있고 이 때마다 Snapshot을 받아올 수 있다. 따라서 Stream으로 받아올 경우, 실시간으로 변경된 데이터를 갖고 오면서 Render 할 수 있는 것이다.
** Android의 Firebase 연동 시 Multi Dex Archive 관련 Error가 뜬다면, 프로젝트 Level이 아닌 app Level의 build.gradle에서 defaultConfig에 multiDexEnabled true와 dependencies에 implementation "com.android.support:multidex:1.0.3"를 준다.
Theming에서 Accent Color을 지정할 때, 지정한 색이 많이 어둡다면 해당 색이 어두운지 어둡지 않은지 Flutter에게 알려줄 수 있다.
accentColor Field를 채우고 accentColorBrightness Field를 Brightness.dark라고 설정하면 Accent Color가 어두운 것을 Flutter가 알 수 있고, 이에 따라 이 어두운 색 위에 검은색의 Text가 나오지 않게 방지할 수 있다.
Form에 FormState Key를 할당하여 onSaved() Method와 validate() Method를 사용할 수 있더라도, 만일 setState를 통해서 Form의 Child Widget이 유동적으로 바뀐다고 하면 Widget의 값들이 이상하게 매칭 되는 것을 확인할 수 있다.
이 현상은 Form의 Child Widget들에게 각각 별도로 Value Key를 할당함으로써 방지할 수 있다.
Widget Tree, Element Tree와 관련이 있다.
Firebase의 Collection에서 Document 생성 시, add() Method를 이용하면 Document의 ID 값이 Dynamic하게 생성된다. 특정 값으로 생성하고 싶다면 document() Method를 이용하도록 한다.
Document에 데이터를 저장하고 싶다면 setData() Method를 활용한다. 인자는 Map 형태로 받는다.
Firebase의 Cloud Firestore 규칙 경우 Pattern Matching이 되면 Allow 조건을 보고 접근을 허용하게 된다. 즉, Pattern Matching이 존재하지 않을 경우에는 데이터베이스 접근을 불허한다. 또한 Pattern Matching이 되더라도 Allow 할 만한 조건이 아니라면 마찬가지로 접근을 불허한다.
allow의 값으로 줄 수 있는 것들은 read, write이며 write는 create, delete, update를 포함한다.
allow의 값을 줬다면 언제 허용할지 request를 통해서 설정이 가능하다. (심지어 특정 사용자에게만 허용할 수 있도록 규칙을 만들 수 있다. 이에 대해서 read, write가 서로 다른 조건이라면 같은 Pattern에 대해서 두번 정의가 가능하다.)
$path/{$name=**}는 path 이하 존재하는 'Nested된 모든 것에 대한' 이라는 뜻이다.
시간에 대한 것을 Firebase에 저장할 때는 DateTime이 아니라 Timestamp라는 Class를 이용한다. (Timestamp라는 Class는 Firestore Package 내에 존재한다.)
Firestore의 모든 Document들은 documentID라는 값을 갖고 있다.
** 자세한 것은 Firestore 참고하자.
채팅과 같이 무한 스크롤이 가능한 Widget들은 새로운 데이터가 생길 때마다 Stream의 Snapshot이 바뀌기 때문에, 바뀐 데이터에 대해서 모두 Render를 하는 불상사가 발생한다.
이를 방지하기 위해서, Key를 할당 해주면 기존 Widget Tree의 속성 값들은 Element Tree에 귀속된 채로 남아 있기 때문에 새로 생기는 데이터들만 Render 된다.
ListView와 같이 반복되는 Widget을 생성할 때, 해당 Widget의 생성자에 Key 값을 할당할 수 있도록 한다. Code를 참고하자.
** 즉, 데이터를 받아 올 때는 1. Stream으로 데이터를 받아 올 수 있는지 2. Stream으로 받아 온 데이터가 Future가 요구 되는지 3. 보이는 부분만 Rendering 할 수 있도록 Builder를 써야 하는지 4. Builder를 사용하더라도 Stream의 새로운 Snapshot이 들어오면 전체를 다 Render를 해야 하는지 고민해야 한다!
Firestore는 가져온 데이터에 대해서 자체적으로 Caching 처리를 하기 때문에, 동일한 데이터에 대해서 수 많은 Request를 보내도 그렇게 많은 API양을 차지 하지는 않는다.
그럼에도 매 데이터마다 Future로 동일 데이터를 긁어 오는 것은 바람직하지 않기 떄문에 다른 방법이 요구 된다. (Code 참고하자. Code의 경우 매 Message마다 API를 호출하면, Message 읽는데 1회, User Name 읽는데 1회, Loading 될 때마다 추가 횟수가 요구 된다. 하지만 Message를 등록할 때 User Name을 같이 등록하게 되면, Message 읽는데 1회만 들이면 된다.)
FireStorage는 최초 1회에 한 해 저장소를 생성하게 된다. 이 저장소는 Bucket이라고 불린다.
FireStorage를 이용함에 있어서 FireStorage의 Root, 즉 Bucket에 접근하기 위해서 이용하는 Method는 ref() Method이다.
Push Notification은 어떤 Event가 발생했을 때, 이와 관련된 사용자들에게 알리기 위해서 사용되곤 한다.
하지만 사용자들에게 알리기 위해 작성한 Message를 Device로 Direct하게 보내는 것은 불가능하다.
iOS, Android에서 오용, 남용, 보안 이슈들을 방지하기 위해 Push Notification을 관리한다. 따라서 Notification을 보내고 싶다면 DM으로 보내는 것이 아니라 Official Notification Server를 쓰거나 Protocol을 사용해야 한다.
Google이나 Apple로 부터 제공 받은 Official Service를 이용하게 되면, App을 만든 개발자에게 Notification을 보내는 것을 허용 해주고, App을 이용하는 사용자들은Notification을 받게 되는 것이다. (App이 없는 사용자들에게는 Notification이 가지 않게 보호 할 수 있다.)
아무 유저나 Notification을 보낼 수 있도록 하는 것이 아니라, 개발자에게 Notification을 보낼 수 있도록 하는 것이기 때문에 Idenfification이 요구된다. 따라서 개발한 App을 Google과 Apple에 연동하여 Notification을 보낼 수 있도록 해야 한다.
Service에 연동되면, 직접 Message를 보내는 것이 아니라 Notification Server가 그 역할을 대신 해준다. 쉽게 하는 방법은 Firebase Cloud Messaging Service를 이용하는 것이다. (Firebase를 사용하지 않더라도 FCM을 이용하는 것은 꽤나 좋은 선택일 수 있다.) 이는 Firebase Messaging SDK를 이용한다.
Notification에는 Push Notification, Data Notification 두 종류가 있다.
Push Notification은 우리가 아는 일반적인 Notification이다. Device가 Sleep 모드여도 Notification이 Pop Up 되며 나타난다. 또한 Notification을 누르면 App의 데이터를 받을 수 있다.
Data Notification은 조금 다르다. Push Notification은 어플리케이션을 구동하고 있지 않은 상태에서도 Push Notification을 받을 수 있었다면, Data Notification은 어플리케이션을 구동하고 있을 때만 받을 수 있으며 데이터를 업데이트 받는다.
Push Notification을 보내기 위해선 Google과 Apple에 연동하는 Identification이 필요한데, FCM을 이용하게 되면 Firebase Console에 어플리케이션을 등록하는 과정이 Identification을 거치는 과정이다.
onMessage → Foreground / onLaunch → Terminated / onResume → Background 에서 실행되는 Method들이다. 상황에 맞게 사용하여 Navigating 등 로직을 처리하면 된다. (Android의 Foreground를 제외한 경우들에 대해서 click_action : FLUTTER_NOTIFICATION_CLICK을 설정해야 한다.)
iOS의 경우 개발자 등록을 해야할 뿐더러 설정 과정이 꽤나 복잡하나, 한 번 설정하고 나면 그 뒤로는 꽤나 편하다. (초기에 Notification을 받을지에 대한 권한을 받을 수 있도록 확인용 함수를 호출해서 권한을 얻어내야 한다.)
Android의 경우 Foreground에서 FCM 이용 시, failed to find callback이라는 Error가 자주 뜨게 되는데 이를 해결해줘야 한다. 또한 Background나 Terminated에서 FCM을 이용하게 되면, failed to find callback과 Missing Default Notification Channel metadata in AndroidManifest. Default value will be used가 자주 뜨게 된다. 이 역시도 해결해야 한다. (해당 Error들은 Push Notification이 정상적으로 작동함에도 발생한다.) → Document를 통해서 해결한다.
Cloud Firestore 데이터베이스에 값이 쌓일 때마다 FCM을 보내고 싶다면, Firebase Cloud Function을 이용하면 쉽게 처리할 수 있다. Firebase Cloud Function은 특정 Event가 발생할 때마다 실행하게 된다.
sudo npm install -g firebase-tools를 이용하여 Firebase Tools를 설치했다면, firebase init은 프로젝트 내에서 수행한다.
firebase init을 통해서 수행하고자 하는 Function을 프로젝트 내에서 작성할 수 있도록 새로운 Script들이 생긴다. 이 Script에 Function을 작성하여 특정 Event마다 실행될 수 있도록 만들 수 있다. 작성후에는 firebase deply를 통해서 Online에 해당 Function을 배포한다.
작성한 Code들이 특정 Event마다 실행될 수 있도록 만들기 위해서, Firebase Cloud Function의 공식 문서에서 Trigger Background Functions 관련으로 찾아본다.
Firebase Cloud Function에서 Push Notification을 발생시키는 것은 어렵지 않다. FirebaseMessaging Instance를 통해서 Token을 받아올 수 있는데, 이 Token을 이용하여 Notification을 보낼 수 있다. (그렇다면 Token 값을 받아오는 것은 쉽게 한다고 치는데, Firebase Cloud Function에서 해당 Token을 받아오는 방법은? → Flutter에서 Cloud Firestore 데이터베이스에 해당 Token을 저장하여 접근할 수 있도록 한다.)
getToken() Method를 통해서 받은 Token으로 Notification을 발생 시키기 싫다면, subscribeToTopic() 이라는 Method를 사용할 수 있다. 해당 Method는 인자로 넣은 Identifier를 통해, Identifier에 발생한 Notification을 받게 만들 수 있다. 따라서 Notification은 해당 Identifier로 Firebase Cloud Function을 통해서 발생 시킨다. (Identifier로 Notification을 발생시키는 Method는 Firebase Admin의 sendToTopic() Method이다.) Code 참고하자.
** Firebase Cloud Function을 이용하기 위해선, Firebase Application을 Initialize 해줘야 한다. (admin.initializeApp()을 호출한다.)
** FCM을 이용하면서 FirebaseMessaging을 이용할 때는, super.initState()를 먼저 호출한 후에 이용해야 한다.
** 단일 Document에 대해선 DocumentSnapshot, 여러 Document에 대해선 QuerySnapshot으로 들어온다. DocumentSnapshot의 경우에 snapshot.data안에 data가 한 번 더 있으니 주의해야 한다.
** Document의 setData() 시에는 다른 Field들을 모두 지우고, 인자로 받은 데이터만 저장을 하게 된다. 반면에 updateData() 시에는 다른 Field들을 그대로 놔두고, 인자로 받은 데이터에 대해서만 Update 하게 된다. 즉, Update 시에 Field에 대한 데이터 변경만 가능한 것이지 Field 자체의 데이터 내에서 추가 삭제는 어렵다.