Página base en Xamarin Forms

Cuando estamos desarrollando una aplicación, ya sea con Xamarin Forms o con cualquier otra tecnología, muchas de las páginas que tenemos que crear tienen partes en común, tanto en apariencia como en funcionalidad. Por lo que, es muy interesante, tener esa apariencia y funcionalidad junta, de tal forma, que no solo no habrá que implementarla una y otra vez en cada página, sino que, si se desea cambiar o corregir algún problema, no será necesario retocar todas y cada una de las páginas.
A continuación, voy a mostrar un ejemplo en Xamarin Forms, en el que se va a crear una página base con un indicador de actividad. Asimismo, se creará otra página que “hereda” de esta primera con el fin de hacer ver, que no es necesario replicar ni una sola línea de código. Cabe destacar, que en el ejemplo he utilizado el patrón MVVM, por lo que también se va a crear un ViewModel base que tendrá la funcionalidad para manejar el indicador de actividad. Todos los ViewModel de las páginas que tengan un indicador de actividad, deben heredar de dicho ViewModel
Creando la página base
La página base, será una ContentPage que lo único que tiene es un ActivityIndicator, que debe estar corriendo siempre que se realice una acción pesada, y un Label, que debería mostrar un texto descriptivo de la acción pesada que se está realizando.
Además, el ContentPage tendrá un ContentPresenter, el cual será utilizado por su páginas “hijas” para mostrar su contenido.
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ViewBaseExample.Views.Base.BaseView"
xmlns:converters="clr-namespace:ViewBaseExample.Converters;assembly=ViewBaseExample"
x:Name="_parent">
<ContentPage.Resources>
<converters:InverseBoolConverter x:Key="InverseBoolConverter"/>
</ContentPage.Resources>
<ContentPage.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- CONTENT -->
<ContentPresenter HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
Content="{Binding Path=ViewContent, Source={x:Reference _parent}}"
IsVisible="{Binding Bussy, Mode=OneWay, Converter={StaticResource InverseBoolConverter}}"/>
<!-- BUSSY INDICATOR -->
<StackLayout HorizontalOptions="Center" VerticalOptions="Center" IsVisible="{Binding Bussy, Mode=OneWay}">
<ActivityIndicator HorizontalOptions="Center" VerticalOptions="Center" IsRunning="{Binding Bussy, Mode=OneWay}"/>
<Label HorizontalOptions="Center" Text="{Binding BussyText, Mode=OneWay}" />
</StackLayout>
</Grid>
</ContentPage.Content>
</ContentPage>
Cabe destacar, que a la ContentPage se le ha dado un nombre para poder bindear el contenido del ContentPresenter con la propiedad ViewContent que se ha declarado en el code behind y se utilizará el las páginas hijas para mostrar su contenido.
public partial class BaseView : ContentPage
{
public static readonly BindableProperty ViewContentProperty = BindableProperty.Create(nameof(ViewContent), typeof(View), typeof(BaseView));
public View ViewContent
{
get { return (View)GetValue(ViewContentProperty); }
set { SetValue(ViewContentProperty, value); }
}
public BaseView()
{
InitializeComponent();
}
}
Asimismo, indicar que se utiliza el converter InverseBoolConverter, para ocultar el ContentPresenter siempre que el indicador de actividad esté corriendo.
Creando el viewModel base
Este viewModel será el asociado a la página base. Solamente contendrá las siguientes propiedades:
- Bussy: booleano para indicar que se está ocupado, es decir, se está realizando alguna tarea pesada.
- BussyText: string para especificar qué acción se está realizando.
public class BaseViewModel : BindableObject
{
private bool _bussy;
private string _bussyText;
public bool Bussy
{
get { return _bussy; }
set
{
_bussy = value;
OnPropertyChanged();
}
}
public string BussyText
{
get { return _bussyText; }
set
{
_bussyText = value;
OnPropertyChanged();
}
}
public BaseViewModel()
{
}
}
Si volvemos a revisar el XAML de la página base, se aprecia que la propiedad IsRunning del indicador de actividad, está bindeada contra la propiedad Bussy del ViewModel. De la misma manera, el texto del label está bindeado contra la propiedad BussyText.
Una vez creada la página base y su viewModel, solo falta ver como se utilizan.
Creando una página a partir de la base
Ahora, al crear una página, en lugar de ser un ContentPage será una BaseView, y el contenido, en lugar de estar en el Content estará en el ViewContent (recordad que es la propiedad que se creó en el code behind del BaseView).
Para ver un ejemplo sencillo, se ha creado una vista cuyo único contenido es un label con un texto fijo.
<?xml version="1.0" encoding="UTF-8"?>
<viewBase:BaseView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewBase="clr-namespace:ViewBaseExample.Views.Base;assembly=ViewBaseExample"
x:Class="ViewBaseExample.Views.FirstView">
<viewBase:BaseView.ViewContent>
<!-- CONTENT -->
<Label Text="This is the first view" VerticalOptions="Center" HorizontalOptions="Center" />
</viewBase:BaseView.ViewContent>
</viewBase:BaseView>
Creando un viewModel a partir de la base
El último paso que se va a dar es crear el ViewModel que hereda de BaseViewModel. Siguiendo con nuestro ejemplo, este ViewModel no tiene nada de funcionalidad, ya que su view, lo único que muestra es un texto fijo en un label. No obstante, para simular una tarea que conlleva cierto tiempo, se ha introducido un delay de 3 segundos en el constructor, durante el cual, se indica que el ViewModel está ocupado (Bussy = true) y el motivo por el que lo está (BussyText = “Loading…”). Tras terminar la tarea, se indica que el view model ya no está ocupado.
public class FirstViewModel : BaseViewModel
{
public FirstViewModel()
{
Load();
}
private async void Load()
{
Bussy = true;
BussyText = "Loading...";
await System.Threading.Tasks.Task.Delay(3000);
Bussy = false;
BussyText = string.Empty;
}
}
En este ejemplo, se ha creado una página base con un indicador de actividad, pero se podrían hacer distintas páginas que nos hiciesen más productivos a la hora de crear nuestras apps. Por ejemplo, se podría poner título, mensajes de aviso/error/información, botones para guardar/eliminar, listas genéricas que permitan búsquedas y selección… Imaginación al poder en este sentido.
Aquí os dejo un enlace en el que encontrar un proyecto con una implementación completa del ejemplo.
https://github.com/jorgediegocrespo/BasePageXamarinForms
Un saludo!!