From a1393f831712e109f141699674a4cb2e3e9e6c3d Mon Sep 17 00:00:00 2001 From: grechka62 Date: Mon, 26 May 2025 17:36:37 +0300 Subject: [PATCH] Make resolver the only source of truth commit_hash:d1e09b695ddeab7dd443a0002811c5252b7e24fd --- .../actions/DivActionTypedSubmitHandler.kt | 20 +++--- .../animation/DivVariableAnimatorBuilder.kt | 15 ++-- .../core/expression/ExpressionResolverImpl.kt | 20 ++---- .../div/core/expression/ExpressionsRuntime.kt | 8 +-- .../expression/ExpressionsRuntimeProvider.kt | 22 +++--- .../expression/local/DivRuntimeVisitor.kt | 9 +-- .../div/core/expression/local/RuntimeStore.kt | 10 +-- .../yandex/div/core/expression/local/utils.kt | 6 ++ .../variables/TwoWayVariableBinder.kt | 20 ++---- .../yandex/div/core/view2/BindingContext.kt | 13 ++-- .../com/yandex/div/core/view2/Div2View.kt | 2 +- .../core/view2/divs/DivCollectionAdapter.kt | 4 +- .../div/core/view2/divs/DivStateBinder.kt | 8 +-- .../internal/core/DivCollectionExtensions.kt | 4 +- .../internal/core/VariableMutationHandler.kt | 16 ++--- .../expression/ExpressionResolverImplTest.kt | 4 +- .../core/expression/local/RuntimeStoreTest.kt | 20 +++--- .../variables/TwoWayVariableBinderTest.kt | 68 +++++++++---------- 18 files changed, 119 insertions(+), 150 deletions(-) diff --git a/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt b/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt index e11c7c682..e9a6c7544 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/actions/DivActionTypedSubmitHandler.kt @@ -2,12 +2,15 @@ package com.yandex.div.core.actions import com.yandex.div.core.DivActionHandler import com.yandex.div.core.DivRequestExecutor +import com.yandex.div.core.expression.local.variableController import com.yandex.div.core.expression.name import com.yandex.div.core.state.DivStatePath import com.yandex.div.core.view2.BindingContext import com.yandex.div.core.view2.Div2View import com.yandex.div.evaluable.MissingVariableException +import com.yandex.div.internal.core.DivItemBuilderResult import com.yandex.div.internal.core.DivTreeVisitor +import com.yandex.div.internal.core.toItemBuilderResult import com.yandex.div.json.expressions.ExpressionResolver import com.yandex.div2.Div import com.yandex.div2.DivAction @@ -30,7 +33,7 @@ class DivActionTypedSubmitHandler @Inject constructor( val submitAction = (action as? DivActionTyped.Submit)?.value ?: return false val containerId = submitAction.containerId.evaluate(resolver) - val (containerDiv, context, path) = ContainerFinder(containerId).findContainer(view) ?: return false + val container = ContainerFinder(containerId).findContainer(view) ?: return false val headers = submitAction.request.headers?.map { DivRequestExecutor.Header(it.name.evaluate(resolver), it.value.evaluate(resolver)) @@ -40,7 +43,7 @@ class DivActionTypedSubmitHandler @Inject constructor( submitAction.request.url.evaluate(resolver), submitAction.request.method.evaluate(resolver).toString(), headers, - createBody(containerDiv, context, path, view), + createBody(container, view), ) val callback = createCallback(submitAction.onSuccessActions, submitAction.onFailActions, view, resolver) @@ -49,12 +52,11 @@ class DivActionTypedSubmitHandler @Inject constructor( return true } - private fun createBody(div: Div, context: BindingContext, path: DivStatePath, view: Div2View): String { - val variables = div.value().variables + private fun createBody(container: DivItemBuilderResult, view: Div2View): String { + val variables = container.div.value().variables if (variables.isNullOrEmpty()) return "" - val variableController = context.runtimeStore - ?.getOrCreateRuntime(path.fullPath, div, context.expressionResolver)?.variableController ?: return "" + val variableController = container.expressionResolver.variableController ?: return "" val body = JSONObject() variables.forEach { @@ -93,9 +95,9 @@ class DivActionTypedSubmitHandler @Inject constructor( private class ContainerFinder(private val id: String) : DivTreeVisitor() { - private val containers = mutableListOf>() + private val containers = mutableListOf() - fun findContainer(view: Div2View): Triple? { + fun findContainer(view: Div2View): DivItemBuilderResult? { val data = view.divData ?: return null data.states.forEach { state -> visit(state.div, view.bindingContext, DivStatePath.fromState(state)) @@ -120,7 +122,7 @@ class DivActionTypedSubmitHandler @Inject constructor( override fun defaultVisit(data: Div, context: BindingContext, path: DivStatePath) { if (data.value().id == id) { - containers.add(Triple(data, context, path)) + containers.add(data.toItemBuilderResult(context.expressionResolver)) } } } diff --git a/client/android/div/src/main/java/com/yandex/div/core/animation/DivVariableAnimatorBuilder.kt b/client/android/div/src/main/java/com/yandex/div/core/animation/DivVariableAnimatorBuilder.kt index e090ea36c..7433eb447 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/animation/DivVariableAnimatorBuilder.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/animation/DivVariableAnimatorBuilder.kt @@ -10,6 +10,7 @@ import com.yandex.div.core.actions.colorIntValue import com.yandex.div.core.actions.doubleValue import com.yandex.div.core.actions.logError import com.yandex.div.core.actions.longValue +import com.yandex.div.core.expression.local.variableController import com.yandex.div.core.util.androidInterpolator import com.yandex.div.core.util.isAlternated import com.yandex.div.core.util.isReversed @@ -50,7 +51,7 @@ internal object DivVariableAnimatorBuilder { startAction: DivActionAnimatorStart, resolver: ExpressionResolver ): Animator? { - return when (val variable = findVariable(divView, animator.variableName, resolver)) { + return when (val variable = findVariable(animator.variableName, resolver)) { is Variable.IntegerVariable -> buildIntegerAnimator(divView, animator, startAction, resolver, variable) is Variable.DoubleVariable -> buildDoubleAnimator(divView, animator, startAction, resolver, variable) @@ -103,7 +104,7 @@ internal object DivVariableAnimatorBuilder { startAction: DivActionAnimatorStart, resolver: ExpressionResolver ): Animator? { - val variable = findVariable(divView, animator.variableName, resolver) + val variable = findVariable(animator.variableName, resolver) if (variable == null) { divView.logError(MissingVariableException("Unable to find color variable with name '${animator.variableName}'")) return null @@ -152,12 +153,6 @@ internal object DivVariableAnimatorBuilder { return this } - private inline fun findVariable( - divView: Div2View, - name: String, - resolver: ExpressionResolver, - ): T? { - val runtime = divView.runtimeStore?.getRuntimeWithOrNull(resolver) ?: divView.expressionsRuntime - return runtime?.variableController?.getMutableVariable(name) as? T - } + private inline fun findVariable(name: String, resolver: ExpressionResolver) = + resolver.variableController?.getMutableVariable(name) as? T } diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt index e5e37e85a..64ac25604 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionResolverImpl.kt @@ -30,10 +30,10 @@ internal class ExpressionResolverImpl( /** * Unique path by which resolver could be found at [RuntimeStore]. */ - internal val path: String, - internal val runtimeStore: RuntimeStore, - private val variableController: VariableController, - private val evaluator: Evaluator, + val path: String, + val runtimeStore: RuntimeStore, + val variableController: VariableController, + val evaluator: Evaluator, private val errorCollector: ErrorCollector, private val onCreateCallback: OnCreateCallback, ) : ExpressionResolver { @@ -46,11 +46,7 @@ internal class ExpressionResolverImpl( var suppressMissingVariableException: Boolean = false init { - onCreateCallback.onCreate( - this, - variableController, - evaluator.evaluationContext.functionProvider as FunctionProviderDecorator - ) + onCreateCallback.onCreate(this) } override fun get( @@ -289,10 +285,6 @@ internal class ExpressionResolverImpl( * as a new ExpressionRuntime we are using OnCreateCallback. */ internal fun interface OnCreateCallback { - fun onCreate( - resolver: ExpressionResolverImpl, - variableController: VariableController, - functionProvider: FunctionProviderDecorator - ) + fun onCreate(resolver: ExpressionResolverImpl) } } diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt index 44edcf62b..963b57e96 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntime.kt @@ -1,19 +1,17 @@ package com.yandex.div.core.expression import com.yandex.div.core.DivViewFacade -import com.yandex.div.core.expression.local.RuntimeStore import com.yandex.div.core.expression.triggers.TriggersController -import com.yandex.div.core.expression.variables.VariableController internal class ExpressionsRuntime( val expressionResolver: ExpressionResolverImpl, - val variableController: VariableController, val triggersController: TriggersController? = null, - val functionProvider: FunctionProviderDecorator, - val runtimeStore: RuntimeStore, ) { private var unsubscribed = true + val variableController = expressionResolver.variableController + val runtimeStore = expressionResolver.runtimeStore + fun clearBinding(view: DivViewFacade) { triggersController?.clearBinding(view) } diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt index 632416530..2902b5636 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/ExpressionsRuntimeProvider.kt @@ -8,7 +8,6 @@ import com.yandex.div.core.expression.local.RuntimeStore import com.yandex.div.core.expression.storedvalues.StoredValuesController import com.yandex.div.core.expression.triggers.TriggersController import com.yandex.div.core.expression.variables.DivVariableController -import com.yandex.div.core.expression.variables.VariableController import com.yandex.div.core.expression.variables.VariableControllerImpl import com.yandex.div.core.expression.variables.toVariable import com.yandex.div.core.view2.Div2View @@ -20,7 +19,6 @@ import com.yandex.div.data.VariableDeclarationException import com.yandex.div.evaluable.EvaluationContext import com.yandex.div.evaluable.Evaluator import com.yandex.div.evaluable.function.GeneratedBuiltinFunctionProvider -import com.yandex.div.json.expressions.ExpressionResolver import com.yandex.div2.DivData import com.yandex.div2.DivVariable import java.util.Collections @@ -46,7 +44,7 @@ internal class ExpressionsRuntimeProvider @Inject constructor( val result = runtimes.getOrPut(tag.id) { createRuntimeFor(data, tag) } val errorCollector = errorCollectors.getOrCreate(tag, data) divDataTags.getOrPut(div2View, ::mutableSetOf).add(tag.id) - ensureVariablesSynced(result.variableController, result.expressionResolver, data, errorCollector) + ensureVariablesSynced(result.expressionResolver, data, errorCollector) result.triggersController?.ensureTriggersSynced(data.variableTriggers ?: emptyList()) return result } @@ -69,11 +67,11 @@ internal class ExpressionsRuntimeProvider @Inject constructor( } private fun ensureVariablesSynced( - v: VariableController, - resolver: ExpressionResolver, + resolver: ExpressionResolverImpl, data: DivData, errorCollector: ErrorCollector ) { + val v = resolver.variableController data.variables?.forEach { val existingVariable = v.getMutableVariable(it.name) ?: run { try { @@ -133,9 +131,9 @@ internal class ExpressionsRuntimeProvider @Inject constructor( val evaluator = Evaluator(evaluationContext) val runtimeStore = RuntimeStore(evaluator, errorCollector, logger, divActionBinder) - val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider -> + val callback = ExpressionResolverImpl.OnCreateCallback { resolver -> runtimeStore.putRuntime( - runtime = ExpressionsRuntime(resolver, variableController, null, functionProvider, runtimeStore) + runtime = ExpressionsRuntime(resolver, null) ) } @@ -165,13 +163,9 @@ internal class ExpressionsRuntimeProvider @Inject constructor( divActionBinder ) - return ExpressionsRuntime( - expressionResolver, - variableController, - triggersController, - functionProvider, - runtimeStore, - ).also { runtimeStore.rootRuntime = it } + return ExpressionsRuntime(expressionResolver, triggersController).also { + runtimeStore.rootRuntime = it + } } } diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/local/DivRuntimeVisitor.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/local/DivRuntimeVisitor.kt index e9fa24cfb..57a875fab 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/local/DivRuntimeVisitor.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/local/DivRuntimeVisitor.kt @@ -2,6 +2,7 @@ package com.yandex.div.core.expression.local import com.yandex.div.core.annotations.Mockable import com.yandex.div.core.dagger.DivScope +import com.yandex.div.core.expression.ExpressionResolverImpl import com.yandex.div.core.expression.ExpressionsRuntime import com.yandex.div.core.state.DivPathUtils.getId import com.yandex.div.core.state.DivStatePath @@ -160,7 +161,7 @@ internal class DivRuntimeVisitor @Inject constructor( div: DivState, divView: Div2View, states: MutableList, - parentRuntime: ExpressionsRuntime, + resolver: ExpressionResolverImpl, ): String? { val statePath = states.joinToString("/") val cardId = divView.divTag.id @@ -168,9 +169,9 @@ internal class DivRuntimeVisitor @Inject constructor( return temporaryStateCache.getState(cardId, statePath) ?: divStateCache.getState(cardId, statePath) ?: div.stateIdVariable?.let { - parentRuntime.variableController.getMutableVariable(it)?.getValue().toString() + resolver.variableController.getMutableVariable(it)?.getValue().toString() } - ?: parentRuntime.let { div.defaultStateId?.evaluate(it.expressionResolver) } + ?: div.defaultStateId?.evaluate(resolver) ?: div.states.firstOrNull()?.stateId } @@ -182,7 +183,7 @@ internal class DivRuntimeVisitor @Inject constructor( parentRuntime: ExpressionsRuntime, ) { states.add(div.getId()) - val activeStateId = getActiveStateId(div, divView, states, parentRuntime) + val activeStateId = getActiveStateId(div, divView, states, parentRuntime.expressionResolver) div.states.forEach { val childDiv = it.div ?: return@forEach val childPath = path.appendChild(it.stateId) diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt index a27ba06c0..50a3c0aea 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/local/RuntimeStore.kt @@ -5,6 +5,7 @@ import com.yandex.div.core.DivViewFacade import com.yandex.div.core.ObserverList import com.yandex.div.core.expression.ExpressionResolverImpl import com.yandex.div.core.expression.ExpressionsRuntime +import com.yandex.div.core.expression.FunctionProviderDecorator import com.yandex.div.core.expression.triggers.TriggersController import com.yandex.div.core.expression.variables.VariableController import com.yandex.div.core.expression.variables.VariableControllerImpl @@ -46,8 +47,8 @@ internal class RuntimeStore( } private val onCreateCallback by lazy { - ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider -> - ExpressionsRuntime(resolver, variableController, null, functionProvider, this).also { + ExpressionResolverImpl.OnCreateCallback { resolver -> + ExpressionsRuntime(resolver, null).also { /** * we cannot provide path here, otherwise descendants of ExpressionResolver will * receive the same callback and override runtime for provided path. @@ -155,7 +156,8 @@ internal class RuntimeStore( val localVariableController = VariableControllerImpl(baseRuntime.variableController) val functions = div.functions - var functionProvider = baseRuntime.functionProvider + var functionProvider = + baseRuntime.expressionResolver.evaluator.evaluationContext.functionProvider as FunctionProviderDecorator if (!functions.isNullOrEmpty()) { functionProvider += functions.toLocalFunctions() } @@ -183,7 +185,7 @@ internal class RuntimeStore( val triggerController = div.variableTriggers.toTriggersController(localVariableController, resolver, evaluator) - return ExpressionsRuntime(resolver, localVariableController, triggerController, functionProvider, this).also { + return ExpressionsRuntime(resolver, triggerController).also { putRuntime(it, path, parentRuntime) } } diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/local/utils.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/local/utils.kt index 1f7e4b382..781dea713 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/local/utils.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/local/utils.kt @@ -1,6 +1,12 @@ package com.yandex.div.core.expression.local +import com.yandex.div.core.expression.ExpressionResolverImpl +import com.yandex.div.json.expressions.ExpressionResolver import com.yandex.div2.Div internal val Div.needLocalRuntime get() = !value().run { variables.isNullOrEmpty() && variableTriggers.isNullOrEmpty() && functions.isNullOrEmpty() } + +internal val ExpressionResolver.asImpl get() = this as? ExpressionResolverImpl + +internal val ExpressionResolver.variableController get() = asImpl?.variableController diff --git a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/TwoWayVariableBinder.kt b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/TwoWayVariableBinder.kt index 5f9fd09a6..d70e07bd9 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/expression/variables/TwoWayVariableBinder.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/expression/variables/TwoWayVariableBinder.kt @@ -4,7 +4,7 @@ import androidx.annotation.MainThread import com.yandex.div.core.Disposable import com.yandex.div.core.annotations.Mockable import com.yandex.div.core.dagger.DivScope -import com.yandex.div.core.expression.ExpressionsRuntimeProvider +import com.yandex.div.core.expression.local.variableController import com.yandex.div.core.state.DivStatePath import com.yandex.div.core.view2.BindingContext import com.yandex.div.core.view2.errors.ErrorCollectors @@ -16,8 +16,7 @@ import javax.inject.Inject @Mockable internal class TwoWayStringVariableBinder @Inject constructor( errorCollectors: ErrorCollectors, - expressionsRuntimeProvider: ExpressionsRuntimeProvider -) : TwoWayVariableBinder(errorCollectors, expressionsRuntimeProvider) { +) : TwoWayVariableBinder(errorCollectors) { interface Callbacks : TwoWayVariableBinder.Callbacks @@ -28,8 +27,7 @@ internal class TwoWayStringVariableBinder @Inject constructor( @Mockable internal class TwoWayIntegerVariableBinder @Inject constructor( errorCollectors: ErrorCollectors, - expressionsRuntimeProvider: ExpressionsRuntimeProvider -) : TwoWayVariableBinder(errorCollectors, expressionsRuntimeProvider) { +) : TwoWayVariableBinder(errorCollectors) { interface Callbacks : TwoWayVariableBinder.Callbacks @@ -40,8 +38,7 @@ internal class TwoWayIntegerVariableBinder @Inject constructor( @Mockable internal class TwoWayBooleanVariableBinder @Inject constructor( errorCollectors: ErrorCollectors, - expressionsRuntimeProvider: ExpressionsRuntimeProvider -) : TwoWayVariableBinder(errorCollectors, expressionsRuntimeProvider) { +) : TwoWayVariableBinder(errorCollectors) { interface Callbacks : TwoWayVariableBinder.Callbacks @@ -49,10 +46,7 @@ internal class TwoWayBooleanVariableBinder @Inject constructor( } @Mockable -internal abstract class TwoWayVariableBinder( - private val errorCollectors: ErrorCollectors, - private val expressionsRuntimeProvider: ExpressionsRuntimeProvider -) { +internal abstract class TwoWayVariableBinder(private val errorCollectors: ErrorCollectors) { @Mockable interface Callbacks { @@ -72,9 +66,7 @@ internal abstract class TwoWayVariableBinder( var pendingValue: T? = null val tag = divView.dataTag - val variableController = bindingContext.runtimeStore?.let { runtimeStore -> - runtimeStore.getRuntimeWithOrNull(bindingContext.expressionResolver)?.variableController - } ?: expressionsRuntimeProvider.getOrCreate(tag, data, divView).variableController + val variableController = bindingContext.expressionResolver.variableController ?: return Disposable.NULL callbacks.setViewStateChangeListener { value -> if (pendingValue == value) return@setViewStateChangeListener diff --git a/client/android/div/src/main/java/com/yandex/div/core/view2/BindingContext.kt b/client/android/div/src/main/java/com/yandex/div/core/view2/BindingContext.kt index e527b4ec8..1566ee974 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/view2/BindingContext.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/view2/BindingContext.kt @@ -1,20 +1,19 @@ package com.yandex.div.core.view2 -import com.yandex.div.core.expression.local.RuntimeStore +import com.yandex.div.core.expression.local.asImpl import com.yandex.div.json.expressions.ExpressionResolver internal class BindingContext private constructor( val divView: Div2View, val expressionResolver: ExpressionResolver, - val runtimeStore: RuntimeStore?, ) { - fun getFor(resolver: ExpressionResolver) = - if (expressionResolver == resolver) this else BindingContext(divView, resolver, runtimeStore) - fun getFor(resolver: ExpressionResolver, runtimeStore: RuntimeStore?) = - if (expressionResolver == resolver) this else BindingContext(divView, resolver, runtimeStore) + val runtimeStore = expressionResolver.asImpl?.runtimeStore + + fun getFor(resolver: ExpressionResolver) = + if (expressionResolver == resolver) this else BindingContext(divView, resolver) companion object { - fun createEmpty(divView: Div2View) = BindingContext(divView, ExpressionResolver.EMPTY, null) + fun createEmpty(divView: Div2View) = BindingContext(divView, ExpressionResolver.EMPTY) } } diff --git a/client/android/div/src/main/java/com/yandex/div/core/view2/Div2View.kt b/client/android/div/src/main/java/com/yandex/div/core/view2/Div2View.kt index 17c4b002a..e84ffb59b 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/view2/Div2View.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/view2/Div2View.kt @@ -225,7 +225,7 @@ class Div2View private constructor( runtimeStore?.clearBindings(this) } runtimeStore = expressionsRuntime?.runtimeStore - bindingContext = bindingContext.getFor(expressionResolver, runtimeStore) + bindingContext = bindingContext.getFor(expressionResolver) } private fun attachVariableTriggers(data: DivData) { diff --git a/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivCollectionAdapter.kt b/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivCollectionAdapter.kt index 576f86570..ac4bb1155 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivCollectionAdapter.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivCollectionAdapter.kt @@ -5,7 +5,7 @@ import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import com.yandex.div.core.downloader.DivPatchApply import com.yandex.div.core.downloader.DivPatchCache -import com.yandex.div.core.expression.ExpressionResolverImpl +import com.yandex.div.core.expression.local.asImpl import com.yandex.div.core.view2.BindingContext import com.yandex.div.internal.core.DivItemBuilderResult import com.yandex.div.internal.core.toDivItemBuilderResult @@ -131,7 +131,7 @@ internal abstract class DivCollectionAdapter( } private fun DivItemBuilderResult.suppressMissingVariableException(suppress: Boolean) { - (expressionResolver as? ExpressionResolverImpl)?.suppressMissingVariableException = suppress + expressionResolver.asImpl?.suppressMissingVariableException = suppress } } diff --git a/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivStateBinder.kt b/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivStateBinder.kt index 4e4497dd5..47d1c53bf 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivStateBinder.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/view2/divs/DivStateBinder.kt @@ -5,6 +5,7 @@ import android.view.ViewGroup import android.view.animation.AnimationSet import androidx.core.view.children import androidx.core.view.doOnNextLayout +import androidx.core.view.isNotEmpty import androidx.transition.Transition import androidx.transition.TransitionManager import androidx.transition.TransitionSet @@ -15,6 +16,7 @@ import com.yandex.div.core.dagger.DivScope import com.yandex.div.core.downloader.DivPatchCache import com.yandex.div.core.downloader.DivPatchManager import com.yandex.div.core.expression.local.DivRuntimeVisitor +import com.yandex.div.core.expression.local.variableController import com.yandex.div.core.expression.variables.TwoWayStringVariableBinder import com.yandex.div.core.state.DivPathUtils.getId import com.yandex.div.core.state.DivStatePath @@ -152,7 +154,7 @@ internal class DivStateBinder @Inject constructor( val newStateDiv = newState.div val newStateDivValue = newStateDiv?.value() - val outgoing = if (childCount > 0) getChildAt(0) else null + val outgoing = if (isNotEmpty()) getChildAt(0) else null val incoming: View? val reusableIncomingView = newStateDiv?.let { divView.currentRebindReusableList?.getUniqueViewForDiv(newStateDiv) @@ -271,9 +273,7 @@ internal class DivStateBinder @Inject constructor( } private fun getValueFromVariable(context: BindingContext, variableName: String): String? { - val variableController = context.runtimeStore?.getRuntimeWithOrNull(context.expressionResolver)?.variableController - ?: context.divView.expressionsRuntime?.variableController ?: return null - + val variableController = context.expressionResolver.variableController ?: return null return variableController.getMutableVariable(variableName)?.getValue()?.toString() } diff --git a/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt b/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt index 32dbf99d4..18adf1427 100644 --- a/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt +++ b/client/android/div/src/main/java/com/yandex/div/internal/core/DivCollectionExtensions.kt @@ -2,7 +2,7 @@ package com.yandex.div.internal.core import com.yandex.div.core.DivViewFacade import com.yandex.div.core.annotations.InternalApi -import com.yandex.div.core.expression.ExpressionResolverImpl +import com.yandex.div.core.expression.local.asImpl import com.yandex.div.core.expression.variables.ConstantsProvider import com.yandex.div.internal.Assert import com.yandex.div.internal.util.forEach @@ -85,7 +85,7 @@ private fun DivCollectionItemBuilder.getItemResolver( index: Int, resolver: ExpressionResolver, ): ExpressionResolver? { - val resolverImpl = resolver as? ExpressionResolverImpl ?: return resolver + val resolverImpl = resolver.asImpl ?: return resolver val validElement = resolverImpl.validateItemBuilderDataElement(dataElement, index) ?: return null val localDataProvider = ConstantsProvider(mapOf( dataElementName to validElement, diff --git a/client/android/div/src/main/java/com/yandex/div/internal/core/VariableMutationHandler.kt b/client/android/div/src/main/java/com/yandex/div/internal/core/VariableMutationHandler.kt index 856141f4d..c981aa9e2 100644 --- a/client/android/div/src/main/java/com/yandex/div/internal/core/VariableMutationHandler.kt +++ b/client/android/div/src/main/java/com/yandex/div/internal/core/VariableMutationHandler.kt @@ -2,6 +2,7 @@ package com.yandex.div.internal.core import com.yandex.div.core.actions.logError import com.yandex.div.core.annotations.InternalApi +import com.yandex.div.core.expression.local.variableController import com.yandex.div.core.view2.Div2View import com.yandex.div.data.Variable import com.yandex.div.data.VariableMutationException @@ -21,7 +22,7 @@ class VariableMutationHandler { value: String, resolver: ExpressionResolver ): VariableMutationException? { - val mutableVariable = findVariable(div2View, name, resolver) + val mutableVariable = findVariable(name, resolver) ?: return createAndReportError(null, div2View, "Variable '$name' not defined!") runCatching { @@ -45,7 +46,7 @@ class VariableMutationHandler { resolver: ExpressionResolver, valueMutation: (T) -> T, ): VariableMutationException? { - val mutableVariable = findVariable(div2View, name, resolver) + val mutableVariable = findVariable(name, resolver) ?: return createAndReportError(null, div2View, "Variable '$name' not defined!") runCatching { @@ -58,15 +59,8 @@ class VariableMutationHandler { return null } - private fun findVariable( - div2View: Div2View, - name: String, - resolver: ExpressionResolver, - ): Variable? { - val runtime = div2View.runtimeStore?.getRuntimeWithOrNull(resolver) ?: div2View.expressionsRuntime - - return runtime?.variableController?.getMutableVariable(name) - } + private fun findVariable(name: String, resolver: ExpressionResolver) = + resolver.variableController?.getMutableVariable(name) private fun createAndReportError( e: Throwable?, diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt index f84a037a2..20c43916c 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/ExpressionResolverImplTest.kt @@ -86,7 +86,7 @@ class ExpressionResolverImplTest { externalVariables, Evaluator(evaluationContext), mock() - ) { _, _, _ -> } + ) { _ -> } private val withFuncGetCallback = { callback: () -> Unit -> ExpressionResolverImpl( @@ -112,7 +112,7 @@ class ExpressionResolverImplTest { ) ), mock() - ) { _, _, _ -> } + ) { _ -> } } @Before diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt index 0002da131..9e059720d 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/local/RuntimeStoreTest.kt @@ -47,24 +47,20 @@ class RuntimeStoreTest { private val underTest = RuntimeStore(evaluator, errorCollector, div2Logger, divActionBinder) private var runtimeFromCallback: ExpressionsRuntime? = null - private val callback = ExpressionResolverImpl.OnCreateCallback { resolver, variableController, functionProvider -> - runtimeFromCallback = ExpressionsRuntime( - resolver, variableController, null, functionProvider, underTest - ) + private val callback = ExpressionResolverImpl.OnCreateCallback { resolver -> + runtimeFromCallback = ExpressionsRuntime(resolver, null) underTest.putRuntime(runtimeFromCallback!!) } private val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) private val rootVariableController: VariableController = mock() - private val functionProvider = mock() private val divBase = mock() private val divView = mock() private val div = mock
{ on { value() } doReturn divBase } private val rootResolver = ExpressionResolverImpl("", mock(), rootVariableController, evaluator, errorCollector, callback) - private val rootRuntime: ExpressionsRuntime = - ExpressionsRuntime(rootResolver, rootVariableController, null, functionProvider, underTest) + private val rootRuntime: ExpressionsRuntime = ExpressionsRuntime(rootResolver, null) @Before fun putRootResolver() { @@ -104,7 +100,7 @@ class RuntimeStoreTest { @Test fun `getOrCreateRuntime returns runtime for path if exist`() { val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) - val runtime = ExpressionsRuntime(resolver, mock(), null, functionProvider, underTest) + val runtime = ExpressionsRuntime(resolver, null) underTest.putRuntime(runtime, PATH, rootRuntime) Assert.assertEquals(runtime, underTest.getOrCreateRuntime(path.fullPath, div, resolver)) @@ -115,8 +111,7 @@ class RuntimeStoreTest { @Test fun `getOrCreateRuntime returns parent runtime for path if no variables provided`() { val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) - val parentVariableController = mock() - val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest) + val runtime = ExpressionsRuntime(resolver, null) underTest.putRuntime(runtime, PARENT_PATH, rootRuntime) Assert.assertEquals(runtime, underTest.getOrCreateRuntime(path.fullPath, div, parentResolver = resolver)) @@ -125,11 +120,12 @@ class RuntimeStoreTest { @Test fun `getOrCreateRuntime returns wrapped parent runtime with new variables if new variables provided`() { - val resolver = ExpressionResolverImpl("", mock(), mock(), evaluator, errorCollector, callback) val parentVariableController = mock { on { getMutableVariable(PARENT_VARIABLE) } doReturn Variable.StringVariable(PARENT_VARIABLE, "123") } - val runtime = ExpressionsRuntime(resolver, parentVariableController, null, functionProvider, underTest) + val resolver = + ExpressionResolverImpl("", mock(), parentVariableController, evaluator, errorCollector, callback) + val runtime = ExpressionsRuntime(resolver, null) setVariable() underTest.putRuntime(runtime, PARENT_PATH, rootRuntime) diff --git a/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt b/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt index b72c24f32..a8cf37d8a 100644 --- a/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt +++ b/client/android/div/src/test/java/com/yandex/div/core/expression/variables/TwoWayVariableBinderTest.kt @@ -1,23 +1,16 @@ package com.yandex.div.core.expression.variables +import com.yandex.div.core.Disposable import com.yandex.div.core.expression.ExpressionResolverImpl -import com.yandex.div.core.expression.ExpressionsRuntime -import com.yandex.div.core.expression.ExpressionsRuntimeProvider -import com.yandex.div.core.expression.FunctionProviderDecorator -import com.yandex.div.core.expression.local.RuntimeStore -import com.yandex.div.core.state.DivStatePath import com.yandex.div.core.view2.BindingContext import com.yandex.div.core.view2.Div2View -import com.yandex.div.core.view2.errors.ErrorCollector -import com.yandex.div.core.view2.errors.ErrorCollectors import com.yandex.div.data.Variable -import com.yandex.div.evaluable.EvaluationContext -import com.yandex.div.evaluable.Evaluator import com.yandex.div2.DivData import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.any +import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn @@ -34,30 +27,32 @@ private const val ANOTHER_VALUE = "another_value" @RunWith(RobolectricTestRunner::class) class TwoWayVariableBinderTest { - private val errorCollector = mock() - private val errorCollectors = mock { - on { getOrCreate(any(), any()) } doReturn errorCollector - } private val variable = Variable.StringVariable(VARIABLE_NAME, INITIAL_VALUE) - private val variableController = VariableControllerImpl().apply { - declare(variable) + private val variableUpdateCaptor = argumentCaptor<(Variable?) -> Unit>() + private val invokeOnSubscriptionCaptor = argumentCaptor() + private val variableController = mock { + on { getMutableVariable(VARIABLE_NAME) } doReturn variable + on { + subscribeToVariableChange( + any(), + anyOrNull(), + invokeOnSubscriptionCaptor.capture(), + variableUpdateCaptor.capture() + ) + } doAnswer { + if (invokeOnSubscriptionCaptor.firstValue) { + variableUpdateCaptor.firstValue.invoke(variable) + } + Disposable.NULL + } } - private val path = DivStatePath(0) - private val store = RuntimeStore(mock(), mock(), mock(), mock()) - private val evaluationContext = EvaluationContext(mock(), mock(), mock(), mock()) - private val evaluator = mock { - on { evaluationContext } doReturn evaluationContext - } - private val expressionResolver = ExpressionResolverImpl("", mock(), mock(), evaluator, mock(), mock()) - private val expressionsRuntime = ExpressionsRuntime(expressionResolver, variableController, mock(), mock(), store) - private val expressionsRuntimeProvider = mock { - on { getOrCreate(any(), any(), any()) } doReturn expressionsRuntime + private val expressionResolver = mock { + on { variableController } doReturn variableController } private val divView = mock { on { dataTag } doReturn mock() on { divData } doReturn DivData(logId = "test", states = emptyList()) - on { runtimeStore } doReturn store } private val updateCaptor = argumentCaptor<(String) -> Unit>() private val callbacks = mock { @@ -65,10 +60,8 @@ class TwoWayVariableBinderTest { } init { - store.rootRuntime = expressionsRuntime - val bindingContext = BindingContext.createEmpty(divView).getFor(expressionResolver, store) - TwoWayStringVariableBinder(errorCollectors, expressionsRuntimeProvider) - .bindVariable(bindingContext, VARIABLE_NAME, callbacks, path) + val bindingContext = BindingContext.createEmpty(divView).getFor(expressionResolver) + TwoWayStringVariableBinder(mock()).bindVariable(bindingContext, VARIABLE_NAME, callbacks, mock()) } @Test @@ -78,7 +71,7 @@ class TwoWayVariableBinderTest { @Test fun `invoke callback on variable change`() { - variable.set(NEW_VALUE) + updateVariable() verify(callbacks).onVariableChanged(NEW_VALUE) } @@ -91,13 +84,13 @@ class TwoWayVariableBinderTest { @Test fun `not invoke callback on variable change after view state change with same value`() { updateCaptor.firstValue.invoke(NEW_VALUE) - variable.set(NEW_VALUE) + updateVariable() verify(callbacks, never()).onVariableChanged(NEW_VALUE) } @Test fun `not set variable value on view state change after variable change with same value`() { - variable.set(NEW_VALUE) + updateVariable() val variableObserver = mock<(Variable) -> Unit>() variable.addObserver(variableObserver) @@ -109,14 +102,19 @@ class TwoWayVariableBinderTest { @Test fun `invoke callback on variable change after view state change with another value`() { updateCaptor.firstValue.invoke(NEW_VALUE) - variable.set(ANOTHER_VALUE) + updateVariable(ANOTHER_VALUE) verify(callbacks).onVariableChanged(ANOTHER_VALUE) } @Test fun `set variable value on view state change after variable change with another value`() { - variable.set(NEW_VALUE) + updateVariable() updateCaptor.firstValue.invoke(ANOTHER_VALUE) assertEquals(ANOTHER_VALUE, variable.getValue()) } + + private fun updateVariable(value: String = NEW_VALUE) { + variable.set(value) + variableUpdateCaptor.firstValue.invoke(variable) + } }