VISITAS:

sábado, 7 de octubre de 2017

Mi experiencia con Apple pencil y Goodnotes

Este post va a ser un poco distinto a los demás, ya que no voy a explicar ningún tema concreto, sino que voy a contar mi experiencia (siendo ambiciosos podríamos llamarle 'review') con un producto y una aplicación.

Hasta ahora...

Hasta hace poco, yo era un consumidor casi compulsivo de cuadernos, bolígrafos, lápices y material relacionado.  ¿Por qué? Porque cada vez que leo un artículo, cada vez que empiezo a leer un libro, cada vez que voy a una charla, incluso cada vez que asisto a una reunión, siento la imperiosa necesidad de apuntarlo todo. Y siempre me ha gustado apuntarlo todo en un cuaderno, aunque la realidad es que terminaba rellenando folios de apuntes sin ninguna clasificación ni control, con el riesgo obvio de perderlos o incluso peor, saber que esos apuntes estaban entre un buen montón de folios en algún sitio, pero no poder localizarlos.

Siempre he deseado tomar estos apuntes de forma digital y poder tenerlo todo bien organizado. He probado muchas aplicaciones de notas (con teclado), por ejemplo OneNote de Microsoft es una de las mejores. También Evernote está bastante bien. Pero siempre me he tropezado con el escollo (para mí) de que necesito hacer dibujos en medio de mis notas. No lo puedo evitar, me gusta escribir mientras aprendo o escucho, pero necesito hacer muchos dibujos acompañando los textos. Por este motivo, he intentado utilizar sistemas como IrisNotes, un 'bolígrafo digital' muy interesante. Se trata de un bolígrafo casi normal, y un "aparatito" que se encaja en la parte superior del cuaderno. Cuando escribimos con el bolígrafo, el aparatito recoge la información de movimiento y va almacenando los trazos que vamos haciendo. No está mal la idea.
Pero falla en un pequeño detalle que yo valoro mucho: la espontaneidad. Para empezar a tomar notas hay que preparar: el bolígrafo, el aparatito. No es práctico. Porque cuando quieres tomar notas, quieres espontaneidad.
También he probado sistemas como el stylus de Samsung en sus dispositivos Galaxy Note, tanto móvil como tablet. La verdad es que podrían servir, salvo por otro pequeño detalle: el dichoso 'lag'. ¿Qué es el 'lag'? Pues es algo tan sencillo de definir y tan difícil de reducir como el tiempo que transcurre desde que hago un trazo en la pantalla del dispositivo hasta que la pantalla lo visualiza. Este tiempo son unos pocos milisegundos, pero si se supera cierta cantidad, 100ms más o menos, es inadmisible para el cerebro humano. Y por desgracia, los dispositivos de Samsung tienen un lag tan grande, que la sensación de escribir con naturalidad desaparece. Quizás se pueda utilizar para subrayar un pdf o bien para retocar una fotografía, pero para escribir o dibujar NO VALE.
Entre medias he utilizado algunas tabletas digitalizadoras, pero en este caso, el problema ya no sólo es el lag, sino que se añade el hecho de que estás escribiendo sobre una superficie (la tableta) y el resultado sale en otro sitio: la pantalla del ordenador. Esto es inadmisible para la escritura y casi también para el dibujo (aunque hay artistas digitales que lo utilizan y les encanta). Además, con esta solución se pierde completamente el concepto de portabilidad, tan necesario en la toma de apuntes.

Apple pencil

Y entonces llegó el Apple pencil. ¿En qué se diferencia el Apple Pencil de los demás stylus? En algo fundamental: el lag es prácticamente inapreciable. Sobre todo sobre las pantallas de los iPad Pro con refresco de 120Hz. Esto no sólo es un salto cuantitativo (lag casi cero), sino también cuantitativo: la sensación de estar escribiendo sobre la pantalla y que ésta responda a tus trazos de forma inmediata te hace sentir como si estuvieras sobre un papel, SALVO que la superficie es de cristal, lo cual se hace un poco raro al principio, pero te acostumbras.
La escritura con Apple pencil es sencillamente casi perfecta. Inmediatamente te olvidas que estás escribiendo sobre una pantalla y ves como todas tus ideas fluyen inmediatamente hacia el dispositivo. La sensación es maravillosa. No soy un experto dibujante, pero por lo que me han comentado los profesionales del dibujo tienen una sensación muy cercana a la realidad cuando trazan sus dibujos sobre el iPad con ayuda de un Apple pencil.
Además del lag inapreciable, otro tema muy importante con los stylus es 'palm rejection' o sea, que mientras estás escribiendo/dibujando en la pantalla, puedas apoyar la palma de la mano en la pantalla y que este gesto no dispare otras acciones o que incluso no haga rayas en el dibujo. Y Apple pencil esto lo hace muy muy bien. Mientras la punta del lápiz esta cerca de la pantalla, el resto de interacciones se desactivan. Así que si eres zurdo como yo o bien tienes la costumbre de escribir apoyando la palma de la mano sobre la pantalla, no vas a tener ningún problema.

Goodnotes

