[Tips and Tricks] Windows 10: Custom State Trigger para gestionar cambios en el estado de la batería

Introducción

Entre la lista de factores importantes que marcan diferencia en practicamente cualquier dispositivo móvil encontramos la batería. Hemos ido viendo una mejora paulatina pero gradual en la calidad y duración de las mismas. Sin embargo, las exigencias requeridas también han ido incrementando. Mayor cantidad de aplicaciones usadas por los usuarios, más sensores, etc.

A pesar de las posibilidades ofrecidas por el sistema para poder gestionar el uso de la batería, reducir opciones activas cuando empieza a tener un nivel escaso, en nuestras propias aplicaciones y como desarrolladores, contamos con APIs para poder trabajar con la batería; detectar capacidad, porcentaje y otras opciones.

En este artículo, vamos a crear varios Adaptive Triggers personalizados con el objetivo de obtener información del estado y porcentaje de la batería.

¿AdaptiveTriggers?

Con la llegada del SDK de Windows 10 tenemos la posibilidad de crear Apps Universales con un único binario que funcione en múltiples plataformas. Es un paso importante pero que conlleva realizar una acción que sera comun, diferenciar entre las diferentes plataformas donde correrá dicho binario para poder adaptar la interfaz de usuario. Con ese objetivo llegan los Adaptive Triggers. Recibimos potentes novedades en los Visual States:

  • Podemos modificar propiedades sin necesidad de animaciones. Anteriormente, la síntaxis era mucho más verbosa y necesitábamos utilizar StoryBoards para cambiar cualquier propiedad por simple que fuese.
  • Contamos con los StateTrigger. Podemos lanzar Triggers cuando se aplica un estado visual sin necesidad de código adyacente en el code-behind. El concepto es muy similar a los Triggers que tenemos disponible en WPF y Silverlight y el objetivo es el mismo, realizar un cambio cuando una condición cambia.

Actualmente contamos con un tipo de StateTrigger por defecto, el Adaptive Trigger que cuenta con los siguientes tipos:

  • MinWindowWidth
  • MinWindowHeight

Ambos nos permiten detectar cambios dinámicos en el tamaño de la pantalla de modo que se adapte el tamaño de la pantalla entre pantallas pequeñas y grandes. El funcionamiento es similar a las media queries en CSS por ejemplo. Se crearán diferentes estados estableciendo unos altos y anchos mínimos, de modo que, cuando la pantalla es más grande que el tamaño asignado se activará el estado visual.

Custom State Triggers relacionados con estado de batería

Los Triggers disponibles son lo suficientemente potentes como para permitirnos un trabajo correcto adaptando la interfaz de usuario pero… ¿podemos llegar más lejos?, ¿y si no es suficiente?

Podemos crear state triggers propios creando una clase que herede de la clase base llamada StateTriggerBase disponible dentro del namespace Windows.UI.Xaml.

En esta ocasión vamos a crear dos state triggers:

  • BaterryStatusTrigger: Nos permitirá detectar el estado de la batería, es decir, si esta cargándose, descargándose, etc.
  • BatteryPercentageTrigger: Nos indicará si el nivel de la batería es alto, medio o bajo para poder actuar en consecuencia.

BatteryStatusTrigger

Comenzamos creando el primer StateTrigger. Creamos una clase que hereda de StateTriggerBase:

public class BatteryStatusTrigger : StateTriggerBase
{
 
}

Para facilitar la verificación del estado, evitando errores con cadenas vamos a crear una enumeración llamada BatteryChargingStatus:

public enum BatteryChargingStatus        
{            
     Charging = 0,
     Discharging = 1,
     Unknown = 2        
}

Y una propiedad del tipo de la enumeración que usaremos para verificar el valor actual:

private static BatteryChargingStatus _charging;

En nuestro StateTrigger crearemos una propiedad de dependencia llamada BatteryStatus que nos permitirá pasar el estado de la batería, de modo que podamos lanzar la condición idónea en cada caso:

public BatteryChargingStatus BatteryStatus
{            
     get { return (BatteryChargingStatus)GetValue(BatteryStatusProperty); }            
     set { SetValue(BatteryStatusProperty, value); }
}
         
