Clone App/Twitter

[Twitter Clone] Set user Info into firebase storage

밤새는 탐험가89 2024. 6. 9. 08:22

🟨 구현 화면

 

 

🟨 구현 순서

 

1️⃣ TwitterUser 정보 관련 데이터 모델 생성 

import Foundation
import Firebase

struct TwitterUser: Codable {
    let id: String
    var displayName: String = ""
    var username: String = ""
    var followersCount: Int = 0
    var followingCount: Int = 0
    var createdOn: Date = Date()
    var bio: String = ""
    var avatarPath: String = ""
    var isUserOnboarded: Bool = false
    
    
    init(from user: User) {
        self.id = user.uid
    }
}

 

 

2️⃣ Networking 폴더 내에 DatabaseManager 파일 생성 

  • 데이터 베이스 접근 및 관리 목적
  • collectionUsers(add user: User) 메서드
    • 데이터 베이스 내에 계정 정보를 등록하게 되면 오토 id가 생성된다. 
    • 이를 통해 계정에 대한 정보를 설정할 수 있도록 하는 함수이다. 
  • collectionUsers(retreive id: String) 메서드
    • 데이터 베이스 내에 저장된 계정 정보의 id를 통해 접근할 수 있도록 해주는 메서드이다. 
import Foundation
import Firebase
import FirebaseFirestoreSwift
import FirebaseFirestoreCombineSwift
import Combine



class DatabaseManager {
    
    static let shared = DatabaseManager()
    
    
    let db = Firestore.firestore()
    let usersPath: String = "users"
    let tweetsPath: String = "tweets"
    
    
    
    func collectionUsers(add user: User) -> AnyPublisher<Bool, Error> {
        let twitterUser = TwitterUser(from: user)
        return db.collection(usersPath).document(twitterUser.id).setData(from: twitterUser)
            .map { _ in return true }
            .eraseToAnyPublisher()
    }
    
    
    func collectionUsers(retreive id: String) -> AnyPublisher<TwitterUser, Error> {
        db.collection(usersPath).document(id).getDocument()
            .tryMap { try $0.data(as: TwitterUser.self) }
            .eraseToAnyPublisher()
    }

 

 

3️⃣ ViewModels 폴더 내에 AuthenticationViewViewModel 파일 수정 

  • 계정을 등록하기 전에 해당 계정에 대한 정보를 입력하기 위함
  • createUser() 메서드가 동작할 때 계정에 대한 정보 (TwitterUser) 라는 걸 설정한다. 
final class AuthenticationViewViewModel: ObservableObject {
    
    
    @Published var email: String?
    @Published var password: String?
    @Published var isAuthenticationFormValid: Bool = false
    @Published var user: User?
    @Published var error: String?
    
    
    private var subscriptions: Set<AnyCancellable> = []
    
    ...
    
    func createUser() {
        guard let email = email,
              let password = password else { return }
        
        AuthManager.shared.registerUser(with: email, password: password)
        
            // handleEvents?
            .handleEvents(receiveSubscription: { [weak self] user  in
                self?.user = user as? User
            })
            .sink { [weak self] completion in
                
                if case .failure(let error) = completion {
                    self?.error = error.localizedDescription
                
                }

            } receiveValue: { [weak self] user in
                self?.createRecord(for: user)
            }
            .store(in: &subscriptions)
    }
    
    func createRecord(for user: User) {
        DatabaseManager.shared.collectionUsers(add: user)
            .sink { [weak self] completion in
                if case .failure(let error) = completion{
                    self?.error = error.localizedDescription
                }
            } receiveValue: { state in
                print("Adding user record to database: \(state)")
            }
            .store(in: &subscriptions)

    }
    
    ...
}

 

⭐️ handleEvents?

  • 게시자 (publisher)의 이벤트가 발생할 떄 특정 클로저를 실행하는데 사용된다.
  • 여기서는 user 정보에 대한 변동사항? 이 있을 떄를 이를 통해 계정 정보를 저장하기 위함 (createRecord 사용)이다.

 

 

4️⃣ ViewModels 폴더 내에 HomeViewViewModel 파일 생성 

  • TwitterUser 구조체 타입의 user의 정보를 관리하기 위함 
import Foundation
import Combine
import FirebaseAuth


final class HomeViewViewModel: ObservableObject {
    
    @Published var user: TwitterUser?
    @Published var error: String?
    
    private var subscriptions: Set<AnyCancellable> = []
    
    
    func retreiveUser() {
        guard let id = Auth.auth().currentUser?.uid else { return }
        DatabaseManager.shared.collectionUsers(retreive: id)
            .handleEvents(receiveOutput: { [weak self] user in
                self?.user = user
            })
            .sink { [weak self] completion in
                if case .failure(let error) = completion {
                    self?.error = error.localizedDescription
                }
            } receiveValue: { [weak self] user in
                self?.user = user
            }
            .store(in: &subscriptions)
    }
}

 

 

5️⃣ Networking 폴더 내에 DatabaseManager 파일에 collectionsUsers(retreive ~) 메서드 추가

class DatabaseManager {
    
    static let shared = DatabaseManager()
    
    
    let db = Firestore.firestore()
    let usersPath: String = "users"
    let tweetsPath: String = "tweets"
    
    ...
    
    
    func collectionUsers(retreive id: String) -> AnyPublisher<TwitterUser, Error> {
        db.collection(usersPath).document(id).getDocument()
            .tryMap { try $0.data(as: TwitterUser.self) }
            .eraseToAnyPublisher()
    }

 

 

6️⃣ HomeViewController 파일 내 사용자 반환 메서드 구현

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.navigationBar.isHidden = false
        
        // 로그인 여부 확인
        handleAuthentication()
        
        // 뷰가 새로 열릴 때마다 사용자에 대한 정보 확인
        viewModel.retreiveUser()
    }

 

 

  • 사용자의 정보 유무에 따른 Profile뷰 열기
    func completeUserOnboarding() {
        let profileDataFormVC = ProfileDataFormViewController()
        present(profileDataFormVC, animated: true)
    }
    
    func bindViews() {
        viewModel.$user.sink { [weak self] user in
            guard let user = user else { return }
            if !user.isUserOnboarded {
                self?.completeUserOnboarding()
            }
        }
        .store(in: &subscriptions)
    }