Y una vez entusiasmado con el pencil, sólo me faltaba una cosa: una buena aplicación para tomar notas. Sabía que eso no iba a ser difícil de encontrar, que incluso tendría problemas para elegir la que más me convenía. Y así fue.
Las empresas de desarrollo de aplicaciones se han esmerado muy mucho en lanzar aplicaciones que se integran a la perfección con el Apple pencil.
Pasé por algunas de estas aplicaciones, y la verdad es que casi todas me dieron muy buena impresión. Hasta que di con Goodnotes y entonces me di cuenta de que algo a lo que muchas veces no hacemos caso, la experiencia de usuario (UX user experience en inglés) es algo fundamental cuando quieres agilidad y espontaneidad. Tomar notas con Goodnotes es muy parecido a la sensación de tener un cuaderno, varios bolígrafos de distintos colores y una goma de borrar.
No voy a juzgar la forma que tiene Goodnotes de organizar las libretas en carpetas, no es la mejor, pero es suficiente. Tampoco necesito 10000 libretas!!! Lo que hace me vale, aunque esto podría mejorar en un futuro.
Escribir con Apple pencil y Goodnotes es una experiencia fantástica. Una vez te acostumbras a escribir sobre un cristal (la pantalla), la sensación es casi la misma que escribir en un cuaderno. Pero claro, tienes muchas ventajas: puedes hacer 'undo' y 'redo'; puedes borrar cualquier cosa, puedes mover trazados por la pantalla, ampliarlos o reducirlos, cambiarlos de color, etc, etc. Las posibilidades son enormes. Y lo bueno es que la interacción que ha diseñado el equipo de Goodnotes hace que todo esto sea casi natural.

Cómo utilizo Goodnotes con Apple pencil

Casi todas las opciones de Goodnotes aparecen en la línea superior (yo habitualmente utilizo las que tienen un número):

Voy a intentar explicar las opciones que uso yo, pero no en el orden en el que aparecen en la figura.
  • Lápiz (4) : selecciona el modo de dibujo o escritura con lápiz. Si se vuelve a pulsar aparece un menú con opciones de color del lápiz y grosor
  • Subrayador (5) : selecciona el modo de subrayado (el efecto es parecido al de los rotuladores subrayadores que solemos utilizar para resaltar palabras). Al igual que ocurre con el lápiz, si se vuelve a pulsar, aparecen opciones para elegir el color y el grosor
  • Borrador (6) : borra libremente por la página. Si se vuelve a seleccionar, aparecen opciones para el grosor de la goma de borrar
  • Undo (8) : deshace lo último que hayamos hecho
  • Formas geométricas (3) : si esta opción está seleccionada, Goodnotes intentará "arreglar" nuestros trazados para que se conviertan en formas geométricas. Por ejemplo, un trazado más o menos redondo, se convertirá en un círculo. Yo uso mucho esta opción para subrayar texto: la activo, hago una raya más o menos recta, y Goodnotes la transforma en un subrayado perfecto
  • Selección (7) : permite seleccionar cosas trazando un lazo libremente. Hay que tener en cuenta que si la selección corta un trazado por la mitad, entonces Goodnotes lo incluye completamente en la selección. Es muy útil para mover zonas dentro de la página o para redimensionarlas. Aunque también se puede usar para cambiar el color de una zona
  • Visor de páginas (1) : muestra todas las páginas del cuaderno en miniaturas. Permite realizar rápidamente operaciones como borrar páginas, insertar, cambiar de orden
  • Insertar (3) : permite insertar páginas provenientes por ejemplo de un fichero PDF externo 

Además de este menú superior, yo también utilizo las siguientes acciones:

  • dibujo con el pencil sobre la hoja del cuaderno.
  • deslizando con dos dedos paso a la página siguiente o anterior (dependiendo si deslizamos hacia la izquierda o hacia la derecha)
  • en la última página del cuaderno, deslizando con dos dedos hacia la derecha y aguantando un poco, aparece la opción de crear una nueva página al final. 

Uso real

Hasta ahora, he estado utilizando Apple pencil y Goodnotes en:
  • Charlas: la experiencia es fenomenal, porque escribes a tu ritmo y no hay desventajas sobre el papel. Tiene la ventaja de que puedes mover trozos de texto hacia abajo e insertar cosas que se te hayan pasado anteriormente
  • Reuniones: también muy buena experiencia, ya que puedes hacer dibujos rápidamente en el "cuaderno" y mostrarlos a los asistentes
  • Aprendizaje: me encanta seguir cursos online, y ahora mucho más, ya que mientras estoy viendo un vídeo en el ordenador, puedo estar tomando apuntes en el iPad.

Otros dispositivos

