Pantallas de contenido faltante en una aplicación móvil usando Xamarin como ejemplo

Cómo empezó todo

Muy a menudo, cuando trabajamos con aplicaciones empresariales, tenemos que lidiar con estas pantallas en ausencia de datos recibidos del backend, cuando simplemente necesitamos mostrar una lista en la pantalla.





, , , , , .





, , . , .





-, . -, UI- - .





public class EmptyStateViewModel : ViewModel
{
    public EmptyStateViewModel(string image, string title, string description)
    {
        Image = image;
        Title = title;
        Description = description;
    }

    public string Image { get; }

    public string Title { get; }

    public string Description { get; }
}
      
      



( xaml Xamarin Forms) Bindings , , mvvm- .





?

- , - . - EmptyStateView, Retry, . EmptyStateViewModel, , .





public class ErrorStateViewModel : EmptyStateViewModel
{
    public ErrorStateViewModel(string image, string title, string description, string actionTitle, Command actionCommand)
        : base(image, title, description)
    {
        ActionTitle = actionTitle;
        ActionCommand = actionCommand;
    }

    public string ActionTitle { get; }

    public Command ActionCommand { get; }
}
      
      



?

- . . , -. None, null.





public static class OverlayFactory
{
    public static T None<T>()
        where T : EmptyStateViewModel
    {
        return null;
    }

    public static EmptyStateViewModel CreateCustom(string image, string title, string description)
    {
        return new EmptyStateViewModel(image, title, description);
    }

    public static ErrorStateViewModel CreateCustom(string image, string title, string description, string actionTitle, Command actionCommand)
    {
        return new ErrorStateViewModel(image, title, description, actionTitle, actionCommand);
    }
}
      
      



- -, , ,





public class SomeViewModel : BaseViewModel
{
    private IItemsLoadingService _itemsLoadingService;
    
    public SomeViewModel(IItemsLoadingService itemsLoadingService)
    {
        _itemsLoadingService = itemsLoadingService;
    }

    public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel>();

    public EmptyStateViewModel EmptyState { get; protected set; }

    public ErrorStateViewModel ErrorState { get; protected set; }

    public override async Task InitializeAsync()
    {
        await base.InitializeAsync();

        await LoadItemsAsync();
    }

    private async Task LoadItemsAsync()
    {
        try
        {
            var result = await _itemsLoadingService.GetItemsAsync();
            var items = result.ToList();

            ErrorState = OverlayFactory.None<ErrorStateViewModel>();

            if (items.Count == 0)
            {
                EmptyState = OverlayFactory.CreateCustom("img_empty_state", "Title", "Description");
            }
            else
            {
                EmptyState = OverlayFactory.None<ErrorStateViewModel>();
                // Add items to list
            }
        }
        catch
        {
            ErrorState = OverlayFactory.CreateCustom("img_error_state", "Title", "Description", "Retry", new Command(() => LoadItemsAsync));
        }
    }
}
      
      



Binding EmptyState/ErrorState , mvvm-, , EmptyStateViewModel/ErrorStateViewModel null, . SetViewModel.





, View ViewModel View ViewState . ViewModel null - ViewState Gone, - Visible:





public void SetViewModel(EmptyStateViewModel viewModel)
{
    ViewModel = viewModel;

    View.Visibility = viewModel != null ? ViewStates.Visible : ViewStates.Gone;
}
      
      



iOS - constraints , - . enum, Android.





public void SetViewModel(EmptyStateViewModel viewModel)
{
    ViewModel = viewModel;

    View.SetVisibility(viewModel != null ? ViewStates.Visible : ViewStates.Gone);
}
      
      



extension





public static void SetVisibility(this UIView view, ViewVisibility visibility)
{
    var constraints = GetViewConstraints(view) ?? new NSLayoutConstraint[] {};

    if (visibility == ViewVisibility.Gone)
    {
        SaveViewConstraints(view, constraints);
        NSLayoutConstraint.DeactivateConstraints(constraints);
        view.Hidden = true;
        return;
    }
  
    if (visibility == ViewVisibility.Visible)
    {
        SaveViewConstraints(view, null);
        NSLayoutConstraint.ActivateConstraints(constraints);
        view.Hidden = false;
        return;
    }
}
      
      



Aquí, en el caso de configurar ViewVisibility.Gone, pre-guardamos las restricciones de nuestra vista y las desactivamos, y cuando la visibilidad está activada, por el contrario, eliminamos las restricciones previamente guardadas, restablecemos el guardado y luego las activamos.





private static NSLayoutConstraint[] GetViewConstraints(UIView view)
{
    return view.GetAssociatedObject<NSMutableArray<NSLayoutConstraint>>(Key)?.ToArray() ??
           view.Superview?.Constraints
               .Where(constraint => (constraint.FirstItem?.Equals(view) == true) || constraint.SecondItem.Equals(view))
               .ToArray();
}

private static void SaveViewConstraints(UIView view, NSLayoutConstraint[] constraints)
{
    NSMutableArray<NSLayoutConstraint> viewConstraints = null;

    if (constraints.Length > 0)
    {
        viewConstraints = new NSMutableArray<NSLayoutConstraint>();
        viewConstraints.AddObjects(constraints);
    }

    view.SetAssociatedObject(Key, viewConstraints, AssociationPolicy.RetainNonAtomic);
}
      
      



El primer método le permite obtener restricciones previamente guardadas, si las hay, o, si no, obtener las actuales. Si no hay una vista principal, se devolverá un valor nulo.





El segundo método guardará las restricciones actuales para que se puedan restaurar más tarde.





Por lo tanto, resultó hacer pantallas más agradables con datos faltantes o pantallas de estado de error.





PD: el primer artículo sobre Habré, por lo tanto, no juzgues estrictamente. Pero necesitas empezar por alguna parte.








All Articles