Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[네트워킹] 파이어베이스 RealtimeDatabase를 활용해서 채팅 기능 구현 #102

Merged
52 changes: 47 additions & 5 deletions NearTalk/NearTalk.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ final class AppCoordinator: Coordinator {
extension AppCoordinator: LaunchScreenCoordinatorDependency {
func showMainViewController() {
self.navigationController?.popViewController(animated: false)
let rootTabBarCoordinator: RootTabBarCoordinator = RootTabBarDIContainer()
.makeTabBarCoordinator(navigationController: self.navigationController)
guard let rootTabBarCoordinator: RootTabBarCoordinator = RootTabBarDIContainer()
.makeTabBarCoordinator(navigationController: self.navigationController) else {
return
}
rootTabBarCoordinator.start()
}

func showLoginViewController() {
print(Self.self, #function)
self.navigationController?.viewControllers.insert(LoginViewController(), at: 0)
self.navigationController?.popViewController(animated: false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ final class ChatRoomListDIContainer {

// MARK: - Dependencies
struct Dependencies {
let apiDataTransferService: DefaultStorageService
let imageDataTransferService: DefaultStorageService
let apiDataTransferService: StorageService
let imageDataTransferService: StorageService
}

private let dependencies: Dependencies
Expand All @@ -42,6 +42,25 @@ final class ChatRoomListDIContainer {
}

// MARK: - Services
func makeFirestoreService() -> FirestoreService {
return DefaultFirestoreService()
}

func makeDatabaseService() -> RealTimeDatabaseService {
return DefaultRealTimeDatabaseService()
}

func makeAuthService() -> AuthService {
return DefaultFirebaseAuthService()
}

// MARK: - Repository
func makeProfileRepository() -> ProfileRepository {
return DefaultProfileRepository(
firestoreService: makeFirestoreService(),
firebaseAuthService: makeAuthService()
)
}

// MARK: - UseCases
func makeChatRoomListUseCase() -> FetchChatRoomUseCase {
Expand All @@ -50,7 +69,12 @@ final class ChatRoomListDIContainer {

// MARK: - Repositories
func makeRepository() -> ChatRoomListRepository {
return DefaultChatRoomListRepository(dataTransferService: dependencies.apiDataTransferService)
return DefaultChatRoomListRepository(
dataTransferService: dependencies.apiDataTransferService,
profileRepository: makeProfileRepository(),
databaseService: makeDatabaseService(),
firestoreService: makeFirestoreService()
)
}

// ExampleMVVM에서는 보여줄수 있는 Scene의 뷰컨트롤러와 뷰모델이 존재
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class FriendListDIContainer {
// MARK: - Dependencies
struct Dependencies {
let firestoreService: FirestoreService
let firebaseAuthService: FirebaseAuthService
let firebaseAuthService: AuthService
}

private let dependencies: Dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ final class LaunchScreenDIContainer {
// MARK: - Dependencies

// MARK: - Services
func makeAuthService() -> FirebaseAuthService {
func makeAuthService() -> AuthService {
return DefaultFirebaseAuthService()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// Created by Preston Kim on 2022/11/15.
//

import Foundation
import UIKit

final class DefaultOnboardingDIContainer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@
// Created by 고병학 on 2022/11/18.
//

import Foundation
import UIKit

final class RootTabBarDIContainer {
// MARK: - Dependencies

// MARK: - Services
func makeStorageService() -> StorageService {
return DefaultStorageService()
}

func makeFirebaseAuthService() -> AuthService {
return DefaultFirebaseAuthService()
}

func makeFirestoreService() -> FirestoreService {
return DefaultFirestoreService()
}

// MARK: - UseCases
func makeTabBarUseCase() -> TabBarUseCase {
Expand All @@ -27,30 +37,34 @@ final class RootTabBarDIContainer {
func makeViewModel() -> RootTabBarViewModel {
return DefaultRootTabBarViewModel()
}

#warning("mapViewController DI Container 필요")
#warning("chatRoomListViewController DI Container 필요")
#warning("friendListViewController DI Container 필요")
#warning("myProfileViewController DI Container 필요")

// MARK: - Create viewController
func createTabBarController() -> RootTabBarController {
let chatRoomListRepository = DefaultChatRoomListRepository(dataTransferService: DefaultStorageService())
let chatRoomListUseCase: FetchChatRoomUseCase = DefaultFetchChatRoomUseCase(chatRoomListRepository: chatRoomListRepository)

let myProfileDIContainer: MyProfileDIContainer = .init()
let myProfileVC: MyProfileViewController = .init(coordinator: myProfileDIContainer.makeMyProfileCoordinator(), viewModel: myProfileDIContainer.makeViewModel())

let dependency: RootTabBarControllerDependency = .init(
mapViewController: MainMapViewController(),
chatRoomListViewController: ChatRoomListViewController.create(with: DefaultChatRoomListViewModel(useCase: chatRoomListUseCase)),
friendListViewController: FriendListViewController(),
myProfileViewController: myProfileVC
)
func createTabBarController(dependency: RootTabBarControllerDependency) -> RootTabBarController {
return RootTabBarController(viewModel: makeViewModel(), dependency: dependency)
}

// MARK: - Coordinator
func makeTabBarCoordinator(navigationController: UINavigationController?) -> RootTabBarCoordinator {
return RootTabBarCoordinator(navigationController: navigationController)
func makeTabBarCoordinator(navigationController: UINavigationController?) -> RootTabBarCoordinator? {
let chatRoomListDIContainerDependency: ChatRoomListDIContainer.Dependencies = .init(
apiDataTransferService: self.makeStorageService(),
imageDataTransferService: self.makeStorageService()
)
let chatRoomListDIContainer: ChatRoomListDIContainer = .init(dependencies: chatRoomListDIContainerDependency)
let chatRoomListCoordinator: ChatRoomListCoordinator = chatRoomListDIContainer.makeChatRoomListCoordinator(navigationController: .init())

let friendListDIContainerDependencies: FriendListDIContainer.Dependencies = .init(
firestoreService: self.makeFirestoreService(),
firebaseAuthService: self.makeFirebaseAuthService()
)
let friendListDIContainer: FriendListDIContainer = .init(dependencies: friendListDIContainerDependencies)
let friendListCoordinator: FriendListCoordinator = friendListDIContainer.makeFriendListCoordinator(navigationController: .init())

let dependency: RootTabBarCoordinatorDependency = .init(
mainMapCoordinator: MainMapCoordinator(),
chatRoomListCoordinator: chatRoomListCoordinator,
friendListCoordinator: friendListCoordinator,
myProfileCoordinator: MyProfileCoordinator(navigationController: .init())
)
return RootTabBarCoordinator(navigationController: navigationController, dependency: dependency)
}
}
12 changes: 8 additions & 4 deletions NearTalk/NearTalk/Data/Repositories/ChatRoomListRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ protocol ChatRoomListRepository {
// fireBase 채팅방 목록 가져오기
func fetchChatRoomList() -> Observable<[ChatRoom]>
func fetchUserChatRoomModel() -> Observable<[UserChatRoomModel]>

// coreData 채팅방 목록 가져오기
// func fetchCoreDataChatRoomList() -> Observable<[ChatRoom]>


func createChatRoom(_ chatRoom: ChatRoom) -> Completable
func fetchChatRoomListWithCoordinates(southWest: NCLocation, northEast: NCLocation) -> Single<[ChatRoom]>
func fetchUserChatRoomUUIDList() -> Single<[String]>
func fetchChatRoomInfo(_ chatRoomID: String) -> Single<ChatRoom>
func observeChatRoomInfo(_ chatRoomID: String) -> Observable<ChatRoom>
func fetchUserChatRoomTickets() -> Single<[UserChatRoomTicket]>
func updateUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import RxSwift

final class DefaultAuthRepository: AuthRepository {
private let authService: any FirebaseAuthService
private let authService: any AuthService

init(authService: any FirebaseAuthService) {
init(authService: any AuthService) {
self.authService = authService
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// DefaultChatMessageRepository.swift
// NearTalk
//
// Created by 고병학 on 2022/11/23.
//

import Foundation
import RxSwift

final class DefaultChatMessageRepository: ChatMessageRepository {
private let databaseService: RealTimeDatabaseService

init(databaseService: RealTimeDatabaseService) {
self.databaseService = databaseService
}

func sendMessage(_ message: ChatMessage) -> Completable {
self.databaseService.sendMessage(message)
}

func fetchMessage(page: Int, skip: Int, count: Int, roomID: String) -> Single<[ChatMessage]> {
self.databaseService.fetchMessages(page: page, skip: skip, pageCount: count, roomID: roomID)
}

func observeChatRoomMessages(roomID: String) -> Observable<ChatMessage> {
self.databaseService.observeNewMessage(roomID)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,81 @@ import RxSwift

final class DefaultChatRoomListRepository {

private let dataTransferService: StorageService
private let dataTransferService: any StorageService
private let databaseService: any RealTimeDatabaseService
private let firestoreService: any FirestoreService
private let profileRepository: any ProfileRepository
private(set) var dummyData: ChatRoomDummyData = ChatRoomDummyData()

init(dataTransferService: StorageService) {
init(
dataTransferService: any StorageService,
profileRepository: any ProfileRepository,
databaseService: any RealTimeDatabaseService,
firestoreService: any FirestoreService
) {
self.dataTransferService = dataTransferService
self.profileRepository = profileRepository
self.databaseService = databaseService
self.firestoreService = firestoreService
}
}

extension DefaultChatRoomListRepository: ChatRoomListRepository {
func createChatRoom(_ chatRoom: ChatRoom) -> Completable {
Single.zip(
self.firestoreService.create(data: chatRoom, dataKey: .chatRoom),
self.databaseService.createChatRoom(chatRoom)
).asCompletable()
}

func fetchChatRoomListWithCoordinates(southWest: NCLocation, northEast: NCLocation) -> Single<[ChatRoom]> {
let queryList: [FirebaseQueryDTO] = [
.init(key: "latitude", value: southWest.latitude, queryKey: .isGreaterThan),
.init(key: "latitude", value: northEast.latitude, queryKey: .isLessThan),
.init(key: "longitude", value: southWest.latitude, queryKey: .isGreaterThan),
.init(key: "longitude", value: northEast.latitude, queryKey: .isLessThan)
]
return self.firestoreService.fetchList(dataKey: .chatRoom, queryList: queryList)
}

func fetchUserChatRoomUUIDList() -> Single<[String]> {
self.profileRepository
.fetchMyProfile()
.flatMap { [weak self] (profile: UserProfile) in
guard let self,
let uuid: String = profile.uuid else {
throw ChatRoomListRepositoryError.failedToFetch
}
return self.databaseService.fetchUserChatRoomTicketList(uuid)
}
.asObservable()
.map { (tickets: [UserChatRoomTicket]) in
return tickets.compactMap({ $0.roomID })
}.asSingle()
}

func fetchChatRoomInfo(_ chatRoomID: String) -> Single<ChatRoom> {
self.databaseService.fetchChatRoomInfo(chatRoomID)
}

func observeChatRoomInfo(_ chatRoomID: String) -> Observable<ChatRoom> {
self.databaseService.observeChatRoomInfo(chatRoomID)
}

func fetchUserChatRoomTickets() -> Single<[UserChatRoomTicket]> {
self.profileRepository.fetchMyProfile()
.flatMap { [weak self] (profile: UserProfile) in
guard let self,
let uuid: String = profile.uuid else {
throw ChatRoomListRepositoryError.failedToFetch
}
return self.databaseService.fetchUserChatRoomTicketList(uuid)
}
}

func updateUserChatRoomTicket(_ ticket: UserChatRoomTicket) -> Completable {
self.databaseService.updateUserChatRoomTicket(ticket)
}

func fetchChatRoomList() -> Observable<[ChatRoom]> {
return Observable<[ChatRoom]>.create { observer in
Expand Down Expand Up @@ -60,3 +126,8 @@ struct ChatRoomDummyData {
userChatRoomData.append(UserChatRoomModel(userID: "s001", chatRoomID: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"]))
}
}

enum ChatRoomListRepositoryError: Error {
case failedToFetch
case failedToCrate
}
25 changes: 25 additions & 0 deletions NearTalk/NearTalk/Data/Repositories/DefaultMediaRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// DefaultMediaRepository.swift
// NearTalk
//
// Created by 고병학 on 2022/11/23.
//

import Foundation
import RxSwift

final class DefaultMediaRepository: MediaRepository {
private let storageService: StorageService

init(storageService: StorageService) {
self.storageService = storageService
}

func uploadImage(_ imageData: Data) -> RxSwift.Single<String> {
self.storageService.uploadData(data: imageData, fileName: "\(UUID().uuidString).jpg", dataType: .images)
}

func uploadVideo(_ videoData: Data) -> RxSwift.Single<String> {
self.storageService.uploadData(data: videoData, fileName: "\(UUID().uuidString).mov", dataType: .videos)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import RxSwift

final class DefaultProfileRepository: ProfileRepository {
private let firestoreService: FirestoreService
private let firebaseAuthService: FirebaseAuthService
private let firebaseAuthService: AuthService

init(firestoreService: FirestoreService, firebaseAuthService: FirebaseAuthService) {
init(firestoreService: FirestoreService, firebaseAuthService: AuthService) {
self.firestoreService = firestoreService
self.firebaseAuthService = firebaseAuthService
}
Expand Down
1 change: 1 addition & 0 deletions NearTalk/NearTalk/Domain/Entities/ChatRoom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ struct ChatRoom: BaseEntity {
var accessibleRadius: Double?
var recentMessageID: String?
var maxNumberOfParticipants: Int?
var messageCount: Int?
}
16 changes: 16 additions & 0 deletions NearTalk/NearTalk/Domain/Entities/UserChatRoomTicket.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// UserChatRoomTicket.swift
// NearTalk
//
// Created by 고병학 on 2022/11/23.
//

import Foundation

struct UserChatRoomTicket: BaseEntity {
var uuid: String?
var userID: String?
var roomID: String?
var lastReadMessageID: String?
var lastRoomMessageCount: Int?
}
Loading