Hace unos dos años una startup noruega, reMarkable, y concretamente su CEO Magnus Wanberg, decidió embarcarse en un proyecto muy ambicioso: sustituir el papel, bolígrafos, lápices por un dispositivo de tinta electrónico. Todos conocemos los ebooks, dispositivos de tinta electrónica, que tienen la gran ventaja sobre las pantallas de móviles y tablets, de que no funcionan con luz, sino con una especie de polvo magnético que simula muy de cerca las páginas de un libro. Al tener una pantalla no iluminada con luz, puede utilizarse perfectamente en condiciones lumínicas muy difíciles para una tablet, por ejemplo a pleno sol en una playa. Además, el consumo de batería de estas pantallas es tan bajo, que con una carga podemos tener lectura para varias semanas. Pero no todo podían ser ventajas: la tinta electrónica es monocroma, no hay colores; el refresco de la pantalla completa es muy lento, puede llegar al segundo o más; aunque algunas de estas pantallas son táctiles, el lag es tan alto que lo hace inmanejable como dispositivo de escritura.
Pero Magnus Wanberf se empeñó en conseguir un dispositivo de tinta electrónica con un lag inapreciable, similar al apple pencil. Desde hace unas semanas está distribuyendo los primeros dispositivos, y las críticas son bastante buenas. Me han pedido que evalúe uno de estos dispositivos, así que escribiré en este blog los resultados y mi experiencia. Estad atentos...

sábado, 11 de febrero de 2017

Javascript: use strict

Javascript permite realizar algunos chequeos al código que escribimos. No es que se convierta en un lenguaje con los chequeos de los lenguajes compilados, ni siquiera se aproxima a su primo-hermano typescript, pero con estos chequeos podremos detectar algunos errores de otra forma pasarían inadvertidos y sólo provocarían errores de ejecución. Errores que muchas veces son muy difíciles de detectar.

Modo estricto

Para activar el modo "estricto" de javascript, tenemos que poner en la primera línea del fichero .js lo siguiente:

    "use strict";

Obsérvese que utilizamos comillas, no ponemos directamente use strict. ¿Por qué? Porque de esta forma, un string pasará inadvertido en versiones antiguas de los navegadores. Sin embargo, cuando un navegador moderno, vea este string al comienzo del fichero, activará los chequeos de javascript.
Si se desean activar los chequeos en todo el fichero, entonces "use strict" tiene que ser la primera sentencia del fichero .js Sin embargo, es posible, por ejemplo, colocar la sentencia "use strict" al comienzo de una función, y se aplicarán los chequeos sólo a esa función.

Variables globales

En javascript podemos utilizar una variable en cualquier lugar sin haberla declarado antes. Por ejemplo:

    contador = 1;

Esto funciona, pero no es una práctica recomendable, como sabe muy bien cualquier programador.
Sin embargo, si activamos los chequeos:

    "use strict";
    contador = 1;

Al ejecutar este código, obtendremos un error de javascript: "contador is not defined" indicando que la variable contador no ha sido definida anteriormente. La forma correcta sería:

    "use strict";
    var contador;
    contador = 1;

Variables mal escritas

Otro error típico en javascript es escribir mal el nombre de una variable:

    var contador = 0;
    contdor = 1
    console.log(contador);

Por un error tecleando, hemos asignado el valor 1 a una variable que no es contador, lo cual está permitido en javascript, y la consola nos mostrará el valor cero, en lugar de 1, que es lo que esperábamos. No tendremos ninguna otra pista de que estemos haciendo algo mal, simplemente obtendremos el valor cero en la consola.
Si activamos el modo estricto, tendremos el aviso que nos llevará al error:

    "use strict";
    var contador = 0;
    contdor = 1
    console.log(contador);
Al ejecutar nos mostrará el mensaje: "contdor is not defined"

Palabras reservadas

Otro error común en javascript es el uso de palabras reservadas como nombres de variables:

    var let = 4;

Esta línea se ejecutaría correctamente en javascript. Sin embargo, si activamos el modo estricto, obtendremos un error:

    "use strict";
    var let = 4;

"reserved word"

eval

Los programadores javascript saben que no es recomendable utilizar la función eval() por los peligros que implica. Sobre todo, porque puede cambiar el contexto del código javascript que le rodea.

Por ejemplo:

    var a = 1;
    eval("var a = 12");
    console.log(a)

En la consola veremos un 12, en lugar de 1 que es el valor que esperamos. O sea, eval() ha creado una variable en nuestro código y ha "machacado" la que ya teníamos.
En modo estricto, lo que se ejecute con eval() no tiene consecuencias en el código que le rodea:

    "use strict";
    var a = 1;
    eval("var a = 12");
    console.log(a)

En este caso, la salida es 1, y el código dentro de eval() se ejecuta autónomamente sin efectos laterales con nuestro código.


jueves, 22 de diciembre de 2016

¿Typescript en 30 minutos?

¿Es posible aprender Typescript en 30 minutos o menos? Depende, si ya conoces algo de Javascript y también algún lenguaje de programación orientado a objetos como Java o C#, seguro que sí. Si no, te costará un poco más, pero no mucho. Te lo voy a demostrar en este post. Abre una nueva pestaña en playground Pon el cronómetro en marcha y empieza a leer... Empezamos con el lenguaje de moda y, quizás, el lenguaje del futuro para la web.


Qué es Typescript

