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>