Merge pull request #29 from kirmanak/data-store
Migrate from Shared Preferences to Data Store
This commit is contained in:
@@ -120,9 +120,6 @@ dependencies {
|
|||||||
// https://github.com/Kotlin/kotlinx.serialization/releases
|
// https://github.com/Kotlin/kotlinx.serialization/releases
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
|
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
|
// https://github.com/JakeWharton/timber/releases
|
||||||
implementation 'com.jakewharton.timber:timber:5.0.1'
|
implementation 'com.jakewharton.timber:timber:5.0.1'
|
||||||
|
|
||||||
@@ -145,6 +142,12 @@ dependencies {
|
|||||||
// https://github.com/square/picasso/releases
|
// https://github.com/square/picasso/releases
|
||||||
implementation "com.squareup.picasso:picasso:2.8"
|
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
|
// https://github.com/junit-team/junit4/releases
|
||||||
testImplementation "junit:junit:4.13.2"
|
testImplementation "junit:junit:4.13.2"
|
||||||
|
|
||||||
@@ -163,9 +166,6 @@ dependencies {
|
|||||||
// https://mockk.io/
|
// https://mockk.io/
|
||||||
testImplementation "io.mockk:mockk:1.12.3"
|
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
|
// https://github.com/facebook/flipper/releases
|
||||||
def flipper_version = "0.140.0"
|
def flipper_version = "0.140.0"
|
||||||
debugImplementation "com.facebook.flipper:flipper:$flipper_version"
|
debugImplementation "com.facebook.flipper:flipper:$flipper_version"
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package gq.kirmanak.mealient.data
|
|||||||
import androidx.room.Database
|
import androidx.room.Database
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
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.RecipeDao
|
||||||
import gq.kirmanak.mealient.data.recipes.db.entity.*
|
import gq.kirmanak.mealient.data.recipes.db.entity.*
|
||||||
|
import gq.kirmanak.mealient.extensions.RoomTypeConverters
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 1,
|
version = 1,
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ interface AuthRepo {
|
|||||||
|
|
||||||
fun authenticationStatuses(): Flow<Boolean>
|
fun authenticationStatuses(): Flow<Boolean>
|
||||||
|
|
||||||
fun logout()
|
suspend fun logout()
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.auth
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AuthStorage {
|
interface AuthStorage {
|
||||||
fun storeAuthData(authHeader: String, baseUrl: String)
|
suspend fun storeAuthData(authHeader: String, baseUrl: String)
|
||||||
|
|
||||||
suspend fun getBaseUrl(): String?
|
suspend fun getBaseUrl(): String?
|
||||||
|
|
||||||
@@ -11,5 +11,5 @@ interface AuthStorage {
|
|||||||
|
|
||||||
fun authHeaderObservable(): Flow<String?>
|
fun authHeaderObservable(): Flow<String?>
|
||||||
|
|
||||||
fun clearAuthData()
|
suspend fun clearAuthData()
|
||||||
}
|
}
|
||||||
@@ -2,9 +2,9 @@ package gq.kirmanak.mealient.data.auth.impl
|
|||||||
|
|
||||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||||
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.*
|
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.*
|
||||||
import gq.kirmanak.mealient.data.impl.ErrorDetail
|
import gq.kirmanak.mealient.data.network.ErrorDetail
|
||||||
import gq.kirmanak.mealient.data.impl.util.decodeErrorBodyOrNull
|
|
||||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||||
|
import gq.kirmanak.mealient.extensions.decodeErrorBodyOrNull
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -12,7 +12,9 @@ import retrofit2.HttpException
|
|||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class AuthDataSourceImpl @Inject constructor(
|
class AuthDataSourceImpl @Inject constructor(
|
||||||
private val authServiceFactory: ServiceFactory<AuthService>,
|
private val authServiceFactory: ServiceFactory<AuthService>,
|
||||||
private val json: Json,
|
private val json: Json,
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import kotlinx.coroutines.flow.map
|
|||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class AuthRepoImpl @Inject constructor(
|
class AuthRepoImpl @Inject constructor(
|
||||||
private val dataSource: AuthDataSource,
|
private val dataSource: AuthDataSource,
|
||||||
private val storage: AuthStorage,
|
private val storage: AuthStorage,
|
||||||
@@ -44,7 +46,7 @@ class AuthRepoImpl @Inject constructor(
|
|||||||
return storage.authHeaderObservable().map { it != null }
|
return storage.authHeaderObservable().map { it != null }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun logout() {
|
override suspend fun logout() {
|
||||||
Timber.v("logout() called")
|
Timber.v("logout() called")
|
||||||
storage.clearAuthData()
|
storage.clearAuthData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +1,48 @@
|
|||||||
package gq.kirmanak.mealient.data.auth.impl
|
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.auth.AuthStorage
|
||||||
import gq.kirmanak.mealient.data.impl.util.changesFlow
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import gq.kirmanak.mealient.data.impl.util.getStringOrNull
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val AUTH_HEADER_KEY = "AUTH_TOKEN"
|
@Singleton
|
||||||
private const val BASE_URL_KEY = "BASE_URL"
|
|
||||||
|
|
||||||
class AuthStorageImpl @Inject constructor(
|
class AuthStorageImpl @Inject constructor(
|
||||||
private val sharedPreferences: SharedPreferences
|
private val preferencesStorage: PreferencesStorage,
|
||||||
) : AuthStorage {
|
) : 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")
|
Timber.v("storeAuthData() called with: authHeader = $authHeader, baseUrl = $baseUrl")
|
||||||
sharedPreferences.edit {
|
preferencesStorage.storeValues(
|
||||||
putString(AUTH_HEADER_KEY, authHeader)
|
Pair(authHeaderKey, authHeader),
|
||||||
putString(BASE_URL_KEY, baseUrl)
|
Pair(baseUrlKey, baseUrl),
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getBaseUrl(): String? {
|
override suspend fun getBaseUrl(): String? {
|
||||||
val baseUrl = sharedPreferences.getStringOrNull(BASE_URL_KEY)
|
val baseUrl = preferencesStorage.getValue(baseUrlKey)
|
||||||
Timber.d("getBaseUrl: base url is $baseUrl")
|
Timber.d("getBaseUrl: base url is $baseUrl")
|
||||||
return baseUrl
|
return baseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAuthHeader(): String? {
|
override suspend fun getAuthHeader(): String? {
|
||||||
Timber.v("getAuthHeader() called")
|
Timber.v("getAuthHeader() called")
|
||||||
val token = sharedPreferences.getStringOrNull(AUTH_HEADER_KEY)
|
val token = preferencesStorage.getValue(authHeaderKey)
|
||||||
Timber.d("getAuthHeader: header is \"$token\"")
|
Timber.d("getAuthHeader: header is \"$token\"")
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun authHeaderObservable(): Flow<String?> {
|
override fun authHeaderObservable(): Flow<String?> {
|
||||||
Timber.v("authHeaderObservable() called")
|
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")
|
Timber.v("clearAuthData() called")
|
||||||
sharedPreferences.edit {
|
preferencesStorage.removeValues(authHeaderKey, baseUrlKey)
|
||||||
remove(AUTH_HEADER_KEY)
|
|
||||||
remove(BASE_URL_KEY)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ package gq.kirmanak.mealient.data.disclaimer
|
|||||||
interface DisclaimerStorage {
|
interface DisclaimerStorage {
|
||||||
suspend fun isDisclaimerAccepted(): Boolean
|
suspend fun isDisclaimerAccepted(): Boolean
|
||||||
|
|
||||||
fun acceptDisclaimer()
|
suspend fun acceptDisclaimer()
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,26 @@
|
|||||||
package gq.kirmanak.mealient.data.disclaimer
|
package gq.kirmanak.mealient.data.disclaimer
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import gq.kirmanak.mealient.data.impl.util.getBooleanOrFalse
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
private const val IS_DISCLAIMER_ACCEPTED_KEY = "IS_DISCLAIMER_ACCEPTED"
|
@Singleton
|
||||||
|
|
||||||
class DisclaimerStorageImpl @Inject constructor(
|
class DisclaimerStorageImpl @Inject constructor(
|
||||||
private val sharedPreferences: SharedPreferences
|
private val preferencesStorage: PreferencesStorage,
|
||||||
) : DisclaimerStorage {
|
) : DisclaimerStorage {
|
||||||
|
|
||||||
|
private val isDisclaimerAcceptedKey by preferencesStorage::isDisclaimerAcceptedKey
|
||||||
|
|
||||||
override suspend fun isDisclaimerAccepted(): Boolean {
|
override suspend fun isDisclaimerAccepted(): Boolean {
|
||||||
Timber.v("isDisclaimerAccepted() called")
|
Timber.v("isDisclaimerAccepted() called")
|
||||||
val isAccepted = sharedPreferences.getBooleanOrFalse(IS_DISCLAIMER_ACCEPTED_KEY)
|
val isAccepted = preferencesStorage.getValue(isDisclaimerAcceptedKey) ?: false
|
||||||
Timber.v("isDisclaimerAccepted() returned: $isAccepted")
|
Timber.v("isDisclaimerAccepted() returned: $isAccepted")
|
||||||
return isAccepted
|
return isAccepted
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun acceptDisclaimer() {
|
override suspend fun acceptDisclaimer() {
|
||||||
Timber.v("acceptDisclaimer() called")
|
Timber.v("acceptDisclaimer() called")
|
||||||
sharedPreferences.edit()
|
preferencesStorage.storeValues(Pair(isDisclaimerAcceptedKey, true))
|
||||||
.putBoolean(IS_DISCLAIMER_ACCEPTED_KEY, true)
|
|
||||||
.apply()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.data.impl.util
|
|
||||||
|
|
||||||
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<Pair<SharedPreferences, String?>> = 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.data.impl
|
package gq.kirmanak.mealient.data.network
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.data.impl
|
package gq.kirmanak.mealient.data.network
|
||||||
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@@ -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 com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
@@ -8,7 +8,9 @@ import okhttp3.OkHttpClient
|
|||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class RetrofitBuilder @Inject constructor(
|
class RetrofitBuilder @Inject constructor(
|
||||||
private val okHttpClient: OkHttpClient,
|
private val okHttpClient: OkHttpClient,
|
||||||
private val json: Json
|
private val json: Json
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package gq.kirmanak.mealient.data.network
|
package gq.kirmanak.mealient.data.network
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.impl.RetrofitBuilder
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
inline fun <reified T> RetrofitBuilder.createServiceFactory() =
|
inline fun <reified T> RetrofitBuilder.createServiceFactory() =
|
||||||
|
|||||||
@@ -3,17 +3,19 @@ package gq.kirmanak.mealient.data.recipes.db
|
|||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
import gq.kirmanak.mealient.data.AppDb
|
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.db.entity.*
|
||||||
import gq.kirmanak.mealient.data.recipes.impl.FullRecipeInfo
|
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.GetRecipeResponse
|
||||||
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse
|
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 timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class RecipeStorageImpl @Inject constructor(
|
class RecipeStorageImpl @Inject constructor(
|
||||||
private val db: AppDb
|
private val db: AppDb
|
||||||
) : RecipeStorage {
|
) : RecipeStorage {
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import gq.kirmanak.mealient.ui.ImageLoader
|
|||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class RecipeImageLoaderImpl @Inject constructor(
|
class RecipeImageLoaderImpl @Inject constructor(
|
||||||
private val imageLoader: ImageLoader,
|
private val imageLoader: ImageLoader,
|
||||||
private val authRepo: AuthRepo
|
private val authRepo: AuthRepo
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@OptIn(ExperimentalPagingApi::class)
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
@Singleton
|
||||||
class RecipeRepoImpl @Inject constructor(
|
class RecipeRepoImpl @Inject constructor(
|
||||||
private val mediator: RecipesRemoteMediator,
|
private val mediator: RecipesRemoteMediator,
|
||||||
private val storage: RecipeStorage,
|
private val storage: RecipeStorage,
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@OptIn(ExperimentalPagingApi::class)
|
@OptIn(ExperimentalPagingApi::class)
|
||||||
|
@Singleton
|
||||||
class RecipesRemoteMediator @Inject constructor(
|
class RecipesRemoteMediator @Inject constructor(
|
||||||
private val storage: RecipeStorage,
|
private val storage: RecipeStorage,
|
||||||
private val network: RecipeDataSource,
|
private val network: RecipeDataSource,
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeResponse
|
|||||||
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse
|
import gq.kirmanak.mealient.data.recipes.network.response.GetRecipeSummaryResponse
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class RecipeDataSourceImpl @Inject constructor(
|
class RecipeDataSourceImpl @Inject constructor(
|
||||||
private val authRepo: AuthRepo,
|
private val authRepo: AuthRepo,
|
||||||
private val recipeServiceFactory: ServiceFactory<RecipeService>,
|
private val recipeServiceFactory: ServiceFactory<RecipeService>,
|
||||||
|
|||||||
@@ -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<String>
|
||||||
|
|
||||||
|
val authHeaderKey: Preferences.Key<String>
|
||||||
|
|
||||||
|
val isDisclaimerAcceptedKey: Preferences.Key<Boolean>
|
||||||
|
|
||||||
|
suspend fun <T> getValue(key: Preferences.Key<T>): T?
|
||||||
|
|
||||||
|
suspend fun <T> requireValue(key: Preferences.Key<T>): T
|
||||||
|
|
||||||
|
suspend fun <T> storeValues(vararg pairs: Pair<Preferences.Key<T>, T>)
|
||||||
|
|
||||||
|
fun <T> valueUpdates(key: Preferences.Key<T>): Flow<T?>
|
||||||
|
|
||||||
|
suspend fun <T> removeValues(vararg keys: Preferences.Key<T>)
|
||||||
|
}
|
||||||
@@ -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<Preferences>
|
||||||
|
) : PreferencesStorage {
|
||||||
|
|
||||||
|
override val baseUrlKey = stringPreferencesKey("baseUrl")
|
||||||
|
|
||||||
|
override val authHeaderKey = stringPreferencesKey("authHeader")
|
||||||
|
|
||||||
|
override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted")
|
||||||
|
|
||||||
|
override suspend fun <T> getValue(key: Preferences.Key<T>): T? {
|
||||||
|
val value = dataStore.data.first()[key]
|
||||||
|
Timber.v("getValue() returned: $value for $key")
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> requireValue(key: Preferences.Key<T>): T =
|
||||||
|
checkNotNull(getValue(key)) { "Value at $key is null when it was required" }
|
||||||
|
|
||||||
|
override suspend fun <T> storeValues(vararg pairs: Pair<Preferences.Key<T>, T>) {
|
||||||
|
Timber.v("storeValues() called with: pairs = ${pairs.contentToString()}")
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
pairs.forEach { preferences += it.toPreferencesPair() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T> valueUpdates(key: Preferences.Key<T>): Flow<T?> {
|
||||||
|
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 <T> removeValues(vararg keys: Preferences.Key<T>) {
|
||||||
|
Timber.v("removeValues() called with: key = ${keys.contentToString()}")
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
keys.forEach { preferences -= it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> Pair<Preferences.Key<T>, T>.toPreferencesPair(): Preferences.Pair<T> {
|
||||||
|
val (key, value) = this
|
||||||
|
return key to value
|
||||||
|
}
|
||||||
@@ -1,23 +1,27 @@
|
|||||||
package gq.kirmanak.mealient.di
|
package gq.kirmanak.mealient.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import gq.kirmanak.mealient.data.AppDb
|
import gq.kirmanak.mealient.data.AppDb
|
||||||
import gq.kirmanak.mealient.data.impl.OkHttpBuilder
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import kotlinx.serialization.json.Json
|
import gq.kirmanak.mealient.data.storage.PreferencesStorageImpl
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object AppModule {
|
interface AppModule {
|
||||||
|
companion object {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun createDb(@ApplicationContext context: Context): AppDb =
|
fun createDb(@ApplicationContext context: Context): AppDb =
|
||||||
@@ -25,18 +29,11 @@ object AppModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun createSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
|
fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
|
||||||
PreferenceManager.getDefaultSharedPreferences(context)
|
PreferenceDataStoreFactory.create { context.preferencesDataStoreFile("settings") }
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun createOkHttp(okHttpBuilder: OkHttpBuilder): OkHttpClient =
|
|
||||||
okHttpBuilder.buildOkHttp()
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun createJson(): Json = Json {
|
|
||||||
coerceInputValues = true
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindPreferencesStorage(preferencesStorage: PreferencesStorageImpl): PreferencesStorage
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
package gq.kirmanak.mealient.di
|
package gq.kirmanak.mealient.di
|
||||||
|
|
||||||
import android.accounts.AccountManager
|
|
||||||
import android.content.Context
|
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
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.AuthRepoImpl
|
||||||
import gq.kirmanak.mealient.data.auth.impl.AuthService
|
import gq.kirmanak.mealient.data.auth.impl.AuthService
|
||||||
import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl
|
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.ServiceFactory
|
||||||
import gq.kirmanak.mealient.data.network.createServiceFactory
|
import gq.kirmanak.mealient.data.network.createServiceFactory
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|||||||
27
app/src/main/java/gq/kirmanak/mealient/di/NetworkModule.kt
Normal file
27
app/src/main/java/gq/kirmanak/mealient/di/NetworkModule.kt
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import dagger.Module
|
|||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
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.ServiceFactory
|
||||||
import gq.kirmanak.mealient.data.network.createServiceFactory
|
import gq.kirmanak.mealient.data.network.createServiceFactory
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
import gq.kirmanak.mealient.data.recipes.RecipeImageLoader
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.data.impl.util
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -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.RecipeEntity
|
||||||
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity
|
import gq.kirmanak.mealient.data.recipes.db.entity.RecipeIngredientEntity
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.data.impl.util
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
@@ -38,7 +38,9 @@ class AuthenticationViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun logout() {
|
fun logout() {
|
||||||
Timber.v("logout() called")
|
Timber.v("logout() called")
|
||||||
|
viewModelScope.launch {
|
||||||
authRepo.logout()
|
authRepo.logout()
|
||||||
viewModelScope.launch { recipeRepo.clearLocalData() }
|
recipeRepo.clearLocalData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,9 +36,11 @@ class DisclaimerViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun acceptDisclaimer() {
|
fun acceptDisclaimer() {
|
||||||
Timber.v("acceptDisclaimer() called")
|
Timber.v("acceptDisclaimer() called")
|
||||||
|
viewModelScope.launch {
|
||||||
disclaimerStorage.acceptDisclaimer()
|
disclaimerStorage.acceptDisclaimer()
|
||||||
_isAccepted.value = true
|
_isAccepted.value = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun startCountDown() {
|
fun startCountDown() {
|
||||||
Timber.v("startCountDown() called")
|
Timber.v("startCountDown() called")
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import com.squareup.picasso.Picasso
|
|||||||
import gq.kirmanak.mealient.ui.ImageLoader
|
import gq.kirmanak.mealient.ui.ImageLoader
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class ImageLoaderPicasso @Inject constructor(
|
class ImageLoaderPicasso @Inject constructor(
|
||||||
private val picasso: Picasso
|
private val picasso: Picasso
|
||||||
) : ImageLoader {
|
) : ImageLoader {
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import gq.kirmanak.mealient.BuildConfig
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
class PicassoBuilder @Inject constructor(
|
class PicassoBuilder @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val okHttpClient: OkHttpClient
|
private val okHttpClient: OkHttpClient
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.auth.impl
|
|||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.*
|
import gq.kirmanak.mealient.data.auth.impl.AuthenticationError.*
|
||||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
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_BASE_URL
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
|
||||||
@@ -33,7 +33,7 @@ class AuthDataSourceImplTest {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
subject = AuthDataSourceImpl(authServiceFactory, AppModule.createJson())
|
subject = AuthDataSourceImpl(authServiceFactory, NetworkModule.createJson())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME
|
|||||||
import gq.kirmanak.mealient.test.RobolectricTest
|
import gq.kirmanak.mealient.test.RobolectricTest
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
import io.mockk.verify
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
@@ -102,12 +102,12 @@ class AuthRepoImplTest : RobolectricTest() {
|
|||||||
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
||||||
} returns TEST_TOKEN
|
} returns TEST_TOKEN
|
||||||
subject.authenticate(TEST_USERNAME, TEST_PASSWORD, TEST_BASE_URL)
|
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
|
@Test
|
||||||
fun `when logout then clearAuthData is called`() = runTest {
|
fun `when logout then clearAuthData is called`() = runTest {
|
||||||
subject.logout()
|
subject.logout()
|
||||||
verify { storage.clearAuthData() }
|
coVerify { storage.clearAuthData() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.data.impl
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.impl.util.RoomTypeConverters
|
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -8,22 +8,16 @@ import org.junit.BeforeClass
|
|||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@Config(application = HiltTestApplication::class, manifest = Config.NONE)
|
@Config(application = HiltTestApplication::class, manifest = Config.NONE)
|
||||||
abstract class HiltRobolectricTest {
|
abstract class HiltRobolectricTest {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun setupTimber() {
|
fun setupTimber() = plantPrintLn()
|
||||||
Timber.plant(object : Timber.Tree() {
|
|
||||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
|
||||||
println(message)
|
|
||||||
t?.printStackTrace()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule
|
||||||
|
|||||||
@@ -2,9 +2,18 @@ package gq.kirmanak.mealient.test
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.BeforeClass
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@Config(application = Application::class, manifest = Config.NONE)
|
@Config(application = Application::class, manifest = Config.NONE)
|
||||||
abstract class RobolectricTest
|
abstract class RobolectricTest {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
@JvmStatic
|
||||||
|
fun setupTimber() = plantPrintLn()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,5 +2,15 @@ package gq.kirmanak.mealient.test
|
|||||||
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
fun String.toJsonResponseBody() = toResponseBody("application/json".toMediaType())
|
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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user