Typescript es un lenguaje de programación desarrollado y mantenido por Microsoft. La primera versión pública salió en Octubre de 2012 y la presentó Anders Hejlsberg, el creador de lenguajes tan conocidos como Turbo Pascal, Delphi y C#.
Typescript es un superconjunto de Javascript, que no se utiliza directamente como tal, sino que se convierte a Javascript, que ya se puede utilizar en cualquier navegador o en NodeJS. Cualquier código Javascript válido es también código Typescript válido. Soporta Javascript ES5, ES2015 y ES2016, e incluso está previsto el soporte ES2017.
Typescript proporciona chequeo de tipos, interfaces, clases y módulos, entre otros mecanismos propios de los lenguajes orientados a objetos.
Existe una web que proporciona los ahora tan de moda, playground para aprender y experimentar con el lenguaje Typescript.
Veamos un poco más despacio las nuevas características del lenguaje Typescript.

Tipos

Typescript soporta chequeo de tipos estático que permite detectar muchos errores en la fase de desarrollo.
Los chequeos de tipos se realizan en la fase de compilación (realmente conversión) de Typescript a Javascript.
Las anotaciones de tipos son opcionales. Si no se especifica un tipo, entonces no se efectuará ningún chequeo.
Los tipos soportados son: number, boolean, string y los arrays de estos tipos. También existe el tipo any para cualquier tipo, incluidas las estructuras de datos u objetos.
Ejemplo:

function multiply(a: number, b: number) : number {
    return a * b;
}

Funciones arrow

En Javascript podemos crear funciones anónimas de la siguiente forma:

setTimeout(function() {
    console.log("Timed out function");
}, 500);

Typescript permite una sintaxis, denominada arrow, para definir de una forma más sencilla las funciones anónimas:

setTimeout( () => { console.log("Timed out function"); }, 500);

Variables con ámbito de bloque

En Javascript, las variables declaradas con var tienen ámbito de la función donde se declaran. De hecho, aunque se declare la función en un punto intermedio de la función, Javascript traslada esa declaración al principio de la función. Esto puede llevar a situaciones extrañas, como por ejemplo:

var x = 3;
function show(param) {
    if (param == true) {
        return x;
    } else {
        var x = 4;
        return x;
    }
}
console.log(show(true));

Aunque podríamos pensar que el resultado en la consola es 3, realmente veremos "undefined". ¿Por qué? Porque el motor Javascript traslada la declaración var x al principio de la función. Es como si fuera:

var x = 3;
function show(param) {
    var x;
    if (param == true) {
        return x;
    } else {
        x = 4;
        return x;
    }
}
console.log(show(true));

Así se entiende porqué el valor retornado es undefined.

Typescript introduce la declaración de variables con let y con const.
let hace que la variable tenga ámbito de bloque y const es similar pero sólo se puede inicializar el valor y luego no se puede modificar.

Cambiemos el ejemplo anterior utilizando let y quedará mucho más claro:

let x = 3;
function show(param) {
    if (param == true) {
        return x;
    } else {
        let x = 4;
        return x;
    }
}
console.log(show(true));

En este caso, el valor mostrado en consola es 3, como era de esperar.

Se recomienda utilizar siempre let y const en lugar de var.

Patrones de cadenas

Muchas veces necesitamos concatenar cadenas con valores de variables:

let str : string = "( " + a + ", " + b +" )";

Con Typescript podemos conseguir el mismo efecto mediante patrones de cadenas, utilizando la comilla que va hacia la izquierda:

let str : string = `(  {a} , {b} )`;

Además, los patrones de cadenas en Typescript también permiten cadenas multilínea:

let str : string = `
    Esta cadena tiene
    múltiples líneas
    y así se guardará`;

Parámetros por defecto y opcionales

En Typescript podemos especificar valores por defecto para los parámetros de una función:

function add( a : number, b : number = 0): number  {
    return a + b;
}

add(3, 4);     // 7
add(3);        // 3

