Extract login and credentials verification logic into usecases

This commit is contained in:
Z. Charles Dziura 2024-10-31 09:12:44 -04:00
parent 511a36516b
commit fac5a55251
20 changed files with 149 additions and 73 deletions

View file

@ -8,8 +8,8 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import ing.bikeshedengineer.debtpirate.presentation.theme.DebtPirateTheme
import ing.bikeshedengineer.debtpirate.presentation.ui.auth.AuthScreen
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme
import ing.bikeshedengineer.debtpirate.auth.presentation.AuthScreen
import kotlinx.serialization.Serializable
@Serializable
@ -26,9 +26,7 @@ class MainActivity : ComponentActivity() {
DebtPirateTheme {
NavHost(navController = navController, startDestination = AuthRoute) {
composable<AuthRoute> {
AuthScreen()
}
composable<AuthRoute>() { AuthScreen() }
}
}
}

View file

@ -1,8 +1,8 @@
package ing.bikeshedengineer.debtpirate.data.remote.endpoint
package ing.bikeshedengineer.debtpirate.auth.data.remote.endpoint
import ing.bikeshedengineer.debtpirate.data.remote.model.ApiResponse
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST

View file

@ -1,3 +1,3 @@
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
package ing.bikeshedengineer.debtpirate.auth.data.remote.model
data class AuthLoginRequest(val username: String, val password: String)

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.data.remote.model.auth
package ing.bikeshedengineer.debtpirate.auth.data.remote.model
data class AuthLoginResponse(
val userId: Int,

View file

@ -0,0 +1,8 @@
package ing.bikeshedengineer.debtpirate.auth.data.remote.repository
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
interface AuthRepository {
suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse
}

View file

@ -0,0 +1,33 @@
package ing.bikeshedengineer.debtpirate.auth.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
import ing.bikeshedengineer.debtpirate.auth.domain.repository.AuthRepositoryImpl
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase
import retrofit2.Retrofit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AuthDiModule {
@Provides
@Singleton
fun provideAuthRepository(httpClient: Retrofit): AuthRepository {
return AuthRepositoryImpl(httpClient)
}
@Provides
@Singleton
fun provideSubmitLoginCredentialsUseCase(authRepository: AuthRepository): SubmitLoginCredentialsUseCase {
return SubmitLoginCredentialsUseCase(authRepository)
}
@Provides
@Singleton
fun provideValidateLoginCredentialsUseCase() = ValidateLoginCredentialsUseCase()
}

View file

@ -1,12 +1,12 @@
package ing.bikeshedengineer.debtpirate.domain.repository
package ing.bikeshedengineer.debtpirate.auth.domain.repository
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import ing.bikeshedengineer.debtpirate.data.remote.endpoint.AuthEndpoint
import ing.bikeshedengineer.debtpirate.data.remote.model.ApiResponse
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
import ing.bikeshedengineer.debtpirate.auth.data.remote.endpoint.AuthEndpoint
import ing.bikeshedengineer.debtpirate.data.remote.ApiResponse
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.Retrofit

View file

@ -0,0 +1,24 @@
package ing.bikeshedengineer.debtpirate.auth.domain.usecase
import android.util.Log
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginResponse
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
class SubmitLoginCredentialsUseCase(
private val authRepository: AuthRepository
) {
suspend operator fun invoke(username: String, password: String): AuthLoginResponse {
val credentials = AuthLoginRequest(username, password)
try {
val response = authRepository.submitLoginRequest(credentials)
Log.d("AuthScreen", "Login successful! $response")
return response
} catch (err: Throwable) {
// TODO...
Log.e("AuthScreen", err.message!!)
throw err
}
}
}

View file

@ -0,0 +1,24 @@
package ing.bikeshedengineer.debtpirate.auth.domain.usecase
import android.util.Log
sealed class LoginCredentialsValidationResult {
object EmptyCredentials : LoginCredentialsValidationResult()
object EmptyUsernameField : LoginCredentialsValidationResult()
object EmptyPasswordField : LoginCredentialsValidationResult()
object ValidCredentials : LoginCredentialsValidationResult()
}
class ValidateLoginCredentialsUseCase {
operator fun invoke(username: String, password: String): LoginCredentialsValidationResult {
return if (username.isEmpty() && password.isEmpty()) {
LoginCredentialsValidationResult.EmptyCredentials;
} else if (username.isEmpty() && password.isNotEmpty()) {
LoginCredentialsValidationResult.EmptyUsernameField;
} else if (username.isNotEmpty() && password.isEmpty()) {
LoginCredentialsValidationResult.EmptyPasswordField
} else {
LoginCredentialsValidationResult.ValidCredentials
}
}
}

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.presentation.ui.auth
package ing.bikeshedengineer.debtpirate.auth.presentation
import android.annotation.SuppressLint
import androidx.compose.foundation.background

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.presentation.ui.auth
package ing.bikeshedengineer.debtpirate.auth.presentation
import android.util.Log
import androidx.datastore.core.DataStore
@ -6,24 +6,41 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import ing.bikeshedengineer.debtpirate.PrefsDataStore
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.LoginCredentialsValidationResult
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase
import ing.bikeshedengineer.debtpirate.domain.model.Token
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
enum class InvalidField {
Username,
Password
}
sealed class FormState {
class Pristine : FormState()
class Valid : FormState()
class Invalid(reason: InvalidReason) : FormState()
}
enum class InvalidReason {
MissingUsernameValue,
MissingPasswordValue,
PasswordTooShort
}
@HiltViewModel
class AuthScreenViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val prefsStore: DataStore<PrefsDataStore>
private val prefsStore: DataStore<PrefsDataStore>,
private val submitLoginCredentials: SubmitLoginCredentialsUseCase,
private val validateLoginCredentials: ValidateLoginCredentialsUseCase,
) : ViewModel() {
private val tag: String
get() {
return javaClass.simpleName
}
// private val storeLoginData = StoreLoginDataUseCase(dataStore)
private val _username = MutableStateFlow("")
@ -41,16 +58,21 @@ class AuthScreenViewModel @Inject constructor(
}
fun submitLoginRequest() {
val credentials = AuthLoginRequest(this._username.value, this._password.value)
viewModelScope.launch {
try {
val response = authRepository.submitLoginRequest(credentials)
Log.i(tag, "Login successful! $response")
} catch (err: Throwable) {
when (validateLoginCredentials(username.value, password.value)) {
is LoginCredentialsValidationResult.ValidCredentials -> {
viewModelScope.launch {
try {
val result = submitLoginCredentials(username.value, password.value)
} catch (err: Throwable) {
// TODO...
}
}
}
else -> {
// TODO...
Log.e(tag, err.message!!)
}
}
}
fun storeAuthData(userId: Int, sessionToken: Token, authToken: Token) {

View file

@ -1,3 +1,3 @@
package ing.bikeshedengineer.debtpirate.data.remote.model
package ing.bikeshedengineer.debtpirate.data.remote
data class ApiResponse<T>(val data: T?, val error: String?)

View file

@ -1,4 +0,0 @@
package ing.bikeshedengineer.debtpirate.data.remote.client
class AuthEndpointClient {
}

View file

@ -1,8 +0,0 @@
package ing.bikeshedengineer.debtpirate.data.remote.repository
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginRequest
import ing.bikeshedengineer.debtpirate.data.remote.model.auth.AuthLoginResponse
interface AuthRepository {
suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse
}

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.di.modules
package ing.bikeshedengineer.debtpirate.di
import dagger.Module
import dagger.Provides

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.di.modules
package ing.bikeshedengineer.debtpirate.di
import android.app.Application
import androidx.datastore.core.DataStore

View file

@ -1,21 +0,0 @@
package ing.bikeshedengineer.debtpirate.di.modules
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository
import ing.bikeshedengineer.debtpirate.domain.repository.AuthRepositoryImpl
import retrofit2.Retrofit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object AuthRepositoryModule {
@Provides
@Singleton
fun provideAuthRepository(httpClient: Retrofit): AuthRepository {
return AuthRepositoryImpl(httpClient)
}
}

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.presentation.theme
package ing.bikeshedengineer.debtpirate.theme
import androidx.compose.ui.graphics.Color

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.presentation.theme
package ing.bikeshedengineer.debtpirate.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.presentation.theme
package ing.bikeshedengineer.debtpirate.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle