From 8fee0c3a3db3766622175a80a31ac08d77630254 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 3 Apr 2022 17:21:18 +0500 Subject: [PATCH 1/4] Reorganize code --- .../java/gq/kirmanak/mealient/data/AppDb.kt | 2 +- .../data/auth/impl/AuthDataSourceImpl.kt | 4 +-- .../data/auth/impl/AuthStorageImpl.kt | 4 +-- .../data/disclaimer/DisclaimerStorageImpl.kt | 2 +- .../data/{impl => network}/ErrorDetail.kt | 2 +- .../data/{impl => network}/OkHttpBuilder.kt | 2 +- .../data/{impl => network}/RetrofitBuilder.kt | 2 +- .../data/network/RetrofitServiceFactory.kt | 1 - .../data/recipes/db/RecipeStorageImpl.kt | 8 +++--- .../java/gq/kirmanak/mealient/di/AppModule.kt | 15 ----------- .../gq/kirmanak/mealient/di/AuthModule.kt | 5 +--- .../gq/kirmanak/mealient/di/NetworkModule.kt | 27 +++++++++++++++++++ .../gq/kirmanak/mealient/di/RecipeModule.kt | 2 +- .../util => extensions}/NetworkExtensions.kt | 2 +- .../RemoteToLocalMappings.kt | 2 +- .../util => extensions}/RoomTypeConverters.kt | 2 +- .../SharedPrefExtensions.kt | 2 +- .../data/auth/impl/AuthDataSourceImplTest.kt | 4 +-- .../data/impl/RoomTypeConvertersTest.kt | 2 +- 19 files changed, 49 insertions(+), 41 deletions(-) rename app/src/main/java/gq/kirmanak/mealient/data/{impl => network}/ErrorDetail.kt (80%) rename app/src/main/java/gq/kirmanak/mealient/data/{impl => network}/OkHttpBuilder.kt (93%) rename app/src/main/java/gq/kirmanak/mealient/data/{impl => network}/RetrofitBuilder.kt (95%) create mode 100644 app/src/main/java/gq/kirmanak/mealient/di/NetworkModule.kt rename app/src/main/java/gq/kirmanak/mealient/{data/impl/util => extensions}/NetworkExtensions.kt (93%) rename app/src/main/java/gq/kirmanak/mealient/{data/impl/util => extensions}/RemoteToLocalMappings.kt (97%) rename app/src/main/java/gq/kirmanak/mealient/{data/impl/util => extensions}/RoomTypeConverters.kt (93%) rename app/src/main/java/gq/kirmanak/mealient/{data/impl/util => extensions}/SharedPrefExtensions.kt (97%) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/AppDb.kt b/app/src/main/java/gq/kirmanak/mealient/data/AppDb.kt index 9a6aab4..e08b8e6 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/AppDb.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/AppDb.kt @@ -3,9 +3,9 @@ package gq.kirmanak.mealient.data import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters -import gq.kirmanak.mealient.data.impl.util.RoomTypeConverters import gq.kirmanak.mealient.data.recipes.db.RecipeDao import gq.kirmanak.mealient.data.recipes.db.entity.* +import gq.kirmanak.mealient.extensions.RoomTypeConverters @Database( version = 1, diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt index c587045..a25fcde 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt @@ -2,9 +2,9 @@ package gq.kirmanak.mealient.data.auth.impl import gq.kirmanak.mealient.data.auth.AuthDataSource import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.* -import gq.kirmanak.mealient.data.impl.ErrorDetail -import gq.kirmanak.mealient.data.impl.util.decodeErrorBodyOrNull +import gq.kirmanak.mealient.data.network.ErrorDetail import gq.kirmanak.mealient.data.network.ServiceFactory +import gq.kirmanak.mealient.extensions.decodeErrorBodyOrNull import kotlinx.coroutines.CancellationException import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt index 5399314..9bfc7b8 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt @@ -3,8 +3,8 @@ package gq.kirmanak.mealient.data.auth.impl import android.content.SharedPreferences import androidx.core.content.edit import gq.kirmanak.mealient.data.auth.AuthStorage -import gq.kirmanak.mealient.data.impl.util.changesFlow -import gq.kirmanak.mealient.data.impl.util.getStringOrNull +import gq.kirmanak.mealient.extensions.changesFlow +import gq.kirmanak.mealient.extensions.getStringOrNull import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import timber.log.Timber diff --git a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt index b979be8..823a977 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.data.disclaimer import android.content.SharedPreferences -import gq.kirmanak.mealient.data.impl.util.getBooleanOrFalse +import gq.kirmanak.mealient.extensions.getBooleanOrFalse import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/gq/kirmanak/mealient/data/impl/ErrorDetail.kt b/app/src/main/java/gq/kirmanak/mealient/data/network/ErrorDetail.kt similarity index 80% rename from app/src/main/java/gq/kirmanak/mealient/data/impl/ErrorDetail.kt rename to app/src/main/java/gq/kirmanak/mealient/data/network/ErrorDetail.kt index c7a1df0..674de44 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/impl/ErrorDetail.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/network/ErrorDetail.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.impl +package gq.kirmanak.mealient.data.network import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/app/src/main/java/gq/kirmanak/mealient/data/impl/OkHttpBuilder.kt b/app/src/main/java/gq/kirmanak/mealient/data/network/OkHttpBuilder.kt similarity index 93% rename from app/src/main/java/gq/kirmanak/mealient/data/impl/OkHttpBuilder.kt rename to app/src/main/java/gq/kirmanak/mealient/data/network/OkHttpBuilder.kt index df5d1bf..feae5e9 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/impl/OkHttpBuilder.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/network/OkHttpBuilder.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.impl +package gq.kirmanak.mealient.data.network import okhttp3.Interceptor import okhttp3.OkHttpClient diff --git a/app/src/main/java/gq/kirmanak/mealient/data/impl/RetrofitBuilder.kt b/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt similarity index 95% rename from app/src/main/java/gq/kirmanak/mealient/data/impl/RetrofitBuilder.kt rename to app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt index a22242a..0c5ae74 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/impl/RetrofitBuilder.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.impl +package gq.kirmanak.mealient.data.network import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.serialization.ExperimentalSerializationApi diff --git a/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitServiceFactory.kt b/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitServiceFactory.kt index 17e973f..b08ed87 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitServiceFactory.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitServiceFactory.kt @@ -1,6 +1,5 @@ package gq.kirmanak.mealient.data.network -import gq.kirmanak.mealient.data.impl.RetrofitBuilder import timber.log.Timber inline fun RetrofitBuilder.createServiceFactory() = diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index 2a55d28..be0b13c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -3,14 +3,14 @@ package gq.kirmanak.mealient.data.recipes.db import androidx.paging.PagingSource import androidx.room.withTransaction import gq.kirmanak.mealient.data.AppDb -import gq.kirmanak.mealient.data.impl.util.recipeEntity -import gq.kirmanak.mealient.data.impl.util.toRecipeEntity -import gq.kirmanak.mealient.data.impl.util.toRecipeIngredientEntity -import gq.kirmanak.mealient.data.impl.util.toRecipeInstructionEntity import gq.kirmanak.mealient.data.recipes.db.entity.* import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeResponse import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse +import gq.kirmanak.mealient.extensions.recipeEntity +import gq.kirmanak.mealient.extensions.toRecipeEntity +import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity +import gq.kirmanak.mealient.extensions.toRecipeInstructionEntity import timber.log.Timber import javax.inject.Inject diff --git a/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt index e3de864..3418f3e 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt @@ -10,9 +10,6 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import gq.kirmanak.mealient.data.AppDb -import gq.kirmanak.mealient.data.impl.OkHttpBuilder -import kotlinx.serialization.json.Json -import okhttp3.OkHttpClient import javax.inject.Singleton @Module @@ -27,16 +24,4 @@ object AppModule { @Singleton fun createSharedPreferences(@ApplicationContext context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - - @Provides - @Singleton - fun createOkHttp(okHttpBuilder: OkHttpBuilder): OkHttpClient = - okHttpBuilder.buildOkHttp() - - @Provides - @Singleton - fun createJson(): Json = Json { - coerceInputValues = true - ignoreUnknownKeys = true - } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt index 4c1a274..2ddd619 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/AuthModule.kt @@ -1,12 +1,9 @@ package gq.kirmanak.mealient.di -import android.accounts.AccountManager -import android.content.Context import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import gq.kirmanak.mealient.data.auth.AuthDataSource import gq.kirmanak.mealient.data.auth.AuthRepo @@ -15,7 +12,7 @@ import gq.kirmanak.mealient.data.auth.impl.AuthDataSourceImpl import gq.kirmanak.mealient.data.auth.impl.AuthRepoImpl import gq.kirmanak.mealient.data.auth.impl.AuthService import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl -import gq.kirmanak.mealient.data.impl.RetrofitBuilder +import gq.kirmanak.mealient.data.network.RetrofitBuilder import gq.kirmanak.mealient.data.network.ServiceFactory import gq.kirmanak.mealient.data.network.createServiceFactory import javax.inject.Singleton diff --git a/app/src/main/java/gq/kirmanak/mealient/di/NetworkModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/NetworkModule.kt new file mode 100644 index 0000000..80de298 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/di/NetworkModule.kt @@ -0,0 +1,27 @@ +package gq.kirmanak.mealient.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import gq.kirmanak.mealient.data.network.OkHttpBuilder +import kotlinx.serialization.json.Json +import okhttp3.OkHttpClient +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + @Provides + @Singleton + fun createOkHttp(okHttpBuilder: OkHttpBuilder): OkHttpClient = + okHttpBuilder.buildOkHttp() + + @Provides + @Singleton + fun createJson(): Json = Json { + coerceInputValues = true + ignoreUnknownKeys = true + } +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt index a01c838..21699a6 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/RecipeModule.kt @@ -6,7 +6,7 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import gq.kirmanak.mealient.data.impl.RetrofitBuilder +import gq.kirmanak.mealient.data.network.RetrofitBuilder import gq.kirmanak.mealient.data.network.ServiceFactory import gq.kirmanak.mealient.data.network.createServiceFactory import gq.kirmanak.mealient.data.recipes.RecipeImageLoader diff --git a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/NetworkExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/NetworkExtensions.kt similarity index 93% rename from app/src/main/java/gq/kirmanak/mealient/data/impl/util/NetworkExtensions.kt rename to app/src/main/java/gq/kirmanak/mealient/extensions/NetworkExtensions.kt index c226ace..3de9dd7 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/NetworkExtensions.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/NetworkExtensions.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.impl.util +package gq.kirmanak.mealient.extensions import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json diff --git a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt similarity index 97% rename from app/src/main/java/gq/kirmanak/mealient/data/impl/util/RemoteToLocalMappings.kt rename to app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index fb8a4be..8a0bafa 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.impl.util +package gq.kirmanak.mealient.extensions import gq.kirmanak.mealient.data.recipes.db.entity.RecipeEntity import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity diff --git a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/RoomTypeConverters.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RoomTypeConverters.kt similarity index 93% rename from app/src/main/java/gq/kirmanak/mealient/data/impl/util/RoomTypeConverters.kt rename to app/src/main/java/gq/kirmanak/mealient/extensions/RoomTypeConverters.kt index 244fd15..abfcfbb 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/RoomTypeConverters.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RoomTypeConverters.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.impl.util +package gq.kirmanak.mealient.extensions import androidx.room.TypeConverter import kotlinx.datetime.* diff --git a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/SharedPrefExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/SharedPrefExtensions.kt similarity index 97% rename from app/src/main/java/gq/kirmanak/mealient/data/impl/util/SharedPrefExtensions.kt rename to app/src/main/java/gq/kirmanak/mealient/extensions/SharedPrefExtensions.kt index 46b5df5..edd88ca 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/impl/util/SharedPrefExtensions.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/SharedPrefExtensions.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.data.impl.util +package gq.kirmanak.mealient.extensions import android.content.SharedPreferences import kotlinx.coroutines.Dispatchers diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImplTest.kt index 8c51447..ca8869e 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImplTest.kt @@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.auth.impl import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.* import gq.kirmanak.mealient.data.network.ServiceFactory -import gq.kirmanak.mealient.di.AppModule +import gq.kirmanak.mealient.di.NetworkModule import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN @@ -33,7 +33,7 @@ class AuthDataSourceImplTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = AuthDataSourceImpl(authServiceFactory, AppModule.createJson()) + subject = AuthDataSourceImpl(authServiceFactory, NetworkModule.createJson()) } @Test diff --git a/app/src/test/java/gq/kirmanak/mealient/data/impl/RoomTypeConvertersTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/impl/RoomTypeConvertersTest.kt index 9632268..85ff1de 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/impl/RoomTypeConvertersTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/impl/RoomTypeConvertersTest.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.data.impl import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.data.impl.util.RoomTypeConverters +import gq.kirmanak.mealient.extensions.RoomTypeConverters import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import org.junit.Test From ec5d05c81924a832ddafb49d2efe599616ef2a91 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 3 Apr 2022 02:18:12 +0500 Subject: [PATCH 2/4] Use @Singleton where possible --- .../gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt | 2 ++ .../java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt | 2 ++ .../java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt | 2 ++ .../kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt | 2 ++ .../java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt | 2 ++ .../gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt | 2 ++ .../mealient/data/recipes/impl/RecipeImageLoaderImpl.kt | 2 ++ .../gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt | 2 ++ .../mealient/data/recipes/impl/RecipesRemoteMediator.kt | 2 ++ .../mealient/data/recipes/network/RecipeDataSourceImpl.kt | 2 ++ .../java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt | 2 ++ .../main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt | 2 ++ 12 files changed, 24 insertions(+) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt index a25fcde..0d3ddac 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt @@ -12,7 +12,9 @@ import retrofit2.HttpException import retrofit2.Response import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class AuthDataSourceImpl @Inject constructor( private val authServiceFactory: ServiceFactory, private val json: Json, diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt index 20f0867..d20a9e3 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt @@ -11,7 +11,9 @@ import kotlinx.coroutines.flow.map import okhttp3.HttpUrl.Companion.toHttpUrl import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class AuthRepoImpl @Inject constructor( private val dataSource: AuthDataSource, private val storage: AuthStorage, diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt index 9bfc7b8..5a81763 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt @@ -9,10 +9,12 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton private const val AUTH_HEADER_KEY = "AUTH_TOKEN" private const val BASE_URL_KEY = "BASE_URL" +@Singleton class AuthStorageImpl @Inject constructor( private val sharedPreferences: SharedPreferences ) : AuthStorage { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt index 823a977..41f8139 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt @@ -4,9 +4,11 @@ import android.content.SharedPreferences import gq.kirmanak.mealient.extensions.getBooleanOrFalse import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton private const val IS_DISCLAIMER_ACCEPTED_KEY = "IS_DISCLAIMER_ACCEPTED" +@Singleton class DisclaimerStorageImpl @Inject constructor( private val sharedPreferences: SharedPreferences ) : DisclaimerStorage { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt b/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt index 0c5ae74..2f6cb3e 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/network/RetrofitBuilder.kt @@ -8,7 +8,9 @@ import okhttp3.OkHttpClient import retrofit2.Retrofit import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class RetrofitBuilder @Inject constructor( private val okHttpClient: OkHttpClient, private val json: Json diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index be0b13c..3039b99 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -13,7 +13,9 @@ import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity import gq.kirmanak.mealient.extensions.toRecipeInstructionEntity import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class RecipeStorageImpl @Inject constructor( private val db: AppDb ) : RecipeStorage { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt index 0f33b44..b83de42 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageLoaderImpl.kt @@ -9,7 +9,9 @@ import gq.kirmanak.mealient.ui.ImageLoader import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class RecipeImageLoaderImpl @Inject constructor( private val imageLoader: ImageLoader, private val authRepo: AuthRepo diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt index b8926f8..a36f7e1 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt @@ -11,8 +11,10 @@ import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import kotlinx.coroutines.CancellationException import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton @OptIn(ExperimentalPagingApi::class) +@Singleton class RecipeRepoImpl @Inject constructor( private val mediator: RecipesRemoteMediator, private val storage: RecipeStorage, diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt index 7c0b5c3..96cbfa4 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt @@ -10,8 +10,10 @@ import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import kotlinx.coroutines.CancellationException import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton @OptIn(ExperimentalPagingApi::class) +@Singleton class RecipesRemoteMediator @Inject constructor( private val storage: RecipeStorage, private val network: RecipeDataSource, diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSourceImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSourceImpl.kt index 6a11cde..05cd801 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSourceImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSourceImpl.kt @@ -6,7 +6,9 @@ import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeResponse import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class RecipeDataSourceImpl @Inject constructor( private val authRepo: AuthRepo, private val recipeServiceFactory: ServiceFactory, diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt b/app/src/main/java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt index 9b44c67..8779d4d 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/picasso/ImageLoaderPicasso.kt @@ -5,7 +5,9 @@ import com.squareup.picasso.Picasso import gq.kirmanak.mealient.ui.ImageLoader import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class ImageLoaderPicasso @Inject constructor( private val picasso: Picasso ) : ImageLoader { diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt b/app/src/main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt index 4ccc879..31e885f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/picasso/PicassoBuilder.kt @@ -8,7 +8,9 @@ import gq.kirmanak.mealient.BuildConfig import okhttp3.OkHttpClient import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class PicassoBuilder @Inject constructor( @ApplicationContext private val context: Context, private val okHttpClient: OkHttpClient From fd9f7e5aa149a899d03cbab284a168a3d767066e Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 3 Apr 2022 17:28:12 +0500 Subject: [PATCH 3/4] Reorganize test code --- .../impl => extensions}/RoomTypeConvertersTest.kt | 13 ++++++------- .../kirmanak/mealient/test/HiltRobolectricTest.kt | 12 +++--------- .../gq/kirmanak/mealient/test/RobolectricTest.kt | 11 ++++++++++- .../gq/kirmanak/mealient/test/TestExtensions.kt | 12 +++++++++++- 4 files changed, 30 insertions(+), 18 deletions(-) rename app/src/test/java/gq/kirmanak/mealient/{data/impl => extensions}/RoomTypeConvertersTest.kt (74%) diff --git a/app/src/test/java/gq/kirmanak/mealient/data/impl/RoomTypeConvertersTest.kt b/app/src/test/java/gq/kirmanak/mealient/extensions/RoomTypeConvertersTest.kt similarity index 74% rename from app/src/test/java/gq/kirmanak/mealient/data/impl/RoomTypeConvertersTest.kt rename to app/src/test/java/gq/kirmanak/mealient/extensions/RoomTypeConvertersTest.kt index 85ff1de..1068a18 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/impl/RoomTypeConvertersTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/extensions/RoomTypeConvertersTest.kt @@ -1,7 +1,6 @@ -package gq.kirmanak.mealient.data.impl +package gq.kirmanak.mealient.extensions -import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.extensions.RoomTypeConverters +import com.google.common.truth.Truth import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import org.junit.Test @@ -11,27 +10,27 @@ class RoomTypeConvertersTest { fun `when localDateTimeToTimestamp then correctly converts`() { val input = LocalDateTime.parse("2021-11-13T15:56:33") val actual = RoomTypeConverters.localDateTimeToTimestamp(input) - assertThat(actual).isEqualTo(1636818993000) + Truth.assertThat(actual).isEqualTo(1636818993000) } @Test fun `when timestampToLocalDateTime then correctly converts`() { val expected = LocalDateTime.parse("2021-11-13T15:58:38") val actual = RoomTypeConverters.timestampToLocalDateTime(1636819118000) - assertThat(actual).isEqualTo(expected) + Truth.assertThat(actual).isEqualTo(expected) } @Test fun `when localDateToTimeStamp then correctly converts`() { val input = LocalDate.parse("2021-11-13") val actual = RoomTypeConverters.localDateToTimeStamp(input) - assertThat(actual).isEqualTo(1636761600000) + Truth.assertThat(actual).isEqualTo(1636761600000) } @Test fun `when timestampToLocalDate then correctly converts`() { val expected = LocalDate.parse("2021-11-13") val actual = RoomTypeConverters.timestampToLocalDate(1636761600000) - assertThat(actual).isEqualTo(expected) + Truth.assertThat(actual).isEqualTo(expected) } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/HiltRobolectricTest.kt b/app/src/test/java/gq/kirmanak/mealient/test/HiltRobolectricTest.kt index 80f50bc..b4dc9fb 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/HiltRobolectricTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/HiltRobolectricTest.kt @@ -8,22 +8,16 @@ import org.junit.BeforeClass import org.junit.Rule import org.junit.runner.RunWith import org.robolectric.annotation.Config -import timber.log.Timber @RunWith(AndroidJUnit4::class) @Config(application = HiltTestApplication::class, manifest = Config.NONE) abstract class HiltRobolectricTest { + companion object { + @BeforeClass @JvmStatic - fun setupTimber() { - Timber.plant(object : Timber.Tree() { - override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { - println(message) - t?.printStackTrace() - } - }) - } + fun setupTimber() = plantPrintLn() } @get:Rule diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RobolectricTest.kt b/app/src/test/java/gq/kirmanak/mealient/test/RobolectricTest.kt index 5fc79bf..b58b3b4 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RobolectricTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RobolectricTest.kt @@ -2,9 +2,18 @@ package gq.kirmanak.mealient.test import android.app.Application import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.BeforeClass import org.junit.runner.RunWith import org.robolectric.annotation.Config @RunWith(AndroidJUnit4::class) @Config(application = Application::class, manifest = Config.NONE) -abstract class RobolectricTest \ No newline at end of file +abstract class RobolectricTest { + + companion object { + + @BeforeClass + @JvmStatic + fun setupTimber() = plantPrintLn() + } +} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/TestExtensions.kt b/app/src/test/java/gq/kirmanak/mealient/test/TestExtensions.kt index b80b093..da0a67f 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/TestExtensions.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/TestExtensions.kt @@ -2,5 +2,15 @@ package gq.kirmanak.mealient.test import okhttp3.MediaType.Companion.toMediaType import okhttp3.ResponseBody.Companion.toResponseBody +import timber.log.Timber -fun String.toJsonResponseBody() = toResponseBody("application/json".toMediaType()) \ No newline at end of file +fun String.toJsonResponseBody() = toResponseBody("application/json".toMediaType()) + +fun plantPrintLn() { + Timber.plant(object : Timber.Tree() { + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + println(message) + t?.printStackTrace() + } + }) +} \ No newline at end of file From b3e25db4df938fd676e772232039bd4f32cde497 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 3 Apr 2022 19:55:31 +0500 Subject: [PATCH 4/4] Replace Shared Preferences with Data Store --- app/build.gradle | 12 +-- .../gq/kirmanak/mealient/di/DebugModule.kt | 86 +++++++++---------- .../kirmanak/mealient/data/auth/AuthRepo.kt | 2 +- .../mealient/data/auth/AuthStorage.kt | 4 +- .../mealient/data/auth/impl/AuthRepoImpl.kt | 2 +- .../data/auth/impl/AuthStorageImpl.kt | 37 ++++---- .../data/disclaimer/DisclaimerStorage.kt | 2 +- .../data/disclaimer/DisclaimerStorageImpl.kt | 17 ++-- .../data/storage/PreferencesStorage.kt | 23 +++++ .../data/storage/PreferencesStorageImpl.kt | 60 +++++++++++++ .../java/gq/kirmanak/mealient/di/AppModule.kt | 32 ++++--- .../extensions/SharedPrefExtensions.kt | 36 -------- .../ui/auth/AuthenticationViewModel.kt | 6 +- .../ui/disclaimer/DisclaimerViewModel.kt | 6 +- .../data/auth/impl/AuthRepoImplTest.kt | 6 +- .../storage/PreferencesStorageImplTest.kt | 47 ++++++++++ .../extensions/RoomTypeConvertersTest.kt | 10 +-- 17 files changed, 244 insertions(+), 144 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorage.kt create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt delete mode 100644 app/src/main/java/gq/kirmanak/mealient/extensions/SharedPrefExtensions.kt create mode 100644 app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt diff --git a/app/build.gradle b/app/build.gradle index da8da87..62e7704 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -120,9 +120,6 @@ dependencies { // https://github.com/Kotlin/kotlinx.serialization/releases implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2" - // https://developer.android.com/jetpack/androidx/releases/preference - implementation "androidx.preference:preference-ktx:1.2.0" - // https://github.com/JakeWharton/timber/releases implementation 'com.jakewharton.timber:timber:5.0.1' @@ -145,6 +142,12 @@ dependencies { // https://github.com/square/picasso/releases implementation "com.squareup.picasso:picasso:2.8" + // https://github.com/androidbroadcast/ViewBindingPropertyDelegate/releases + implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6" + + // https://developer.android.com/topic/libraries/architecture/datastore + implementation "androidx.datastore:datastore-preferences:1.0.0" + // https://github.com/junit-team/junit4/releases testImplementation "junit:junit:4.13.2" @@ -163,9 +166,6 @@ dependencies { // https://mockk.io/ testImplementation "io.mockk:mockk:1.12.3" - // https://github.com/androidbroadcast/ViewBindingPropertyDelegate/releases - implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.6" - // https://github.com/facebook/flipper/releases def flipper_version = "0.140.0" debugImplementation "com.facebook.flipper:flipper:$flipper_version" diff --git a/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt b/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt index 12b6d63..d35f6e6 100644 --- a/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt +++ b/app/src/debug/java/gq/kirmanak/mealient/di/DebugModule.kt @@ -25,54 +25,54 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object DebugModule { - @Provides - @Singleton - @IntoSet - fun provideLoggingInterceptor(): Interceptor { - val interceptor = HttpLoggingInterceptor { message -> Timber.tag("OkHttp").v(message) } - interceptor.level = HttpLoggingInterceptor.Level.BODY - return interceptor - } + @Provides + @Singleton + @IntoSet + fun provideLoggingInterceptor(): Interceptor { + val interceptor = HttpLoggingInterceptor { message -> Timber.tag("OkHttp").v(message) } + interceptor.level = HttpLoggingInterceptor.Level.BODY + return interceptor + } - @Provides - @Singleton - @IntoSet - fun provideFlipperInterceptor(networkFlipperPlugin: NetworkFlipperPlugin): Interceptor { - return FlipperOkhttpInterceptor(networkFlipperPlugin) - } + @Provides + @Singleton + @IntoSet + fun provideFlipperInterceptor(networkFlipperPlugin: NetworkFlipperPlugin): Interceptor { + return FlipperOkhttpInterceptor(networkFlipperPlugin) + } - @Provides - @Singleton - fun networkFlipperPlugin() = NetworkFlipperPlugin() + @Provides + @Singleton + fun networkFlipperPlugin() = NetworkFlipperPlugin() - @Provides - @Singleton - @IntoSet - fun bindNetworkFlipperPlugin(plugin: NetworkFlipperPlugin): FlipperPlugin = plugin + @Provides + @Singleton + @IntoSet + fun bindNetworkFlipperPlugin(plugin: NetworkFlipperPlugin): FlipperPlugin = plugin - @Provides - @Singleton - @IntoSet - fun sharedPreferencesPlugin(@ApplicationContext context: Context): FlipperPlugin = - SharedPreferencesFlipperPlugin(context) + @Provides + @Singleton + @IntoSet + fun sharedPreferencesPlugin(@ApplicationContext context: Context): FlipperPlugin = + SharedPreferencesFlipperPlugin(context) - @Provides - @Singleton - @IntoSet - fun leakCanaryPlugin(): FlipperPlugin { - LeakCanary.config = LeakCanary.config.copy(onHeapAnalyzedListener = FlipperLeakListener()) - return LeakCanary2FlipperPlugin() - } + @Provides + @Singleton + @IntoSet + fun leakCanaryPlugin(): FlipperPlugin { + LeakCanary.config = LeakCanary.config.copy(onHeapAnalyzedListener = FlipperLeakListener()) + return LeakCanary2FlipperPlugin() + } - @Provides - @Singleton - @IntoSet - fun databasesPlugin(@ApplicationContext context: Context): FlipperPlugin = - DatabasesFlipperPlugin(context) + @Provides + @Singleton + @IntoSet + fun databasesPlugin(@ApplicationContext context: Context): FlipperPlugin = + DatabasesFlipperPlugin(context) - @Provides - @Singleton - @IntoSet - fun inspectorPlugin(@ApplicationContext context: Context): FlipperPlugin = - InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()) + @Provides + @Singleton + @IntoSet + fun inspectorPlugin(@ApplicationContext context: Context): FlipperPlugin = + InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()) } diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt index 794e69c..1426dbe 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt @@ -15,5 +15,5 @@ interface AuthRepo { fun authenticationStatuses(): Flow - fun logout() + suspend fun logout() } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt index 88cd966..215a5da 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt @@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.auth import kotlinx.coroutines.flow.Flow interface AuthStorage { - fun storeAuthData(authHeader: String, baseUrl: String) + suspend fun storeAuthData(authHeader: String, baseUrl: String) suspend fun getBaseUrl(): String? @@ -11,5 +11,5 @@ interface AuthStorage { fun authHeaderObservable(): Flow - fun clearAuthData() + suspend fun clearAuthData() } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt index d20a9e3..89f0bfd 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt @@ -46,7 +46,7 @@ class AuthRepoImpl @Inject constructor( return storage.authHeaderObservable().map { it != null } } - override fun logout() { + override suspend fun logout() { Timber.v("logout() called") storage.clearAuthData() } diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt index 5a81763..43d4618 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt @@ -1,55 +1,48 @@ package gq.kirmanak.mealient.data.auth.impl -import android.content.SharedPreferences -import androidx.core.content.edit import gq.kirmanak.mealient.data.auth.AuthStorage -import gq.kirmanak.mealient.extensions.changesFlow -import gq.kirmanak.mealient.extensions.getStringOrNull +import gq.kirmanak.mealient.data.storage.PreferencesStorage import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -private const val AUTH_HEADER_KEY = "AUTH_TOKEN" -private const val BASE_URL_KEY = "BASE_URL" - @Singleton class AuthStorageImpl @Inject constructor( - private val sharedPreferences: SharedPreferences + private val preferencesStorage: PreferencesStorage, ) : AuthStorage { - override fun storeAuthData(authHeader: String, baseUrl: String) { + private val authHeaderKey by preferencesStorage::authHeaderKey + private val baseUrlKey by preferencesStorage::baseUrlKey + + override suspend fun storeAuthData(authHeader: String, baseUrl: String) { Timber.v("storeAuthData() called with: authHeader = $authHeader, baseUrl = $baseUrl") - sharedPreferences.edit { - putString(AUTH_HEADER_KEY, authHeader) - putString(BASE_URL_KEY, baseUrl) - } + preferencesStorage.storeValues( + Pair(authHeaderKey, authHeader), + Pair(baseUrlKey, baseUrl), + ) } override suspend fun getBaseUrl(): String? { - val baseUrl = sharedPreferences.getStringOrNull(BASE_URL_KEY) + val baseUrl = preferencesStorage.getValue(baseUrlKey) Timber.d("getBaseUrl: base url is $baseUrl") return baseUrl } override suspend fun getAuthHeader(): String? { Timber.v("getAuthHeader() called") - val token = sharedPreferences.getStringOrNull(AUTH_HEADER_KEY) + val token = preferencesStorage.getValue(authHeaderKey) Timber.d("getAuthHeader: header is \"$token\"") return token } override fun authHeaderObservable(): Flow { Timber.v("authHeaderObservable() called") - return sharedPreferences.changesFlow().map { it.first.getStringOrNull(AUTH_HEADER_KEY) } + return preferencesStorage.valueUpdates(authHeaderKey) } - override fun clearAuthData() { + override suspend fun clearAuthData() { Timber.v("clearAuthData() called") - sharedPreferences.edit { - remove(AUTH_HEADER_KEY) - remove(BASE_URL_KEY) - } + preferencesStorage.removeValues(authHeaderKey, baseUrlKey) } } diff --git a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorage.kt index 7ace2de..22ffbb4 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorage.kt @@ -3,5 +3,5 @@ package gq.kirmanak.mealient.data.disclaimer interface DisclaimerStorage { suspend fun isDisclaimerAccepted(): Boolean - fun acceptDisclaimer() + suspend fun acceptDisclaimer() } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt index 41f8139..f7e313b 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImpl.kt @@ -1,29 +1,26 @@ package gq.kirmanak.mealient.data.disclaimer -import android.content.SharedPreferences -import gq.kirmanak.mealient.extensions.getBooleanOrFalse +import gq.kirmanak.mealient.data.storage.PreferencesStorage import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton -private const val IS_DISCLAIMER_ACCEPTED_KEY = "IS_DISCLAIMER_ACCEPTED" - @Singleton class DisclaimerStorageImpl @Inject constructor( - private val sharedPreferences: SharedPreferences + private val preferencesStorage: PreferencesStorage, ) : DisclaimerStorage { + private val isDisclaimerAcceptedKey by preferencesStorage::isDisclaimerAcceptedKey + override suspend fun isDisclaimerAccepted(): Boolean { Timber.v("isDisclaimerAccepted() called") - val isAccepted = sharedPreferences.getBooleanOrFalse(IS_DISCLAIMER_ACCEPTED_KEY) + val isAccepted = preferencesStorage.getValue(isDisclaimerAcceptedKey) ?: false Timber.v("isDisclaimerAccepted() returned: $isAccepted") return isAccepted } - override fun acceptDisclaimer() { + override suspend fun acceptDisclaimer() { Timber.v("acceptDisclaimer() called") - sharedPreferences.edit() - .putBoolean(IS_DISCLAIMER_ACCEPTED_KEY, true) - .apply() + preferencesStorage.storeValues(Pair(isDisclaimerAcceptedKey, true)) } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorage.kt new file mode 100644 index 0000000..f63dc9b --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorage.kt @@ -0,0 +1,23 @@ +package gq.kirmanak.mealient.data.storage + +import androidx.datastore.preferences.core.Preferences +import kotlinx.coroutines.flow.Flow + +interface PreferencesStorage { + + val baseUrlKey: Preferences.Key + + val authHeaderKey: Preferences.Key + + val isDisclaimerAcceptedKey: Preferences.Key + + suspend fun getValue(key: Preferences.Key): T? + + suspend fun requireValue(key: Preferences.Key): T + + suspend fun storeValues(vararg pairs: Pair, T>) + + fun valueUpdates(key: Preferences.Key): Flow + + suspend fun removeValues(vararg keys: Preferences.Key) +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt new file mode 100644 index 0000000..a382682 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt @@ -0,0 +1,60 @@ +package gq.kirmanak.mealient.data.storage + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.* +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PreferencesStorageImpl @Inject constructor( + private val dataStore: DataStore +) : PreferencesStorage { + + override val baseUrlKey = stringPreferencesKey("baseUrl") + + override val authHeaderKey = stringPreferencesKey("authHeader") + + override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted") + + override suspend fun getValue(key: Preferences.Key): T? { + val value = dataStore.data.first()[key] + Timber.v("getValue() returned: $value for $key") + return value + } + + override suspend fun requireValue(key: Preferences.Key): T = + checkNotNull(getValue(key)) { "Value at $key is null when it was required" } + + override suspend fun storeValues(vararg pairs: Pair, T>) { + Timber.v("storeValues() called with: pairs = ${pairs.contentToString()}") + dataStore.edit { preferences -> + pairs.forEach { preferences += it.toPreferencesPair() } + } + } + + override fun valueUpdates(key: Preferences.Key): Flow { + Timber.v("valueUpdates() called with: key = $key") + return dataStore.data + .map { it[key] } + .distinctUntilChanged() + .onEach { Timber.d("valueUpdates: new value at $key is $it") } + .onCompletion { Timber.i(it, "valueUpdates: finished") } + } + + override suspend fun removeValues(vararg keys: Preferences.Key) { + Timber.v("removeValues() called with: key = ${keys.contentToString()}") + dataStore.edit { preferences -> + keys.forEach { preferences -= it } + } + } +} + +private fun Pair, T>.toPreferencesPair(): Preferences.Pair { + val (key, value) = this + return key to value +} diff --git a/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt index 3418f3e..43b19a5 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/AppModule.kt @@ -1,27 +1,39 @@ package gq.kirmanak.mealient.di import android.content.Context -import android.content.SharedPreferences -import androidx.preference.PreferenceManager +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.PreferenceDataStoreFactory +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStoreFile import androidx.room.Room +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import gq.kirmanak.mealient.data.AppDb +import gq.kirmanak.mealient.data.storage.PreferencesStorage +import gq.kirmanak.mealient.data.storage.PreferencesStorageImpl import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -object AppModule { - @Provides - @Singleton - fun createDb(@ApplicationContext context: Context): AppDb = - Room.databaseBuilder(context, AppDb::class.java, "app.db").build() +interface AppModule { + companion object { - @Provides + @Provides + @Singleton + fun createDb(@ApplicationContext context: Context): AppDb = + Room.databaseBuilder(context, AppDb::class.java, "app.db").build() + + @Provides + @Singleton + fun provideDataStore(@ApplicationContext context: Context): DataStore = + PreferenceDataStoreFactory.create { context.preferencesDataStoreFile("settings") } + } + + @Binds @Singleton - fun createSharedPreferences(@ApplicationContext context: Context): SharedPreferences = - PreferenceManager.getDefaultSharedPreferences(context) + fun bindPreferencesStorage(preferencesStorage: PreferencesStorageImpl): PreferencesStorage } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/SharedPrefExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/SharedPrefExtensions.kt deleted file mode 100644 index edd88ca..0000000 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/SharedPrefExtensions.kt +++ /dev/null @@ -1,36 +0,0 @@ -package gq.kirmanak.mealient.extensions - -import android.content.SharedPreferences -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.channels.onClosed -import kotlinx.coroutines.channels.onFailure -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.withContext -import timber.log.Timber - -suspend fun SharedPreferences.getStringOrNull(key: String) = - withContext(Dispatchers.IO) { getString(key, null) } - -suspend fun SharedPreferences.getBooleanOrFalse(key: String) = - withContext(Dispatchers.IO) { getBoolean(key, false) } - -@OptIn(ExperimentalCoroutinesApi::class) -fun SharedPreferences.changesFlow(): Flow> = callbackFlow { - val listener = SharedPreferences.OnSharedPreferenceChangeListener { prefs, key -> - Timber.v("watchChanges: listener called with key $key") - trySend(prefs to key) - .onFailure { Timber.e(it, "watchChanges: can't send preference change, key $key") } - .onClosed { Timber.e(it, "watchChanges: flow has been closed") } - } - Timber.v("watchChanges: registering listener") - registerOnSharedPreferenceChangeListener(listener) - send(this@changesFlow to null) - awaitClose { - Timber.v("watchChanges: flow has been closed") - unregisterOnSharedPreferenceChangeListener(listener) - } -} - diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt index 9a846cd..f630531 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt @@ -38,7 +38,9 @@ class AuthenticationViewModel @Inject constructor( fun logout() { Timber.v("logout() called") - authRepo.logout() - viewModelScope.launch { recipeRepo.clearLocalData() } + viewModelScope.launch { + authRepo.logout() + recipeRepo.clearLocalData() + } } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModel.kt index ea056d1..9f3535a 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/disclaimer/DisclaimerViewModel.kt @@ -36,8 +36,10 @@ class DisclaimerViewModel @Inject constructor( fun acceptDisclaimer() { Timber.v("acceptDisclaimer() called") - disclaimerStorage.acceptDisclaimer() - _isAccepted.value = true + viewModelScope.launch { + disclaimerStorage.acceptDisclaimer() + _isAccepted.value = true + } } fun startCountDown() { diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt index 66b0457..f7677a1 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt @@ -13,8 +13,8 @@ import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME import gq.kirmanak.mealient.test.RobolectricTest import io.mockk.MockKAnnotations import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf @@ -102,12 +102,12 @@ class AuthRepoImplTest : RobolectricTest() { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL)) } returns TEST_TOKEN subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL) - verify { storage.storeAuthData(TEST_AUTH_HEADER, TEST_BASE_URL) } + coVerify { storage.storeAuthData(TEST_AUTH_HEADER, TEST_BASE_URL) } } @Test fun `when logout then clearAuthData is called`() = runTest { subject.logout() - verify { storage.clearAuthData() } + coVerify { storage.clearAuthData() } } } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt new file mode 100644 index 0000000..b250045 --- /dev/null +++ b/app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt @@ -0,0 +1,47 @@ +package gq.kirmanak.mealient.data.storage + +import com.google.common.truth.Truth.assertThat +import dagger.hilt.android.testing.HiltAndroidTest +import gq.kirmanak.mealient.test.HiltRobolectricTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Test +import javax.inject.Inject + +@OptIn(ExperimentalCoroutinesApi::class) +@HiltAndroidTest +class PreferencesStorageImplTest : HiltRobolectricTest() { + + @Inject + lateinit var subject: PreferencesStorage + + @Test + fun `when getValue without writes then null`() = runTest { + assertThat(subject.getValue(subject.authHeaderKey)).isNull() + } + + @Test(expected = IllegalStateException::class) + fun `when requireValue without writes then throws IllegalStateException`() = runTest { + subject.requireValue(subject.authHeaderKey) + } + + @Test + fun `when getValue after write then returns value`() = runTest { + subject.storeValues(Pair(subject.authHeaderKey, "test")) + assertThat(subject.getValue(subject.authHeaderKey)).isEqualTo("test") + } + + @Test + fun `when storeValue then valueUpdates emits`() = runTest { + subject.storeValues(Pair(subject.authHeaderKey, "test")) + assertThat(subject.valueUpdates(subject.authHeaderKey).first()).isEqualTo("test") + } + + @Test + fun `when remove value then getValue returns null`() = runTest { + subject.storeValues(Pair(subject.authHeaderKey, "test")) + subject.removeValues(subject.authHeaderKey) + assertThat(subject.getValue(subject.authHeaderKey)).isNull() + } +} \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/extensions/RoomTypeConvertersTest.kt b/app/src/test/java/gq/kirmanak/mealient/extensions/RoomTypeConvertersTest.kt index 1068a18..240d593 100644 --- a/app/src/test/java/gq/kirmanak/mealient/extensions/RoomTypeConvertersTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/extensions/RoomTypeConvertersTest.kt @@ -1,6 +1,6 @@ package gq.kirmanak.mealient.extensions -import com.google.common.truth.Truth +import com.google.common.truth.Truth.assertThat import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import org.junit.Test @@ -10,27 +10,27 @@ class RoomTypeConvertersTest { fun `when localDateTimeToTimestamp then correctly converts`() { val input = LocalDateTime.parse("2021-11-13T15:56:33") val actual = RoomTypeConverters.localDateTimeToTimestamp(input) - Truth.assertThat(actual).isEqualTo(1636818993000) + assertThat(actual).isEqualTo(1636818993000) } @Test fun `when timestampToLocalDateTime then correctly converts`() { val expected = LocalDateTime.parse("2021-11-13T15:58:38") val actual = RoomTypeConverters.timestampToLocalDateTime(1636819118000) - Truth.assertThat(actual).isEqualTo(expected) + assertThat(actual).isEqualTo(expected) } @Test fun `when localDateToTimeStamp then correctly converts`() { val input = LocalDate.parse("2021-11-13") val actual = RoomTypeConverters.localDateToTimeStamp(input) - Truth.assertThat(actual).isEqualTo(1636761600000) + assertThat(actual).isEqualTo(1636761600000) } @Test fun `when timestampToLocalDate then correctly converts`() { val expected = LocalDate.parse("2021-11-13") val actual = RoomTypeConverters.timestampToLocalDate(1636761600000) - Truth.assertThat(actual).isEqualTo(expected) + assertThat(actual).isEqualTo(expected) } } \ No newline at end of file