Los parámetros con valores por defecto se aplican cuando se pasa un valor undefined (no cuando se pasa null, y siempre son los últimos.
En Javascript todos los parámetros de una función son opcionales. Si un parámetro no se pasa al llamar a la función, la función recibe undefinedSin embargo, en Typescript se deben especificar los parámetros que son opcionales poniendo ? después del nombre del parámetro:

function add( a : number, b? : number): number  {
    if (b) {
        return a + b;
    } else {
        return a;
    }
}

add(3, 4);     // 7
add(3);        // 3

En Javascript se puede pasar un número indeterminado de argumentos a una función y ésta los recoge utilizando la variable llamada arguments. En Typescript esto se puede hacer con la siguiente sintaxis:

function add( a : number, ...other: number[] ) {
    let total = a;
    for (int i = 0; i < other.length; i++) {
        a += other[i];
    }
}

add(3, 4, 5, 6);    // 18

Interfaces

Los interfaces son una forma de definir contratos:

interface Graphic {
    toString() : string;
    getX() : number;
    getY() : number;
    setX(x:number) : void;
    setY(y:number) : void;
    draw() : void;
}

Este interfaz define las operaciones que tendrán los objetos gráficos.

Utilización de este interfaz:

function move(obj : Graphic, x : number, y : number) : void {
    obj.setX(x);
    obj.setY(y);
}

Para obligar a una clase a implementar la interfaz:

class Point implements Graphic {
    ...
}

Clases

La sintaxis para la definición de clases en Typescript es muy similar a la de los lenguajes orientados a objetos tradicionales:

class Rectangle {
    protected x : number;
    protected y : number;
    protected w : number;
    protected h : number;

    constructor(x: number, y: number, w: number, h: number) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    scale(factor: number): void {
        this.w = this.w * factor;
        this.h = this.h * factor;
    }
}

Podemos crear objetos de una clase utilizando new:

let r = new Rectangle(3, 2, 60, 50);
r.scale(1.3);

Se puede extender una clase mediante herencia:

class Square extends Rectangle {
    private name : string;
    constructor(x: number, y: number, s: number, name: string) {
        super(x, y, s, s);
        this.name = name;
    }
    getName() {
        return this.name;
    }
}

Los miembros de una clase (atributos y métodos) soportan los modificadores private, protected y public para definir el acceso. Por defecto, todos los miembros son public, y se puede acceder a ellos desde cualquier sitio. Los miembros protected sólo se pueden acceder desde la propia clase y desde sus clases derivadas. Por último, los miembros private sólo se pueden acceder desde la propia clase.

En Typescript se pueden definir métodos get y set para interceptar el acceso a atributos de una clase:

class Point {
    private _x : number;
    private _y : number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }
    public get x() : number {
        console.log("getting x value");
        return this._x;
    }
    public set x(v: number) {
        console.log("setting x value");
        this._x = v;
    }
    public get y() : number {
        return this._y;
    }
    public set y(v: number) {
        this._y = v;
    }
}

let point = new Point(3, 5);
point.x = 9;
console.log(point.x);

En la consola de Javascript veremos lo siguiente:

setting x value
getting x value
9

Se pueden definir miembros estáticos en las clases (tanto atributos como métodos):

class Types {
    static VALUE_1: number = 1;
    static VALUE_2: number = 2;
    static VALUE_3: number = 3;
    static add(a: number, b: number): number {
        return a + b;
    }
}

Types.add(Types.VALUE_1, 5);   // 6

Por último, en Typescript podemos definir clases abstractas, las cuales no se pueden instanciar, pero podemos definir clases que heredan de ellas:

abstract class BaseClass {
    protected _name: string;
    abstract toString(): string;
    public set name(n: string) {
        _name = n;
    }
}

Módulos

Cuando un proyecto empieza a crecer, suele ser una buena idea dividir nuestro código en ficheros diferentes, también llamados módulos. En Typescript cada módulo lo pondremos en un fichero.
Si queremos exportar algo desde un módulo, ya sea un interfaz, una clase, una función, etc, utilizamos la palabra clave export:
Módulo para validación de strings:

// stringvalidator.ts
export interface StringValidator {
    isValid(s: string) : boolean;
}

Otro módulo para validación de código postal:

// zipvalidator.ts
export class ZipValidator implements StringValidator {
    isValid(n: string) : boolean {
        return .......
    }
}

Podemos hacer un módulo de validaciones que exporte a los otros dos:

// validators.ts
export * from "./stringvalidator";
export * from "./zipvalidator";

Para importar desde otro módulo:

import { ZipValidator } from "./validators"
let v = new ZipValidator();
v.isValid("28041");

Se puede importar cambiando el nombre (una especie de alias):

import { ZipValidator as Zip } from "./validators"

Finalmente, podemos importar todo el contenido de un módulo y meterlo en una variable:

import * as validators from "./validators"
let s = new validators.StringValidator();

Genéricos

Las clases genéricas permiten:

  • Mayor seguridad de tipos: un Array sólo puede contener cadenas
  • El compilador chequea los tipos de los genéricos
  • Son más rápidas que utilizar cualquier tipo libremente
  • Podemos escribir código que es aplicable a muchos tipos
Ejemplo:

class Pair {
    _left: T;
    _right: T;
    set left(v:T) { this.left = v; }
    set right(v:T) { this.right = v; }
    get left(): T { return this._left; }
    get right(): T { return this._right; }
}

let pair = new Pair();
pair.left = "left part";


Enumerados

Los enumerados son tipos que definen una serie de valores.
Ejemplo:

enumeration RecordType {
    text = 1,
    image,
    audio,
    video
}

let type: RecordType = RecordType.text;























lunes, 12 de diciembre de 2016

NativeScript y múltiples tamaños de pantalla

NativeScript: múltiples tamaños de pantalla

Hoy voy a escribir sobre NativeScript y sobre el soporte a múltiples tamaños de pantalla.

Cuando definimos la UI con NativeScript, situamos los objetos y los dimensionamos utilizando puntos (dp). Estos puntos no se corresponden directamente con pixels de la pantalla. Cuando el móvil o tablet ejecuta nuestro programa, convierte estos puntos en pixels reales de la pantalla (px).

Cada dispositivo tiene una densidad de pixels (pixels por pulgada, dpi) diferente. Hay dispositivos con 90 dpi y hay dispositivos con 320 dpi.

