> For the complete documentation index, see [llms.txt](https://elguerre.gitbook.io/de0an/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://elguerre.gitbook.io/de0an/capitulo-9-la-construccion.-navegacion-y-configuracion-de-usuario/carga-incremental-incremental-loading.md).

# Carga incremental (Incremental Loading)

La **carga incremental**, también conocida como *lazy loading*, es una técnica que mejora el rendimiento de las aplicaciones al cargar solo la información que el usuario necesita en el momento preciso. Esto no solo reduce los tiempos de carga inicial, sino que también optimiza el uso de recursos, proporcionando una experiencia de usuario más fluida y responsiva.

En el contexto de nuestra aplicación UWP siguiendo el patrón **MVVM**, la implementación de carga incremental permite manejar eficientemente grandes conjuntos de datos provenientes de servicios externos, como APIs REST, sin sobrecargar la interfaz de usuario ni el backend.

## ¿Por Qué Implementar Carga Incremental?

Implementar carga incremental ofrece múltiples beneficios:

* **Mejora del Rendimiento:** Al cargar datos de forma progresiva, se reduce el tiempo de espera inicial, permitiendo que la aplicación responda más rápidamente a las interacciones del usuario.
* **Optimización de Recursos:** Minimiza el uso de memoria y ancho de banda al evitar la carga de datos innecesarios.
* **Escalabilidad:** Facilita el manejo de grandes volúmenes de datos sin comprometer la estabilidad de la aplicación.
* **Experiencia de Usuario Mejorada:** Proporciona una interfaz más ágil, donde los usuarios pueden comenzar a interactuar con los datos disponibles mientras se carga el resto en segundo plano.

## Implementación de Carga Incremental en MVVM

A continuación, se detalla cómo implementar la carga incremental en una aplicación UWP utilizando el patrón **MVVM**.

### Paso 1: Definir el Modelo de Datos

Primero, definimos el modelo de datos que representará los elementos que se mostrarán en la interfaz de usuario.

```csharp
public class Project
{
    public int Id { get; set; }
    public string Name { get; set; }
    // Otras propiedades relevantes
}
```

### Paso 2: Crear un Servicio de Datos con Soporte para Paginación

Implementamos un servicio que maneje la comunicación con el API y la paginación de los datos.

```csharp
public interface IDataService
{
    Task<PaginatedResult<Project>> GetProjectsAsync(int pageNumber, int pageSize);
}

public class DataService : IDataService
{
    private readonly HttpClient _httpClient;

    public DataService()
    {
        _httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://api.ejemplo.com/")
        };
    }

    public async Task<PaginatedResult<Project>> GetProjectsAsync(int pageNumber, int pageSize)
    {
        var response = await _httpClient.GetAsync($"projects?page={pageNumber}&size={pageSize}");
        response.EnsureSuccessStatusCode();

        var content = await response.Content.ReadAsStringAsync();
        var paginatedResult = JsonConvert.DeserializeObject<PaginatedResult<Project>>(content);

        return paginatedResult;
    }
}

public class PaginatedResult<T>
{
    [JsonProperty("current_page")]
    public int CurrentPage { get; set; }

    [JsonProperty("total_pages")]
    public int TotalPages { get; set; }

    [JsonProperty("items")]
    public List<T> Items { get; set; }
}
```

### Paso 3: Implementar la Colección con Soporte para Carga Incremental

Utilizamos `ObservableCollection` junto con la interfaz `ISupportIncrementalLoading` para manejar la carga incremental de datos. Es importante configurar correctamente las propiedades `IncrementalLoadingTrigger`, `IncrementalLoadingThreshold` y `DataFetchSize` para optimizar el comportamiento de la carga.

```csharp
public class ProjectCollection : ObservableCollection<Project>, ISupportIncrementalLoading
{
    private bool _hasMoreItems = true;
    private int _currentPage = 0;
    private readonly int _pageSize = 20;
    private readonly IDataService _dataService;

    public ProjectCollection(IDataService dataService)
    {
        _dataService = dataService;
    }

    public bool HasMoreItems => _hasMoreItems;

    public async Task<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
    {
        try
        {
            var result = await _dataService.GetProjectsAsync(_currentPage, _pageSize);
            foreach(var project in result.Items)
            {
                this.Add(project);
            }
            _currentPage++;
            _hasMoreItems = _currentPage < result.TotalPages;
        }
        catch(Exception ex)
        {
            // Manejo de errores, por ejemplo, loguear el error o notificar al usuario
            Debug.WriteLine($"Error al cargar más elementos: {ex.Message}");
            _hasMoreItems = false;
        }
        return new LoadMoreItemsResult { Count = (uint)this.Count };
    }
}
```

### Paso 4: Configurar el ViewModel

El **ViewModel** manejará la lógica de carga de datos y expondrá la colección al **View**.

```csharp
public class MainViewModel : INotifyPropertyChanged
{
    private readonly IDataService _dataService;
    public ProjectCollection Projects { get; }

    public MainViewModel()
    {
        _dataService = new DataService();
        Projects = new ProjectCollection(_dataService);
    }

    public async Task LoadInitialDataAsync()
    {
        await Projects.LoadMoreItemsAsync(20);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string name = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
```

### Paso 5: Configurar la Vista (XAML)

En la interfaz de usuario, utilizamos un `ListView` o `GridView` que soporte carga incremental. Es crucial configurar las propiedades `IncrementalLoadingTrigger`, `IncrementalLoadingThreshold` y `DataFetchSize` adecuadamente.

```xml
<Page
    x:Class="MyApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyApp"
    xmlns:vm="using:MyApp.ViewModels"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Page.DataContext>
        <vm:MainViewModel x:Name="ViewModel"/>
    </Page.DataContext>

    <Grid>
        <ListView ItemsSource="{Binding Projects}"
                  IsItemClickEnabled="True"
                  IncrementalLoadingTrigger="Edge"
                  IncrementalLoadingThreshold="100"
                  SelectionMode="None"
                  DataFetchSize="20"
                  ShowsScrollingPlaceholders="True">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Project">
                    <StackPanel Margin="10">
                        <TextBlock Text="{x:Bind Name}" FontSize="18" FontWeight="Bold"/>
                        <!-- Otras propiedades del proyecto -->
                    </StackPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>
```

**Explicación de las Propiedades:**

* **IncrementalLoadingTrigger="Edge":** Define que la carga de más elementos se disparará cuando el usuario se acerque al borde de la lista.
* **IncrementalLoadingThreshold="100":** Establece el umbral en píxeles desde el borde donde se disparará la carga de más elementos. Un valor de cero solo cargará la primera página.
* **DataFetchSize="20":** Define la cantidad de elementos que se cargarán en cada solicitud.
* **ShowsScrollingPlaceholders="True":** Indica al `ListView` que muestre marcadores de posición mientras se cargan los datos, mejorando la experiencia de usuario durante el repintado.

### Paso 6: Inicializar la Carga de Datos

En el code-behind de la página, iniciamos la carga de datos cuando la página se carga.

```csharp
public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;

    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += MainPage_Loaded;
    }

    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadInitialDataAsync();
    }
}
```

## Mejora en el Repintado tras la Carga de Datos

Optimizar el repintado de la interfaz de usuario tras la carga de datos es crucial para mantener una experiencia de usuario fluida. Podemos lograr esto mediante el uso de **fases** declarativas y programáticas.

* **Fases Declarativas:** Utilizar propiedades como `ShowsScrollingPlaceholders="True"` permite que los controles como `ListView` muestren marcadores de posición mientras se cargan los datos, mejorando la percepción de rapidez y evitando que la interfaz se congele durante la carga.

  ```xml
  <ListView
      ItemsSource="{Binding Projects}"
      IsItemClickEnabled="True"
      IncrementalLoadingTrigger="Edge"
      IncrementalLoadingThreshold="100"
      SelectionMode="None"
      DataFetchSize="20"
      ShowsScrollingPlaceholders="True">
      <!-- Template -->
  </ListView>
  ```
* **Fases Programáticas:** Controlar manualmente el repintado de la interfaz mediante código puede ser útil en situaciones donde se requiere una optimización adicional.

  ```csharp
  private void RefreshUI()
  {
      // Lógica para optimizar el repintado de la UI
      // Por ejemplo, suspender la actualización de la UI mientras se agregan múltiples elementos
  }
  ```

  Para más detalles sobre cómo implementar mejoras en el repintado, puedes consultar el siguiente artículo: [Optimización del rendimiento de ListView y GridView](https://learn.microsoft.com/es-es/windows/apps/develop/performance/optimize-gridview-and-listview)

## Cacheado de Imágenes al Trabajar con Byte\[] o Descargar Imágenes de Servicios Web

El **cacheado de imágenes** es fundamental para mejorar el rendimiento y reducir el consumo de ancho de banda al evitar descargas repetidas de las mismas imágenes. Aunque en el **Capítulo 8** ya abordamos este tema en relación con la elección de una nueva imagen a partir de la librería de fotos del dispositivo, en esta sección profundizaremos en el cacheado de imágenes provenientes de fuentes externas o en formato byte\[].

### Estrategias de Cacheado:

* **Cacheado en Memoria:**

  Utilizar estructuras de datos en memoria para almacenar imágenes temporalmente durante la sesión de la aplicación.

  ```csharp
  public class ImageCache
  {
      private readonly Dictionary<string, BitmapImage> _cache = new Dictionary<string, BitmapImage>();

      public BitmapImage GetImage(string uri)
      {
          if(_cache.ContainsKey(uri))
              return _cache[uri];

          var image = new BitmapImage(new Uri(uri));
          _cache.Add(uri, image);
          return image;
      }
  }
  ```
* **Cacheado Persistente:**

  Almacenar imágenes en el almacenamiento local para mantenerlas entre sesiones de la aplicación.

  ```csharp
  public async Task<StorageFile> GetCachedImageAsync(string uri)
  {
      string fileName = Path.GetFileName(uri);
      StorageFolder cacheFolder = ApplicationData.Current.LocalCacheFolder;
      try
      {
          return await cacheFolder.GetFileAsync(fileName);
      }
      catch (FileNotFoundException)
      {
          // Descargar y guardar la imagen
          var httpClient = new HttpClient();
          var imageBytes = await httpClient.GetByteArrayAsync(uri);
          var file = await cacheFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
          await FileIO.WriteBytesAsync(file, imageBytes);
          return file;
      }
  }
  ```

### Implementación en XAML:

Al cargar imágenes desde servicios web, es importante utilizar el cache para evitar descargas innecesarias.

```xml
<Image Source="{Binding ImageUri, Converter={StaticResource ImageCacheConverter}}" />
```

```csharp
public class ImageCacheConverter : IValueConverter
{
    private static ImageCache _imageCache = new ImageCache();

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if(value is string uri)
        {
            return _imageCache.GetImage(uri);
        }
        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
```

## Buenas Prácticas en la Implementación de Carga Incremental

Para asegurar una implementación eficiente y mantenible de la carga incremental, sigue estas buenas prácticas:

* **Uso de Servicios Reutilizables:** Implementa servicios de datos que puedan ser reutilizados en diferentes partes de la aplicación.
* **Manejo de Errores:** Implementa mecanismos robustos para manejar errores en las solicitudes de datos, como retries o mensajes de error claros para el usuario.
* **Optimización de Consultas:** Asegúrate de que las consultas al API estén optimizadas para devolver solo los datos necesarios, utilizando filtros y campos específicos.
* **Documentación del Código:** Mantén una documentación clara y concisa del código para facilitar el mantenimiento y la colaboración.
* **Pruebas y Validaciones:** Realiza pruebas unitarias y de integración para asegurar que la carga incremental funcione correctamente bajo diferentes condiciones.

## Consideraciones Finales

Implementar **Carga Incremental** en tu aplicación UWP siguiendo el patrón **MVVM** no solo mejora el rendimiento y la eficiencia, sino que también mantiene una estructura de código limpia y mantenible. Al integrar esta funcionalidad con prácticas de optimización como el repintado eficiente y el cacheado de imágenes, aseguras que tu aplicación pueda manejar grandes volúmenes de datos de manera efectiva, proporcionando una experiencia de usuario óptima y satisfactoria.

Recuerda siempre seguir las buenas prácticas:

* **Optimiza las Consultas:** Asegúrate de que las solicitudes al API estén optimizadas para devolver solo los datos necesarios.
* **Maneja Errores Adecuadamente:** Implementa mecanismos robustos para manejar errores en las solicitudes de datos, proporcionando retroalimentación clara al usuario.
* **Documenta tu Código:** Mantén una documentación clara y concisa para facilitar el mantenimiento y la colaboración.
* **Prueba Extensivamente:** Realiza pruebas unitarias y de integración para asegurar que la carga incremental funcione correctamente bajo diferentes condiciones.

Con estos pasos y consideraciones, estarás en camino de desarrollar aplicaciones UWP eficientes, escalables y centradas en el usuario, aprovechando al máximo las capacidades que ofrece el patrón **MVVM**.
