Skip to content

Commit

Permalink
Inject coroutine scopes and dispatchers instead of hardcoding. (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
prashanDYDX authored Apr 11, 2024
1 parent 0ffc8e3 commit 41f045b
Show file tree
Hide file tree
Showing 13 changed files with 145 additions and 74 deletions.
7 changes: 6 additions & 1 deletion v4/app/src/main/java/exchange/dydx/trading/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import exchange.dydx.platformui.designSystem.theme.ThemeConfig
import exchange.dydx.platformui.designSystem.theme.ThemeSettings
import exchange.dydx.trading.common.AppConfig
import exchange.dydx.trading.common.AppConfigImpl
import exchange.dydx.trading.common.di.CoroutineScopes
import exchange.dydx.trading.common.theme.DydxTheme
import exchange.dydx.trading.common.theme.DydxThemeImpl
import exchange.dydx.trading.feature.shared.PreferenceKeys
Expand All @@ -57,6 +58,7 @@ import exchange.dydx.trading.integration.cosmos.CosmosV4ClientWebview
import exchange.dydx.trading.integration.cosmos.CosmosV4WebviewClientProtocol
import exchange.dydx.utilities.utils.JsonUtils
import exchange.dydx.utilities.utils.SharedPreferencesStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.json.Json
import javax.inject.Singleton
Expand Down Expand Up @@ -109,10 +111,13 @@ interface AppModule {
fun provideLanguageKey(): String = PreferenceKeys.Language

@Provides
fun providePlatformInfo(): PlatformInfo =
fun providePlatformInfo(
@CoroutineScopes.App appScope: CoroutineScope,
): PlatformInfo =
PlatformInfo(
snackbarHostState = SnackbarHostState(),
infoType = MutableStateFlow(PlatformInfo.InfoType.Info),
appScope = appScope,
)

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package exchange.dydx.trading.common.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import javax.inject.Qualifier

object CoroutineDispatchers {

@Qualifier annotation class Main

@Qualifier annotation class IO

@Qualifier annotation class Default
}

@Module
@InstallIn(SingletonComponent::class)
object CoroutineDispatchersModule {
@Provides @CoroutineDispatchers.Main
fun provideMain(): CoroutineDispatcher = Dispatchers.Main

@Provides @CoroutineDispatchers.IO
fun provideIO(): CoroutineDispatcher = Dispatchers.IO

@Provides @CoroutineDispatchers.Default
fun provideDefault(): CoroutineDispatcher = Dispatchers.Default
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package exchange.dydx.trading.common.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.ViewModelLifecycle
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.scopes.ViewModelScoped
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import javax.inject.Qualifier
import javax.inject.Singleton

object CoroutineScopes {
@Qualifier annotation class App

@Qualifier annotation class ViewModel
}

@Module
@InstallIn(SingletonComponent::class)
object AppScopeModule {
@Provides @CoroutineScopes.App @Singleton
fun provideScope(): CoroutineScope = MainScope()
}

@Module
@InstallIn(ViewModelComponent::class)
object ViewModelScopeModule {
@Provides @CoroutineScopes.ViewModel @ViewModelScoped
fun provideScope(viewModelLifecycle: ViewModelLifecycle): CoroutineScope {
val scope = MainScope()
viewModelLifecycle.addOnClearedListener {
scope.cancel()
}
return scope
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import exchange.dydx.abacus.protocols.LocalizerProtocol
import exchange.dydx.platformui.components.PlatformInfo
import exchange.dydx.trading.common.DydxViewModel
import exchange.dydx.trading.common.di.CoroutineDispatchers
import exchange.dydx.trading.common.navigation.DydxRouter
import exchange.dydx.utilities.utils.EmailUtils
import exchange.dydx.utilities.utils.FileUtils
import exchange.dydx.utilities.utils.LogCatReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
Expand All @@ -30,6 +31,7 @@ class DydxReportIssueViewModel @Inject constructor(
private val localizer: LocalizerProtocol,
private val router: DydxRouter,
@ApplicationContext private val context: Context,
@CoroutineDispatchers.IO private val ioDispatcher: CoroutineDispatcher,
val platformInfo: PlatformInfo,
) : ViewModel(), DydxViewModel {

Expand All @@ -44,7 +46,7 @@ class DydxReportIssueViewModel @Inject constructor(

viewModelScope.launch {
var logUri: Uri? = null
withContext(Dispatchers.IO) {
withContext(ioDispatcher) {
// add a delay to show the loading text
kotlinx.coroutines.delay(500)
logUri = createLog()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package exchange.dydx.trading.feature.shared.analytics

import exchange.dydx.dydxstatemanager.AbacusStateManagerProtocol
import exchange.dydx.trading.common.di.CoroutineDispatchers
import exchange.dydx.trading.common.di.CoroutineScopes
import exchange.dydx.trading.integration.analytics.Tracking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.take
Expand All @@ -15,6 +17,8 @@ import javax.inject.Singleton
class OnboardingAnalytics @Inject constructor(
private val tracker: Tracking,
private val abacusStateManager: AbacusStateManagerProtocol,
@CoroutineScopes.App private val appScope: CoroutineScope,
@CoroutineDispatchers.IO private val ioDispatcher: CoroutineDispatcher,
) {
// The three main OnboardingStates:
// - Disconnected
Expand Down Expand Up @@ -46,7 +50,7 @@ class OnboardingAnalytics @Inject constructor(
DEPOSIT_FUNDS("DepositFunds")
}

private val scope = MainScope() + Dispatchers.IO
private val scope = appScope + ioDispatcher

fun log(step: OnboardingSteps) {
abacusStateManager.state.currentWallet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import dagger.hilt.android.scopes.ActivityRetainedScoped
import exchange.dydx.abacus.output.SubaccountOrder
import exchange.dydx.abacus.state.model.TradeInputField
import exchange.dydx.dydxstatemanager.AbacusStateManagerProtocol
import exchange.dydx.trading.common.di.CoroutineScopes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -12,7 +13,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import javax.inject.Inject

interface TradeStreaming {
Expand All @@ -28,10 +28,9 @@ interface MutableTradeStreaming : TradeStreaming {
@ActivityRetainedScoped
class TradeStream @Inject constructor(
val abacusStateManager: AbacusStateManagerProtocol,
@CoroutineScopes.App val appScope: CoroutineScope
) : MutableTradeStreaming {

private val streamScope = CoroutineScope(newSingleThreadContext("TradeStream"))

private var _submissionStatus: MutableStateFlow<AbacusStateManagerProtocol.SubmissionStatus?> =
MutableStateFlow(null)
override val submissionStatus: Flow<AbacusStateManagerProtocol.SubmissionStatus?> = _submissionStatus
Expand Down Expand Up @@ -59,7 +58,7 @@ class TradeStream @Inject constructor(

override fun submitTrade() {
_submissionStatus.update { null }
streamScope.launch {
appScope.launch {
val tradeInput = abacusStateManager.state.tradeInput.first() ?: return@launch

abacusStateManager.placeOrder { submissionStatus ->
Expand All @@ -73,7 +72,7 @@ class TradeStream @Inject constructor(

override fun closePosition() {
_submissionStatus.update { null }
streamScope.launch {
appScope.launch {
val closePositionInput = abacusStateManager.state.closePositionInput.first() ?: return@launch

abacusStateManager.closePosition { submissionStatus ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import android.app.Application
import android.util.Log
import exchange.dydx.integration.javascript.JavascriptApiImpl
import exchange.dydx.integration.javascript.JavascriptRunnerV4
import exchange.dydx.trading.common.di.CoroutineScopes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking
import java.io.IOException
import java.util.Locale
Expand All @@ -17,11 +19,12 @@ private const val TAG = "CosmosV4ClientWebview"
@Singleton
class CosmosV4ClientWebview @Inject constructor(
application: Application,
@CoroutineScopes.App appScope: CoroutineScope,
) : CosmosV4WebviewClientProtocol,
JavascriptApiImpl(
context = application,
description = WEBVIEW_FILENAME,
runner = JavascriptRunnerV4.runnerFromFile(application, WEBVIEW_FILENAME)
runner = JavascriptRunnerV4.runnerFromFile(appScope, application, WEBVIEW_FILENAME)
?: throw IOException("Fatal, unable to load runner from: $WEBVIEW_FILENAME"),
) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ import exchange.dydx.dydxstatemanager.clientState.wallets.DydxWalletInstance
import exchange.dydx.dydxstatemanager.clientState.wallets.DydxWalletStateManagerProtocol
import exchange.dydx.dydxstatemanager.protocolImplementations.UIImplementationsExtensions
import exchange.dydx.trading.common.R
import exchange.dydx.trading.common.di.CoroutineScopes
import exchange.dydx.trading.common.featureflags.DydxFeatureFlag
import exchange.dydx.trading.common.featureflags.DydxFeatureFlags
import exchange.dydx.trading.integration.cosmos.CosmosV4ClientProtocol
import exchange.dydx.utilities.utils.SharedPreferencesStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
Expand Down Expand Up @@ -130,6 +130,7 @@ class AbacusStateManager @Inject constructor(
private val preferencesStore: SharedPreferencesStore,
@EnvKey private val envKey: String,
private val featureFlags: DydxFeatureFlags,
@CoroutineScopes.App private val appScope: CoroutineScope,
parser: ParserProtocol,
) : AbacusStateManagerProtocol, StateNotificationProtocol {

Expand Down Expand Up @@ -429,7 +430,7 @@ class AbacusStateManager @Inject constructor(
}

private fun start() {
CoroutineScope(Dispatchers.Main).launch {
appScope.launch {
val currentWallet = walletStateManager.state.first()?.currentWallet
if (currentWallet != null) {
val ethereumAddress = currentWallet.ethereumAddress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,35 @@ package exchange.dydx.dydxstatemanager.protocolImplementations

import exchange.dydx.abacus.protocols.ThreadingProtocol
import exchange.dydx.abacus.protocols.ThreadingType
import kotlinx.coroutines.Dispatchers
import exchange.dydx.abacus.protocols.ThreadingType.abacus
import exchange.dydx.abacus.protocols.ThreadingType.main
import exchange.dydx.abacus.protocols.ThreadingType.network
import exchange.dydx.trading.common.di.CoroutineDispatchers
import exchange.dydx.trading.common.di.CoroutineScopes
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
@OptIn(ExperimentalCoroutinesApi::class)
class AbacusThreadingImp @Inject constructor() : ThreadingProtocol {
private val mainScope = MainScope()

// Abacus runs lots of computations, but needs to be run without parallelism
private val abacusScope = MainScope() + Dispatchers.Default.limitedParallelism(1)
private val networkScope = MainScope() + Dispatchers.IO
class AbacusThreadingImp @Inject constructor(
@CoroutineScopes.App private val appScope: CoroutineScope,
@CoroutineDispatchers.IO private val ioDispatcher: CoroutineDispatcher,
@CoroutineDispatchers.Default private val defaultDispatcher: CoroutineDispatcher,
) : ThreadingProtocol {
override fun async(type: ThreadingType, block: () -> Unit) {
when (type) {
ThreadingType.main ->
mainScope
.launch {
block()
}

ThreadingType.abacus ->
abacusScope
.launch {
block()
}

ThreadingType.network ->
networkScope
.launch {
block()
}
appScope.launch {
when (type) {
main -> block()
// Abacus runs lots of computations, but needs to be run without parallelism
abacus -> withContext(defaultDispatcher.limitedParallelism(1)) { block() }
network -> withContext(ioDispatcher) { block() }
}
}
}
}
Loading

0 comments on commit 41f045b

Please sign in to comment.