Se establece el siguiente criterio: suponemos una densidad de pixels de 160 dpi, y sobre esa densidad definimos los puntos. O sea, los puntos se corresponden exactamente con pixels en pantallas con 160 dpi. Pero en pantallas de otra densidad, los puntos hay que transformarlos en pixels (si tenemos más de 160 dpi, se pintarán varios pixels por cada punto, y si tenemos menos de 160 dpi, necesitaremos varios puntos para completar un pixel).

En un dispositivo con densidad dpi, la conversión a pixels del dispositivo se hace con la siguiente fórmula:

    px = dp * dpi / 160

Al programar, se posiciona y se dimensiona todo en puntos, y luego esto se convierte a pixels en cada dispositivo según la fórmula anterior.

Por ejemplo, si creamos un círculo en la posición (300, 200) con 100 puntos de radio, se dibujará de la siguiente forma:

    - en un dispositivo de 160 dpi: radio 100 pixels, posición (300, 200)
    - en un dispositivo de 320 dpi: radio 200 pixels, posición (600, 400)
    - en un dispositivo de 220 dpi: radio 137 pixels, posición (412, 275)


el tamaño y posición del círculo será exactamente igual en ambos dispositivos.

Esto resuelve el problema de que cada dispositivo tiene una densidad de pixels distinta, y los objetos gráficos tendrán el mismo tamaño en todos los dispositivos.

Sin embargo, esto no resuelve el problema de los distintos tamaños de pantalla. Tenemos pantallas desde 3.5" hasta 5.5" y más. Aunque el círculo del ejemplo anterior se mostraría en la misma posición y con igual tamaño en cualquier dispositivo, sin embargo, en función del tamaño físico de la pantalla, el círculo estaría en el centro, o hacia la izquierda o hacia la derecha.

Este problema se resuelve con los layouts que posicionan los objetos y los redimensionan en función del tamaño de la pantalla.

Se considera que un dispositivo es una tablet cuando su dimensión más pequeña es superior a 3,75", o sea, cuando hay más de 600 dp en su dimensión más pequeña.

    Si la dimensión más pequeña es mayor que 3,75" (600 dp) => es una tablet

NativeScript sigue el siguiente criterio de nombrado para los ficheros:

    filename.qualifier*.extension

Por ejemplo: main_page.android.xml

Los cualificadores pueden ser:

  • cualificadores de orientación: port, land
  • cualificadores de plataforma: android, ios
  • cualificadores de tamaño de pantalla: minWHxxx la dimensión más pequeña debe ser al menos xxx dp's, minWxxx el ancho debe ser al menos xxx dp's, minHxxx el alto debe ser al menos xxx dp's


Con este criterio, podemos definir diseños de pantalla y estilos específicos para una orientación, para un tamaño o para un sistema operativo.

miércoles, 3 de agosto de 2016

Redis sorted sets

Qué son los sorted sets

Una de las estructuras de datos más potentes que tiene redis son los sorted sets (en castellano, conjuntos ordenados).
Los sorted sets son similares a los sets, o sea, son una colección de elementos (strings) únicos (no repetidos). La diferencia es que cada miembro de una lista ordenada tiene un score (puntuación), un valor en punto flotante que se utiliza para mantener ordenados los elementos, desde el más pequeño al más grande.
En resumen, un sorted set es un conjunto de elementos con un valor, que se mantiene ordenado por el valor.
Los elementos de un sorted set son únicos, pero los valores se pueden repetir.

Las operaciones de añadir, actualizar y borrar elemento+valor en un sorted set tienen un orden de computación de log(N), donde N es el número de elementos que tiene el set. O sea, es computacionalmente muy eficiente.

Ejemplos de uso

Ejemplos que se pueden modelar con sorted sets:

  • lista de records:
    • ZADD para añadir un nuevo record
    • ZRANGE para obtener las mejores puntuaciones
    • ZSCORE para saber los puntos de un usuario
    • ZRANK para saber el ranking de un usuario
  • indexar otros datos de redis, por ej, datos de usuarios
    • En un sorted set se puede almacenar element=userid y score=edad
    • ZRANGEBYSCORE para sacar los usuarios en un rango de edades

Ordenación

Los elementos en un sorted set se mantienen (siempre) ordenados, no se ordenan cuando se realiza una petición.
La ordenación sigue las siguientes reglas, suponiendo dos elementos A y B:
  • Si A.score != B.score, entonces se ordenan por score
  • Si A.score == B.score, entonces se ordenan lexicográficamente, es decir, por el elemento (A, B)

Comandos

A continuación se explican los comandos usados más frecuentemente para sorted sets. Aunque hay algunos más que se pueden consultar en: comandos redis para sorted sets

ZADD set-name score element

Añade un elemento con score a un sorted-set

ZRANGE set-name start stop [withscores]

Devuelve los elementos en las posiciones entre start y stop (inclusive). Los elementos van ordenados de menos a mayor (si se desea el orden inverso, utilizar ZREVRANGE). El primer elemento de la lista es el 0. Opcionalmente, WITHSCORES, puede devolver también el score asocado a cada elemento retornado.

ZRANGEBYSCORE set-name min max [WITHSCORES] [LIMIT offset count]