public static readonly DependencyProperty BatteryStatusProperty =
       DependencyProperty.Register("BatteryStatus", typeof(BatteryChargingStatus), typeof(BatteryStatusTrigger),
       new PropertyMetadata(false, OnBatteryStatusPropertyChanged));
         
private static void OnBatteryStatusPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)       
{           
     var obj = (BatteryStatusTrigger)d;
     var val = (BatteryChargingStatus)e.NewValue;
 
     obj.SetActive(val == _charging);       
}

Para acceder a información relacionada con la batería utilizaremos el
namespace Windows.Devices.Power donde tendremos acceso a la clase AggregateBattery.
Esta clase agrupa todas las baterías que pueda tener el dispositivo
(podrían ser más de una) otorgando acceso a información y reportes de
forma agrupada.

Utilizamos AggregateBattery para obtener información de la batería utilizando el método GetReport que nos devolverá una instancia de tipo BatteryReport.

Accederemos a la propiedad Status (enumeración BatteryStatus) para verificar el estado de la batería:

public BatteryStatusTrigger()       
{
     UpdateStatus();
 
     Windows.Devices.Power.Battery.AggregateBattery.ReportUpdated += (s, a) =>
     {
          UpdateStatus();
     };        
}

Dependiendo del valor de Status:

  • NotPresent: No se encuentra batería.
  • Discharging: Batería descargándose.
  • Idle: Inactiva.
  • Charging: Batería cargándose.

Estableceremos el valor adecuado de nuestra propia enumeración, BatteryChargingStatus.

private void UpdateStatus()
{
     var batteryReport = Windows.Devices.Power.Battery.AggregateBattery.GetReport();
 
     switch (batteryReport.Status)
     {
          case Windows.System.Power.BatteryStatus.Charging:
               _charging = BatteryChargingStatus.Charging;
               break;
          case Windows.System.Power.BatteryStatus.Discharging:
               _charging = BatteryChargingStatus.Discharging;
               break;
          default:
               _charging = BatteryChargingStatus.Unknown;
               break;            
     }        
}

De esta forma, podemos facilmente notificar al usuario o realizar ajustes en la interfaz dependiendo del estado de la batería.

BatteryPercentageTrigger

Continuamos con el segundo de los StateTrigger. En esta ocasión nuestra intención es verificar el porcentaje de carga para poder realizar ajustes en la interfaz con facilidad dependiendo de la capacidad de batería disponible.

De nuevo, creamos una clase que hereda de StateTriggerBase:

public class BatteryPercentageTrigger : StateTriggerBase    
{
 
}

Creamos enumeración personalizada para saber el estado de la batería:

public enum BaterryPercentageState        
{            
     VeryHight = 0,
     Hight = 1,
     Medium = 2,
     Low = 3,
     Verylow = 4,
     Unknown = 5        
}

Desde valores muy elevados a muy bajos en función del nivel de batería.

private static BaterryPercentageState _batteryPercentageState;

En nuestro StateTrigger crearemos una propiedad de dependencia llamada BatteryPercentage que nos permitirá pasar el estado de carga de la batería, de modo que podamos lanzar la condición idónea en cada caso:

public BaterryPercentageState BaterryPercentage        
{            
     get { return (BaterryPercentageState)GetValue(BaterryPercentageProperty); }            
     set { SetValue(BaterryPercentageProperty, value); }        
}
         
public static readonly DependencyProperty BaterryPercentageProperty =
       DependencyProperty.Register("BaterryPercentage", typeof(BaterryPercentageState), typeof(BatteryPercentageTrigger),
       new PropertyMetadata(BaterryPercentageState.Unknown, OnBaterryPercentagePropertyChanged));
         
private static void OnBaterryPercentagePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)        
{            
     var obj = (BatteryPercentageTrigger)d;
     var val = (BaterryPercentageState)e.NewValue;
 
     obj.SetActive(val == _batteryPercentageState);        
}

Al igual que en StateTrigger anterior utilizaremos los métodos
de obtención de reportes y evento de cambio en reporte para verificar de
manera exacta el porcentaje de la batería:

public BatteryPercentageTrigger()        
{            
     UpdatePercentage();
 
     Windows.Devices.Power.Battery.AggregateBattery.ReportUpdated += (s, a) =>
     {
          UpdatePercentage();
     };        
}

