Interface de Fonte de Dados IDataSource no HarmonyOS

No desenvolvimento de aplicativos para HarmonyOS, a IDataSource é uma interface fornecida pelo framework ArkUI, utilizada principalmente para implementar o carregamento preguiçoso (lazy loading) de dados em componentes de lista. Ela representa uma dependência fundamental do LazyForEach, permitindo uma renderização e atualização eficientes de grandes volumes de dados.

Na abordagem tradicional de desenvolvimento, o uso direto do ForEach para renderizar grandes quantidades de dados em listas apresenta diversos desafios:

  • Problemas de performance: Todos os dados são carregados de uma vez, consumindo considerável quantidade de memória
  • Ineficiência nas atualizações: Alterações nos dados exigem a re-renderização de toda a lista
  • Péssima experiência do usuário: Interfaces travadas ao lidar com volumes grandes de dados

A solução combinando IDataSource com LazyForEach oferece:

  • Carregamento sob demanda: Apenas os dados visíveis na tela são renderizados
  • Atualizações eficientes: Suporte a atualizações parciais, modificando apenas os itens alterados
  • Otimização de memória: Itens fora da área visível são liberados, reduzindo o consumo
  • Rolagem fluida: Mantém a fluidez mesmo com milhares de itens na lista

Definição da Interface

2.1 Código-fonte da interface IDataSource

interface IDataSource {
   /**
    * Obtém o total de dados disponíveis
    */
   totalCount(): number;
   
   /**
    * Recupera um item de dados pelo seu índice
    */
   getData(index: number): any;
   
   /**
    * Registra um ouvinte de mudanças de dados (chamado pelo framework)
    */
   registerDataChangeListener(listener: DataChangeListener): void;
   
   /**
    * Remove um ouvinte de mudanças de dados (chamado pelo framework)
    */
   unregisterDataChangeListener(listener: DataChangeListener): void;
}

2.2 Interface DataChangeListener

interface DataChangeListener {
   /**
    * Acionado quando os dados são completamente recarregados
    */
   onDataReloaded(): void;
   
   /**
    * Acionado quando um novo item é adicionado
    */
   onDataAdded(index: number): void;
   
   /**
    * Acionado quando um item é removido
    */
   onDataDeleted(index: number): void;
   
   /**
    * Acionado quando um item é modificado
    */
   onDataChanged(index: number): void;
   
   /**
    * Acionado quando um item é movido de uma posição para outra
    */
   onDataMoved(from: number, to: number): void;
}

Exemplos de Implementação

3.1 Implementação básica de fonte de dados

// FonteDadosBasico.ets
import { IDataSource, DataChangeListener } from '@ohos.arkui.DataPanel';

class FonteDadosBasico implements IDataSource {
   private ouvintes: DataChangeListener[] = [];
   private itensDados: any[] = [];

   constructor(dados: any[]) {
       this.itensDados = dados;
   }

   // Implementação da interface IDataSource
   totalCount(): number {
       return this.itensDados.length;
   }

   getData(index: number): any {
       return this.itensDados[index];
   }

   registerDataChangeListener(ouvinte: DataChangeListener): void {
       if (this.ouvintes.indexOf(ouvinte) < 0) {
           this.ouvintes.push(ouvinte);
       }
   }

   unregisterDataChangeListener(ouvinte: DataChangeListener): void {
       const indice = this.ouvintes.indexOf(ouvinte);
       if (indice >= 0) {
           this.ouvintes.splice(indice, 1);
       }
   }

   // Métodos de manipulação de dados
   incluirItem(indice: number, dado: any): void {
       this.itensDados.splice(indice, 0, dado);
       // Notifica os ouvintes sobre a inclusão
       this.ouvintes.forEach(ouvinte => {
           ouvinte.onDataAdded(indice);
       });
   }

   removerItem(indice: number): void {
       this.itensDados.splice(indice, 1);
       // Notifica os ouvintes sobre a remoção
       this.ouvintes.forEach(ouvinte => {
           ouvinte.onDataDeleted(indice);
       });
   }

   atualizarItem(indice: number, dado: any): void {
       this.itensDados[indice] = dado;
       // Notifica os ouvintes sobre a modificação
       this.ouvintes.forEach(ouvinte => {
           ouvinte.onDataChanged(indice);
       });
   }

   recarregarDados(novosDados: any[]): void {
       this.itensDados = novosDados;
       // Notifica os ouvintes sobre o recarregamento completo
       this.ouvintes.forEach(ouvinte => {
           ouvinte.onDataReloaded();
       });
   }
}

3.2 Utilização com LazyForEach em um componente List

// PaginaLista.ets
import { FonteDadosBasico } from './FonteDadosBasico';

@Entry
@Component
struct PaginaLista {
   // Instância da fonte de dados
   private fonteDados: FonteDadosBasico = new FonteDadosBasico([]);

