diff --git a/arkoala-arkts/arkui/src/DataChangeListener.ts b/arkoala-arkts/arkui/src/DataChangeListener.ts new file mode 100644 index 0000000000000000000000000000000000000000..72531abd39cd64fe92d61dca3281ff0f75b8c00f --- /dev/null +++ b/arkoala-arkts/arkui/src/DataChangeListener.ts @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { pointer, nullptr } from "@koalaui/interop"; +import { DataOperation, DataOperationType, DataAddOperation, DataDeleteOperation, DataChangeOperation, DataMoveOperation, DataExchangeOperation, LazyForEachOps } from "./generated"; +import { int32 } from "@koalaui/common" + +export interface DataChangeListener { + onDataReloaded(): void; + onDataAdded(index: number): void; + onDataAdd(index: number): void; + onDataMoved(from: number, to: number): void; + onDataMove(from: number, to: number): void; + onDataDeleted(index: number): void; + onDataDelete(index: number): void; + onDataChanged(index: number): void; + onDataChange(index: number): void; + onDatasetChange(dataOperations: DataOperation[]): void; +} + +export class InternalListener implements DataChangeListener { + parent: pointer = nullptr + startIndex = Number.NEGATIVE_INFINITY // Tracks the minimum item index that has changed + endIndex = Number.NEGATIVE_INFINITY + changeCount = 0 // Tracks the number of items added or deleted + + constructor(parent: pointer) { + this.parent = parent; + } + /** + * Notify the change of data to backend + */ + flush(offset: int32): void { + if (this.startIndex === Number.NEGATIVE_INFINITY) { + return + } + // Notify the change with the cached index and count + LazyForEachOps.NotifyChange( + this.parent, + this.startIndex as int32 + offset, + this.endIndex as int32 + offset, + this.changeCount as int32 + ); + // Reset the cache after flushing + this.startIndex = Number.NEGATIVE_INFINITY; + this.endIndex = Number.NEGATIVE_INFINITY; + this.changeCount = 0; + } + + onDataReloaded(): void { + this.startIndex = 0; + this.endIndex = Number.POSITIVE_INFINITY + } + + onDataAdd(index: number): void { + this.startIndex = Math.min(this.startIndex, index); + ++this.changeCount + } + + onDataMove(from: number, to: number): void { + this.startIndex = Math.min(this.startIndex, Math.min(from, to)); + this.endIndex = Math.max(this.endIndex, Math.max(from, to)); + } + + onDataDelete(index: number): void { + this.startIndex = Math.min(this.startIndex, index); + --this.changeCount + } + + onDataChange(index: number): void { + this.startIndex = Math.min(this.startIndex, index); + } + + onDatasetChange(dataOperations: DataOperation[]): void { + // Iterate through each operation and update the cache + for (const operation of dataOperations) { + switch (operation.type) { + case DataOperationType.ADD: { + const addOp = operation as DataAddOperation; + this.onDataAdd(addOp.index) + break; + } + case DataOperationType.DELETE: { + const deleteOp = operation as DataDeleteOperation; + this.onDataDelete(deleteOp.index) + break; + } + case DataOperationType.CHANGE: { + const changeOp = operation as DataChangeOperation; + this.onDataChange(changeOp.index) + break; + } + case DataOperationType.MOVE: { + const moveOp = operation as DataMoveOperation; + this.onDataMove(moveOp.index.from, moveOp.index.to) + break; + } + case DataOperationType.EXCHANGE: { + const exchangeOp = operation as DataExchangeOperation; + this.onDataMove(exchangeOp.index.start, exchangeOp.index.end) + break; + } + case DataOperationType.RELOAD: { + this.onDataReloaded() + break; + } + } + } + } + + /* deprecated */ + onDataAdded(index: number): void { + this.onDataAdd(index) + } + onDataMoved(from: number, to: number): void { + this.onDataMove(from, to) + } + onDataDeleted(index: number): void { + this.onDataDelete(index) + } + onDataChanged(index: number): void { + this.onDataChange(index) + } +} \ No newline at end of file diff --git a/arkoala-arkts/arkui/src/LazyForEach.ts b/arkoala-arkts/arkui/src/LazyForEach.ts index b62b4cee920b72df0220489d659ddbe0135760dc..aeced6024ea177b832abc38c7d148fc92029dd20 100644 --- a/arkoala-arkts/arkui/src/LazyForEach.ts +++ b/arkoala-arkts/arkui/src/LazyForEach.ts @@ -18,9 +18,10 @@ import { hashCodeFromString, int32, KoalaCallsiteKey } from "@koalaui/common" import { nullptr, pointer } from "@koalaui/interop"; import { LazyForEachType, PeerNode, PeerNodeType } from "./PeerNode"; import { LazyForEachOps } from "./generated/ArkLazyForEachOpsMaterialized" +import { DataChangeListener, InternalListener } from "./DataChangeListener"; + +export { DataChangeListener } from "./DataChangeListener"; // temporary compilation workaround -// TODO: proper import [DataChangeListener] from lazy_for_each.d.ts -export type DataChangeListener = Object /** * Developers need to implement this interface to provide data to LazyForEach component. * @since 7 @@ -164,12 +165,16 @@ export function LazyForEach(dataSource: IDataSource, console.log(`LazyForEach current=${current.value} version=${version.value} mark=${mark.value}`) let parent = contextNode() - const offset = getOffset(parent, __id()) + + let listener = remember(() => new InternalListener(parent.peer.ptr)) + listener.flush(offset) + const currentLocal = current.value >= 0 ? Math.max(current.value - offset, 0) as int32 : -1; // translated to local index console.log(`currentLocal = ${currentLocal}`) const visibleRange = new VisibleRange(parent, currentLocal, currentLocal) remember(() => { + dataSource.registerDataChangeListener(listener) LazyForEachManager.OnRangeUpdate(visibleRange.parent, dataSource.totalCount() as int32, (currentIndex: int32, currentMark: pointer, end: int32) => { console.log(`LazyForEach[${parent}]: current updated to ${currentIndex} ${currentMark} end=${end}`) current.value = currentIndex diff --git a/arkoala-arkts/arkui/src/generated/ArkLazyForEachInterfaces.ts b/arkoala-arkts/arkui/src/generated/ArkLazyForEachInterfaces.ts index 933f06927ad4b71fe0d6fe21e1be34c5d3e5be36..8d8422da3536c9fe97903c88ee1922b3fd1281c1 100644 --- a/arkoala-arkts/arkui/src/generated/ArkLazyForEachInterfaces.ts +++ b/arkoala-arkts/arkui/src/generated/ArkLazyForEachInterfaces.ts @@ -27,11 +27,22 @@ export enum DataOperationType { CHANGE = "change", RELOAD = "reload" } +export interface DataAddOperation { + type: DataOperationType; + index: number; + count?: number; + key?: string | Array; +} export interface DataDeleteOperation { type: DataOperationType; index: number; count?: number; } +export interface DataChangeOperation { + type: DataOperationType; + index: number; + key?: string; +} export interface MoveIndex { from: number; to: number; @@ -54,6 +65,7 @@ export interface DataExchangeOperation { index: ExchangeIndex; key?: ExchangeKey; } -export interface DataOperation { - stub: string; +export interface DataReloadOperation { + type: DataOperationType; } +export type DataOperation = DataAddOperation | DataDeleteOperation | DataChangeOperation | DataMoveOperation | DataExchangeOperation | DataReloadOperation; diff --git a/arkoala-arkts/arkui/src/generated/ArkLazyForEachOpsMaterialized.ts b/arkoala-arkts/arkui/src/generated/ArkLazyForEachOpsMaterialized.ts index 881ef79a987984c70f4611be9b2ea1fc86030aa6..b7bb9caa0588e5ab77b94a0df9ab3f27fb2609f7 100644 --- a/arkoala-arkts/arkui/src/generated/ArkLazyForEachOpsMaterialized.ts +++ b/arkoala-arkts/arkui/src/generated/ArkLazyForEachOpsMaterialized.ts @@ -76,6 +76,14 @@ export class LazyForEachOps implements MaterializedBase { LazyForEachOps.Prepare_serialize(node_casted, itemCount_casted, offset_casted) return } + public static NotifyChange(node: KPointer, startIdx: int32, endIdx: int32, count: int32): void { + const node_casted = node as (KPointer) + const startIdx_casted = startIdx as (int32) + const endIdx_casted = endIdx as (int32) + const count_casted = count as (int32) + LazyForEachOps.NotifyChange_serialize(node_casted, startIdx_casted, endIdx_casted, count_casted) + return + } private static NeedMoreElements_serialize(node: KPointer, mark: KPointer, direction: int32): KPointer { const retval = ArkUIGeneratedNativeModule._LazyForEachOps_NeedMoreElements(node, mark, direction) return retval @@ -92,4 +100,7 @@ export class LazyForEachOps implements MaterializedBase { private static Prepare_serialize(node: KPointer, itemCount: int32, offset: int32): void { ArkUIGeneratedNativeModule._LazyForEachOps_Prepare(node, itemCount, offset) } + private static NotifyChange_serialize(node: KPointer, startIdx: int32, endIdx: int32, count: int32): void { + ArkUIGeneratedNativeModule._LazyForEachOps_NotifyChange(node, startIdx, endIdx, count) + } } diff --git a/arkoala-arkts/arkui/src/generated/arkts/ArkUIGeneratedNativeModule.ts b/arkoala-arkts/arkui/src/generated/arkts/ArkUIGeneratedNativeModule.ts index c607dcedb1622597b038a428b58bfe89c4fe76c4..dba2d02efbc09255ecb4e2604396f8a14091e78f 100644 --- a/arkoala-arkts/arkui/src/generated/arkts/ArkUIGeneratedNativeModule.ts +++ b/arkoala-arkts/arkui/src/generated/arkts/ArkUIGeneratedNativeModule.ts @@ -1576,6 +1576,7 @@ export class ArkUIGeneratedNativeModule { native static _LazyForEachOps_OnRangeUpdate(node: KPointer, totalCount: KInt, thisArray: KUint8ArrayPtr, thisLength: int32): void native static _LazyForEachOps_SetCurrentIndex(node: KPointer, index: KInt): void native static _LazyForEachOps_Prepare(node: KPointer, itemCount: KInt, offset: KInt): void + native static _LazyForEachOps_NotifyChange(node: KPointer, startIdx: KInt, endIdx: KInt, count: KInt): void native static _SystemOps_ctor(): KPointer native static _SystemOps_getFinalizer(): KPointer native static _SystemOps_StartFrame(): KPointer diff --git a/arkoala-arkts/arkui/src/generated/arkts/type_check.ts b/arkoala-arkts/arkui/src/generated/arkts/type_check.ts index 3018c4f982f5f663fdcb91ac034cf6076a473440..e2b139b65d506e2aa50d549dfaaed8752c62f17e 100644 --- a/arkoala-arkts/arkui/src/generated/arkts/type_check.ts +++ b/arkoala-arkts/arkui/src/generated/arkts/type_check.ts @@ -146,7 +146,7 @@ import { ColorFilter } from "./../ArkColorFilterMaterialized" import { ImageAnimatorInterface, ImageAnimatorAttribute, ImageFrameInfo } from "./../ArkImageAnimatorInterfaces" import { ImageAnalyzerController } from "./../ArkImageAnalyzerControllerMaterialized" import { ImageSpanInterface, ImageSpanAttribute, ImageCompleteCallback, ImageLoadResult } from "./../ArkImageSpanInterfaces" -import { DataOperationType, DataDeleteOperation, MoveIndex, ExchangeIndex, ExchangeKey, DataMoveOperation, DataExchangeOperation, DataOperation } from "./../ArkLazyForEachInterfaces" +import { DataOperationType, DataAddOperation, DataDeleteOperation, DataChangeOperation, MoveIndex, ExchangeIndex, ExchangeKey, DataMoveOperation, DataExchangeOperation, DataReloadOperation } from "./../ArkLazyForEachInterfaces" import { LineOptions, LineInterface, LineAttribute } from "./../ArkLineInterfaces" import { ListScroller } from "./../ArkListScrollerMaterialized" import { Sticky, EditMode, SwipeEdgeEffect, SwipeActionState, SwipeActionItem, SwipeActionOptions, ListItemStyle, ListItemOptions, ListItemInterface, ListItemAttribute } from "./../ArkListItemInterfaces" @@ -935,6 +935,12 @@ export class TypeChecker { static isCutEvent(value: object|string|number|undefined|null, arg0: boolean): boolean { return value instanceof CutEvent } + static isDataAddOperation(value: object|string|number|undefined|null, arg0: boolean, arg1: boolean, arg2: boolean, arg3: boolean): boolean { + return value instanceof DataAddOperation + } + static isDataChangeOperation(value: object|string|number|undefined|null, arg0: boolean, arg1: boolean, arg2: boolean): boolean { + return value instanceof DataChangeOperation + } static isDataDeleteOperation(value: object|string|number|undefined|null, arg0: boolean, arg1: boolean, arg2: boolean): boolean { return value instanceof DataDeleteOperation } @@ -944,9 +950,6 @@ export class TypeChecker { static isDataMoveOperation(value: object|string|number|undefined|null, arg0: boolean, arg1: boolean, arg2: boolean): boolean { return value instanceof DataMoveOperation } - static isDataOperation(value: object|string|number|undefined|null, arg0: boolean): boolean { - return value instanceof DataOperation - } static isDataOperationType(value: object|string|number|undefined|null): boolean { return value instanceof DataOperationType } @@ -968,6 +971,9 @@ export class TypeChecker { static isDataPanelType(value: object|string|number|undefined|null): boolean { return value instanceof DataPanelType } + static isDataReloadOperation(value: object|string|number|undefined|null, arg0: boolean): boolean { + return value instanceof DataReloadOperation + } static isDataResubmissionHandler(value: object|string|number|undefined|null): boolean { return value instanceof DataResubmissionHandler } diff --git a/arkoala-arkts/arkui/src/generated/ts/type_check.ts b/arkoala-arkts/arkui/src/generated/ts/type_check.ts index 00fc3c6fd000273e0bda5c0e3d1f9cb82a0dfe4b..736588589c674f5b8751a561bacdaa7973829cc2 100644 --- a/arkoala-arkts/arkui/src/generated/ts/type_check.ts +++ b/arkoala-arkts/arkui/src/generated/ts/type_check.ts @@ -146,7 +146,7 @@ import { ColorFilter } from "./../ArkColorFilterMaterialized" import { ImageAnimatorInterface, ImageAnimatorAttribute, ImageFrameInfo } from "./../ArkImageAnimatorInterfaces" import { ImageAnalyzerController } from "./../ArkImageAnalyzerControllerMaterialized" import { ImageSpanInterface, ImageSpanAttribute, ImageCompleteCallback, ImageLoadResult } from "./../ArkImageSpanInterfaces" -import { DataOperationType, DataDeleteOperation, MoveIndex, ExchangeIndex, ExchangeKey, DataMoveOperation, DataExchangeOperation, DataOperation } from "./../ArkLazyForEachInterfaces" +import { DataOperationType, DataAddOperation, DataDeleteOperation, DataChangeOperation, MoveIndex, ExchangeIndex, ExchangeKey, DataMoveOperation, DataExchangeOperation, DataReloadOperation } from "./../ArkLazyForEachInterfaces" import { LineOptions, LineInterface, LineAttribute } from "./../ArkLineInterfaces" import { ListScroller } from "./../ArkListScrollerMaterialized" import { Sticky, EditMode, SwipeEdgeEffect, SwipeActionState, SwipeActionItem, SwipeActionOptions, ListItemStyle, ListItemOptions, ListItemInterface, ListItemAttribute } from "./../ArkListItemInterfaces" @@ -4017,6 +4017,37 @@ export class TypeChecker { throw new Error("Can not discriminate value typeof CutEvent") } } + static isDataAddOperation(value: object|string|number|undefined|null|boolean, duplicated_type: boolean, duplicated_index: boolean, duplicated_count: boolean, duplicated_key: boolean): boolean { + if ((!duplicated_type) && (value?.hasOwnProperty("type"))) { + return true + } + else if ((!duplicated_index) && (value?.hasOwnProperty("index"))) { + return true + } + else if ((!duplicated_count) && (value?.hasOwnProperty("count"))) { + return true + } + else if ((!duplicated_key) && (value?.hasOwnProperty("key"))) { + return true + } + else { + throw new Error("Can not discriminate value typeof DataAddOperation") + } + } + static isDataChangeOperation(value: object|string|number|undefined|null|boolean, duplicated_type: boolean, duplicated_index: boolean, duplicated_key: boolean): boolean { + if ((!duplicated_type) && (value?.hasOwnProperty("type"))) { + return true + } + else if ((!duplicated_index) && (value?.hasOwnProperty("index"))) { + return true + } + else if ((!duplicated_key) && (value?.hasOwnProperty("key"))) { + return true + } + else { + throw new Error("Can not discriminate value typeof DataChangeOperation") + } + } static isDataDeleteOperation(value: object|string|number|undefined|null|boolean, duplicated_type: boolean, duplicated_index: boolean, duplicated_count: boolean): boolean { if ((!duplicated_type) && (value?.hasOwnProperty("type"))) { return true @@ -4059,14 +4090,6 @@ export class TypeChecker { throw new Error("Can not discriminate value typeof DataMoveOperation") } } - static isDataOperation(value: object|string|number|undefined|null|boolean, duplicated_stub: boolean): boolean { - if ((!duplicated_stub) && (value?.hasOwnProperty("stub"))) { - return true - } - else { - throw new Error("Can not discriminate value typeof DataOperation") - } - } static isDataOperationType(value: object|string|number|undefined|null|boolean): boolean { if ((value) === (DataOperationType.ADD)) { return true @@ -4160,6 +4183,14 @@ export class TypeChecker { throw new Error("Can not discriminate value typeof DataPanelType") } } + static isDataReloadOperation(value: object|string|number|undefined|null|boolean, duplicated_type: boolean): boolean { + if ((!duplicated_type) && (value?.hasOwnProperty("type"))) { + return true + } + else { + throw new Error("Can not discriminate value typeof DataReloadOperation") + } + } static isDataResubmissionHandler(value: object|string|number|undefined|null|boolean): boolean { throw new Error("Can not discriminate value typeof DataResubmissionHandler") } diff --git a/arkoala-arkts/arkui/types/index-full.d.ts b/arkoala-arkts/arkui/types/index-full.d.ts index e12309d8d44a3ce2abebda1b10de473c26a79d35..d20f8f56e1e003aa4d5fe537b362111eabfdc3e9 100644 --- a/arkoala-arkts/arkui/types/index-full.d.ts +++ b/arkoala-arkts/arkui/types/index-full.d.ts @@ -4307,11 +4307,22 @@ declare enum DataOperationType { CHANGE = "change", RELOAD = "reload", } +declare interface DataAddOperation { + type: DataOperationType; + index: number; + count?: number; + key?: (string|string[]); +} declare interface DataDeleteOperation { type: DataOperationType; index: number; count?: number; } +declare interface DataChangeOperation { + type: DataOperationType; + index: number; + key?: string; +} declare interface MoveIndex { from: number; to: number; @@ -4334,9 +4345,10 @@ declare interface DataExchangeOperation { index: ExchangeIndex; key?: ExchangeKey; } -declare interface DataOperation { - stub: string; +declare interface DataReloadOperation { + type: DataOperationType; } +declare type DataOperation = (DataAddOperation|DataDeleteOperation|DataChangeOperation|DataMoveOperation|DataExchangeOperation|DataReloadOperation); declare interface DataChangeListener { onDataReloaded(): void; onDataAdded(index: number): void; diff --git a/arkoala/framework/native/src/generated/arkoala_api_generated.h b/arkoala/framework/native/src/generated/arkoala_api_generated.h index 61a239b7cd38067aa879b259a9fef6f0275c7379..7f35da2b09b957ea143a94928e847efd32445296 100644 --- a/arkoala/framework/native/src/generated/arkoala_api_generated.h +++ b/arkoala/framework/native/src/generated/arkoala_api_generated.h @@ -19471,6 +19471,10 @@ typedef struct GENERATED_ArkUILazyForEachOpsAccessor { void (*Prepare)(Ark_NativePointer node, Ark_Int32 itemCount, Ark_Int32 offset); + void (*NotifyChange)(Ark_NativePointer node, + Ark_Int32 startIdx, + Ark_Int32 endIdx, + Ark_Int32 count); } GENERATED_ArkUILazyForEachOpsAccessor; typedef struct GENERATED_ArkUISystemOpsAccessor { diff --git a/arkoala/framework/native/src/generated/bridge_generated.cc b/arkoala/framework/native/src/generated/bridge_generated.cc index ccb6ef1ac15fecd9df3baa2e1b9c1e06a1ad7cd6..b9a37a1dbd98dc81cb62ec77f6c0e8e8c6259665 100644 --- a/arkoala/framework/native/src/generated/bridge_generated.cc +++ b/arkoala/framework/native/src/generated/bridge_generated.cc @@ -17131,6 +17131,10 @@ void impl_LazyForEachOps_Prepare(Ark_NativePointer node, Ark_Int32 itemCount, Ar GetAccessors()->getLazyForEachOpsAccessor()->Prepare(node, itemCount, offset); } KOALA_INTEROP_V3(LazyForEachOps_Prepare, Ark_NativePointer, Ark_Int32, Ark_Int32) +void impl_LazyForEachOps_NotifyChange(Ark_NativePointer node, Ark_Int32 startIdx, Ark_Int32 endIdx, Ark_Int32 count) { + GetAccessors()->getLazyForEachOpsAccessor()->NotifyChange(node, startIdx, endIdx, count); +} +KOALA_INTEROP_V4(LazyForEachOps_NotifyChange, Ark_NativePointer, Ark_Int32, Ark_Int32, Ark_Int32) Ark_NativePointer impl_SystemOps_ctor() { return GetAccessors()->getSystemOpsAccessor()->ctor(); } diff --git a/arkoala/framework/native/src/generated/dummy_impl.cc b/arkoala/framework/native/src/generated/dummy_impl.cc index be0db2ab3ffa99b344982792f4e0d5b817c59913..bd9b84337e7fd77c03e2e6e980efc6756620ed9e 100644 --- a/arkoala/framework/native/src/generated/dummy_impl.cc +++ b/arkoala/framework/native/src/generated/dummy_impl.cc @@ -912,6 +912,15 @@ namespace OHOS::Ace::NG::GeneratedModifier { out.append(") \n"); appendGroupedLog(1, out); } + void NotifyChangeImpl(Ark_NativePointer node, Ark_Int32 startIdx, Ark_Int32 endIdx, Ark_Int32 count) + { + if (!needGroupedLog(1)) + return; + string out("NotifyChange("); + WriteToString(&out, node); + out.append(") \n"); + appendGroupedLog(1, out); + } } // LazyForEachOpsAccessor namespace CommonMethodModifier { @@ -34111,6 +34120,7 @@ namespace OHOS::Ace::NG::GeneratedModifier { LazyForEachOpsAccessor::OnRangeUpdateImpl, LazyForEachOpsAccessor::SetCurrentIndexImpl, LazyForEachOpsAccessor::PrepareImpl, + LazyForEachOpsAccessor::NotifyChangeImpl, }; return &LazyForEachOpsAccessorImpl; } diff --git a/arkoala/framework/native/src/generated/real_impl.cc b/arkoala/framework/native/src/generated/real_impl.cc index 49eecdeeb4f63b00ce53115bcf38684a3001b45f..eb073832777d0bdb29856828782ca7f7ca35a489 100644 --- a/arkoala/framework/native/src/generated/real_impl.cc +++ b/arkoala/framework/native/src/generated/real_impl.cc @@ -912,6 +912,15 @@ namespace OHOS::Ace::NG::GeneratedModifier { out.append(") \n"); appendGroupedLog(1, out); } + void NotifyChangeImpl(Ark_NativePointer node, Ark_Int32 startIdx, Ark_Int32 endIdx, Ark_Int32 count) + { + if (!needGroupedLog(1)) + return; + string out("NotifyChange("); + WriteToString(&out, node); + out.append(") \n"); + appendGroupedLog(1, out); + } } // LazyForEachOpsAccessor namespace CommonMethodModifier { @@ -16770,6 +16779,7 @@ namespace OHOS::Ace::NG::GeneratedModifier { LazyForEachOpsAccessor::OnRangeUpdateImpl, LazyForEachOpsAccessor::SetCurrentIndexImpl, LazyForEachOpsAccessor::PrepareImpl, + LazyForEachOpsAccessor::NotifyChangeImpl, }; return &LazyForEachOpsAccessorImpl; }