[Flutter] Instagram Clone Coding (인스타그램 클론코딩) 6 (resources)
파이어베이스 혹은 파이어스토어를 다뤄본 사람들이라면 알겠지만 단순히 우리가 파이어베이스를 initialization하거나 코드 내 심어놓기만 했다고 해서 바로 연결이 되는것은 아니다. 우리가 어떤식으로 데이터를 전달할지 혹은 받아올지들을 상세하게 적어놓아야지 파이어베이스와 같은 모바일 어플리케이션 플랫폼과 소통이 가능한 것이다.
여기에서 또 빠질 수 없이 등장하는것이 저번 포스팅에서도 이야기를 하였던, MVC, MVP, MVVM 패턴이다.
https://riris01.tistory.com/41
[이론] MVC, MVP 패턴 + MVVM 패턴과 비교
목표 MVC와 MVP의 개념을 이해하고 그 차이점을 확인할 수 있다. 자신의 프로젝트에 따라 MVC, MVP, MVVM 등을 어떻게 적용할지 확인할 수 있으며 각각의 장단점을 서술할 수 있다. 1. MVC (Model + View + Con
riris01.tistory.com
여기서 Method라고 언급은 직접하지 않았지만 이번에 볼 resources라는 부분에서 가장 자주 사용 단어가 Method라는 것이다. 이것은 제공하기로 한 데이터 혹은 앱 내에서 사용할 데이터들을 어떠한 '방법'으로 사용을 할것인가 혹은 이 데이터를 어떠한 방법으로 플랫폼과 연결을 할 것인가 라는 '방법'을 적어놓은 공간이라고 보면된다.
모델이라는 것이 데이터와 데이터의 처리를 단순하게 적었다면 Method라는 부분은 데이터의 처리 '방법'까지를 적어놓은 공간이다.
그럼 여기서 사용할 Method는 총 3가지 이다.
(1) auth_method.dart: 로그인 방법
(2) firestore_methods.dart: firestore내 정보 저장 방법
(3) storage_methods.dart: storage내 사진 정보 저장방법이다.
이렇게 코드라는 것이 하나하나 설정을 해줘야한다.
자 그럼 이제부터 method들을 설정해서 firebase측에 제공할 데이터들을 처리할 수 있도록 코드를 작성해보자.
파일생성: lib/resources/auth_methods.dart
import 'dart:typed_data';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:instagram_flutter/models/user.dart' as model;
import 'package:instagram_flutter/resources/storage_method.dart';
class AuthMethods {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<model.User> getUserDetails() async {
User currentUser = _auth.currentUser!;
DocumentSnapshot snap =
await _firestore.collection('users').doc(currentUser.uid).get();
return model.User.fromSnap(snap);
}
// sing up the user
Future<String> signUpUser({
required String email,
required String password,
required String username,
required String bio,
required Uint8List file,
// required Uint8List file,
}) async {
String res = "Some error occurred";
try {
if (email.isNotEmpty ||
password.isNotEmpty ||
username.isNotEmpty ||
bio.isNotEmpty
// file != null
) {
// register user
UserCredential cred = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
print(cred.user!.uid);
String photoUrl = await StorageMethods()
.uploadImageToStorage('profilePics', file, false);
// add user to our database
model.User user = model.User(
username: username,
uid: cred.user!.uid,
email: email,
bio: bio,
photoUrl: photoUrl,
followers: [],
following: [],
);
await _firestore.collection('users').doc(cred.user!.uid).set(
user.toJson(),
);
res = "success";
}
} catch (err) {
res = err.toString();
}
return res;
}
// loggin in user
Future<String> loginUser(
{required String email, required String password}) async {
String res = "Some error occurred";
try {
if (email.isNotEmpty || password.isNotEmpty) {
await _auth.signInWithEmailAndPassword(
email: email, password: password);
res = "success";
} else {
res = "Please enter all the fields";
}
} catch (err) {
res = err.toString();
}
return res;
}
Future<void> signOut() async {
await _auth.signOut();
}
}
해당 부분은 인증 메서드를 구현하는 법이다. 여기서 중요한 메서드는 다음과 같다.
getUserDetails(): Firebase Firestore 데이터 베이스에 사용자 세부 정보를 가져온다.
signUpUser(): 새로운 사용자를 Firebase Auth 시스템에 등록하고 데이터베이스에서 사용자를 추가한다.
loginUser(): Firebase Auth 시스템에서 사용자를 로그인한다.
signOut(): 이 메서드의 경우에는 현재 사용자를 Firebase Auth 시스템에서 로그아웃 한다.
이렇게 보면 각각의 메서드는 앱에서 데이터를 처리함에 있어서 가장 기본적들인 것으로 구현이 되어 있다. 그 외에는 메서드를 시도할때 무엇인가 오류 사항이 발생하면 그에 대한 '대응' 코드들을 작성해 두었다.
자 그 다음의 파일은 이거다.
파일생성: lib/resources/firestore_methods.dart
import 'dart:typed_data';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:instagram_flutter/models/post.dart';
import 'package:instagram_flutter/resources/storage_method.dart';
import 'package:uuid/uuid.dart';
class FirestoreMethods {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
// upload post
Future<String> uploadPost(
String description,
Uint8List file,
String uid,
String username,
String profImage,
) async {
String res = "some error occurred";
try {
String photoUrl =
await StorageMethods().uploadImageToStorage('posts', file, true);
String postId = const Uuid().v1();
Post post = Post(
description: description,
uid: uid,
username: username,
postId: postId,
datePublished: DateTime.now(),
postUrl: photoUrl,
profImage: profImage,
likes: [],
);
_firestore.collection('posts').doc(postId).set(
post.toJson(),
);
res = "success";
} catch (err) {
res = err.toString();
}
return res;
}
Future<String> likePost(String postId, String uid, List likes) async {
String res = "Some error occurred";
try {
if (likes.contains(uid)) {
await _firestore.collection('posts').doc(postId).update({
'likes': FieldValue.arrayRemove([uid]),
});
} else {
await _firestore.collection('posts').doc(postId).update({
'likes': FieldValue.arrayUnion([uid]),
});
}
res = 'success';
} catch (err) {
print(err.toString());
}
return res;
}
Future<String> postComment(String postId, String text, String uid,
String name, String profilePic) async {
String res = "Some error occurred";
try {
if (text.isNotEmpty) {
String commentId = const Uuid().v1();
await _firestore
.collection('posts')
.doc(postId)
.collection('comments')
.doc(commentId)
.set({
'profilePic': profilePic,
'name': name,
'uid': uid,
'text': text,
'commentId': commentId,
'datePublished': DateTime.now()
});
res = 'success';
} else {
res = "Please enter text";
}
} catch (err) {
res = err.toString();
}
return res;
}
// deleting post
Future<String> deletePost(String postId) async {
String res = "Some error occurred";
try {
await _firestore.collection('posts').doc(postId).delete();
res = 'success';
} catch (err) {
res = err.toString();
}
return res;
}
Future<void> followUser(
String uid,
String followId,
) async {
try {
DocumentSnapshot snap =
await _firestore.collection('users').doc(uid).get();
List following = (snap.data()! as dynamic)['following'];
if (following.contains(followId)) {
await _firestore.collection('users').doc(followId).update({
'followers': FieldValue.arrayRemove([uid])
});
await _firestore.collection('users').doc(uid).update({
'following': FieldValue.arrayRemove([followId])
});
} else {
await _firestore.collection('users').doc(followId).update({
'followers': FieldValue.arrayUnion([uid])
});
await _firestore.collection('users').doc(uid).update({
'following': FieldValue.arrayUnion([followId])
});
}
} catch (e) {
print(e.toString());
}
}
}
여기에서 생성한 각각의 메서드들은 FirestoreMethods 들이라고 보면 된다.
uploadPost()
likePost()
postComment()
deletePost()
followUser()
와 같다. 각각은 업로드 / 좋아요 / 댓글 작성 / 게시물 삭제 / 그리고 팔로우 순으로 기능을 담당하고 있다.
여기서 조금 더 자세하게 설명을 하자면 uploadPost의 경우에는 게시물 설명, 이미지, 사용자 ID 입력을 받고 Firebase, Firestore 데이터베이스에 게시물을 업로드 하도록 하였다. 메서드는 Uuid() 클래스를 사용하여 고유한 게시물 ID를 생성하도록 하였고 StorageMethods 클래스를 사용하여 이미지를 Firebase에 업로드 하는 식이다. 그리고 새로운 게시물 문서가 만들어지면 게시물의 설명, 이미지 URL, 사용자 ID 및 게시 날짜를 설정하도록 하였다.
likePost()는 메서드가 가장 먼저 하는 것이 사용자가 게시물을 이미 좋아요라고 처리 했는지를 확인해본다. 맞다면 게시물의 좋아요에서 사용자의 ID를 배제 (중복하지 않도록) 하고, 그렇지 않다면 게시물의 좋아요를 배제 하지 않도록 한다.
postComment()의 경우에는 uploadPost와 비슷하되 Firestore 데이터 베이스에 새 댓글 문서를 만들고 텍스트가 이미지 대신에 설정이 되는 방식이다.
그다음으로는
파일생성: lib/resources/storage_method.dart
를 만들자.
import 'dart:typed_data';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:uuid/uuid.dart';
class StorageMethods {
final FirebaseStorage _storage = FirebaseStorage.instance;
final FirebaseAuth _auth = FirebaseAuth.instance;
// adding image to firebase storage
Future<String> uploadImageToStorage(
String childName, Uint8List file, bool isPost) async {
Reference ref =
_storage.ref().child(childName).child(_auth.currentUser!.uid);
if (isPost) {
String id = const Uuid().v1();
ref = ref.child(id);
}
UploadTask uploadTask = ref.putData(file);
TaskSnapshot snap = await uploadTask;
String downloadUrl = await snap.ref.getDownloadURL();
return downloadUrl;
}
}
음 뭐 이건 uploadImageToStorage() 함수 이다. 다시 말하자면 이미지를 Firebase Storage에 추가하는 것이다.
childName 매개변수는 이미지가 저장될 Firebase storage의 경로를 나타내는 것이며, Uint8List는 업로드할 이미지의 데이터를 나타낸다. 또한 isPost는 이미지가 게시물에 사용되는지 여부를 확인한다. 그 다음 return downloadUrl을 통해서 업로드 작업이 완료될 시 다운로드 URL을 반환한다. 이미지 업로드의 전형적인 방법이다.
이제 얼마 안남았다. 앞으로는 이 부분들에 대해서 더 알아볼 예정이다.
utils: imagePicker (사진 정보 불러오는 라이브러리)
widgets: 각종 버튼 및 기타 레이아웃 설정부분
main.dart: 메인으로 불려질 화면의 설정
그럼 구우우우우우웅웃 나잇