diff --git a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt index d76bd6cd67..0a78c5558c 100644 --- a/app/src/main/java/com/tangem/tap/DecomposeFragment.kt +++ b/app/src/main/java/com/tangem/tap/DecomposeFragment.kt @@ -35,7 +35,7 @@ internal class DecomposeFragment : ComposeFragment() { ) } - private class ComponentBuilder>( + private class ComponentBuilder>( private val contextProvider: Provider, private val params: P, private val componentFactory: F, @@ -48,7 +48,7 @@ internal class DecomposeFragment : ComposeFragment() { private var componentBuilder: ComponentBuilder<*, *, *>? = null - fun > newInstance( + fun > newInstance( contextProvider: Provider, params: P, componentFactory: F, diff --git a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt index c1203a4758..201f65ecad 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/RouteMappers.kt @@ -22,7 +22,7 @@ internal fun AppRoute.asFragmentChild(fragmentProvider: Provider): Chi return Child.LegacyFragment(path, provider) } -internal fun > AppRoute.asComponentChild( +internal fun > AppRoute.asComponentChild( contextProvider: Provider, params: P, componentFactory: F, diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt index b5df451866..74fba5a48e 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/di/DecomposeComponent.kt @@ -1,5 +1,6 @@ package com.tangem.core.decompose.di +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import dagger.BindsInstance @@ -37,6 +38,14 @@ interface DecomposeComponent { */ fun uiMessageSender(@BindsInstance uiMessageSender: UiMessageSender): Builder + /** + * Sets the parameters container for the component. + * + * @param container The parameters container to set. + * @return The builder instance. + */ + fun paramsContainer(@BindsInstance container: ParamsContainer): Builder + /** * Builds the Decompose component. * diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/factory/ComponentFactory.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/factory/ComponentFactory.kt index b365fcea59..20c1159324 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/factory/ComponentFactory.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/factory/ComponentFactory.kt @@ -2,7 +2,7 @@ package com.tangem.core.decompose.factory import com.tangem.core.decompose.context.AppComponentContext -interface ComponentFactory { +interface ComponentFactory

{ - fun create(context: AppComponentContext, params: Params): Component + fun create(context: AppComponentContext, params: P): C } \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt index 58cad4cf7a..286678b4c3 100644 --- a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ModelEntryPoint.kt @@ -22,24 +22,43 @@ interface ModelsEntryPoint { } /** - * Gets or creates a component's [Model]. + * Gets or creates a component's [Model] with no parameters. */ -inline fun AppComponentContext.getOrCreateModel(): M { - val modelKey = "model_${M::class.simpleName}" +inline fun AppComponentContext.getOrCreateModel(): M = getOrCreateModel(params = null) +/** + * Gets or creates a component's [Model]. + * + * Be careful with objects you pass in parameters as they will be used in [Model] lifecycle. + * If you pass a object with different lifecycle than the [Model] then you can face memory leaks. + * + * @param params The parameters to store in the [ParamsContainer], + + */ +inline fun AppComponentContext.getOrCreateModel(params: P?): M { val entryPoint = instanceKeeper.getOrCreateSimple(key = "modelsEntryPoint") { val hiltComponent = hiltComponentBuilder .router(router) .uiMessageSender(messageSender) + .let { builder -> + if (params != null) { + val container = MutableParamsContainer(params) + + builder.paramsContainer(container) + } else { + builder + } + } .build() EntryPoints.get(hiltComponent, ModelsEntryPoint::class.java) } + val modelKey = "model_${M::class.simpleName}" val model = instanceKeeper.getOrCreate(modelKey) { requireNotNull(entryPoint.models()[M::class.java]?.get()) { "Model ${M::class.simpleName} is not provided" - } + } as M } val isModelExist = tags.getOrElse(modelKey) { false } as Boolean @@ -47,5 +66,5 @@ inline fun AppComponentContext.getOrCreateModel(): M { tags[modelKey] = true } - return model as M + return model } \ No newline at end of file diff --git a/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt new file mode 100644 index 0000000000..910764d661 --- /dev/null +++ b/core/decompose/src/main/kotlin/com/tangem/core/decompose/model/ParamsContainer.kt @@ -0,0 +1,45 @@ +package com.tangem.core.decompose.model + +/** + * Lazy container for [Model] params. + * + * This contrainer can be accessed by DI because it's provided via [com.tangem.core.decompose.di.DecomposeComponent]. + * */ +interface ParamsContainer { + + /** Returns stored value if it is of type [T], otherwise returns null. */ + fun get(): T? + + /** Returns stored value if it is of type [T], otherwise throws an exception. */ + fun require(): T +} + +/** + * Mutable implementation of [ParamsContainer]. + * + * ***You should not use this class directly in other modules, use immutable [ParamsContainer] instead.*** + * */ +class MutableParamsContainer private constructor() : ParamsContainer { + + private var value: Any? = null + + /** Stores [value] inside a container, replaces any previous stored value. */ + fun set(value: Any) { + this.value = value + } + + @Suppress("UNCHECKED_CAST") + override fun get(): T? = value as? T + + override fun require(): T = get() ?: error("Contrainer is empty or contains a value of a different type.") + + companion object { + + /** Creates a new [MutableParamsContainer] and stores [value] inside it. */ + operator fun invoke(value: T): MutableParamsContainer { + return MutableParamsContainer().apply { + set(value) + } + } + } +} \ No newline at end of file diff --git a/features/details/api/build.gradle.kts b/features/details/api/build.gradle.kts index 103bd2c268..ce267fe5d1 100644 --- a/features/details/api/build.gradle.kts +++ b/features/details/api/build.gradle.kts @@ -16,7 +16,4 @@ dependencies { /* Project - Core */ implementation(projects.core.decompose) implementation(projects.core.ui) - - /* AndroidX */ - implementation(deps.androidx.fragment.ktx) } \ No newline at end of file diff --git a/features/details/api/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt b/features/details/api/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt index f53bc84d85..8634199b45 100644 --- a/features/details/api/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt +++ b/features/details/api/src/main/kotlin/com/tangem/features/details/component/DetailsComponent.kt @@ -1,18 +1,14 @@ package com.tangem.features.details.component -import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.ComposableContentComponent import com.tangem.domain.wallets.models.UserWalletId interface DetailsComponent : ComposableContentComponent { - interface Factory : ComponentFactory { - - override fun create(context: AppComponentContext, params: Params): DetailsComponent - } + interface Factory : ComponentFactory data class Params( - val selectedUserWalletId: UserWalletId, + val userWalletId: UserWalletId, ) } \ No newline at end of file diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt index 08d302a183..9a9c5575d1 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/component/impl/DefaultDetailsComponent.kt @@ -17,20 +17,16 @@ import dagger.assisted.AssistedInject internal class DefaultDetailsComponent @AssistedInject constructor( @Assisted context: AppComponentContext, - @Assisted private val params: DetailsComponent.Params, + @Assisted params: DetailsComponent.Params, userWalletListComponentFactory: UserWalletListComponent.Factory, ) : DetailsComponent, AppComponentContext by context { - private val model: DetailsModel = getOrCreateModel() + private val model: DetailsModel = getOrCreateModel(params) private val userWalletListComponent = userWalletListComponentFactory.create( context = child(key = "user_wallet_list"), ) - init { - model.provideUserWalletId(params.selectedUserWalletId) - } - @Composable override fun Content(modifier: Modifier) { val state by model.state.collectAsStateWithLifecycle() diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index 189172b899..f1bbd73bdb 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -3,9 +3,10 @@ package com.tangem.features.details.model import arrow.core.getOrElse import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.domain.walletconnect.CheckIsWalletConnectAvailableUseCase -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.features.details.component.DetailsComponent import com.tangem.features.details.entity.DetailsFooterUM import com.tangem.features.details.entity.DetailsItemUM import com.tangem.features.details.entity.DetailsUM @@ -24,15 +25,19 @@ import timber.log.Timber import javax.inject.Inject @ComponentScoped +@Suppress("LongParameterList") internal class DetailsModel @Inject constructor( private val socialsBuilder: SocialsBuilder, private val itemsBuilder: ItemsBuilder, private val appVersionProvider: AppVersionProvider, private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase, private val router: Router, + private val paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, ) : Model() { + private val params: DetailsComponent.Params = paramsContainer.require() + private val items: MutableStateFlow> = MutableStateFlow(value = persistentListOf()) val state: MutableStateFlow = MutableStateFlow( @@ -50,10 +55,12 @@ internal class DetailsModel @Inject constructor( items .onEach(::updateState) .launchIn(modelScope) + + checkWalletConnectAvailability() } - fun provideUserWalletId(userWalletId: UserWalletId) = modelScope.launch { - val isWalletConnectAvailable = checkIsWalletConnectAvailableUseCase(userWalletId).getOrElse { + private fun checkWalletConnectAvailability() = modelScope.launch { + val isWalletConnectAvailable = checkIsWalletConnectAvailableUseCase(params.userWalletId).getOrElse { Timber.w("Unable to check WalletConnect availability: $it") false