diff --git a/data/src/main/java/com/whyranoid/data/account/RunningHistoryLocalDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/account/RunningHistoryLocalDataSourceImpl.kt index 2b3411e0..cc181da6 100644 --- a/data/src/main/java/com/whyranoid/data/account/RunningHistoryLocalDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/account/RunningHistoryLocalDataSourceImpl.kt @@ -22,7 +22,7 @@ class RunningHistoryLocalDataSourceImpl @Inject constructor( } override suspend fun saveRunningHistory(runningHistoryEntity: RunningHistoryEntity): Result { - return kotlin.runCatching { + return runCatching { runningHistoryDao.addRunningHistory(runningHistoryEntity) runningHistoryEntity.toRunningHistory() } diff --git a/data/src/main/java/com/whyranoid/data/di/CoroutineModule.kt b/data/src/main/java/com/whyranoid/data/di/CoroutineModule.kt new file mode 100644 index 00000000..2be541a0 --- /dev/null +++ b/data/src/main/java/com/whyranoid/data/di/CoroutineModule.kt @@ -0,0 +1,16 @@ +package com.whyranoid.data.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.Dispatchers + +@Module +@InstallIn(SingletonComponent::class) +class CoroutineModule { + + @Provides + @IODispatcher + fun provideIODispatcher() = Dispatchers.IO +} diff --git a/data/src/main/java/com/whyranoid/data/di/DispatchersQualifiers.kt b/data/src/main/java/com/whyranoid/data/di/DispatchersQualifiers.kt new file mode 100644 index 00000000..3aadf7c8 --- /dev/null +++ b/data/src/main/java/com/whyranoid/data/di/DispatchersQualifiers.kt @@ -0,0 +1,7 @@ +package com.whyranoid.data.di + +import javax.inject.Qualifier + +@Retention(AnnotationRetention.RUNTIME) +@Qualifier +annotation class IODispatcher diff --git a/data/src/main/java/com/whyranoid/data/group/GroupDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/group/GroupDataSourceImpl.kt index d3971cdf..243399dc 100644 --- a/data/src/main/java/com/whyranoid/data/group/GroupDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/group/GroupDataSourceImpl.kt @@ -2,6 +2,7 @@ package com.whyranoid.data.group import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.ktx.snapshots import com.whyranoid.data.constant.CollectionId.GROUPS_COLLECTION import com.whyranoid.data.constant.CollectionId.USERS_COLLECTION import com.whyranoid.data.constant.FieldId.GROUP_INTRODUCE @@ -17,10 +18,9 @@ import com.whyranoid.domain.model.GroupInfo import com.whyranoid.domain.model.MoGakRunException import com.whyranoid.domain.model.Rule import com.whyranoid.domain.model.toRule -import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.tasks.await import java.util.* @@ -177,31 +177,23 @@ class GroupDataSourceImpl @Inject constructor( } private fun getGroupInfoResponse(groupId: String): Flow { - return callbackFlow { - db.collection(GROUPS_COLLECTION) - .document(groupId) - .addSnapshotListener { documentSnapshot, _ -> - trySend( - documentSnapshot?.toObject(GroupInfoResponse::class.java) - ?: throw MoGakRunException.FileNotFoundedException - ) - } - awaitClose() - } + return db.collection(GROUPS_COLLECTION) + .document(groupId) + .snapshots() + .map { documentSnapshot -> + documentSnapshot.toObject(GroupInfoResponse::class.java) + ?: throw MoGakRunException.FileNotFoundedException + } } private fun getUserResponse(uid: String): Flow { - return callbackFlow { - db.collection(USERS_COLLECTION) - .document(uid) - .addSnapshotListener { documentSnapshot, _ -> - trySend( - documentSnapshot?.toObject(UserResponse::class.java) - ?: throw MoGakRunException.FileNotFoundedException - ) - } - awaitClose() - } + return db.collection(USERS_COLLECTION) + .document(uid) + .snapshots() + .map { documentSnapshot -> + documentSnapshot.toObject(UserResponse::class.java) + ?: throw MoGakRunException.FileNotFoundedException + } } override suspend fun isDuplicatedGroupName(groupName: String): Boolean { diff --git a/data/src/main/java/com/whyranoid/data/group/GroupRepositoryImpl.kt b/data/src/main/java/com/whyranoid/data/group/GroupRepositoryImpl.kt index be946df0..a8f07775 100644 --- a/data/src/main/java/com/whyranoid/data/group/GroupRepositoryImpl.kt +++ b/data/src/main/java/com/whyranoid/data/group/GroupRepositoryImpl.kt @@ -7,7 +7,6 @@ import com.whyranoid.domain.model.GroupNotification import com.whyranoid.domain.model.Rule import com.whyranoid.domain.model.RunningHistory import com.whyranoid.domain.repository.GroupRepository -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -21,7 +20,7 @@ class GroupRepositoryImpl @Inject constructor( return userDataSource.getMyGroupList(uid) } - override fun getMyGroupListFlow(uid: String, coroutineScope: CoroutineScope) = userDataSource.getMyGroupListFlow(uid, coroutineScope) + override fun getMyGroupListFlow(uid: String) = userDataSource.getMyGroupListFlow(uid) override suspend fun updateGroupInfo( groupId: String, diff --git a/data/src/main/java/com/whyranoid/data/groupnotification/GroupNotificationDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/groupnotification/GroupNotificationDataSourceImpl.kt index 9fac8761..35aa6d16 100644 --- a/data/src/main/java/com/whyranoid/data/groupnotification/GroupNotificationDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/groupnotification/GroupNotificationDataSourceImpl.kt @@ -1,6 +1,7 @@ package com.whyranoid.data.groupnotification import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.ktx.snapshots import com.whyranoid.data.constant.CollectionId.FINISH_NOTIFICATION import com.whyranoid.data.constant.CollectionId.GROUP_NOTIFICATIONS_COLLECTION import com.whyranoid.data.constant.CollectionId.RUNNING_HISTORY_COLLECTION @@ -12,14 +13,14 @@ import com.whyranoid.domain.model.FinishNotification import com.whyranoid.domain.model.GroupNotification import com.whyranoid.domain.model.RunningHistory import com.whyranoid.domain.model.StartNotification -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.flattenMerge import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.withContext +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.tasks.await import java.util.UUID import javax.inject.Inject @@ -40,10 +41,10 @@ class GroupNotificationDataSourceImpl @Inject constructor( private fun getGroupStartNotifications(groupId: String): Flow> = callbackFlow { - db.collection(GROUP_NOTIFICATIONS_COLLECTION) + val registration = db.collection(GROUP_NOTIFICATIONS_COLLECTION) .document(groupId) .collection(START_NOTIFICATION) - .addSnapshotListener { snapshot, error -> + .addSnapshotListener { snapshot, _ -> val startNotificationList = mutableListOf() snapshot?.forEach { document -> val startNotification = @@ -53,58 +54,59 @@ class GroupNotificationDataSourceImpl @Inject constructor( trySend(startNotificationList) } - awaitClose() + awaitClose { + registration.remove() + } } - private fun getGroupFinishNotifications(groupId: String): Flow> = - callbackFlow { - db.collection(GROUP_NOTIFICATIONS_COLLECTION) - .document(groupId) - .collection(FINISH_NOTIFICATION) - .addSnapshotListener { snapshot, error -> - val finishNotificationList = mutableListOf() - snapshot?.forEach { document -> - val finishNotificationResponse = - document.toObject(FinishNotificationResponse::class.java) + private fun getGroupFinishNotifications(groupId: String): Flow> { + return db.collection(GROUP_NOTIFICATIONS_COLLECTION) + .document(groupId) + .collection(FINISH_NOTIFICATION) + .snapshots() + .map { snapshot -> + val finishNotificationList = mutableListOf() - finishNotificationResponse.let { finishNotificationResponse -> - db.collection(RUNNING_HISTORY_COLLECTION) - .document(finishNotificationResponse.historyId) - .get() - .addOnSuccessListener { - val runningHistory = it.toObject(RunningHistory::class.java) + snapshot.forEach { document -> + val finishNotificationResponse = + document.toObject(FinishNotificationResponse::class.java) - runningHistory?.let { - finishNotificationList.add( - FinishNotification( - uid = finishNotificationResponse.uid, - runningHistory = runningHistory - ) - ) - } - trySend(finishNotificationList) - } + finishNotificationResponse.let { finishNotification -> + getRunningHistory(finishNotification.historyId)?.let { runningHistory -> + finishNotificationList.add( + FinishNotification( + uid = finishNotification.uid, + runningHistory = runningHistory + ) + ) } } } + finishNotificationList + } + } - awaitClose() - } + // TODO : 예외처리 + private suspend fun getRunningHistory(historyId: String): RunningHistory? { + return db.collection(RUNNING_HISTORY_COLLECTION) + .document(historyId) + .get() + .await() + .toObject(RunningHistory::class.java) + } override suspend fun notifyRunningStart(uid: String, groupIdList: List) { - withContext(Dispatchers.IO) { - groupIdList.forEach { groupId -> - db.collection(GROUP_NOTIFICATIONS_COLLECTION) - .document(groupId) - .collection(START_NOTIFICATION) - .document(UUID.randomUUID().toString()) - .set( - StartNotification( - startedAt = System.currentTimeMillis(), - uid = uid - ) + groupIdList.forEach { groupId -> + db.collection(GROUP_NOTIFICATIONS_COLLECTION) + .document(groupId) + .collection(START_NOTIFICATION) + .document(UUID.randomUUID().toString()) + .set( + StartNotification( + startedAt = System.currentTimeMillis(), + uid = uid ) - } + ) } } @@ -113,19 +115,17 @@ class GroupNotificationDataSourceImpl @Inject constructor( runningHistory: RunningHistory, groupIdList: List ) { - withContext(Dispatchers.IO) { - groupIdList.forEach { groupId -> - db.collection(GROUP_NOTIFICATIONS_COLLECTION) - .document(groupId) - .collection(FINISH_NOTIFICATION) - .document(UUID.randomUUID().toString()) - .set( - FinishNotificationResponse( - uid = uid, - historyId = runningHistory.historyId - ) + groupIdList.forEach { groupId -> + db.collection(GROUP_NOTIFICATIONS_COLLECTION) + .document(groupId) + .collection(FINISH_NOTIFICATION) + .document(UUID.randomUUID().toString()) + .set( + FinishNotificationResponse( + uid = uid, + historyId = runningHistory.historyId ) - } + ) } } } diff --git a/data/src/main/java/com/whyranoid/data/running/RunnerDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/running/RunnerDataSourceImpl.kt index b61a34d6..7da4dc4c 100644 --- a/data/src/main/java/com/whyranoid/data/running/RunnerDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/running/RunnerDataSourceImpl.kt @@ -12,7 +12,7 @@ import kotlin.coroutines.resume class RunnerDataSourceImpl(private val db: FirebaseFirestore) : RunnerDataSource { override fun getCurrentRunnerCount(): Flow = callbackFlow { - db.collection(CollectionId.RUNNERS_COLLECTION) + val registration = db.collection(CollectionId.RUNNERS_COLLECTION) .document(CollectionId.RUNNERS_ID) .addSnapshotListener { snapshot, _ -> snapshot?.let { @@ -21,7 +21,9 @@ class RunnerDataSourceImpl(private val db: FirebaseFirestore) : RunnerDataSource } } - awaitClose() + awaitClose { + registration.remove() + } } override suspend fun startRunning(uid: String): Boolean { diff --git a/data/src/main/java/com/whyranoid/data/user/UserDataSource.kt b/data/src/main/java/com/whyranoid/data/user/UserDataSource.kt index 9a91da2a..2106834d 100644 --- a/data/src/main/java/com/whyranoid/data/user/UserDataSource.kt +++ b/data/src/main/java/com/whyranoid/data/user/UserDataSource.kt @@ -1,12 +1,11 @@ package com.whyranoid.data.user import com.whyranoid.domain.model.GroupInfo -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow interface UserDataSource { suspend fun getMyGroupList(uid: String): Result> - fun getMyGroupListFlow(uid: String, coroutineScope: CoroutineScope): Flow> + fun getMyGroupListFlow(uid: String): Flow> } diff --git a/data/src/main/java/com/whyranoid/data/user/UserDataSourceImpl.kt b/data/src/main/java/com/whyranoid/data/user/UserDataSourceImpl.kt index 6af3b6a5..e1c782eb 100644 --- a/data/src/main/java/com/whyranoid/data/user/UserDataSourceImpl.kt +++ b/data/src/main/java/com/whyranoid/data/user/UserDataSourceImpl.kt @@ -6,6 +6,7 @@ import com.whyranoid.data.constant.CollectionId.USERS_COLLECTION import com.whyranoid.data.constant.Exceptions.NO_GROUP_EXCEPTION import com.whyranoid.data.constant.Exceptions.NO_JOINED_GROUP_EXCEPTION import com.whyranoid.data.constant.Exceptions.NO_USER_EXCEPTION +import com.whyranoid.data.di.IODispatcher import com.whyranoid.data.model.GroupInfoResponse import com.whyranoid.data.model.UserResponse import com.whyranoid.data.model.toGroupInfo @@ -13,7 +14,9 @@ import com.whyranoid.data.model.toUser import com.whyranoid.domain.model.GroupInfo import com.whyranoid.domain.model.User import com.whyranoid.domain.model.toRule +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow @@ -22,7 +25,9 @@ import kotlinx.coroutines.tasks.await import javax.inject.Inject class UserDataSourceImpl @Inject constructor( - private val db: FirebaseFirestore + private val db: FirebaseFirestore, + @IODispatcher + private val dispatcher: CoroutineDispatcher ) : UserDataSource { override suspend fun getMyGroupList(uid: String): Result> { @@ -67,19 +72,18 @@ class UserDataSourceImpl @Inject constructor( // TODO: 예외처리 override fun getMyGroupListFlow( - uid: String, - coroutineScope: CoroutineScope + uid: String ): Flow> = callbackFlow { - db.collection(USERS_COLLECTION) + val coroutineScope = CoroutineScope(dispatcher) + val registration = db.collection(USERS_COLLECTION) .document(uid) .addSnapshotListener { documentSnapshot, _ -> val myGroupInfoList = mutableListOf() val joinedGroupList = documentSnapshot?.toObject(UserResponse::class.java)?.joinedGroupList - joinedGroupList?.forEach { groupId -> - - coroutineScope.launch { + coroutineScope.launch { + joinedGroupList?.forEach { groupId -> val groupInfo = getGroupInfo(groupId) myGroupInfoList.add(groupInfo) trySend(myGroupInfoList) @@ -87,7 +91,10 @@ class UserDataSourceImpl @Inject constructor( } } - awaitClose() + awaitClose { + coroutineScope.cancel() + registration.remove() + } } // TODO : 예외 처리 diff --git a/domain/src/main/java/com/whyranoid/domain/repository/GroupRepository.kt b/domain/src/main/java/com/whyranoid/domain/repository/GroupRepository.kt index 138c7f8a..15244e75 100644 --- a/domain/src/main/java/com/whyranoid/domain/repository/GroupRepository.kt +++ b/domain/src/main/java/com/whyranoid/domain/repository/GroupRepository.kt @@ -4,14 +4,13 @@ import com.whyranoid.domain.model.GroupInfo import com.whyranoid.domain.model.GroupNotification import com.whyranoid.domain.model.Rule import com.whyranoid.domain.model.RunningHistory -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow interface GroupRepository { suspend fun getMyGroupList(uid: String): Result> - fun getMyGroupListFlow(uid: String, coroutineScope: CoroutineScope): Flow> + fun getMyGroupListFlow(uid: String): Flow> // 그룹 정보 수정, 홍보 글 수정 suspend fun updateGroupInfo( diff --git a/domain/src/main/java/com/whyranoid/domain/usecase/GetMyGroupListUseCase.kt b/domain/src/main/java/com/whyranoid/domain/usecase/GetMyGroupListUseCase.kt index e69e7687..c4e04af5 100644 --- a/domain/src/main/java/com/whyranoid/domain/usecase/GetMyGroupListUseCase.kt +++ b/domain/src/main/java/com/whyranoid/domain/usecase/GetMyGroupListUseCase.kt @@ -3,7 +3,6 @@ package com.whyranoid.domain.usecase import com.whyranoid.domain.model.GroupInfo import com.whyranoid.domain.repository.AccountRepository import com.whyranoid.domain.repository.GroupRepository -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -11,8 +10,8 @@ class GetMyGroupListUseCase @Inject constructor( private val groupRepository: GroupRepository, private val accountRepository: AccountRepository ) { - suspend operator fun invoke(coroutineScope: CoroutineScope): Flow> { + suspend operator fun invoke(): Flow> { val uid = accountRepository.getUid() - return groupRepository.getMyGroupListFlow(uid, coroutineScope) + return groupRepository.getMyGroupListFlow(uid) } } diff --git a/presentation/src/main/java/com/whyranoid/presentation/community/CommunityViewModel.kt b/presentation/src/main/java/com/whyranoid/presentation/community/CommunityViewModel.kt index 82a2805d..9262ed9f 100644 --- a/presentation/src/main/java/com/whyranoid/presentation/community/CommunityViewModel.kt +++ b/presentation/src/main/java/com/whyranoid/presentation/community/CommunityViewModel.kt @@ -82,7 +82,7 @@ class CommunityViewModel @Inject constructor( init { viewModelScope.launch { - getMyGroupListUseCase(this).onEach { groupInfoList -> + getMyGroupListUseCase().onEach { groupInfoList -> _myGroupList.value = groupInfoList.map { groupInfo -> groupInfo.toGroupInfoUiModel() }