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

👍 セッションのリフレッシュ処理を追加 #103

Merged
merged 1 commit into from
Dec 2, 2023
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 @@ -5,15 +5,15 @@ import club.nito.core.data.ScheduleRepository
import club.nito.core.domain.GetParticipantScheduleListUseCase
import club.nito.core.domain.GetRecentScheduleUseCase
import club.nito.core.domain.ModifyPasswordUseCase
import club.nito.core.domain.ObserveAuthStatusUseCase
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.ParticipateUseCase
import club.nito.core.domain.LoginUseCase
import club.nito.core.domain.LogoutUseCase
import club.nito.core.network.auth.AuthRemoteDataSource
import club.nito.core.network.participation.ParticipantRemoteDataSource
import club.nito.core.network.schedule.ScheduleRemoteDataSource
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.gotrue.GoTrue
import io.github.jan.supabase.gotrue.Auth
import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertNotNull
Expand All @@ -26,7 +26,7 @@ class EntryPointTest {
// Check finer dependencies first to debug easily
assertNotNull(kmpEntryPoint.get<SupabaseClient>())
assertNotNull(kmpEntryPoint.get<Json>())
assertNotNull(kmpEntryPoint.get<GoTrue>())
assertNotNull(kmpEntryPoint.get<Auth>())

assertNotNull(kmpEntryPoint.get<AuthRemoteDataSource>())
assertNotNull(kmpEntryPoint.get<ScheduleRemoteDataSource>())
Expand All @@ -35,7 +35,7 @@ class EntryPointTest {
assertNotNull(kmpEntryPoint.get<AuthRepository>())
assertNotNull(kmpEntryPoint.get<ScheduleRepository>())

assertNotNull(kmpEntryPoint.get<ObserveAuthStatusUseCase>())
assertNotNull(kmpEntryPoint.get<AuthStatusStreamUseCase>())
assertNotNull(kmpEntryPoint.get<LoginUseCase>())
assertNotNull(kmpEntryPoint.get<ModifyPasswordUseCase>())
assertNotNull(kmpEntryPoint.get<LogoutUseCase>())
Expand Down
2 changes: 1 addition & 1 deletion app/ios/Modules/Sources/Auth/ComposeLoginScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public struct ComposeLoginScreen: UIViewControllerRepresentable {
public func makeUIViewController(context: Context) -> UIViewController {
return LoginScreen_iosKt.LoginRouteViewController(
viewModel: LoginScreenStateMachine(
observeAuthStatusUseCase: Container.shared.get(type: ObserveAuthStatusUseCase.self),
authStatusStream: Container.shared.get(type: AuthStatusStreamUseCase.self),
login: Container.shared.get(type: LoginUseCase.self),
userMessageStateHolder: Container.shared.get(type: UserMessageStateHolder.self)
),
Expand Down
4 changes: 2 additions & 2 deletions app/ios/Modules/Sources/Auth/LoginStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ enum LoginScreenEvent {
@MainActor
final class LoginStateMachine: ObservableObject {
@Dependency(\.loginUseCase) var loginUseCase
@Dependency(\.observeAuthStatusUseCase) var observeAuthStatusUseCase
@Dependency(\.authStatusStreamUseCase) var authStatusStream

@Published var state: LoginViewUIState = .init()
@Published var event: LoginScreenEvent? = nil
Expand All @@ -40,7 +40,7 @@ final class LoginStateMachine: ObservableObject {

loadTask = Task.detached { @MainActor in
do {
for try await authStatus in self.observeAuthStatusUseCase.execute() {
for try await authStatus in self.authStatusStream.execute() {
if case let status as FetchSingleResultSuccess<AuthStatus> = authStatus {
self.cachedAuthStatus = status.data
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Dependencies
import NitoKmp

public struct AuthStatusStreamUseCaseProvider {
private static var observeAuthStatusUseCase: AuthStatusStreamUseCase {
Container.shared.get(type: AuthStatusStreamUseCase.self)
Comment on lines +5 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

プロパティの名前が誤解を招く可能性があります。observeAuthStatusUseCaseAuthStatusStreamUseCaseのインスタンスを参照しているため、名前をauthStatusStreamUseCaseに変更することを検討してください。

- private static var observeAuthStatusUseCase: AuthStatusStreamUseCase {
+ private static var authStatusStreamUseCase: AuthStatusStreamUseCase {

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
private static var observeAuthStatusUseCase: AuthStatusStreamUseCase {
Container.shared.get(type: AuthStatusStreamUseCase.self)
private static var authStatusStreamUseCase: AuthStatusStreamUseCase {
Container.shared.get(type: AuthStatusStreamUseCase.self)

}

public let execute: () -> AsyncThrowingStream<AuthStatus, Error>
}

extension AuthStatusStreamUseCaseProvider: DependencyKey {
@MainActor
static public var liveValue: AuthStatusStreamUseCaseProvider =
AuthStatusStreamUseCaseProvider(
execute: {
observeAuthStatusUseCase.invoke().stream()
}
)
}

extension DependencyValues {
public var authStatusStreamUseCase: AuthStatusStreamUseCaseProvider {
get { self[AuthStatusStreamUseCaseProvider.self] }
set { self[AuthStatusStreamUseCaseProvider.self] = newValue }
}
}

This file was deleted.

9 changes: 4 additions & 5 deletions app/ios/Modules/Sources/Navigation/RootStateMachine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ enum Routing: Hashable {
class RootStateMachine: ObservableObject {
@Published var state: RootViewUIState = .init()
@Published var path: NavigationPath = .init()
@Dependency(\.observeAuthStatusUseCase) var observeAuthStatusUseCase
@Dependency(\.authStatusStreamUseCase) var authStatusStream

private var cachedAuthStatus: AuthStatus? {
didSet {
Expand All @@ -49,10 +49,9 @@ class RootStateMachine: ObservableObject {

loadTask = Task.detached { @MainActor in
do {
for try await authStatus in self.observeAuthStatusUseCase.execute() {
if case let status as FetchSingleResultSuccess<AuthStatus> = authStatus {
self.cachedAuthStatus = status.data
}
for try await authStatus in self.authStatusStream.execute() {
self.cachedAuthStatus = authStatus
print(authStatus)
}
} catch let error {
self.state.authStatus = .failed(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public struct ComposeSettingsScreen: UIViewControllerRepresentable {
public func makeUIViewController(context: Context) -> UIViewController {
return SettingsScreen_iosKt.SettingsScreenUIViewController(
stateMachine: SettingsScreenStateMachine(
observeAuthStatus: Container.shared.get(type: ObserveAuthStatusUseCase.self),
authStatusStream: Container.shared.get(type: AuthStatusStreamUseCase.self),
modifyPassword: Container.shared.get(type: ModifyPasswordUseCase.self),
logout: Container.shared.get(type: LogoutUseCase.self),
userMessageStateHolder: Container.shared.get(type: UserMessageStateHolder.self)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package club.nito.app.shared

import club.nito.core.domain.ObserveAuthStatusUseCase
import club.nito.core.model.FetchSingleResult
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.model.AuthStatus
import club.nito.core.ui.StateMachine
import club.nito.core.ui.buildUiState
import club.nito.core.ui.stateMachineScope
Expand All @@ -10,19 +10,24 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn

class NitoAppStateMachine(
observeAuthStatus: ObserveAuthStatusUseCase,
authStatusStream: AuthStatusStreamUseCase,
) : StateMachine() {
private val authStatus = observeAuthStatus().stateIn(
private val authStatus = authStatusStream().stateIn(
scope = stateMachineScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = FetchSingleResult.Loading,
initialValue = AuthStatus.Loading,
)

val uiState: StateFlow<NitoAppUiState> = buildUiState(authStatus) {
if (it !is FetchSingleResult.Success) {
return@buildUiState NitoAppUiState.Loading
}
when (it) {
AuthStatus.Loading -> NitoAppUiState.Loading

AuthStatus.NotAuthenticated,
is AuthStatus.Authenticated,
-> NitoAppUiState.Success(it)

NitoAppUiState.Success(it.data)
// TODO: 適切なハンドリングを行う
AuthStatus.NetworkError -> NitoAppUiState.Loading
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ private fun RouteBuilder.root(
),
),
)

else -> {}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.koin.dsl.module
val appModule = module {
factory {
NitoAppStateMachine(
observeAuthStatus = get(),
authStatusStream = get(),
)
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package club.nito.core.data

import club.nito.core.model.AuthStatus
import club.nito.core.model.FetchSingleResult
import club.nito.core.model.UserInfo
import kotlinx.coroutines.flow.Flow

Expand All @@ -12,7 +11,7 @@ public sealed interface AuthRepository {
/**
* 認証情報の状態
*/
public val authStatus: Flow<FetchSingleResult<AuthStatus>>
public val authStatus: Flow<AuthStatus>

/**
* ログインする
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package club.nito.core.data

import club.nito.core.model.AuthStatus
import club.nito.core.model.FetchSingleResult
import club.nito.core.model.UserInfo
import club.nito.core.network.auth.AuthRemoteDataSource
import kotlinx.coroutines.flow.Flow

public class DefaultAuthRepository(
private val remoteDataSource: AuthRemoteDataSource,
) : AuthRepository {
override val authStatus: Flow<FetchSingleResult<AuthStatus>> = remoteDataSource.authStatus
override val authStatus: Flow<AuthStatus> = remoteDataSource.authStatus

override suspend fun login(email: String, password: String): Unit = remoteDataSource.login(
email = email,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package club.nito.core.domain

import club.nito.core.data.AuthRepository
import club.nito.core.model.AuthStatus
import kotlinx.coroutines.flow.Flow

/**
* 認証状態を購読するユースケース
*/
public sealed interface AuthStatusStreamUseCase {
public operator fun invoke(): Flow<AuthStatus>
}

public class AuthStatusStreamExecutor(
private val authRepository: AuthRepository,
) : AuthStatusStreamUseCase {
override fun invoke(): Flow<AuthStatus> = authRepository.authStatus
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package club.nito.core.domain.di

import club.nito.core.domain.AuthStatusStreamExecutor
import club.nito.core.domain.AuthStatusStreamUseCase
import club.nito.core.domain.GetParticipantScheduleListExecutor
import club.nito.core.domain.GetParticipantScheduleListUseCase
import club.nito.core.domain.GetRecentScheduleExecutor
Expand All @@ -10,8 +12,6 @@ import club.nito.core.domain.LogoutExecutor
import club.nito.core.domain.LogoutUseCase
import club.nito.core.domain.ModifyPasswordExecutor
import club.nito.core.domain.ModifyPasswordUseCase
import club.nito.core.domain.ObserveAuthStatusExecutor
import club.nito.core.domain.ObserveAuthStatusUseCase
import club.nito.core.domain.ParticipateExecutor
import club.nito.core.domain.ParticipateUseCase
import org.koin.core.module.Module
Expand All @@ -20,7 +20,7 @@ import org.koin.dsl.bind
import org.koin.dsl.module

public val useCaseModule: Module = module {
singleOf(::ObserveAuthStatusExecutor) bind ObserveAuthStatusUseCase::class
singleOf(::AuthStatusStreamExecutor) bind AuthStatusStreamUseCase::class
singleOf(::LoginExecutor) bind LoginUseCase::class
singleOf(::ModifyPasswordExecutor) bind ModifyPasswordUseCase::class
singleOf(::LogoutExecutor) bind LogoutUseCase::class
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package club.nito.core.model

public sealed interface AuthStatus {
public data object Loading : AuthStatus

public data object NotAuthenticated : AuthStatus

public data class Authenticated(val session: UserSession) : AuthStatus

public data object NetworkError : AuthStatus
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package club.nito.core.network

import club.nito.core.model.ApiException
import club.nito.core.model.NitoError
import club.nito.core.network.auth.AuthRemoteDataSource
import io.ktor.client.network.sockets.SocketTimeoutException
import io.ktor.client.plugins.HttpRequestTimeoutException
import io.ktor.client.plugins.ResponseException
import io.ktor.util.cio.ChannelReadException
import kotlinx.coroutines.TimeoutCancellationException

public class NetworkService(
public val authRemoteDataSource: AuthRemoteDataSource,
) {
public suspend inline operator fun <reified T : Any> invoke(
block: () -> T,
): T = try {
authRemoteDataSource.authIfNeeded()
block()
} catch (e: Throwable) {
throw e.toNitoError()
}
}

public fun Throwable.toNitoError(): NitoError = when (this) {
is NitoError -> this

is ResponseException -> ApiException.ServerException(this)

is ChannelReadException -> ApiException.NetworkException(this)

is TimeoutCancellationException,
is HttpRequestTimeoutException,
is SocketTimeoutException,
-> ApiException.TimeoutException(this)

else -> ApiException.UnknownException(this)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package club.nito.core.network.auth

import club.nito.core.model.AuthStatus
import club.nito.core.model.FetchSingleResult
import club.nito.core.model.UserInfo
import kotlinx.coroutines.flow.Flow

public sealed interface AuthRemoteDataSource {
public val authStatus: Flow<FetchSingleResult<AuthStatus>>
public val authStatus: Flow<AuthStatus>

public suspend fun login(email: String, password: String)
public suspend fun logout()
public suspend fun modifyAuthUser(email: String?, password: String?): UserInfo
public suspend fun authIfNeeded()
}
Loading