Skip to content

Commit

Permalink
Replaces Coroutine Dispatcher.IO with seperate BackgroundExecutor in …
Browse files Browse the repository at this point in the history
…repos
  • Loading branch information
mirzemehdi committed Feb 24, 2024
1 parent 875d788 commit 30d7602
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 50 deletions.
3 changes: 2 additions & 1 deletion distribution/whatsnew/whatsnew-en-US
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Subscription Premium is added for premium features.
Subscription Premium is added for premium features.
Fixed crashes when server returns error.
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ android.useAndroidX=true
android.targetSdk=34
android.compileSdk=34
android.minSdk=24
android.versionCode=19
android.versionName=2.3.1
android.versionCode=20
android.versionName=2.3.2


Binary file not shown.
4 changes: 2 additions & 2 deletions iosApp/iosApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>2.3.1</string>
<string>2.3.2</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand All @@ -36,7 +36,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>24</string>
<string>25</string>
<key>GIDClientID</key>
<string>400988245981-9tv75mdhd0he2unncn3t1g3dq0gur3oc.apps.googleusercontent.com</string>
<key>GIDServerClientID</key>
Expand Down
27 changes: 27 additions & 0 deletions shared/src/commonMain/kotlin/data/BackgroundExecutor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package data

import domain.model.result.ErrorEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.cancellation.CancellationException
import domain.model.result.Result

class BackgroundExecutor(val scope: CoroutineContext = Dispatchers.IO) {

companion object {
val IO by lazy { BackgroundExecutor(Dispatchers.IO) }
}

suspend fun <T> execute(
func: suspend () -> Result<T>
): Result<T> = withContext(scope) {
try {
func.invoke()
} catch (e: Exception) {
if (e is CancellationException) throw e
else Result.error(ErrorEntity.unexpected(e))
}
}
}
2 changes: 2 additions & 0 deletions shared/src/commonMain/kotlin/data/di/DataModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package data.di