Utilizaremos las propiedades FullChargeCapacityInMilliwattHours, que nos indica la capacidad de la batería junto la propiedad RemainingCapacityInMilliwattHours, que nos indica la capacidad disponible ambas en mW-h para calcular el porcentaje de la batería:

private void UpdatePercentage()        
{
     var batteryReport = Windows.Devices.Power.Battery.AggregateBattery.GetReport();
 
     if (batteryReport.FullChargeCapacityInMilliwattHours != null && batteryReport.RemainingCapacityInMilliwattHours != null)
     {
          var percentage = (batteryReport.RemainingCapacityInMilliwattHours.Value /
              (double)batteryReport.FullChargeCapacityInMilliwattHours.Value) * 100;
 
          if (percentage <= 100 && percentage > 80)
          {
              _batteryPercentageState = BaterryPercentageState.VeryHight;
          }
          else if (percentage <= 80 && percentage > 60)
          {
              _batteryPercentageState = BaterryPercentageState.Hight;
          }
          else if (percentage <= 60 && percentage > 40)
          {
              _batteryPercentageState = BaterryPercentageState.Medium;
          }
          else if (percentage <= 40 && percentage > 20)
          {
               _batteryPercentageState = BaterryPercentageState.Low;
          }
          else if (percentage <= 20 && percentage > 1)
          {
              _batteryPercentageState = BaterryPercentageState.Verylow;
          }
          else
          {
             _batteryPercentageState = BaterryPercentageState.Unknown;
          }
     }        
}  

Segun porcentaje de la batería estableceremos el valor adecuado de nuestra enumeración de tipo BatteryPercentageState.

Utilizando los StateTriggers creados

Con ambos StateTriggers a nuestro servicio ha llegado el momento de utilizarlos. Contaremos con una interfaz sumamente simple:

<TextBlock x:Name="BatteryText"               
           Text="Battery Status"/>

Mostraremos un texto diferente según el porcentaje de la batería utilizando BatteryPercentageTrigger
y el texto en color verde si el dispositivo esta cargando la batería o
rojo si al contrario, la batería se esta descargando utilizando BatteryStatusTrigger:

<VisualStateManager.VisualStateGroups>
     <VisualStateGroup>
          <VisualState x:Name="Charging">
              <VisualState.StateTriggers>
                  <customStateTriggers:BatteryStatusTrigger BatteryStatus="Charging" />
              </VisualState.StateTriggers>
              <VisualState.Setters>
                  <Setter Target="BatteryText.Foreground" Value="Green" />
              </VisualState.Setters>
          </VisualState>
          <VisualState x:Name="Discharging">
              <VisualState.StateTriggers>
                   <customStateTriggers:BatteryStatusTrigger BatteryStatus="Discharging" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Foreground" Value="Red" />
               </VisualState.Setters>
           </VisualState>
       </VisualStateGroup>
       <VisualStateGroup>
           <VisualState x:Name="PercentageVeryHight">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="VeryHight" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                  <Setter Target="BatteryText.Text" Value="Very hight" />
               </VisualState.Setters>
          </VisualState>
          <VisualState x:Name="PercentageHight">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Hight" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Hight" />
               </VisualState.Setters>
           </VisualState>
           <VisualState x:Name="PercentageMedium">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Medium" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Medium" />
               </VisualState.Setters>
           </VisualState>
           <VisualState x:Name="PercentageLow">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Low" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Low" />
               </VisualState.Setters>
           </VisualState>
           <VisualState x:Name="PercentageVeryLow">
               <VisualState.StateTriggers>
                   <customStateTriggers:BatteryPercentageTrigger BaterryPercentage="Verylow" />
               </VisualState.StateTriggers>
               <VisualState.Setters>
                   <Setter Target="BatteryText.Text" Value="Very low" />
               </VisualState.Setters>
           </VisualState>
     </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

Si ejecutamos la App:

Custom State Triggers de batería

Custom State Triggers de batería

Nuestra aplicación se ejecuta en un dispositivo móvil con la batería
descargándose y a nivel bajo, es decir, por debajo de 40% y por encima
de 20%.

Podéis acceder al código fuente directamente en GitHub:

Ver GitHub

Recordar que cualquier tipo de duda o sugerencia la podéis dejar en los comentarios de la entrada.

