From 511a36516bfdc7a61d07a80148786750bf936ca1 Mon Sep 17 00:00:00 2001 From: "Z. Charles Dziura" Date: Sun, 27 Oct 2024 09:45:46 -0400 Subject: [PATCH] Handle login errors --- .../debtpirate/ExampleInstrumentedTest.kt | 6 ++-- app/app/src/main/AndroidManifest.xml | 3 +- .../debtpirate/app/host/MainActivity.kt | 4 +-- .../data/remote/endpoint/AuthEndpoint.kt | 2 +- .../data/remote/model/ApiResponse.kt | 2 +- .../remote/model/auth/AuthLoginResponse.kt | 6 +++- .../data/remote/repository/AuthRepository.kt | 3 +- .../di/modules/AuthRepositoryModule.kt | 4 --- .../debtpirate/di/modules/HttpClientModule.kt | 11 ++++---- .../debtpirate/domain/model/Token.kt | 5 +++- .../domain/repository/AuthRepositoryImpl.kt | 17 +++++++---- .../presentation/ui/auth/AuthScreen.kt | 5 ++-- .../ui/auth/AuthScreenViewModel.kt | 28 ++++++++++++++++++- app/app/src/main/res/values-night/themes.xml | 2 +- app/app/src/main/res/values/themes.xml | 2 +- .../main/res/xml/network_security_config.xml | 2 +- .../debtpirate/ExampleUnitTest.kt | 3 +- 17 files changed, 68 insertions(+), 37 deletions(-) diff --git a/app/app/src/androidTest/java/ing/bikeshedengineer/debtpirate/ExampleInstrumentedTest.kt b/app/app/src/androidTest/java/ing/bikeshedengineer/debtpirate/ExampleInstrumentedTest.kt index a217b9f..65e290e 100644 --- a/app/app/src/androidTest/java/ing/bikeshedengineer/debtpirate/ExampleInstrumentedTest.kt +++ b/app/app/src/androidTest/java/ing/bikeshedengineer/debtpirate/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package ing.bikeshedengineer.debtpirate -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml index fa37ffe..e51e3fa 100644 --- a/app/app/src/main/AndroidManifest.xml +++ b/app/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt index 2b20eff..1174b2e 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt @@ -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.ui.auth.AuthScreen import ing.bikeshedengineer.debtpirate.presentation.theme.DebtPirateTheme +import ing.bikeshedengineer.debtpirate.presentation.ui.auth.AuthScreen import kotlinx.serialization.Serializable @Serializable @@ -27,7 +27,7 @@ class MainActivity : ComponentActivity() { DebtPirateTheme { NavHost(navController = navController, startDestination = AuthRoute) { composable { - AuthScreen( ) + AuthScreen() } } } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt index 34d4fc4..0d8aa12 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/endpoint/AuthEndpoint.kt @@ -3,9 +3,9 @@ package ing.bikeshedengineer.debtpirate.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 retrofit2.http.POST import retrofit2.Response import retrofit2.http.Body +import retrofit2.http.POST interface AuthEndpoint { @POST("auth/login") diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/ApiResponse.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/ApiResponse.kt index 3d144b2..1d9ece9 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/ApiResponse.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/ApiResponse.kt @@ -1,3 +1,3 @@ package ing.bikeshedengineer.debtpirate.data.remote.model -data class ApiResponse(val data: T?, val err: Any?) +data class ApiResponse(val data: T?, val error: String?) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginResponse.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginResponse.kt index d4ac892..e46d2fc 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginResponse.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/model/auth/AuthLoginResponse.kt @@ -1,5 +1,9 @@ package ing.bikeshedengineer.debtpirate.data.remote.model.auth -data class AuthLoginResponse(val userId: Int, val session: AuthLoginTokenData, val auth: AuthLoginTokenData) +data class AuthLoginResponse( + val userId: Int, + val session: AuthLoginTokenData, + val auth: AuthLoginTokenData +) data class AuthLoginTokenData(val token: String, val expiresAt: String) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/repository/AuthRepository.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/repository/AuthRepository.kt index 29d7202..cb935e2 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/repository/AuthRepository.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/data/remote/repository/AuthRepository.kt @@ -1,9 +1,8 @@ package ing.bikeshedengineer.debtpirate.data.remote.repository -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 interface AuthRepository { - suspend fun submitLoginRequest(credentials: AuthLoginRequest): Result + suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/AuthRepositoryModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/AuthRepositoryModule.kt index fbbbbc8..d0fc816 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/AuthRepositoryModule.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/AuthRepositoryModule.kt @@ -4,13 +4,9 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import ing.bikeshedengineer.debtpirate.BuildConfig import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository import ing.bikeshedengineer.debtpirate.domain.repository.AuthRepositoryImpl -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Singleton @Module diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/HttpClientModule.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/HttpClientModule.kt index c019925..eca8a2f 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/HttpClientModule.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/di/modules/HttpClientModule.kt @@ -21,11 +21,12 @@ object HttpClientModule { return Retrofit.Builder() .baseUrl(BuildConfig.API_BASE_URL) .addConverterFactory(GsonConverterFactory.create()) - .client(OkHttpClient.Builder() - .addInterceptor(HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BODY - }) - .build() + .client( + OkHttpClient.Builder() + .addInterceptor(HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + }) + .build() ) .build() } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/model/Token.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/model/Token.kt index e585916..5274f31 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/model/Token.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/model/Token.kt @@ -4,5 +4,8 @@ import java.time.ZonedDateTime import java.time.format.DateTimeFormatter.ISO_INSTANT data class Token(val token: String, val expiresAt: ZonedDateTime) { - constructor(token: String, expiresAtStr: String): this(token, ZonedDateTime.parse(expiresAtStr, ISO_INSTANT)) + constructor(token: String, expiresAtStr: String) : this( + token, + ZonedDateTime.parse(expiresAtStr, ISO_INSTANT) + ) } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt index 4c1ed7a..6e491aa 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/domain/repository/AuthRepositoryImpl.kt @@ -1,5 +1,7 @@ package ing.bikeshedengineer.debtpirate.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 @@ -8,21 +10,24 @@ import ing.bikeshedengineer.debtpirate.data.remote.repository.AuthRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import retrofit2.Retrofit -import kotlin.Result -class AuthRepositoryImpl constructor(private val httpClient: Retrofit): AuthRepository { +class AuthRepositoryImpl(private val httpClient: Retrofit) : AuthRepository { private val authEndpoint: AuthEndpoint = this.httpClient.create(AuthEndpoint::class.java) - override suspend fun submitLoginRequest(credentials: AuthLoginRequest): Result { + override suspend fun submitLoginRequest(credentials: AuthLoginRequest): AuthLoginResponse { return withContext(Dispatchers.IO) { val response = authEndpoint.submitLoginRequest(credentials) if (response.isSuccessful) { val body = response.body() - return@withContext Result.success(body?.data!!) + return@withContext body!!.data!! } else { - val error = response.errorBody() - return@withContext Result.failure(Throwable(error.toString())) + val gson = Gson() + val errorType = object : TypeToken>() {}.type + val body = + gson.fromJson>(response.errorBody()!!.charStream(), errorType) + + throw Throwable(body!!.error!!) } } } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreen.kt index c58dda1..2a267e3 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreen.kt @@ -94,7 +94,7 @@ private fun LoginComponent( leadingIcon = { Icon(Icons.Outlined.Person, "person") }, singleLine = true, keyboardOptions = KeyboardOptions( - capitalization = KeyboardCapitalization.Words, + capitalization = KeyboardCapitalization.None, keyboardType = KeyboardType.Text, imeAction = ImeAction.Next ), @@ -117,7 +117,8 @@ private fun LoginComponent( onSend = { submitLoginRequest() } ), onValueChange = onUpdatePassword, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .padding(PaddingValues(top = 4.dp)) ) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreenViewModel.kt index 86376e1..cd7c64f 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/presentation/ui/auth/AuthScreenViewModel.kt @@ -1,18 +1,24 @@ package ing.bikeshedengineer.debtpirate.presentation.ui.auth import android.util.Log +import androidx.datastore.core.DataStore 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.domain.model.Token import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class AuthScreenViewModel @Inject constructor(private val authRepository: AuthRepository) : ViewModel() { +class AuthScreenViewModel @Inject constructor( + private val authRepository: AuthRepository, + private val prefsStore: DataStore +) : ViewModel() { private val tag: String get() { return javaClass.simpleName @@ -46,4 +52,24 @@ class AuthScreenViewModel @Inject constructor(private val authRepository: AuthRe } } } + + fun storeAuthData(userId: Int, sessionToken: Token, authToken: Token) { + viewModelScope.launch { + prefsStore.updateData { currentPrefs -> + val updatedSessionToken = currentPrefs.sessionToken.toBuilder() + .setToken(sessionToken.token) + .setExpiresAt(sessionToken.expiresAt.toEpochSecond()) + + val updatedAuthToken = currentPrefs.authToken.toBuilder() + .setToken(authToken.token) + .setExpiresAt(authToken.expiresAt.toEpochSecond()) + + currentPrefs.toBuilder() + .setUserId(userId) + .setSessionToken(updatedSessionToken) + .setAuthToken(updatedAuthToken) + .build() + } + } + } } \ No newline at end of file diff --git a/app/app/src/main/res/values-night/themes.xml b/app/app/src/main/res/values-night/themes.xml index 06112d4..4e8409a 100644 --- a/app/app/src/main/res/values-night/themes.xml +++ b/app/app/src/main/res/values-night/themes.xml @@ -1,4 +1,4 @@ - +