import com.russhwolf.settings.Settings
import data.BackgroundExecutor
import data.repository.FlightsRepository
import data.repository.GlobalAppRepository
import data.repository.UserRepository
Expand Down Expand Up @@ -79,6 +80,7 @@ private val remoteSourceModule = module {

private val repositoryModule = module {
factory { Dispatchers.IO } bind CoroutineContext::class
factory { BackgroundExecutor.IO } bind BackgroundExecutor::class
factoryOf(::FlightsRepository)
factoryOf(::GlobalAppRepository)
singleOf(::UserRepository)
Expand Down
35 changes: 15 additions & 20 deletions shared/src/commonMain/kotlin/data/repository/FlightsRepository.kt
Original file line number Diff line number Diff line change
@@ -1,41 +1,36 @@
package data.repository

import data.BackgroundExecutor
import data.source.remote.apiservice.FlightsApiService
import domain.model.FlightLocation
import domain.model.FlightSort
import domain.model.Top5Flights
import domain.model.result.ErrorEntity
import domain.model.result.Result
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

class FlightsRepository(
private val flightsApiService: FlightsApiService,
private val backgroundScope: CoroutineContext = Dispatchers.IO,
private val backgroundExecutor: BackgroundExecutor = BackgroundExecutor.IO,
) {

suspend fun getTop5Flights(
origin: FlightLocation? = null,
maxPrice: Int = 50,
sortBy: FlightSort = FlightSort.BY_PRICE,
): Result<Top5Flights> = withContext(backgroundScope) {
try {
val apiResponse = flightsApiService.getTop5Flights(
origin?.iataCode, maxPrice.toString(), sortBy.value
)
if (apiResponse.hasSuccessfulData()) Result.success(
apiResponse.getSuccessfulData().mapToDomainModel()
)
else Result.error(
ErrorEntity.apiError(
errorMessage = apiResponse.errorMessage, responseCode = apiResponse.responseCode
)
): Result<Top5Flights> = backgroundExecutor.execute {

val apiResponse = flightsApiService.getTop5Flights(
origin?.iataCode, maxPrice.toString(), sortBy.value
)
if (apiResponse.hasSuccessfulData()) Result.success(
apiResponse.getSuccessfulData().mapToDomainModel()
)
else Result.error(
ErrorEntity.apiError(
errorMessage = apiResponse.errorMessage, responseCode = apiResponse.responseCode
)
} catch (e: Exception) {
Result.error(ErrorEntity.apiError(exception = e))
}
)

}

}
27 changes: 12 additions & 15 deletions shared/src/commonMain/kotlin/data/repository/GlobalAppRepository.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package data.repository

import data.BackgroundExecutor
import data.source.remote.apiservice.FlightsApiService
import data.source.remote.apiservice.GlobalApiService
import domain.model.FlightLocation
Expand All @@ -16,24 +17,20 @@ import kotlin.coroutines.CoroutineContext

class GlobalAppRepository(
private val globalApiService: GlobalApiService,
private val backgroundScope: CoroutineContext = Dispatchers.IO,
private val backgroundExecutor: BackgroundExecutor = BackgroundExecutor.IO,
) {

suspend fun getGlobalConfig(): Result<GlobalAppConfig> = withContext(backgroundScope) {
try {
val apiResponse = globalApiService.getGlobalConfigInfo()
if (apiResponse.hasSuccessfulData())
Result.success(apiResponse.getSuccessfulData().mapToDomainModel())
else
Result.error(
ErrorEntity.apiError(
errorMessage = apiResponse.errorMessage,
responseCode = apiResponse.responseCode
)
suspend fun getGlobalConfig(): Result<GlobalAppConfig> = backgroundExecutor.execute {
val apiResponse = globalApiService.getGlobalConfigInfo()
if (apiResponse.hasSuccessfulData())
Result.success(apiResponse.getSuccessfulData().mapToDomainModel())
else
Result.error(
ErrorEntity.apiError(
errorMessage = apiResponse.errorMessage,
responseCode = apiResponse.responseCode
)
} catch (e: Exception) {
Result.error(ErrorEntity.apiError(exception = e))
}
)
}

}
39 changes: 29 additions & 10 deletions shared/src/commonMain/kotlin/data/repository/UserRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import com.mmk.kmprevenuecat.purchases.Purchases
import com.mmk.kmprevenuecat.purchases.PurchasesException
import com.mmk.kmprevenuecat.purchases.awaitCustomerInfo
import com.mmk.kmprevenuecat.purchases.data.CustomerInfo
import data.BackgroundExecutor
import data.source.remote.apiservice.UserApiService
import data.source.remote.request.UserUpdateRequest
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.auth.auth
import domain.model.AuthProvider
import domain.model.Subscription
import domain.model.User
import domain.model.result.Result
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.MainScope
Expand All @@ -20,6 +22,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
Expand All @@ -32,15 +35,17 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import util.logging.AppLogger
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.cancellation.CancellationException

class UserRepository(
private val userApiService: UserApiService,
private val backgroundScope: CoroutineContext = Dispatchers.IO
private val backgroundExecutor: BackgroundExecutor = BackgroundExecutor.IO
) {

private val currentUserSubscriptionFlow = MutableStateFlow<Subscription?>(null)
private val firebaseCurrentUserFlow = Firebase.auth.authStateChanged
.onEach { firebaseUser ->
AppLogger.d("Firebase auth state is changed")
firebaseUser?.let {
Purchases.login(firebaseUser.uid) { subscriptionLoginResult ->
val subscription =
Expand All @@ -52,7 +57,12 @@ class UserRepository(
.map { firebaseUser ->
if (firebaseUser == null) null
else userApiService.createOrGetUser().data?.mapToDomainModel().also {
Purchases.setAttributes(mapOf("\$email" to it?.email,"\$displayName" to it?.displayName))
Purchases.setAttributes(
mapOf(
"\$email" to it?.email,
"\$displayName" to it?.displayName
)
)
}
}

Expand All @@ -61,20 +71,26 @@ class UserRepository(
combine(firebaseCurrentUserFlow, currentUserSubscriptionFlow) { user, subscription ->
user?.copy(subscription = subscription)
}
.flowOn(backgroundScope)
.catch {
AppLogger.e("Exception while getting current user: $it")
emit(null)
}
.flowOn(backgroundExecutor.scope)
.stateIn(MainScope(), SharingStarted.WhileSubscribed(5000), null)


fun refreshUserSubscription(){
fun refreshUserSubscription() {
MainScope().launch {
currentUserSubscriptionFlow.emit(getUserSubscription())
}
}

private suspend fun getUserSubscription(): Subscription? {
AppLogger.d("Get User Subscription is called")
val customerInfo = try {
Purchases.awaitCustomerInfo(CacheFetchPolicy.FETCH_CURRENT)
} catch (e: PurchasesException) {
} catch (e: Exception) {
if (e is CancellationException) throw e
AppLogger.e("Get User Subscription error: $e")
null
} ?: return null
Expand All @@ -93,24 +109,27 @@ class UserRepository(
}
}

suspend fun logOut() = withContext(backgroundScope) {
suspend fun logOut() = backgroundExecutor.execute {
Firebase.auth.signOut()
Purchases.logOut { }
Result.EMPTY
}

suspend fun deleteAccount() = withContext(backgroundScope) {
suspend fun deleteAccount() = backgroundExecutor.execute {
val currentUser = Firebase.auth.currentUser
currentUser?.delete()
kotlin.runCatching { logOut() }
logOut()
Result.EMPTY
}

suspend fun updateProfile(user: User) = withContext(backgroundScope) {
userApiService.updateUser(
suspend fun updateProfile(user: User) = backgroundExecutor.execute {
val updateUserApiResponse = userApiService.updateUser(
userUpdateRequest = UserUpdateRequest(
displayName = user.displayName,
profilePicUrl = user.profilePicSrc
)
)
Result.success(updateUserApiResponse)
}

fun getCurrentUserProviders(): List<AuthProvider> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ object Strings {
const val btn_get_started = "GET STARTED"
const val btn_skip = "SKIP"
const val error_msg_no_signed_in_user = "Error: There is not any signed-in user"
const val error_msg_unknown_error = "Unknown error occurred"
const val msg_success_delete_user = "Your account data is deleted successfully"


Expand Down

0 comments on commit 30d7602

Please sign in to comment.