   aboutToAppear() {
       // Inicialização com 1000 itens de dados
       const dadosIniciais: string[] = [];
       for (let i = 0; i < 1000; i++) {
           dadosIniciais.push(`Item ${i + 1}`);
       }
       this.fonteDados.recarregarDados(dadosIniciais);
   }

   build() {
       Column() {
           // Controles de ação
           Row() {
               Botão('Adicionar')
                   .onClick(() => {
                       this.fonteDados.incluirItem(0, `Novo item ${Date.now()}`);
                   })
               Botão('Remover')
                   .onClick(() => {
                       this.fonteDados.removerItem(0);
                   })
               Botão('Modificar')
                   .onClick(() => {
                       this.fonteDados.atualizarItem(0, `Modificado ${Date.now()}`);
                   })
           }
           .padding(10)
           .width('100%')
           .justifyContent(FlexAlign.SpaceAround)

           // Componente de lista
           Lista() {
               LazyForEach(this.fonteDados, (item: string, indice: number) => {
                   ItemLista() {
                       Texto(item)
                           .width('100%')
                           .height(60)
                           .backgroundColor(indice % 2 === 0 ? '#F5F5F5' : '#FFFFFF')
                           .textAlign(TextAlign.Center)
                   }
                   .onClick(() => {
                       console.log(`Item ${indice + 1} clicado`);
                   })
               }, (item: string, indice: number) => item + indice) // Função de chave única
           }
           .width('100%')
           .layoutWeight(1)
       }
       .width('100%')
       .height('100%')
   }
}

Problemas Comuns

Dados não atualizando

// Implementação incorreta
class FonteDadosIncorreta implements IDataSource {
   private dados: any[] = [];
   
   atualizarItem(indice: number, novoDado: any) {
       this.dados[indice] = novoDado; // Modificou o array, mas não notificou o ouvinte
       // Falta notificação para o ouvinte
   }
}

// Implementação correta
class FonteDadosCorreta implements IDataSource {
   private ouvintes: DataChangeListener[] = [];
   private dados: any[] = [];
   
   atualizarItem(indice: number, novoDado: any) {
       this.dados[indice] = novoDado;
       this.ouvintes.forEach(ouvinte => {
           ouvinte.onDataChanged(indice); // Notificação obrigatória
       });
   }
}

Resultado da execução:

  • Exibição inicial: Normal, por exemplo: 3 itens de dados
  • Clique no botão para adicionar dados: O array de dados agora tem 4 itens
  • UI exibida: Ainda mostra 3 itens de dados (consequência de não notificar o ouvinte)

Índice fora dos limites

class FonteDadosSegura implements IDataSource {
   private dados: any[] = [];

   getData(indice: number): any {
       // Verificação de segurança
       if (indice < 0 || indice >= this.dados.length) {
           console.warn(`Índice fora dos limites: ${indice}, tamanho: ${this.dados.length}`);
           return null; // Retorna valor padrão
       }
       return this.dados[indice];
   }

   removerItem(indice: number): boolean {
       if (indice < 0 || indice >= this.dados.length) {
           return false;
       }
       this.dados.splice(indice, 1);
       this.ouvintes.forEach(ouvinte => {
           ouvinte.onDataDeleted(indice);
       });
       return true;
   }
}

Modelo completo de DataSource

// Modelo completo de DataSource
abstract class FonteDadosBase<t> implements IDataSource {
   protected ouvintes: DataChangeListener[] = [];
   protected dados: T[] = [];

   abstract totalCount(): number;
   abstract getData(indice: number): T;

   registerDataChangeListener(ouvinte: DataChangeListener): void {
       if (!this.ouvintes.includes(ouvinte)) {
           this.ouvintes.push(ouvinte);
       }
   }

   unregisterDataChangeListener(ouvinte: DataChangeListener): void {
       const indice = this.ouvintes.indexOf(ouvinte);
       if (indice >= 0) {
           this.ouvintes.splice(indice, 1);
       }
   }

   // Métodos protegidos para uso pelas classes derivadas
   protected notificarRecarregamento(): void {
       this.ouvintes.forEach(o => o.onDataReloaded());
   }

   protected notificarAdicao(indice: number): void {
       this.ouvintes.forEach(o => o.onDataAdded(indice));
   }

   protected notificacaoRemocao(indice: number): void {
       this.ouvintes.forEach(o => o.onDataDeleted(indice));
   }

   protected notificacaoAlteracao(indice: number): void {
       this.ouvintes.forEach(o => o.onDataChanged(indice));
   }

   protected notificarMovimento(de: number, para: number): void {
       this.ouvintes.forEach(o => o.onDataMoved(de, para));
   }
}</t>

Tags: HarmonyOS ArkUI IDataSource LazyForEach desenvolvimento de aplicativos

Publicado em 6-4 01:00 por Thomas