1# Developing a Long List with Lazy Loading 2 3For the **List**, **Grid**, **WaterFlow**, and **Swiper** components, the [NodeAdapter](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodeadapterhandle) object is provided as an alternative to the ArkTS **LazyForEach** feature for on-demand child component generation. The specific attribute enumeration values are as follows: 4 5- **List**: **NODE_LIST_NODE_ADAPTER** 6- **Grid**: **NODE_GRID_NODE_ADAPTER** 7- **WaterFlow**: **NODE_WATER_FLOW_NODE_ADAPTER** 8- **Swiper**: **NODE_SWIPER_NODE_ADAPTER** 9 10Key specifications of **NodeAdapter** include: 11 12 13- Nodes with **NodeAdapter** set do not support direct child addition APIs like **addChild**. Child components are managed entirely by **NodeAdapter**. If a parent component already has child nodes, setting **NodeAdapter** will fail and return an error code. 14 15- **NodeAdapter** uses events to notify you to generate components on demand. You must register an [event listener](../reference/apis-arkui/_ark_u_i___native_module.md#oh_arkui_nodeadapter_registereventreceiver) and handle logic within listener events, which are defined by [ArkUI_NodeAdapterEventType](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodeadaptereventtype). **NodeAdapter** does not automatically release off-screen component objects; you must manage object release or caching during the [NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodeadaptereventtype) event. The following image illustrates the event triggering mechanism in a typical list scrolling scenario. 16  17 18 19The following example optimizes the code in the [Integrating with ArkTS Pages](ndk-access-the-arkts-page.md) section, by introducing a lazy loading mechanism for a text list: 20 21 221. Integrate ArkTS into your project. For details, see [Integrating with ArkTS Pages](ndk-access-the-arkts-page.md). 23 242. Implement lazy loading adapter functionality. 25 ``` 26 // ArkUIListItemAdapter 27 // Code for lazy loading functionality in a text list. 28 29 #ifndef MYAPPLICATION_ARKUILISTITEMADAPTER_H 30 #define MYAPPLICATION_ARKUILISTITEMADAPTER_H 31 32 #include <arkui/native_node.h> 33 #include <stack> 34 #include <string> 35 #include <unordered_set> 36 37 #include "ArkUIListItemNode.h" 38 #include "ArkUITextNode.h" 39 #include "nativeModule.h" 40 41 namespace NativeModule { 42 43 class ArkUIListItemAdapter { 44 public: 45 ArkUIListItemAdapter() 46 : module_(NativeModuleInstance::GetInstance()->GetNativeNodeAPI()), handle_(OH_ArkUI_NodeAdapter_Create()) { // Use the NodeAdapter creation function. 47 // Initialize lazy loading data. 48 for (int32_t i = 0; i < 1000; i++) { 49 data_.emplace_back(std::to_string(i)); 50 } 51 // Set lazy loading data. 52 OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, data_.size()); 53 // Register the event receiver for lazy loading. 54 OH_ArkUI_NodeAdapter_RegisterEventReceiver(handle_, this, OnStaticAdapterEvent); 55 } 56 57 ~ArkUIListItemAdapter() { 58 // Release created components. 59 while (!cachedItems_.empty()) { 60 cachedItems_.pop(); 61 } 62 items_.clear(); 63 // Release adapter resources. 64 OH_ArkUI_NodeAdapter_UnregisterEventReceiver(handle_); 65 OH_ArkUI_NodeAdapter_Dispose(handle_); 66 } 67 68 ArkUI_NodeAdapterHandle GetHandle() const { return handle_; } 69 70 void RemoveItem(int32_t index) { 71 // Remove the item at the specified index. 72 data_.erase(data_.begin() + index); 73 // If the index change affects the visibility of items in the visible area, the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event will be triggered to remove the element. 74 // If items are added, the NODE_ADAPTER_EVENT_ON_GET_NODE_ID and NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER events will be triggered accordingly. 75 OH_ArkUI_NodeAdapter_RemoveItem(handle_, index, 1); 76 // Update the new total count. 77 OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, data_.size()); 78 } 79 80 void InsertItem(int32_t index, const std::string &value) { 81 data_.insert(data_.begin() + index, value); 82 // If the index change affects the visibility of elements in the visible area, the NODE_ADAPTER_EVENT_ON_GET_NODE_ID and NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER events will be triggered. 83 // If items are removed, the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event will be triggered accordingly. 84 OH_ArkUI_NodeAdapter_InsertItem(handle_, index, 1); 85 // Update the new total count. 86 OH_ArkUI_NodeAdapter_SetTotalNodeCount(handle_, data_.size()); 87 } 88 89 void MoveItem(int32_t oldIndex, int32_t newIndex) { 90 auto temp = data_[oldIndex]; 91 data_.insert(data_.begin() + newIndex, temp); 92 data_.erase(data_.begin() + oldIndex); 93 // If the move changes the visibility of items within the visible area, the corresponding events will be triggered. 94 OH_ArkUI_NodeAdapter_MoveItem(handle_, oldIndex, newIndex); 95 } 96 97 void ReloadItem(int32_t index, const std::string &value) { 98 data_[index] = value; 99 // If the index is within the visible area, first trigger the NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER event to remove the old item, 100 // then trigger the NODE_ADAPTER_EVENT_ON_GET_NODE_ID and NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER events. 101 OH_ArkUI_NodeAdapter_ReloadItem(handle_, index, 1); 102 } 103 104 void ReloadAllItem() { 105 std::reverse(data_.begin(), data_.end()); 106 // In the scenario where all items are reloaded, the NODE_ADAPTER_EVENT_ON_GET_NODE_ID event will be triggered to obtain new component IDs, 107 // compare the new component IDs, and reuse those whose IDs have not changed, 108 // for items with new IDs, trigger the NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER event to create new components, 109 // then identify any unused IDs from the old data and call NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER to remove the old items. 110 OH_ArkUI_NodeAdapter_ReloadAllItems(handle_); 111 } 112 113 private: 114 static void OnStaticAdapterEvent(ArkUI_NodeAdapterEvent *event) { 115 // Obtain the instance object and invoke its event callback. 116 auto itemAdapter = reinterpret_cast<ArkUIListItemAdapter *>(OH_ArkUI_NodeAdapterEvent_GetUserData(event)); 117 itemAdapter->OnAdapterEvent(event); 118 } 119 120 void OnAdapterEvent(ArkUI_NodeAdapterEvent *event) { 121 auto type = OH_ArkUI_NodeAdapterEvent_GetType(event); 122 switch (type) { 123 case NODE_ADAPTER_EVENT_ON_GET_NODE_ID: 124 OnNewItemIdCreated(event); 125 break; 126 case NODE_ADAPTER_EVENT_ON_ADD_NODE_TO_ADAPTER: 127 OnNewItemAttached(event); 128 break; 129 case NODE_ADAPTER_EVENT_ON_REMOVE_NODE_FROM_ADAPTER: 130 OnItemDetached(event); 131 break; 132 default: 133 break; 134 } 135 } 136 137 // Assign IDs to items that need to be displayed, used for element diffing in the ReloadAllItems scenario. 138 void OnNewItemIdCreated(ArkUI_NodeAdapterEvent *event) { 139 auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event); 140 static std::hash<std::string> hashId = std::hash<std::string>(); 141 auto id = hashId(data_[index]); 142 OH_ArkUI_NodeAdapterEvent_SetNodeId(event, id); 143 } 144 145 // Handle the display of new items in the visible area. 146 void OnNewItemAttached(ArkUI_NodeAdapterEvent *event) { 147 auto index = OH_ArkUI_NodeAdapterEvent_GetItemIndex(event); 148 ArkUI_NodeHandle handle = nullptr; 149 if (!cachedItems_.empty()) { 150 // Use and update the recycled item from the cache. 151 auto recycledItem = cachedItems_.top(); 152 auto textItem = std::dynamic_pointer_cast<ArkUITextNode>(recycledItem->GetChildren().back()); 153 textItem->SetTextContent(data_[index]); 154 handle = recycledItem->GetHandle(); 155 // Release the reference from the cache. 156 cachedItems_.pop(); 157 } else { 158 // Create a new item. 159 auto listItem = std::make_shared<ArkUIListItemNode>(); 160 auto textNode = std::make_shared<ArkUITextNode>(); 161 textNode->SetTextContent(data_[index]); 162 textNode->SetFontSize(16); 163 textNode->SetPercentWidth(1); 164 textNode->SetHeight(100); 165 textNode->SetBackgroundColor(0xFFfffacd); 166 textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER); 167 listItem->AddChild(textNode); 168 listItem->RegisterOnClick([index]() { OH_LOG_INFO(LOG_APP, "on %{public}d list item click", index); }); 169 handle = listItem->GetHandle(); 170 // Keep a reference to the text list item. 171 items_.emplace(handle, listItem); 172 } 173 // Set the item to be displayed. 174 OH_ArkUI_NodeAdapterEvent_SetItem(event, handle); 175 } 176 177 // Remove an item from the visible area. 178 void OnItemDetached(ArkUI_NodeAdapterEvent *event) { 179 auto item = OH_ArkUI_NodeAdapterEvent_GetRemovedNode(event); 180 // Place the item in the cache pool for recycling and reuse. 181 cachedItems_.emplace(items_[item]); 182 } 183 184 185 std::vector<std::string> data_; 186 ArkUI_NativeNodeAPI_1 *module_ = nullptr; 187 ArkUI_NodeAdapterHandle handle_ = nullptr; 188 189 // Manage items generated by the NodeAdapter. 190 std::unordered_map<ArkUI_NodeHandle, std::shared_ptr<ArkUIListItemNode>> items_; 191 192 // Manage the component reuse pool. 193 std::stack<std::shared_ptr<ArkUIListItemNode>> cachedItems_; 194 }; 195 196 } // namespace NativeModule 197 198 #endif // MYAPPLICATION_ARKUILISTITEMADAPTER_H 199 ``` 200 2013. Enhance the encapsulated list class object used in the [Integrating with ArkTS Pages](ndk-access-the-arkts-page.md) section with additional lazy loading capabilities. 202 ``` 203 // ArkUIListNode.h 204 // Encapsulated list class object. 205 206 #ifndef MYAPPLICATION_ARKUILISTNODE_H 207 #define MYAPPLICATION_ARKUILISTNODE_H 208 209 #include "ArkUIListItemAdapter.h" 210 #include "ArkUINode.h" 211 #include <hilog/log.h> 212 213 namespace NativeModule { 214 class ArkUIListNode : public ArkUINode { 215 public: 216 ArkUIListNode() 217 : ArkUINode((NativeModuleInstance::GetInstance()->GetNativeNodeAPI())->createNode(ARKUI_NODE_LIST)) {} 218 219 ~ArkUIListNode() override { 220 nativeModule_->unregisterNodeEvent(handle_, NODE_LIST_ON_SCROLL_INDEX); 221 if (adapter_) { 222 // Unmount UI components associated with the adapter upon destruction. 223 nativeModule_->resetAttribute(handle_, NODE_LIST_NODE_ADAPTER); 224 adapter_.reset(); 225 } 226 } 227 228 void SetScrollBarState(bool isShow) { 229 assert(handle_); 230 ArkUI_ScrollBarDisplayMode displayMode = 231 isShow ? ARKUI_SCROLL_BAR_DISPLAY_MODE_ON : ARKUI_SCROLL_BAR_DISPLAY_MODE_OFF; 232 ArkUI_NumberValue value[] = {{.i32 = displayMode}}; 233 ArkUI_AttributeItem item = {value, 1}; 234 nativeModule_->setAttribute(handle_, NODE_SCROLL_BAR_DISPLAY_MODE, &item); 235 } 236 237 void RegisterOnScrollIndex(const std::function<void(int32_t index)> &onScrollIndex) { 238 assert(handle_); 239 onScrollIndex_ = onScrollIndex; 240 nativeModule_->registerNodeEvent(handle_, NODE_LIST_ON_SCROLL_INDEX, 0, nullptr); 241 } 242 // Import the lazy loading module. 243 void SetLazyAdapter(const std::shared_ptr<ArkUIListItemAdapter> &adapter) { 244 assert(handle_); 245 ArkUI_AttributeItem item{nullptr, 0, nullptr, adapter->GetHandle()}; 246 nativeModule_->setAttribute(handle_, NODE_LIST_NODE_ADAPTER, &item); 247 adapter_ = adapter; 248 } 249 250 protected: 251 void OnNodeEvent(ArkUI_NodeEvent *event) override { 252 auto eventType = OH_ArkUI_NodeEvent_GetEventType(event); 253 switch (eventType) { 254 case NODE_LIST_ON_SCROLL_INDEX: { 255 auto index = OH_ArkUI_NodeEvent_GetNodeComponentEvent(event)->data[0]; 256 if (onScrollIndex_) { 257 onScrollIndex_(index.i32); 258 } 259 } 260 default: { 261 } 262 } 263 } 264 265 private: 266 std::function<void(int32_t index)> onScrollIndex_; 267 268 std::shared_ptr<ArkUIListItemAdapter> adapter_; 269 }; 270 } // namespace NativeModule 271 272 #endif // MYAPPLICATION_ARKUILISTNODE_H 273 ``` 274 2754. Write code for lazy loading of a list. 276 ``` 277 // ArkUILazyTextListExample 278 // Sample code for lazy loading a list. 279 280 #ifndef MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H 281 #define MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H 282 283 #include "ArkUIBaseNode.h" 284 #include "ArkUIListNode.h" 285 #include "UITimer.h" 286 #include <thread> 287 #include <uv.h> 288 289 namespace NativeModule { 290 291 std::shared_ptr<ArkUIBaseNode> CreateLazyTextListExample(napi_env env) { 292 // Create components and mount them. 293 // 1: Create a List component. 294 auto list = std::make_shared<ArkUIListNode>(); 295 list->SetPercentWidth(1); 296 list->SetPercentHeight(1); 297 // 2: Create ListItem child components for lazy loading and mount them to the List component. 298 auto adapter = std::make_shared<ArkUIListItemAdapter>(); 299 list->SetLazyAdapter(adapter); 300 301 // 3: Simulate lazy loading operations. 302 CreateNativeTimer(env, adapter.get(), 4, [](void *userdata, int32_t count) { 303 auto adapter = reinterpret_cast<ArkUIListItemAdapter *>(userdata); 304 switch (count) { 305 case 0: { 306 // Remove the 0th item. 307 adapter->RemoveItem(0); 308 break; 309 } 310 case 1: { 311 // Insert the 0th item. 312 adapter->InsertItem(0, "0"); 313 break; 314 } 315 case 2: { 316 // Move an item to a new position. 317 adapter->MoveItem(0, 2); 318 break; 319 } 320 case 3: { 321 // Reload a single item. 322 adapter->ReloadItem(0, "1112"); 323 break; 324 } 325 case 4: { 326 // Reload all items. 327 adapter->ReloadAllItem(); 328 break; 329 } 330 default: { 331 } 332 } 333 }); 334 335 // 3: Register list-related listening events. 336 list->RegisterOnScrollIndex([](int32_t index) { OH_LOG_INFO(LOG_APP, "on list scroll index: %{public}d", index); }); 337 // 4: Register the appear event. 338 list->RegisterOnAppear([]() { OH_LOG_INFO(LOG_APP, "on list mount to tree"); }); 339 // 4: Register the disappear event. 340 list->RegisterOnDisappear([]() { OH_LOG_INFO(LOG_APP, "on list unmount from tree"); }); 341 return list; 342 } 343 } // namespace NativeModule 344 345 #endif // MYAPPLICATION_LAZYTEXTLISTEXAMPLE_H 346 ``` 347 3485. Implement a simple timer module. 349 ``` 350 // UITimer.h 351 // Timer module. 352 353 #ifndef MYAPPLICATION_UITIMER_H 354 #define MYAPPLICATION_UITIMER_H 355 356 #include <hilog/log.h> 357 #include <js_native_api.h> 358 #include <js_native_api_types.h> 359 #include <node_api.h> 360 #include <node_api_types.h> 361 #include <string> 362 #include <thread> 363 #include <uv.h> 364 365 namespace NativeModule { 366 367 struct UIData { 368 void *userData = nullptr; 369 int32_t count = 0; 370 int32_t totalCount = 0; 371 void (*func)(void *userData, int32_t count) = nullptr; 372 }; 373 374 napi_threadsafe_function threadSafeFunction = nullptr; 375 376 void CreateNativeTimer(napi_env env, void *userData, int32_t totalCount, void (*func)(void *userData, int32_t count)) { 377 napi_value name; 378 std::string str = "UICallback"; 379 napi_create_string_utf8(env, str.c_str(), str.size(), &name); 380 // UI main thread callback function. 381 napi_create_threadsafe_function( 382 env, nullptr, nullptr, name, 0, 1, nullptr, nullptr, nullptr, 383 [](napi_env env, napi_value value, void *context, void *data) { 384 auto userdata = reinterpret_cast<UIData *>(data); 385 userdata->func(userdata->userData, userdata->count); 386 delete userdata; 387 }, 388 &threadSafeFunction); 389 // Start the timer to simulate data changes. 390 std::thread timerThread([data = userData, totalCount, func]() { 391 uv_loop_t *loop = uv_loop_new(); 392 uv_timer_t *timer = new uv_timer_t(); 393 uv_timer_init(loop, timer); 394 timer->data = new UIData{data, 0, totalCount, func}; 395 uv_timer_start( 396 timer, 397 [](uv_timer_t *handle) { 398 OH_LOG_INFO(LOG_APP, "on timeout"); 399 napi_acquire_threadsafe_function(threadSafeFunction); 400 auto *customData = reinterpret_cast<UIData *>(handle->data); 401 // Create callback data. 402 auto *callbackData = 403 new UIData{customData->userData, customData->count, customData->totalCount, customData->func}; 404 napi_call_threadsafe_function(threadSafeFunction, callbackData, napi_tsfn_blocking); 405 customData->count++; 406 if (customData->count > customData->totalCount) { 407 uv_timer_stop(handle); 408 delete handle; 409 delete customData; 410 } 411 }, 412 4000, 4000); 413 uv_run(loop, UV_RUN_DEFAULT); 414 uv_loop_delete(loop); 415 }); 416 timerThread.detach(); 417 } 418 } // namespace NativeModule 419 420 #endif // MYAPPLICATION_UITIMER_H 421 ``` 422 4236. Mount the lazy loading example code onto the **ContentSlot** as described in the [Integrating with ArkTS Pages](ndk-access-the-arkts-page.md) section. 424 ``` 425 // NDK API entry mount file. 426 427 #include "NativeEntry.h" 428 429 #include "ArkUIMixedRefresh.h" 430 #include "LazyTextListExample.h" 431 #include "MixedRefreshExample.h" 432 #include "TextListExample.h" 433 434 #include <arkui/native_node_napi.h> 435 #include <arkui/native_type.h> 436 #include <js_native_api.h> 437 #include <uv.h> 438 439 namespace NativeModule { 440 namespace { 441 napi_env g_env; 442 } 443 444 napi_env GetNapiEnv() { return g_env; } 445 446 napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { 447 size_t argc = 1; 448 napi_value args[1] = {nullptr}; 449 450 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 451 452 // Obtain NodeContent. 453 ArkUI_NodeContentHandle contentHandle; 454 OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle); 455 NativeEntry::GetInstance()->SetContentHandle(contentHandle); 456 457 // Create a lazy-loaded text list. 458 auto node = CreateLazyTextListExample(env); 459 460 // Keep the native side object in the management class to maintain its lifecycle. 461 NativeEntry::GetInstance()->SetRootNode(node); 462 g_env = env; 463 return nullptr; 464 } 465 466 napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { 467 // Release the native side object from the management class. 468 NativeEntry::GetInstance()->DisposeRootNode(); 469 return nullptr; 470 } 471 472 } // namespace NativeModule 473 ``` 474