Devuelve los elementos con un score comprendido entre min y max (inclusive) ordenados de menos a mayor score (si se desea el orden inverso, utilizar ZREVRANGEBYSCORE). Se pueden utilizar los valores -inf y inf para min y max. Opcionalmente, WITHSCORES, puede devolver también el score asocado a cada elemento retornado. Opcionalmente también, se puede limitar el número de valores retornados con LIMIT (offset sería 0 y count el número máximo de elementos a retornar).

ZREM set-name element

Elimina un elemento del sorted set.

ZSCORE set-name element

Devuelve el score de un elemento.

ZCARD set-name

Devuelve el número de elementos en un sorted set.

ZCOUNT set-name min max

Devuelve el número de elementos con scores entre min y max.


miércoles, 30 de marzo de 2016

Funcionamiento de Redis Sentinel

Características de sentinel

Sentinel da alta disponibilidad a redis, permitiendo a redis recuperarse de algunos fallos sin intervención humana.
Estas son las características de sentinel:

  • Monitorización: sentinel está continuamente monitorizando si los redis master y slave están arriba y funcionando.
  • Notificación: sentinel puede avisar a los administradores del sistema o a otros programas, de caídas de instancias redis.
  • Failover automático: si la instancia master de redis no está funcionando adecuadamente, sentinel puede iniciar un proceso de failover donde un slave es promocionado a master y los demás slaves se reconfiguran para utilizar el nuevo master
  • Proveedor de configuración: sentinel actúa como descubridor de la instancia master de redis. Los clientes se conectan a sentinel, y éste les indica a qué instancia de redis se tienen que conectar. Cuando se realiza un failover, sentinel informa a sus clientes del nuevo master.

Sentinel es un sistema distribuido

Sentinel funciona de forma distribuida, es decir, varias instancias de sentinel se comunican entre sí cooperando. Las ventajas de tener varios sentinel ejecutando conjuntamente son:
  • la detección de fallos se realiza cuando varias instancias sentinel acuerdan que la instancia master ya no está disponible. Esto disminuye la probabilidad de falsos positivos.
  • sentinel puede funcionar incluso aunque no todas las instancias de sentinel estén disponibles, haciendo al sistema más robusto.

Ejecutar sentinel

Sentinel se ejecuta de la siguiente forma:

    redis-sentinel sentinel-config-file

Es obligatorio especificar un fichero de configuración para ejecutar sentinel, ya que este fichero puede ser reescrito por sentinel para guardar el estado actual, y se utilizará después de una caída. Además, el proceso sentinel tiene que tener permisos de escritura en este fichero.
Las distintas instancias de sentinel se comunican entre sí por el puerto configurado en sentinel.

Criterios de despliegue de sentinel

  1. Se recomienda desplgar al menos 3 instancias de sentinel
  2. Las 3 instancias de sentinel deben ejecutar en máquinas independientes desde el punto de vista de fallos
  3. No se garantiza que todas las escrituras permanezcan después de la caída de un master, ya que redis utiliza un sistema asíncrono de replicación
  4. No todas las librerías cliente soportan sentinel (jedis sí lo soporta)

Configuración de sentinel

La configuración mínima de una instancia sentinel sería como la siguiente:
sentinel monitor nombre_del_grupo 127.0.0.1 6379 2
sentinel down-after-milliseconds  60000
sentinel failover-timeout  180000
sentinel parallel-syncs  1 1
Se especifica el nombre de un grupo de instancias redis. Este nombre es el que se utilizará para el grupo. Se especifica también la IP de la instancias redis master. No es necesario especificar las instancias slaves de redis, ya que sentinel es capaz de descubrirlas (preguntando al master). Cuando sentinel autodetecta esclavos, los escribe en su fichero de configuración:

    sentinel known-slave my_redis_group 168.190.1.45 6379

También se reescribe el fichero de configuración cuando después de un failover, cambia el master y cada vez que se descubre un nuevo sentinel:

    sentinel known-sentinel my_redis_group 168.190.1.47 26379 xxxxxxx

Detección de master caído

El último argumento de sentinel monitor, un 2 en este ejemplo, se denomina quorum, y su significado es el siguiente:

  • quorum es el número mínimo de instancias sentinel que es necesario que acuerden que un master no está disponible.
  • quorum sólo se utiliza para detectar un fallo del master, no para iniciar un failover. Para realmente iniciar un failover, una de las instancias sentinel es elegida como líder, pero debe ser autorizada para iniciar el failover. Esto sólo ocurre con el voto de la mayoría de las instancias sentinel.
Se puede entender con un ejemplo. Tenemos 5 instancias de sentinel vigilando un master reds y el quorum es 2. Si dos instancias de sentinel detectan que el master se ha caído, entonces uno de los dos es elegido para intentar iniciar el proceso de failover. Pero es necesario que al menos 3 instancias estén de acuerdo en iniciar el failover para realmente llevarlo a cabo.
Ya que es necesario que la mayoría de los sentinel decidan iniciar un failover, se RECOMIENDA/OBLIGA que haya al menos 3 instancias de sentinel en 3 máquinas distintas.

Otras opciones de sentinel

