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

♻️ スケジュール詳細画面のリファクタリング #173

Merged
merged 1 commit into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package club.nito.ios.combined
import club.nito.core.data.AuthRepository
import club.nito.core.data.ScheduleRepository
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.FetchMyParticipantStatusUseCase
import club.nito.core.domain.MyParticipantStatusStreamUseCase
import club.nito.core.domain.FetchParticipantScheduleByIdUseCase
import club.nito.core.domain.GetParticipantScheduleListUseCase
import club.nito.core.domain.GetRecentScheduleUseCase
Expand Down Expand Up @@ -45,6 +45,6 @@ class EntryPointTest {
assertNotNull(kmpEntryPoint.get<GetRecentScheduleUseCase>())
assertNotNull(kmpEntryPoint.get<GetParticipantScheduleListUseCase>())
assertNotNull(kmpEntryPoint.get<ParticipateUseCase>())
assertNotNull(kmpEntryPoint.get<FetchMyParticipantStatusUseCase>())
assertNotNull(kmpEntryPoint.get<MyParticipantStatusStreamUseCase>())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@ public struct ComposeScheduleDetailScreen: UIViewControllerRepresentable {
return ScheduleDetailScreen_iosKt.ScheduleDetailRouteViewController(
id: scheduleId,
stateMachine: ScheduleDetailStateMachine(
id: scheduleId,
fetchParticipantScheduleById: Container.shared.get(
type: FetchParticipantScheduleByIdUseCase.self
),
fetchMyParticipantStatus: Container.shared.get(
type: FetchMyParticipantStatusUseCase.self
),
scheduleId: scheduleId,
scheduleStream: Container.shared.get(type: ScheduleStreamUseCase.self),
scheduleParticipantsStream: Container.shared.get(type: ScheduleParticipantsStreamUseCase.self),
myParticipantStatusStream: Container.shared.get(type: MyParticipantStatusStreamUseCase.self),
participate: Container.shared.get(type: ParticipateUseCase.self),
userMessageStateHolder: Container.shared.get(type: UserMessageStateHolder.self),
dateTimeFormatter: Container.shared.get(type: CommonNitoDateFormatter.self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,29 @@ public class DefaultParticipantRepository(
override suspend fun fetchParticipantStatus(scheduleId: ScheduleId, userId: String): ParticipantStatus =
remoteDataSource.fetchParticipantStatus(scheduleId = scheduleId, userId = userId)

override suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant {
val participant = remoteDataSource.insertParticipate(declaration = declaration)
dao.upsert(participant)
return participant
}
override fun participantStatusStream(scheduleId: ScheduleId, userId: String): Flow<ParticipantStatus> =
dao.participantStatusStream(
scheduleId = scheduleId,
userId = userId,
)

override suspend fun upsertLocalParticipate(participant: Participant): Unit = dao.upsert(participant)

override suspend fun upsertParticipate(participant: Participant): Participant = remoteDataSource
.upsertParticipate(participant = participant)
.also {
dao.upsert(it)
}

override suspend fun updateParticipate(declaration: ParticipantDeclaration): Participant {
dao.upsert(
entity = Participant(
scheduleId = declaration.scheduleId,
userId = declaration.userId,
status = declaration.status,
),
)

val participant = remoteDataSource.updateParticipate(declaration = declaration)
dao.upsert(participant)
return participant
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,28 @@ public sealed interface ParticipantRepository {
*/
public suspend fun fetchParticipantStatus(scheduleId: ScheduleId, userId: String): ParticipantStatus

/**
* 該当の予定の対象のユーザーの参加情報を取得する
*
* @param scheduleId 参加情報を取得するスケジュールID
* @param userId 対象のユーザーID
* @return 参加情報
*/
public fun participantStatusStream(scheduleId: ScheduleId, userId: String): Flow<ParticipantStatus>

/**
* 該当スケジュールへの参加状況を追加する
*
* @param declaration 参加表明データ
* @param participant 参加表明データ
*/
public suspend fun upsertLocalParticipate(participant: Participant)

/**
* 該当スケジュールへの参加状況を追加する
*
* @param participant 参加表明データ
*/
public suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant
public suspend fun upsertParticipate(participant: Participant): Participant

/**
* 該当スケジュールへの参加状況を更新する
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package club.nito.core.domain

import club.nito.core.data.AuthRepository
import club.nito.core.data.ParticipantRepository
import club.nito.core.model.participant.ParticipantStatus
import club.nito.core.model.schedule.ScheduleId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

/**
* 自身の参加状況を取得するユースケース
*/
public sealed interface MyParticipantStatusStreamUseCase {
public operator fun invoke(id: ScheduleId): Flow<ParticipantStatus>
}

public class FetchMyParticipantStatusExecutor(
private val authRepository: AuthRepository,
private val participantRepository: ParticipantRepository,
) : MyParticipantStatusStreamUseCase {
override fun invoke(id: ScheduleId): Flow<ParticipantStatus> = flow {
val currentUserId = authRepository.currentUser().id
participantRepository.participantStatusStream(
scheduleId = id,
userId = currentUserId,
).collect {
emit(it)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,66 @@ import club.nito.core.data.AuthRepository
import club.nito.core.data.ParticipantRepository
import club.nito.core.model.ExecuteResult
import club.nito.core.model.participant.Participant
import club.nito.core.model.participant.ParticipantDeclaration
import club.nito.core.model.participant.ParticipantStatus
import club.nito.core.model.runExecuting
import club.nito.core.model.toNitoError
import kotlin.coroutines.cancellation.CancellationException

/**
* 参加表明するユースケース
*/
public sealed interface ParticipateUseCase {
public suspend operator fun invoke(scheduleId: String, status: ParticipantStatus): ExecuteResult<Participant>
public suspend operator fun invoke(
scheduleId: String,
oldStatus: ParticipantStatus,
newStatus: ParticipantStatus,
): ExecuteResult<Participant>
}

public class ParticipateExecutor(
private val authRepository: AuthRepository,
private val participantRepository: ParticipantRepository,
) : ParticipateUseCase {
override suspend fun invoke(scheduleId: String, status: ParticipantStatus): ExecuteResult<Participant> = runExecuting {
override suspend fun invoke(
scheduleId: String,
oldStatus: ParticipantStatus,
newStatus: ParticipantStatus,
): ExecuteResult<Participant> {
val currentUserId = authRepository.currentUser().id
val exist = participantRepository.existParticipantByUserId(

// NOTE: 失敗時の復元用キャッシュ
val cachedParticipant = Participant(
scheduleId = scheduleId,
userId = currentUserId,
status = oldStatus,
)

if (exist) {
participantRepository.updateParticipate(
declaration = ParticipantDeclaration(
scheduleId = scheduleId,
userId = currentUserId,
status = status,
),
)
} else {
participantRepository.insertParticipate(
declaration = ParticipantDeclaration(
// NOTE: 成功可否に関わらず一旦選択した状態を反映する
participantRepository.upsertLocalParticipate(
participant = Participant(
scheduleId = scheduleId,
userId = currentUserId,
status = newStatus,
),
)

try {
val participant = participantRepository.upsertParticipate(
participant = Participant(
scheduleId = scheduleId,
userId = currentUserId,
status = status,
status = newStatus,
),
)
return ExecuteResult.Success(data = participant)
} catch (e: Exception) {
when (e) {
is CancellationException -> throw e
else -> {
// NOTE: 失敗時はキャッシュを復元する
participantRepository.upsertLocalParticipate(participant = cachedParticipant)
return ExecuteResult.Failure(error = e.toNitoError())
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package club.nito.core.domain

import club.nito.core.data.ParticipantRepository
import club.nito.core.model.FetchMultipleContentResult
import club.nito.core.model.participant.ParticipantUser
import club.nito.core.model.schedule.ScheduleId
import club.nito.core.model.toNitoError
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlin.coroutines.cancellation.CancellationException

/**
* 該当スケジュールの参加者情報のストリームを取得するユースケース
*/
public sealed interface ScheduleParticipantsStreamUseCase {
public operator fun invoke(id: ScheduleId): Flow<FetchMultipleContentResult<ParticipantUser>>
}

public class ScheduleParticipantsStreamExecutor(
private val participantRepository: ParticipantRepository,
) : ScheduleParticipantsStreamUseCase {
override fun invoke(id: ScheduleId): Flow<FetchMultipleContentResult<ParticipantUser>> {
return participantRepository
.participantUsersStream(scheduleId = id)
.map {
if (it.isEmpty()) {
return@map FetchMultipleContentResult.NoContent
}

FetchMultipleContentResult.Success(it)
}
.catch { e ->
if (e is CancellationException) {
throw e
}

emit(FetchMultipleContentResult.Failure(e.toNitoError()))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package club.nito.core.domain

import club.nito.core.data.ScheduleRepository
import club.nito.core.model.FetchSingleContentResult
import club.nito.core.model.schedule.ScheduleId
import club.nito.core.model.schedule.ScheduleWithPlace
import club.nito.core.model.toNitoError
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlin.coroutines.cancellation.CancellationException

/**
* スケジュールを取得するユースケース
*/
public sealed interface ScheduleStreamUseCase {
public operator fun invoke(id: ScheduleId): Flow<FetchSingleContentResult<ScheduleWithPlace>>
}

public class ScheduleStreamExecutor(
private val scheduleRepository: ScheduleRepository,
) : ScheduleStreamUseCase {
override fun invoke(id: ScheduleId): Flow<FetchSingleContentResult<ScheduleWithPlace>> {
return scheduleRepository
.scheduleWithPlaceStream(id = id)
.map {
it?.let { FetchSingleContentResult.Success(it) }
?: FetchSingleContentResult.NoContent
}
.catch { e ->
if (e is CancellationException) {
throw e
}

emit(FetchSingleContentResult.Failure(e.toNitoError()))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package club.nito.core.domain.di
import club.nito.core.domain.AuthStatusStreamExecutor
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.FetchMyParticipantStatusExecutor
import club.nito.core.domain.FetchMyParticipantStatusUseCase
import club.nito.core.domain.MyParticipantStatusStreamUseCase
import club.nito.core.domain.FetchParticipantScheduleByIdExecutor
import club.nito.core.domain.FetchParticipantScheduleByIdUseCase
import club.nito.core.domain.GetParticipantScheduleListExecutor
Expand All @@ -18,6 +18,10 @@ import club.nito.core.domain.ModifyPasswordExecutor
import club.nito.core.domain.ModifyPasswordUseCase
import club.nito.core.domain.ParticipateExecutor
import club.nito.core.domain.ParticipateUseCase
import club.nito.core.domain.ScheduleParticipantsStreamExecutor
import club.nito.core.domain.ScheduleParticipantsStreamUseCase
import club.nito.core.domain.ScheduleStreamExecutor
import club.nito.core.domain.ScheduleStreamUseCase
import org.koin.core.module.Module
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.bind
Expand All @@ -29,8 +33,10 @@ public val useCaseModule: Module = module {
singleOf(::ModifyPasswordExecutor) bind ModifyPasswordUseCase::class
singleOf(::LogoutExecutor) bind LogoutUseCase::class
singleOf(::FetchParticipantScheduleByIdExecutor) bind FetchParticipantScheduleByIdUseCase::class
singleOf(::ScheduleParticipantsStreamExecutor) bind ScheduleParticipantsStreamUseCase::class
singleOf(::ScheduleStreamExecutor) bind ScheduleStreamUseCase::class
singleOf(::GetRecentScheduleExecutor) bind GetRecentScheduleUseCase::class
singleOf(::GetParticipantScheduleListExecutor) bind GetParticipantScheduleListUseCase::class
singleOf(::ParticipateExecutor) bind ParticipateUseCase::class
singleOf(::FetchMyParticipantStatusExecutor) bind FetchMyParticipantStatusUseCase::class
singleOf(::FetchMyParticipantStatusExecutor) bind MyParticipantStatusStreamUseCase::class
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public data object FakeParticipantRemoteDataSource : ParticipantRemoteDataSource
return ParticipantStatus.ATTENDANCE
}

override suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant {
override suspend fun upsertParticipate(participant: Participant): Participant {
delay(1000)
return createFakeNetworkParticipant(
scheduleId = declaration.scheduleId,
userId = declaration.userId,
status = declaration.status.toNetworkModel(),
scheduleId = participant.scheduleId,
userId = participant.userId,
status = participant.status.toNetworkModel(),
).toParticipant()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ public sealed interface ParticipantRemoteDataSource {
/**
* 該当スケジュールへの参加状況を追加する
*
* @param declaration 参加表明データ
* @param participant 参加表明データ
*/
public suspend fun insertParticipate(declaration: ParticipantDeclaration): Participant
public suspend fun upsertParticipate(participant: Participant): Participant

/**
* 該当スケジュールへの参加状況を更新する
Expand Down
Loading
Loading