Skip to content

Commit

Permalink
Cleanup coroutines usage (#2820)
Browse files Browse the repository at this point in the history
- Use `CoroutineContext` instead of `CoroutineDispatcher`
- Instantiate `CoroutineScope` when starting operation
  • Loading branch information
mshafrir-stripe authored Sep 8, 2020
1 parent 3d06178 commit 99bb797
Show file tree
Hide file tree
Showing 29 changed files with 156 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,30 @@ import com.stripe.example.Settings
import com.stripe.example.module.BackendApiFactory
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext

/**
* An implementation of [EphemeralKeyProvider] that can be used to generate
* ephemeral keys on the backend.
*/
internal class ExampleEphemeralKeyProvider(
backendUrl: String
backendUrl: String,
private val workContext: CoroutineContext
) : EphemeralKeyProvider {
constructor(context: Context) : this(Settings(context).backendUrl)
constructor(context: Context) : this(
Settings(context).backendUrl,
Dispatchers.IO
)

private val workScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private val backendApi = BackendApiFactory(backendUrl).create()

override fun createEphemeralKey(
@Size(min = 4) apiVersion: String,
keyUpdateListener: EphemeralKeyUpdateListener
) {
workScope.launch {
CoroutineScope(workContext).launch {
val response =
kotlin.runCatching {
backendApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.IOException
import kotlin.coroutines.CoroutineContext

internal fun interface AnalyticsRequestExecutor {
/**
Expand All @@ -15,10 +16,10 @@ internal fun interface AnalyticsRequestExecutor {
fun executeAsync(request: AnalyticsRequest)

class Default(
private val logger: Logger = Logger.noop()
private val logger: Logger = Logger.noop(),
private val workContext: CoroutineContext = Dispatchers.IO
) : AnalyticsRequestExecutor {
private val connectionFactory = ConnectionFactory.Default()
private val scope = CoroutineScope(Dispatchers.IO)

/**
* Make the request and ignore the response
Expand All @@ -39,7 +40,7 @@ internal fun interface AnalyticsRequestExecutor {
}

override fun executeAsync(request: AnalyticsRequest) {
scope.launch {
CoroutineScope(workContext).launch {
runCatching {
execute(request)
}.recover {
Expand Down
17 changes: 8 additions & 9 deletions stripe/src/main/java/com/stripe/android/ApiOperation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,21 @@ import com.stripe.android.exception.InvalidRequestException
import com.stripe.android.exception.StripeException
import com.stripe.android.model.StripeModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONException
import java.io.IOException
import kotlin.coroutines.CoroutineContext

internal abstract class ApiOperation<out ResultType : StripeModel>(
private val workScope: CoroutineScope = CoroutineScope(IO),
private val workContext: CoroutineContext = Dispatchers.IO,
private val callback: ApiResultCallback<ResultType>
) {
internal abstract suspend fun getResult(): ResultType?

internal fun execute() {
workScope.launch {
CoroutineScope(workContext).launch {
val result: Result<ResultType?> = try {
Result.success(getResult())
} catch (e: StripeException) {
Expand All @@ -38,14 +38,13 @@ internal abstract class ApiOperation<out ResultType : StripeModel>(
)
}

// dispatch the API operation result to the main thread
withContext(Main) {
dispatchResult(result)
}
dispatchResult(result)
}
}

private fun dispatchResult(result: Result<ResultType?>) {
private suspend fun dispatchResult(
result: Result<ResultType?>
) = withContext(Dispatchers.Main) {
result.fold(
onSuccess = {
when {
Expand Down
10 changes: 5 additions & 5 deletions stripe/src/main/java/com/stripe/android/CustomerSession.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.ShippingInformation
import com.stripe.android.model.Source
import com.stripe.android.model.Source.SourceType
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancelChildren
import java.util.Calendar
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.coroutines.CoroutineContext

/**
* Represents a logged-in session of a single Customer.
Expand All @@ -29,7 +29,7 @@ class CustomerSession @VisibleForTesting internal constructor(
stripeRepository: StripeRepository,
publishableKey: String,
stripeAccountId: String?,
private val workDispatcher: CoroutineDispatcher = createCoroutineDispatcher(),
private val workContext: CoroutineContext = createCoroutineDispatcher(),
private val operationIdFactory: OperationIdFactory = StripeOperationIdFactory(),
private val timeSupplier: TimeSupplier = { Calendar.getInstance().timeInMillis },
ephemeralKeyManagerFactory: EphemeralKeyManager.Factory
Expand All @@ -49,7 +49,7 @@ class CustomerSession @VisibleForTesting internal constructor(
publishableKey,
stripeAccountId
),
workDispatcher,
workContext,
listeners
)
)
Expand Down Expand Up @@ -458,7 +458,7 @@ class CustomerSession @VisibleForTesting internal constructor(
@JvmSynthetic
internal fun cancel() {
listeners.clear()
workDispatcher.cancelChildren()
workContext.cancelChildren()
}

private fun <L : RetrievalListener?> getListener(operationId: String): L? {
Expand Down Expand Up @@ -587,7 +587,7 @@ class CustomerSession @VisibleForTesting internal constructor(
instance?.cancel()
}

private fun createCoroutineDispatcher(): CoroutineDispatcher {
private fun createCoroutineDispatcher(): CoroutineContext {
return ThreadPoolExecutor(
THREAD_POOL_SIZE,
THREAD_POOL_SIZE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package com.stripe.android

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext

internal class CustomerSessionEphemeralKeyManagerListener(
private val runnableFactory: CustomerSessionRunnableFactory,
private val workDispatcher: CoroutineDispatcher,
private val workContext: CoroutineContext,
private val listeners: MutableMap<String, CustomerSession.RetrievalListener?>
) : EphemeralKeyManager.KeyManagerListener {
override fun onKeyUpdate(
ephemeralKey: EphemeralKey,
operation: EphemeralOperation
) {
runnableFactory.create(ephemeralKey, operation)?.let {
CoroutineScope(workDispatcher).launch {
CoroutineScope(workContext).launch {
it.run()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.stripe.android

import android.content.Context
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Calendar
import kotlin.coroutines.CoroutineContext

internal interface FingerprintDataRepository {
fun refresh()
Expand All @@ -17,26 +17,25 @@ internal interface FingerprintDataRepository {
private val fingerprintRequestFactory: FingerprintRequestFactory,
private val fingerprintRequestExecutor: FingerprintRequestExecutor =
FingerprintRequestExecutor.Default(),
dispatcher: CoroutineDispatcher = Dispatchers.IO
private val workContext: CoroutineContext
) : FingerprintDataRepository {
private var cachedFingerprintData: FingerprintData? = null

private val timestampSupplier: () -> Long = {
Calendar.getInstance().timeInMillis
}

private val scope = CoroutineScope(dispatcher)

constructor(
context: Context
) : this(
localStore = FingerprintDataStore.Default(context),
fingerprintRequestFactory = FingerprintRequestFactory(context)
fingerprintRequestFactory = FingerprintRequestFactory(context),
workContext = Dispatchers.IO
)

override fun refresh() {
if (Stripe.advancedFraudSignalsEnabled) {
scope.launch {
CoroutineScope(workContext).launch {
localStore.get().let { localFingerprintData ->
if (localFingerprintData == null ||
localFingerprintData.isExpired(timestampSupplier())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.stripe.android

import com.stripe.android.model.parsers.FingerprintDataJsonParser
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Calendar
import kotlin.coroutines.CoroutineContext

internal interface FingerprintRequestExecutor {
suspend fun execute(
Expand All @@ -13,15 +13,15 @@ internal interface FingerprintRequestExecutor {

class Default(
private val connectionFactory: ConnectionFactory = ConnectionFactory.Default(),
private val workDispatcher: CoroutineDispatcher = Dispatchers.IO
private val workContext: CoroutineContext = Dispatchers.IO
) : FingerprintRequestExecutor {
private val timestampSupplier = {
Calendar.getInstance().timeInMillis
}

override suspend fun execute(
request: FingerprintRequest
) = withContext(workDispatcher) {
) = withContext(workContext) {
// fingerprint request failures should be non-fatal
runCatching {
executeInternal(request)
Expand Down
Loading

0 comments on commit 99bb797

Please sign in to comment.