Más Información

[Tips and Tricks] Windows 10: Gestión de la batería

Introducción

Entre la lista de factores importantes que marcan diferencia en practicamente cualquier dispositivo móvil encontramos la batería.
Hemos ido viendo una mejora paulatina pero gradual en la calidad y
duración de las mismas. Sin embargo, las exigencias requeridas también
han ido incrementando. Mayor cantidad de aplicaciones usadas por los
usuarios, más sensores, etc.

A pesar de las posibilidades ofrecidas por el sistema para poder
gestionar el uso de la batería, reducir opciones activas cuando empieza a
tener un nivel escaso, en nuestras propias aplicaciones y como
desarrolladores, contamos con APIs para poder trabajar con la batería;
detectar capacidad, porcentaje y otras opciones.

En este artículo, vamos a repasar todas las posibilidades
relacionadas con la batería disponibles en Windows 10 dentro del
namespace Windows.Devices.Power.

Trabajando con la batería

Crearemos un nuevo proyecto UAP:

Nueva App UAP

Nueva App UAP

Añadimos las carpetas Views, ViewModels y Services además de las clases base necesarias para implementar el patrón MVVM de la misma forma que vimos en este artículo.

Para acceder a información relacionada con la batería utilizaremos el
namespace Windows.Devices.Power donde tendremos acceso a la clase AggregateBattery.
Esta clase agrupa todas las baterías que pueda tener el dispositivo
(podrían ser más de una) otorgando acceso a información y reportes de
forma agrupada.

Utilizamos AggregateBattery para obtener información de la batería utilizando el método GetReport que nos devolverá una instancia de tipo BatteryReport.

var batteryReport = Windows.Devices.Power.Battery.AggregateBattery.GetReport();

Entre el conjunto de propiedades disponibles en el reporte, contamos con una propiedad llamada Status (enumeración BatteryStatus):

public string Status
{
     get { return _status; }
     set
     {
          _status = value;
          RaisePropertyChanged();
     }
}
 
Status = batteryReport.Status.ToString();

La enumeración de tipo BatteryStatus cuenta con las siguientes opciones:

  • NotPresent: No se encuentra batería.
  • Discharging: Batería descargándose.
  • Idle: Inactiva.
  • Charging: Batería cargándose.

Continuamos con otra propiedad, ChargeRateInMilliwatts:

public int ChargeRate
{
     get { return _chargeRate; }
     set
     {
          _chargeRate = value;
          RaisePropertyChanged();
     }
}
 
if (batteryReport.ChargeRateInMilliwatts != null)
     ChargeRate = batteryReport.ChargeRateInMilliwatts.Value;

Nos indica en mW el ritmo de carga o descarga en caso de valores negativos de la batería.

NOTA: El valor puede ser nulo en caso de no encontrar batería.

De igual forma que podemos saber el ritmo de carga, podemos conocer la capacidad de la batería con la propiedad FullChargeCapacityInMilliwattHours:

Y la capacidad disponible con RemainingCapacityInMilliwattHours:

public int RemainingCapacity
{
     get { return _remainingCapacity; }
     set
     {
           _remainingCapacity = value;
           RaisePropertyChanged();
     }
}
 
RemainingCapacity = batteryReport.RemainingCapacityInMilliwattHours.Value;

Ambos dados en mW-h. Con los valores anteriores, obtener el porcentaje actual de la batería es una operación sumamente simple:

public double Percentage
{
     get { return _percentage; }
     set
     {
           _percentage = value;
           RaisePropertyChanged();
     }
}
 
var percentage = (batteryReport.RemainingCapacityInMilliwattHours.Value /
                 (double)batteryReport.FullChargeCapacityInMilliwattHours.Value) * 100;
     
Percentage = percentage; 

Si ejecutamos la aplicación:

Información de la batería

Información de la batería

NOTA: Para obtener información válida en el
BatteryReport se debe ejecutar la App en un dispositivo físico. En caso
de ejecutar en emulador, se obtendrán nulos.

Podéis descargar el ejemplo completo realizado a continuación:

También tenéis el código fuente disponible e GitHub:

Ver GitHub

Recordar que podéis dejar en los comentarios cualquier tipo de sugerencia o pregunta.

Más información