Las otras opciones de sentinel tienen el siguiente formato:

    sentinel

  • down-after-milliseconds es el tiempo que una instancia de redis no es alcanzable (no responde a pings o responde con error) para que sentinel piense que está caída
  • parallel-syncs determina el número de slaves que pueden ser reconfigurados en paralelo para usar el nuevo master después de un failover

Pérdidas de datos en failover

Ya que redis replica asíncronamente (no espera la confirmación), siempre hay un riesgo de perder algún dato cuando se cae el master, ya que éste puede caerse justo después de una escritura y justo antes de replicar esa escritura en los slaves.
Otro caso que puede ocurrir es que el master no se caiga, pero se interrumpa su conexión de red con los slaves. Entonces, podría ocurrir que los sentinel pensaran que se ha caído el master (ya que no responde, aunque realmente no se ha caído) y promocionaran a otra instancia de redis como master. El problema es que si había clientes conectados al master anterior y que no han perdido la conexión con él, pueden seguir escribiendo datos en el antiguo master. Mientras tanto, el master nuevo también puede recibir nuevos datos. Los datos escritos en el master antiguo se perderán cuando la conexión se arregle, ya que éste va a convertirse en slave y va a sincronizarse con el nuevo master.
Este problema puede reducirse bastante utilizando la siguiente configuración:

    min-slaves-to-write 1
    min-slaves-max-lag 10

Con esta configuración, una instancia redis actuando como master, dejará de aceptar escrituras si no puede escribir en al menos un slave en un máximo de 10 segundos. De esta forma, el antiguo master dejará de funcionar en 10 segundos. La desventaja es que si se pierde la conexión con los slaves, la instancia master dejará de funcionar.












lunes, 24 de agosto de 2015

SWIFT (V) : Control de flujo

Sea cual sea el lenguaje de programación en el que estemos trabajando, el desarrollo de aplicaciones es siempre un ejercicio de lógica: tomar decisiones en función de ciertas condiciones y ejecutar múltiples veces cierta lógica sobre unos datos. Todo esto se suele denominar control de flujo, y swift tiene su propia sintaxis, no muy diferente por cierto a la de otros lenguajes.

Sentencia for

Swift ofrece dos tipos de for: condition-increment y for-in

Condition-increment for

La sintaxis es similar (aunque no idéntica) a otros lenguajes C-like:

    for var i = 0; i < 15; i++ {
        println(i)
    }

For-in for

Esta estructura de control permite iterar por los datos de una colección (las colecciones se explican más adelante) o por un rango numérico.
La sintaxis es la siguiente:

    for nombre in colección_o_rango {
        ....
    }

Por ejemplo:

    for i in 1...5 {
        println(i)
    }

Este tipo de for también sirve para iterar por los caracteres de un string:

    for c in "Esto es un ejemplo" {
    }

La variable no es obligatorio declararla. Esto puede ser útil cuando, por ejemplo, sólo se quieren contar los elementos de la colección. En este caso se pone el carácter _:

    for  _  in "Esto es una cadena" {
        counter++
    }

Sentencia while

El bucle for se utiliza cuando sabemos de antemano el número de repeticiones. Pero en otras ocasiones necesitamos repetir hasta que se cumpla una determinada condición y no sabemos de antemano el número de repeticiones que habrá que hacer.

Sintaxis de while:

    while condición {
    }

La condición tiene que se de tipo Bool, y se ejecuta hasta que sea false.

Sentencia do .. while

Este bucle es similar al bucle while, excepto que siempre se ejecuta al menos una vez.

    do {
    } while condición

Salir de un bucle: break

A veces es necesario salir del bucle cuando en su interior se detecta alguna condición que obliga a salir. Para ello se utiliza la sentencia break:

    while a > 10 {
        ....
        if otra_condicion {
            break
        }
        ....
    }

Sentencia continue

La sentencia continue hace que volvamos al principio del bucle, saltando todo lo que queda. Suele ser útil para no hacer nada cuando se cumple una determinada condición.

    while a > 10 {
        if personas[a] == "001" {
            continue
        }
        .....
    }

Sentencia if

La sentencia if (y todas sus variantes) es muy similar a la de otros lenguajes de programación:

    if condición_1 {
        .....
    } else if condición_2 {
        .....
    } else if condición_3 {
        .....
    } else {
        .....
    }

Sentencia switch

La sentencia switch es equivalente a if/else if/else, pero se utiliza cuando el número de casos a comprobar es grande:

    switch expresión {
        case valor1:
            .....  // no se pone break
        case valor2:
            .....  // no se pone break
        default:
            ....
    }

Se pueden combinar varios valores:

    switch expresión {
        case valor1, valor2, valor3:
            ..... 
        case valor4, valor5:
            ..... 
        default:
            ....
    }

También se pueden poner rangos:

    switch expresión {
        case valor1 ... valor2:
            ..... 
        default:
            ....
    }

Algo muy novedoso de la sentencia switch en swift es el uso de la cláusula where, que añade una condición adicional a evaluar:

    switch expresión {
        case valor1 where condición:
            ..... 
        default:
            ....
    }