Posts Tagged ‘apple’


El rollo de Apple siempre ha sido la consistencia, que todo funcione igual siempre o casi siempre, bueno, eso y que se vea simple y fácil de usar tal como es ahora iOS y Mac OSX mas la adición de hardware a medida. Pero si eso ya está completo, para el usuario final, tener un iPhone o iPad nuevo, tendrá siempre un no se qué muy familiar que no varía de equipo en equipo y eso es iOS.

Esto no se va a cambiar a corto plazo porque la inversión en el ecosistema es grande, por lo que la innovación tendría que venir en la parte del hardware y ya se está mencionando el cristal para el próximo iPhone 7.

Imaginemos que sea así, y tendremos para fines de este año un equipo todo en cristal pero con algo que nos parecerá viejo: el sistema operativo.

Me parece una pena que no haya planes para un MacBook con pantalla táctil pero significaría el fin de los iPad. Si bien existe la separación en los sistemas operativos, debemos aceptar que en un Mac tan sencillo como el MacBook, el uso que se le da es muy similar al que se le daría a un iPad Pro con teclado. Si quieres hacer diseño o programación te comprarás al menos un MacBook Air que te sale más barato.

Mientras que la innovación se de en los materiales del hardware de los nuevos equipos de Apple, aumentará la necesidad de hacer converger iOS y Mac OSX porque la intersección en equipos será cada vez mas grande considerando las mejoras grandes que tiene Intel en el tema de duración de la batería, y en velocidad que logran los procesadores de Apple AX.

A Microsoft ya le pasó, los equipos medianos o tablets ya vienen con Windows 10 (Sistema operativo desktop) y Windows Mobile se ha dedicado exclusivamente para teléfonos, aunque esta última línea no haya despegado como se debe. Como muchos podrán ver, es muy probable que alguna de esas dos plataformas desaparezca y es casi obvio cual sería.

Si Microsoft ha movilizado el PC hasta hacerlo tan ligero como un tablet, Apple va por el camino contrario: Empoderando el móvil para competir con el PC. El único problema es que en algún momento tendrá que decidir cuál plataforma tiene que morir.


Termina la primera conferencia de Apple en el 2016 y la novedad es iPad Pro.

Con un tamaño mas pequeño que el original y con las mejoras en la pantalla, y todo eso con las funciones agregadas en iOS, hacen de este pequeño tablet, el mas avanzado y potente en Apple. Todo bien hasta que Tim Cook dice que esto es el futuro de la computación personal. 

El tema es que para muchos la computación personal es una laptop con un sistema operativo “completo” como Windows 10 o MacOSX y por eso invertimos algo de dinero en equipos que tengan suficiente poder para soportar el paso del tiempo. Pero si sale un grande como Apple a decir que la computación personal no va por ahí, se hace todo muy confuso sobre todo porque el perjudicado no será el usuario final que ya hizo la inversión en una laptop, sino en el mismo Apple que hace cada vez más difícil vender una laptop considerando que ya dijo el mismo Tim Cook que no piensan en un MacBook con pantalla táctil.

Es bien fácil, ¿Para qué invertir más dinero en una laptop si con un iPad Pro tienes casi todo lo que necesitas y incluso algo más? Obvio que esto no funciona si eres un programador, pero para el resto de usuarios es una pregunta muy válida.

Si vamos a Apple.com una laptop MacBook Pro de 13 con retina display cuesta alrededor de 1600 USD, mientras que ir por un iPad Pro con todos sus accesorios va por el orden de los 1400 USD. Pero el ahorro disminuye si es que eliges las líneas nuevas como Air pero se vuelve casi equivalente si vas por la nueva MacBook donde el modelo “serio” está 1600 USD. De todo esto se desprende una cosa: Si realmente necesitas poder, vas por un MacBook Pro y ahorras dinero, pero si prefieres el look y las cosas básicas, tienes hasta 3 opciones: iPad Pro o Macbook o Macbook Air, casi por el mismo precio, y según Tim Cook iPad Pro es la elección para asegurar el futuro.

Mi sugerencia ya está hecha: Si quieres poder, ahorras dinero con una MacBook Pro. Si eres básico: puedes elegir por iPad Pro, MacBook o MacBook Air en ese orden.


Apple ha sido el líder en laptops desde hace varios años. Es prácticamente la laptop oficial de cualquiera que tenga que mostrarse con una laptop ( o sea conductores de TV, artistas, hispters, etc). Incluso yo tengo una y puedo decir que es una laptop que no da problemas de ningún tipo, por lo que es mi primera opción si tengo que trabajar fuera de casa. Incluso, antes de mi Macbook Pro Retina, estuve usando una Macbook Air y a pesar de ser menos potente, me funcionó perfectamente para el trabajo. Parece que la batería durara para siempre y trabajar con múltiples aplicaciones es simplemente espectacular. Además, el cool factor es importante. No hay ninguna laptop Windows que den tantas ganas de usar como una Macbook, hasta ayer.

Microsoft presentó el Surface Book y parece que esa es la laptop que hay que tener porque tiene los specs más altos en hardware y además Windows 10 ya ha demostrado que funciona ligero en todas partes, pero además de eso, ha hecho algo que Apple se ha negado por mucho tiempo: Una laptop que pueda trabajar como tablet. La idea no es nueva y ya tenemos algunos ejemplos de esto,  pero Apple ya dijo que no pasará con Macbook. Es cierto que se han introducido ciertos cambios en Mac OSX para ser más parecido a iOS pero ya rechazaron la integración de ambos. Para Microsoft, todo es Windows 10 y ya quedó demostrado que es cierto: corre en Tablets y en las famosas computer stick que se conectan al televisor.

Siempre quise una super laptop con Windows, pero siempre tuve que comprar la menos decepcionante en lugar de la que realmente quería y luego encontrar que hay muchos líos. Estos líos son los que tienen que resolverse para que Surface Book sea realmente la “Ultimate” laptop como dicen.

  • Distribución de teclado: Apple siempre ha puesto a disposición sus Macbook con varias disposiciones de teclado por lo que siempre pude ordenar la mía con la distribución de teclado Western Spanish. Con todas las laptops Windows, siempre tuve que esperar que salieran localmente porque al momento del lanzamiento solamente se ofrecian con teclado inglés.
  • Cargador con conector universal: Casi todas las laptop Windows vienen con un cargador que tiene un enchufe de 3 puntas donde 1 punta es para la toma de tierra. Ok, esto es más seguro, pero al menos debería venir con el adaptador a 2 porque en Latinoamérica es muy raro que las tomas vengan con la toma a tierra. Algo simple pero que marca mucho
  • Garantía Universal: ya se que esto es muy difícil por la cobertura, pero Apple no tiene oficina local en Perú y a pesar de eso tienen socios locales que cubren la garantía de equipos sin problemas y sobre todo , sin costo si tienes el plan de garantía activo, que cuesta un poco mas pero que vale la pena completamente. Personalmente, le cambiaron la pantalla a mi macbook air sin costo y en un tiempo aceptable. La macbook air no volvió a fallar desde entonces.
  • Cargador Universal: De repente es algo tarde, pero si fuera posible que todos los Surface puedan compartir el mismo cargador sería genial.

Ahora, si esto se puede extender incluso a la siguiente generación Surface, podría significar la consolidación de un nuevo líder en la categoría de laptops. Lo paradójico es que para tumbarse el liderazgo de Apple en laptops, Microsoft tuvo que crear una nueva categoría de computador que fuera tan buena que desapareciera todas las anteriores. Veamos si funciona de esa manera, mientras tanto, esperaré por una Surface Book con teclado en español.


Google tuvo un evento hace unos pocos días donde hizo el anuncio de algunos nuevos miembros de su línea Nexus. No fue un gran evento como los que hace Apple, pero creo que la gran noticia es que finalmente Microsoft puede decir que se adelantó a todos y nuevamente es el líder de una categoría nueva que podría llamarse Ultra Convertible como una evolución de los 2-1 y los híbridos.

Google Pixel C es un tablet basado en la última version de Android (Marshmellow) que viene con un teclado, tal como lo hace el Microsoft Surface y el Nuevo iPad Pro. También vimos los nuevos Nexus, fabricados esta vez por Huawei, donde lo más interesante es que soportan el Nuevo  Proyecto FI del que ya hablaremos mas tarde.

En fin, el punto aquí es que la tendencia ya se ha consolidado, tabletas de gran pantalla con un teclado casi igual al que tienen las laptops a fin de reemplazar a una para hacer trabajo “real”. Esto significa que estamos ante el reconocimiento en la industria de que para el trabajo fuerte, el touch no es suficiente.

En algún momento comenté que para el trabajo móvil incluso más importante que el procesador era la duración de la batería y parece que ya se ha consolidado este punto pues todas las opciones actuals ofrecen no menos de 10 horas de funcionamiento.

Pixel C trae otro punto importante y que justamente era la mayor observación que han tenido los equipos en esta categoría: El teclado y el tablet forman una estructura rígida tanto en su forma cerrada como en modo de trabajo. Si revisamos tanto el Surface como el iPad Pro, el teclado es un accesorio adicional que es casi como un case que se ajusta por magnetos. Cuando salió el Surface, la prueba que hacian era poner el equipo con el teclado sobre las piernas para trabajar como si fuera un laptop, algo que obviamente no era nada cómodo; y si hacemos la misma prueba con el iPad Pro seguramente tendrá los mismos resultados. Pixel C, gracias a su estructura sólida, sería el primer equipo en pasar el “lap test”.

El problema ahora es convencer a los trabajadores móviles de que esta es una alternativa real a una laptop, o para el trabajo en general. En este punto, Surface comienza con una ventaja pues es una computadora en todo sentido así que puede estar sujeta a las mismas herramientas de administración que ya tienen las desktops en una corporación.

En esta nueva categoría los factores principales pueden ser:

  • Batería: Surface sufre aquí pero tengamos en cuenta que tanto el iPad como Pixel C tienen Sistema operative de Tablet que no se compara con uno full como Windows 10. Igual estamos hablando de unas 2 o 3 horas menos de uso.
    • Una nota aparte es que Surface es un computadora complete por lo que para la gestión de energía tiene el ya famoso Connected Standby que pone a Surface en la misma categoría que las tablets, pero un Viejo truco del mundo PC es quien trae la ventaja: Hibernación. Yo uso la Hibernación en mi híbrido y hace lo que las tablets nunca podrán hacer: apagarse guardándo el estado, lo que significa que estas 7 a 8 horas de uso contínuo puede extenderse a muchas más horas cuando no utilicemos el equipo. Mientras la Surface en hibernación consume nada de energía, las tablets agotarán sus baterías. De todas maneras, Hibernación no es algo que Microsoft recomiende, pero igual es una alternativa.
  • Precio: Convenientemente Pixel C se ha puesto en el rango de los 500 USD igual que el modelo medio de Surface. Apple ya sabemos que no se mide y ha puesto un precio alto. En todos los casos tenemos que considerer los 150 usd adicionales en el teclado.
  • Sistema Operativo: Pixel C y iPad Pro son ambos tablets con un Sistema operativo limitado cuyo propósito es optimizer recursos antes que rendimiento. Windows 10 del Surface es el primer Windows que no tiene esa aureola de “tragón” que tenían los otros y ahora lo vemos trabajando muy bien incluso en formatos Tablets donde antes solo podíamos imaginar sistemas operativos ligeros como iOS o Android.

Fijense que luego de estos tres factores, podremos considerar todo lo demás casi igual. Por ejemplo, el tema del lápiz puede ser un tema interesante de análisis. Sólo diremos que mientras iPad Pro con su super Lápiz es todo lo contrario a lo que hizo Microsoft al incluir un lápiz menos “responsive”. Algo que solamente les importa a los que se dediquen al arte digital.

Microsoft puede estar saltando en un pie pues al final de cuentas todos estos equipos estarán ejecutando Microsoft Office, porque aceptémoslo, Google Docs ni Apple Page es tan Bueno. Esto también quiere decir que OneDrive tiene una oportunidad. Agreguemos las capacidades de integración que ya tiene Microsoft Office con la infraestructura corporativa y vemos que si es que por alguna razón sale un Surface con mejor batería (y un stylus de mejor rendimiento) podríamos ver un equipo top en su categoría marca Microsoft, algo no tan seguido por estos días.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

En este punto debemos hacer un repaso y considerar algunos puntos que potencian la aplicación.

Primero, comencemos que el código está disponible en Github en la siguiente dirección:

https://github.com/victorpease/SuperDatos

Para que comiencen a verlo funcionar tendrán que seguir los siguientes pasos:

  1. Crear una cuenta en cloudant.com
  2. Crear una base de datos
  3. Crear una key en la sección de permisos y asignarle permisos de Writer
  4. Actualizar la key y la clave en el archivo services.js
  5. Actualizar la url de la base de datos también en el archivo services.js

Con esto ya podrán tener funcionando su propio lector de noticias y verán que cada vez que alguien lea una noticia, un registro será añadido a la base de datos y podrán con esa información hacer reportes sobre las noticias más populares, que luego podremos incluir como un reporte en la misma aplicación.

Bien, ahora los consejos:

  • Hasta ahora hemos considerado la sincronización Live, pero tengan en cuenta que genera mucho tráfico y por lo tanto costo, además, el cobro es por transacción, así que no importa mucho el tamaño de la base de datos. En Cloudant dan gratis un saldo de 50 dólares y eso es bastante, pero para una aplicación seria esto se puede salir de control. Lo mejor es que desactiven Live y que sincronicen manualmente cuando ocurra algún evento relevante. Para que vean la diferencia, Live sincroniza datos cada 25 segundos, así que si cambian que la sincronización sea cada 1 minuto, ya estarían bajando su consumo a la casi la mitad.
  • Si pensamos en este modelo “Offline-First”, es porque no confiamos en el acceso a la red. El mayor impacto es la primera sincronización. Piensen en usar el plugin PouchDB Load, que permite que hagan una carga inicial de datos en el móvil en lugar de esperar los datos de la red. El archivo de texto tiene que ser generado mediante el plugin PouchDB Dump Cli.
  • En el código actual tenemos un estado llamado Splash donde podemos ejecutar todas estas acciones  de inicialización antes de entrar a la aplicación.

Sobre las vistas:

  • Las vistas en PouchDB se generan la primera vez que las llamamos, por lo que pueden demorar un poco mas de lo normal durante la primera vez. Ante esto, pueden controlar que la vista se genere de forma automática sobre todo durante la primera sincronización mediante la siguiente línea:
    pouch.query('vista', {stale: 'update_after'});
  • Hasta ahora hemos usado Map/Reduce para las vistas, y puede ser algo confuso. La alternativa principal es utilizar los índices, no hay nada mas simple y más rápido. La otra alternativa es utilizar PouchDB Find, que es el futuro de las consultas en todo el ecosistema CouchDB. Personalmente, aun me gusta más Map/Reduce aunque sea algo confuso y limitado por una razón: PouchDB Find es simplemente una máscara que hace las cosas fáciles, pero no agrega velocidad.
  • Finalmente, ya les había mencionado que las vistas ocupan sitio

Sobre los límites de espacio:

  • PouchDB maneja varios métodos para almacenar los datos siendo WebSQL (sqlite) y IndexedDB los disponibles en los móviles, estando igualmente sujetos a las restricciones de espacio del navegador del sistema. Aquí pueden revisar los límites . Un truco es utilizar el plugin SQLite que les permite saltarse esos límites y tener un almacenamiento casi ilimitado. Entiendan que esto es solamente recomendado cuando su base de datos esté por los 50 mb. Además, aún tiene algunos problemitas con las vistas.
  • IndexedDB es la alternativa estándar, de hecho WebSQL ya no es mantenido. De hecho es bastante rápido, además que IndexedDB no es tan fácil de abrir que WebSQL, así que les da un nivel de seguridad a los datos.

Sobre la seguridad:

  • Los que utilizan SQLite en Android están acostumbrados a ponerle una clave a la base de datos para protegerla. Esto no está disponible en el modelo Híbrido pero pueden usar el plugin Crypto Pouch que nos permite encriptar los datos.
  • CouchDB tiene usuarios y Cloudant también puede manejar algo parecido. Pueden acceder a esta lista de usuarios mediante el plugin PouchDB Authentication. Recuerden que esto significa que la red es necesaria. Personalmente, prefiero hacer un servicio externo que autentique y que distribuya las key y pass de Cloudant.

Modelamiento:

  • Modelar en NoSQL no es igual que en Relacional, pero hay trucos. Personalmente, prefiero el modelo minimalista de NoSQL pero existe un plugin Relational Pouch que permite utilizar la misma estructura relacional

Finalmente, como verán en algún comentario que hice, pueden tener toda su lógica en la aplicación cliente, siempre y cuando se quieran pegar al modelo “Offline-First”, pero si es que necesitan operaciones en línea, necesitaran de una capa más de lógica.

NOTA Principal: El problema principal con CouchDB y obviamente con Cloudant, es como integrarlo con nuestras bases en SQL. Es un trabajo en progreso en realidad, y no hay una herramienta porque en CouchDB el modelamiento está en función de la velocidad y de la aplicación que va a utilizar los datos, y eso puede ser muy diferente a lo que tenemos en SQL donde se programa para tener consistencia. Hay tantos modelos y tantas transformaciones posibles que una herramienta no puede hasta ahora hacer el trabajo. Felizmente, no es tan complicado porque ya en la base de datos hemos usando db.changes() , Así que el flujo sería:

  • Lenguaje recomendado: Python ( tiene una gran librería de acceso a Cloudant). También pueden usar NodeJS
  • Un script accede a la base de datos SQL y crea los documentos en Cloudant uno por uno según el modelo que hayamos fijado. Podemos cruzar dos tablas para crear documentos Maestro-Detalle siempre que no sean muchos detalles. No es tan rápido y lo ideal es usar BulkDocs para insertar por lotes.
  • Luego se invoca el comando db.changes()  donde cada documento cambiado es actualizado a la base de datos. Otra vez, puede ser como un insert, delete o update, todo depende de como sea su modelo.

Hasta ahora, todo esto es a medida por lo que mejor es que practiquen sus habilidades en NodeJS o Python.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

En este post vamos a incluir algunos puntos propios de la plataforma Cordova que no han sido tan obvios hasta ahora y que deben tenerse en cuenta para aplicaciones reales, pero antes hay que planificar.

Planificación:

Quedó pendiente en el post anterior la implementación de un método para ver cuáles noticias eran las más leídas. Algo tan simple nos plantea los siguientes retos:

  1. Al leer una noticia se tendría que agregar un documento indicando que un usuario determinado la ha leído.
  2. El documento agregado debe ser sincronizado solamente con el servidor. No debe ser sincronizada con los demás usuarios
  3. En el servidor debe existir un mecanismo que concentre todos los documentos de lectura y haga la suma para poder ordenarlos y así presentarlos en el móvil.

El punto 1 requiere de insertar documentos en la base de datos. Para eso recordemos que no hay un esquema así que tendríamos que definir uno que tenga al menos la siguiente información: usuario, noticia leída, la fecha. Digamos que algo así:

{
_id:’id del documento’,
newsId: ‘id de la noticia leída’,
fecha: ‘fecha cuando fue leída la noticia’,
tipo: ‘read’,
usuario: ‘usuario’
}

Por ahora este documento cumple con lo que nos hace falta. Y para grabarlo a la base de datos el comando es super simple “db.put(doc)”, ya veremos como usarlo.

Para el punto 2 debemos considerar que al sincronizar, debe haber alguna información en el documento para definir si hace falta ser sincronizado o no. Además, también debemos considerar que hay información que tiene que ir a todos los usuarios. Entonces podríamos usar el campo “usuario” de la siguiente manera:

  • Consideraremos que la información común a todos, será asignada al usuario “all”
  • La información particular a un usuario, será asignada al usuario actual

De esta manera, podríamos definir una regla para que se sincronicen todos los registros donde el campo usuario sea igual a “all” o al “usuario actual”. Lo único que debemos asegurar es que tengamos un mecanismo para generar un id único. También podríamos considerar que no se pasen las noticias muy antiguas, para eso podríamos pasar la fecha, lo haremos al final si sobra tiempo. Todas estas reglas se entienden fácil, pero hace falta que le hagamos entender esto a CouchDB en Cloudant para lo que tendremos que usar una característica llamada “filtered replication” o replicación filtrada.

Para el punto 3, tendremos que usar una vista en el servidor que utilice una característica llamada map/reduce, que no es otra cosa que un proceso de dos pasos, que está optimizado para cuando trabajamos con un software distribuido en muchos servidores, tal como es nuestro caso. Para entenderlo fácil, MAP concentra todos los documentos que deben ser manipulados para que luego REDUCE realice cierta operación sobre los mismos. En nuestro caso, lo que haremos será buscar los documentos donde registramos que una noticia ha sido leída, luego las agrupamos por noticia y contamos las ocurrencias para hacer el ordenamiento.

Ejecución:

Empecemos por un punto 2, la replicación filtrada pues no queremos que los datos viajen por la red sin control para eso tenemos que crear un filtro así que vayamos a la sección de documentos de diseño:

alldesigndocs

Encontraremos el documento por diseño por defecto y para ver su contenido vamos a hacer click en el icono de lápiz:

designdoc

El documento de diseño tiene varias secciones, podrán reconocer la sección “views” donde he definido unas vistas de prueba. Lo que necesitamos hacer es crear una sección nueva para definir nuestro filtro, el cual debe ser una función que se evalúa por cada documento que se envía en una replicación. Tengamos en cuenta que para nuestro caso, la función debe ser verdadera para cuando el campo de usuario sea igual a “all” o al usuario actual. Digamos que el campo usuario lo llamamos “owner” entonces nuestra función de filtro sería:

"filters": {
"leidos": "function(doc,req){ if (doc._deleted){ return false; } if (doc.owner){ if (doc.owner==req.query.user){ return true; }; if (doc.owner==’all’){return true;};} else {if (doc._id==’_design/news’){return true;} else {return false;}}}"
}

Hemos agregado una condición al inicio y se refiere a los documentos que no hayan sido borrados. Por diseño, Couchdb guarda incluso los documentos que borras y los sincroniza también, así que es mejor filtrarlos para que la comunicación sea más fluida y de pasadita ahorramos algo de espacio.

La replicación filtrada nos permite asegurar que los datos que se replican son los necesarios y esto es super importante por dos razones: seguridad y uso de ancho de banda. Es muy común tener apps donde los usuarios tienen información particular que les es exclusiva, como por ejemplo, tu lista de compras, tu lista de reservas, tus preferencias y así. Con este mecanismo aseguramos que cada usuario tiene lo que realmente necesita y de pasadita al dejar de enviar un montón de documentos inútiles ahorramos espacio y ancho de banda.

Y bien, agregado el filtro a nuestro documento de diseño, éste quedaría así:

{
"_id": "_design/news",
"_rev": "41-667b5e312f96dfec37e4480d2c34479d",
"views": {
"topic": {
"map": "function (doc) {\n if (doc.tipo ==\"news\") {\n emit([doc.topic], {_id:doc._id});\n }\n}"
}
},
"filters": {
"leidos": "function(doc,req){ if (doc._deleted){ return false; } if (doc.owner){ if (doc.owner==req.query.user){ return true; }; if (doc.owner==’all’){return true;};} else {if (doc._id==’_design/news’){return true;} else {return false;}}}"
}

Tengan cuidado de poner todos los valores en una sola línea y de no utilizar la comila (“) dentro de cada valor.

Y listo, ahora vamos al código de replicación y pongamos la llamada a la replicación filtrada paso a paso:

Creamos una función para obtener un id único. Para eso, nuevamente usamos ng-Cordova para usar el plugin Device, esta vez en el archivo app.js ya que tenemos que agregar un concepto propio de la plataforma Cordova:

angular.module('starter', ['ionic','controller','service','ngCordova'])
.run(function($ionicPlatform,$cordovaDevice,$cordovaSplashscreen,$state,db) {
 $ionicPlatform.ready(function() {
 // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
 // for form inputs)
 if(window.cordova && window.cordova.plugins.Keyboard) {
 cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
 }
 if(window.StatusBar) {
 StatusBar.styleDefault();
 };
 var id='1231232132';
 if (ionic.Platform.platform()!="win32"){
 id = $cordovaDevice.getUUID();
 };
 db.setUser(id);
 $state.go('app.news',{status:'loading'});
 });
})

Como verán, hemos incluido el módulo ngCordova como una dependencia y llamamos a $cordovaDevice.getUUID() que genera un código único por equipo móvil. Para ser exactos no es tan único, o al menos no tenemos garantía de eso, pero para nuestro ejemplo es suficiente. Quizá lo mas interesante aquí es como y cuando llamamos a esta función.

Si se dan cuenta en la línea 3, llamamos a la función $ionicPlatform.ready. Como les dije en algún momento, ionic está basado en Apache Cordova y pues Apache Cordova se toma un tiempo en estar listo para contestarnos por lo que usamos la línea 3 para “esperar” a que cargue Cordova y en ese momento, ya podemos utilizar los plugins que nos dan acceso a funciones nativas, en nuestro caso $cordovaDevice. Ahora bien, el valor del id del equipo es crítico, al punto que tendremos que hacer que el app espere a que tengamos este valor disponible y eso es lo que se hace en las líneas siguientes:

  • En la línea 16, pasamos el valor del id del equipo al servicio porque nos va a servir para sincronizar la base de datos
  • En la línea 17, cuando ya tenemos todo listo, recién pasamos al estado que muestra las noticias.

Ahora bien, ¿Que hacemos mientras Cordova termina de cargar? pues lo mejor es mostrar un Splash Screen o el logo de nuestra aplicación y para eso tenemos que agregar un nuevo estado


$stateProvider
.state('splash',{
url:"/splash:status",
cache:false,
views:{
"home":{
templateUrl:"templates/splash.html",
controller:"splashController as splash"
}
},
resolve:{
status: function($stateParams){
return $stateParams.status;
}
}
})

Obviamente, tendremos que agregar una vista y un controlador. Aquí va la vista:

<ion-view view-title="">
<ion-content>
<div class="row">
<div class="col"></div>
<div class="col"></div>
<div class="col"></div>
</div>
<div class="row row-center">
<div class="col">
</div>
<div class="col">
<img src="img/ionic.png">
</div>
<div class="col">
</div>
</div>
<div class="row-bottom">
<div class="col"></div>
<div class="col"></div>
<div class="col"></div>
</div>
</ion-content>
</ion-view>

y aquí va el controlador:


.controller('splashController',
['$state',
'status',SplashController])
function SplashController($state,status){
if (status!='loading'){
$state.go('app.news');
}
};

Y verán que hay código de más. El tema es el siguiente: si el splash screen es un estado quiere decir que cuando se muestren las noticias, el usuario podría “regresar” al splash screen con el botón Back en Android o Windows Phone, cosa que nadie quiere. Para eso, en la línea 17 del archivo app.js al ir al estado de noticias, pasamos un parámetro para indicar que llamamos al estado desde el flujo de inicio del programa. Si vamos al estado de noticias mediante el botón Back, llegará sin éste parámetro y haremos que se vaya automáticamente al estado news nuevamente.

Lo bueno de todo esto es que con este mecanismo implementado, estamos 100% seguros que Cordova está listo y todas las llamadas a plugins funcionarán correctamente.

Ahora vamos hasta la método replicate y le tendremos que agregar el usuario como un parámetro, así que también tenemos que actualizar la línea donde llamamos a la replicación en método init. Mostremos todo el archivo services.js porque hay mas cosas que explicar

angular.module('service',[])
.factory('db',['$q','$rootScope',DbService]);
 function DbService($q,$rootScope){
 var key = 'xxxxxxxx';
 var pass = 'xxxxxxxxxx';
 var remote = 'https://'+key+':'+pass+'@server.cloudant.com/news';
 var db;
 var user;
 var initiated=false; 
 function newNews(change){
 if (!change.deleted){
 if (change.doc.tipo=="news")
 $rootScope.$broadcast('db:newNews',
 {newsId:change.id,
 newsTitle:change.doc.titular});
 }
 };
 function put(doc){
 db.put(doc);
 console.log('documento grabado');
 };
 function changed(change){
 console.log('cambios en la base de datos');
 if (!initiated)
 {
 initiated=true;
 db.changes({live: true, since: 'now', include_docs: true})
 .on('change', newNews);
 };
 $rootScope.$broadcast('db:changed',change);
 };
 return {
 setUser:function(userId){
 user = userId;
 },
 init: function(){
 if (!db) db = new PouchDB('news',{adapter:'websql'});
 this.replicate();
 return true;
 },
 replicate: function(){
 if (!db) this.init();
 db.replicate.to(remote,{live:true,
 retry:true,
 filter:'news/leidos',
 query_params:{'user':user}})
 .on('change',function(info){
 console.log('Evento Chage:'+JSON.stringify(info));
 })
 .on('paused',function(){
 console.log('Evento Paused:');
 })
 .on('complete',function(info){
 console.log('Evento Complete:'+JSON.stringify(info));
 })
 .on('active',function(){
 console.log('Evento Active:');
 })
 .on('error',function(err){
 console.log('Evento error:'+JSON.stringify(err));
 })
 .on('denied',function(info){
 console.log('Evento error:'+JSON.stringify(info));
 });
 db.replicate.from(remote,{live:true,
 retry:true,
 filter:'news/leidos',
 query_params:{'user':user}})
 .on('paused',changed);
 },
 get: function(id){
 if (!db) this.init();
 return db.get(id);
 },
 getCats: function(){
 if (!db) this.init();
 return db.allDocs(
 {startkey:'cat_',
 endkey:'cat_\uffff',
 include_docs:true});
 },
 getNews: function(catId){
 if (!db) this.init();
 if (catId)
 return db.query('news/topic',
 {key:[catId],
 include_docs:true,
 descending:true});
 else return db.allDocs({startkey:'news_\uffff',
 endkey:'news_',
 descending: true,
 include_docs:true});
 },
 putRead: function(pnewsId){
 var doc = {
 _id :'read_'+user+'_'+pnewsId,
 newsId: pnewsId,
 fecha:new Date().toString(),
 tipo:'read',
 owner:user 
 };
 put(doc);
 }
 }
 };

Veamos, vayamos en orden:

  • Primero, tenemos que agregar una función para recoger el dato de usuario, para eso hemos agregado una variable interna en la línea 8 y un método público en la línea 33.
  • Seguidamente, necesitamos grabar registros y para eso tenemos un método privado bastante genérico en la línea 18. Pero como necesitamos agregar un documento por lo que también agregamos un método público en la línea 94 donde verán que usamos una plantilla para crear el documento JSON que describimos al comienzo donde solamente cambiamos los valores de los campos para luego llamar al método interno genérico.
  • Finalmente, agregamos un comando de replicación. Los atentos se habrán dado cuenta que el comando de replicación que hemos venido usando es db.replicate.from lo cual significa que la dirección de los datos es del servidor al móvil, así que faltaría agregar la dirección del móvil al servidor. Para hacer un poco más gráfico el proceso de debug, hemos agregado todos los eventos del proceso. NOTA: Hay un comando db.sync que replica en ambos sentidos, pero prefiero separar las dos direcciones porque quiero capturar solamente los eventos solamente cuando “llegan” nuevas noticias del servidor.

Tanto lío y la pregunta que se cae de madura: ¿Ya podemos grabar y sincronizar? Aún no. Falta que la llave que usamos para conectarnos a la base de datos en Cloudant tenga permisos para escribir. Para eso vamos a Cloudant y hacemos click en el icono de candado al costado del nombre de nuestra base de datos:

Security

permissions

En el segundo gráfico podrán verificar que el key que están utilizando tenga el privilegio Writer.

Ahora, agreguemos la grabación cuando una noticia es leída en el controlador:


function DetailController(db,$scope,$state,noticia){
$scope.event= noticia;
db.putRead(noticia._id);
$scope.$on('db:changed',
function(event,changed){
$state.go('.', null, { reload: true });
console.log('Recargando Noticia');
});
};

En la línea 34, hemos agregado la dependencia al servicio db y en la línea 36 hemos agregado la bendita línea para grabar que hemos leído una noticia.

Y con eso ya podemos ver como nuestra aplicación hace lo siguiente:

  • Graba un documento del tipo ‘read’ cada vez que alguien lee una noticia. Como la clave del documento es única, si entramos a una noticia ya leída, la inserción simplemente fallará y eso esta bien porque asegura que sólo tendremos un documento tipo ‘read’ por noticia.
  • Los documentos serán sincronizados con la base de datos en Cloudant de forma automática.
  • Finalmente, manejamos el evento Ready para verificar que Cordova ya está listo y podemos usar plugins que nos den acceso a servicios nativos.

El Post ya se hizo largo, así que en uno próximo haremos un reporte de las noticias más leídas y ya que el código se ha hecho algo grande, vamos a ponerlo en un repositorio Github.

Por mientras, traten de actualizar su código para que tengan la funcionalidad desarrollada hasta el momento. Y sobre todo revisen los eventos que genera la replicación móvil – Servidor.


A ver, reconozcamos primero que quien opta por una de estas máquinas como computador principal es alguien que reconoce que su mayor herramienta es el office, toma notas, navegación web y por ahí alguna app corporativa. Los programadores aún requieren tener acceso a la línea de commandos y eso es algo que no se beneficia con la pantalla touch.

Con la presentación del iPad Pro, Apple apuesta por la experiencia de uso por lo que mantiene iOS pero aumenta el tamaño del dispositivo y agrega accesorios como teclado y lápiz que ya vimos en otro equipo pero del lado de Microsoft.

Microsoft Surface Pro 3 es la apuesta por la portabilidad potenciada por un Sistema operative que está ahora en todas partes como es Windows 10, por lo que el sacrificio de portabilidad es mínimo, Bueno, si te compras el teclado opcional. El mayor riesgo que tienes al ir por Surface es que tengas una tablet demasiado potente para lo que necesitas, o que te quedes sin espacio.

Apple dice que su iPad Pro es mas rápida que el 80% de pcs portátiles hoy en día, así que supongamos que eso es un Core i5. Veamos como se ve esto en dinero.

Apple iPad Pro Surface Pro 3 basic Surface Pro 3 Full
Config Proc: A9

128 gb

Proc: Core i5

128 gb

Proc: Core i7

256 gb

Accesorios Teclado y Lapiz:

$ 270

Teclado:

$120

Teclado:

$120

Sistema Operativo iOS 9 Windows 10 Pro Windows 10 Pro
Office Gratis Gratis Gratis
Bateria 10 horas 9 horas 9 horas
Precio base $949 $899 $1399
Total final $1120 $1020 $1520

Si nos concentramos solamente en la experiencia Office,  podemos decir que la experiencia es la misma en las 3 opciones. Y si le creemos a Apple, iPad Pro será más potente que el modelo Surface básico, así que la real competencia sería con el modelo full que se aleja en precio con un disco mas grande.

Como usuario de Windows 10, 256gb de disco es justo lo necesario y fácil de llenar, mientras que con iOS el espacio no será problema. Lo que si se vuelve un problema es la duracion de la batería: una hora menos es un golpe duro en movilidad.

Conclusiones:

  • Si sólo necesitara Office, me podría quedar con el iPad Pro, incluso estaría ahorrando dinero y ganando una hora más de movilidad.
  • Si es que la quiero como mi equipo de trabajo móvil, me tendría que ir por Surface Pro 3 pero por ese precio podría conseguir un modelo más punche también en  Windows 10 sacrificando horas de batería seguramente que por cierto, aún existe en el mercado. Este artículo lo estoy escribiendo con un Sony Vaio Duo de 13 pulgadas que es un híbrido con Windows 10 que viene con un teclado rebatible, 8gb de ram y 128 gb de disco y me da hasta 6 horas de movilidad, y creo que no necesito mas por ahora.

Lo que no se debe perder de vista es que los que usan iPad hoy, son gente que van por la experiencia antes que por las funciones, así que el upgrade se vuelve natural. Los que vamos por las capacidades ya tenemos un equipo que nos responde y estamos como resignados en el tema de movilidad, en mi caso, si me quedo sin batería uso un tablet Hp stream 7 con Windows 10 como backup, entonces el Surface se vuelve más un artículo de lujo.

¿Que esperaría de Microsoft? Primero una mejora del rendimiento de la batería en el Surface Pro 3 y una configuración de Core i7 con disco de 128 gb a fin de conseguir una opción de movilidad que no sea lujo.


Ya terminó el evento de Apple hoy  las sorpresas mas grandes no están en los productos mismos sino en la vision que tiene Apple sobre los mismos.

Todo empezó con el Nuevo OS para el Apple Watch lo que no sorprende mucho ya que lo que se ha visto es que se puede mejorar la productividad sin tener que sacar el teléfono del bolsillo. Es cierto, mas importante que la máquina es la usabilidad y son las aplicaciones las que definiran quien gana: Apple Watch o Android Wear.

Siguió la presentación con el iPad Pro, que es la version de Apple del Microsoft Surface, ya que se trata de lo mismo en esencia: una tablet que se puede convertir en laptop mediante un teclado opcional, incluso también presentaron un Lapiz (no es un stylus) lo que lo hace físicamente equivalente al modelo de Microsoft con una gran diferencia: iPad Pro no usa Mac OSX, usa iOS mientras que Surface usa Windows 10 al igual que la pc o laptop que ya tienes. Eso es grande pues puede ser que tengas muchas apps, pero nunca será igual que tener una laptop. Para un buen grupo de usuarios esto será suficiente, pero para los que esperaban una mac con pantalla táctil, creo que queda claro que no pasará muy pronto, incluso, muchos hipsters estarán odiando haberse adelantado a comprar el ya Viejo MacBook, o la ahora vetusta MacBook air ya que los precios indican ahora  que pueden tener un full laptop por aproximadamente 1200 dólares, lo cual es muy parecido a lo que pagaron por las MacBook.

¿Cómo puede entenderse que iPad Pro no tenga ni USB-C ni trackpad? pues parece que Apple estuviera pensando dejar de lado la línea de Macbooks para ir por iPad Pro, o incluso introducir uno con MacOSX. Al menos por el precio, parece que quisiera matar su propia linea de laptops ligeras, incluso ha considerado la ayuda de Microsoft para que Office sirva como punto de atracción para la nueva línea, lo que afecta directamente a la Surface de Microsoft ya que si nos vamos exclusivamente a la funcionalidad estamos hablando de dos equipos que harían lo mismo: Office y donde pierde Microsoft porque tiene ahora un precio mas alto considerando la línea Pro ya que el procesador del iPad proclama ser superior al del modelo básico.

Si solamente necesito Office para cuando estoy en la calle, elegiría el iPad Pro al Surface Pro. Se ve mejor y un precio mejor. Lamentablemente soy programador y necesito más que Office.

Apple TV es la decepción del evento, porque se trata de un equipo para volver el TV en algo más inteligente y fácil de usar mediante la inclusion de Siri, algo que lo iguala al nivel de un iPod Touch. Si consideramos que tenemos ese poder y más ya podríamos pensar en un equipo más pequeño que permita usar todas esas funciones desde el teléfono o desde el iPod, mas aún cuando 4K fue ignorado por completo. Incluso un Sistema operativo solo para esto parece una exageración. El precio más elevado que la generación anterior me hace pensar que éste no será el producto Estrella de apple.

iPhone 6s trae todo lo que se esperaba, mejor procesador, mejores cámaras, mejor pantalla, y 3D touch o lo que hasta hoy era Force Touch. Podríamos explayarnos en las nuevas características técnicas pero eso no es lo más importante. Lo que importa es que Apple ofrece un programa propio que permite que tengas un Nuevo iPhone cada año por 32 dólares mensuales por 2 años. Asombroso, hasta yo aplicaría.

En conclusion, la estrategia es clara, sistemas operativos especializados para cada experiencia: WatchOS, tvOS incluso iOS definen la nueva experiencia de cómputo para Apple, y trata de decirnos que las laptops ya fueron. Y quizá hasta mas importante es que la tecnología es tan cambiante que a veces es mejor alquilarla que comprarla, suscribo eso completamente.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

Me he tomado algo de tiempo para escribir este post por una simple razón: es hora de limpiar el código. Hasta ahora el código que han ido desarrollando es funcional pero tiene cosas que les va a crear problemas cuando quieran hacer algo mas grande. Lo bueno en todo esto es que si han ido cambiado el código desde el post 1, entonces estos cambios les harán mucho sentido. La buena noticia en todo esto, es que puedo publicar todo el código sin problemas.

Comencemos con el archivo app.js:


angular.module('starter', ['ionic','controller','service'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault();
};
});
})
.config(function($stateProvider, $urlRouterProvider) {
$stateProvider
.state('app',{
cache:false,
abstract: true,
views:{
"home":{
templateUrl:"templates/menu.html",
controller:"menuController as menu"
}
},
resolve:{
cats: function(db){
return db.getCats();
}
}
})
.state('app.news', {
cache:false,
url: "/:catId",
views: {
"menuContent":{
templateUrl: "templates/news.html",
controller: "newsController as news"
}
},
resolve:{
noticias:function($stateParams,db){
return db.getNews($stateParams.catId);
}
}
})
.state('app.detail', {
cache:false,
url: "/detail/:id",
views: {
"menuContent":{
templateUrl: "templates/detail.html",
controller: "detailController as detail"
}
},
resolve:{
noticia: function($stateParams,db){
return db.get($stateParams.id);
}
}
});
$urlRouterProvider.otherwise('/');
})

Empecemos poniendo atención en la definición de los estados a partir de la línea 14.

  • Si tenemos un estado que muestra datos que cambian muy seguido en la base de datos, lo mejor es desactivar el cache. Eso lo hacemos mediante el atributo “cache:false” como pueden ver en la definición de cada estado.
  • Los datos es mejor cargarlos en la definición del estado. Esto se traduce en la utilización del atributo “resolve” donde verán que hacemos la llamada al servicio que recupera los datos y los pasa mediante una variable al controlador. Podrán ver que un estado puede recibir parámetros y estos nos sirven para recuperar los datos llamando a los servicios.
  • $ionicPlatform.ready() es muy importante. Conforme nuestro proyecto crezca, notarán que hará falta inicializar algunos componentes o plugins de Cordova. Cordova es el core de todo esto y la noticia es que se demora en cargar, por lo que si tenemos que inicializar algún componente o plugin propio de Cordova tienen que ponerlo dentro de esta función pues se ejecuta cuando ya es un hecho que Cordova ya está cargado.

Ahora nos toca ir a revisar el archivo controller.js


angular.module('controller',['service'])
.controller('menuController',
['$scope',
'$state',
'cats',MenuController])
.controller('newsController',
['$scope',
'$state',
'noticias',NewsController])
.controller('detailController',
['$scope',
'$state',
'noticia',DetailController]);
function MenuController($scope,$state,cats){
$scope.cats = cats.rows;
};
function NewsController($scope,$state,noticias){
$scope.notas=noticias.rows;
$scope.$on('db:changed',
function(event,changed){
$state.go('.', null, { reload: true });
console.log('Recargando noticias');
});
};
function DetailController($scope,$state,noticia){
$scope.event= noticia;
$scope.$on('db:changed',
function(event,changed){
$state.go('.', null, { reload: true });
console.log('Recargando Noticia');
});
};

Aquí si hay varias cosas para revisar. Primero, si bien no hay reglas a la hora de programar, si existen buenas prácticas y la mejor que he encontrado es esta: Angular Style Guide, así que apliquemos algunas reglas.

  • La primera es lo que se llama IIFE. Considerando que el app.js es el archivo principal, tanto controller.js como service.js deben tener todo el código contenido en una estructura como esta:
    • (function(){ … })(); En nuestro caso, hemos incumplido esto para simplificar la explicación, pero pueden agregarlo y verán que el código funciona sin problemas.
  • Verán que las definiciones de cada controlador ahora es algo diferente y es por dos razones:
    • Dependency injection: Esto es casi un formalismo, es decir, la podríamos saltar sin problema pero nos costará cuando usemos herramientas para comprimir nuestro código o para cuando querramos empaquetar nuestro código para evitar copiones. Si se fijan el primer controlador, verán que primero indicamos todos los componentes, entre comillas, que vamos a utilizar dentro del controlador, y luego, tenemos una función como parámetro final sin comillas. Finalmente la función es definida al final incluyendo como parámetros todos los componentes que vamos a usar ya sin comillas.
    • Siguiendo las sugerencias de estilo, el código de cada controlador es ahora puesta en una función aparte a fin de facilitar la lectura del código
  • La base de datos puede cambiar. Para esto tienen que pensar mucho en como diseñar sus estados. En cada controlador he incluido un $scope.$on para recibir el evento $broadcast con el nombre ‘db:changed’ que ya veremos exactamente cuando se genera pero básicamente es que cuando se detecte un cambio en la base de datos debemos refrescar los datos, en nuestro caso, hemos elegido volver a correr el estado mediante la línea $state.go() que vuelve a correr el código.
    • Algo es super importante: Cuando refrescan un estado, el estado padre no se refresca, pero si vemos bien en nuestro caso, el controlador del estado padre no tiene nada de código para refrescar. La clave aquí es que el estado padre se trata de un estado abstracto y estas si se refrescan junto con los estados hijos. Es por eso que basta con refrescar los estados hijos.

Nota final, hay casos en los que su vista no requiera refrescar datos a cada rato, así que evalúen activar el cache o que el refresco de los datos venga de algún evento como un click en algún botón, o un cambio de estado. Refrescar automágicamente cuesta.

Ahora veamos el service.js


angular.module('service',[])
.factory('db',['$q','$rootScope',DbService]);
function DbService($q,$rootScope){
var key = 'bentareadyessharyinessee';
var pass = 'OnEixgKgpt8LyEtl0S5DkAon';
var remote = 'https://'+key+':'+pass+'@supermio.cloudant.com/news';
var db;
function changed(change){
console.log('cambios en la base de datos');
$rootScope.$broadcast('db:changed',change);
};
return {
init: function(){
if (!db) db = new PouchDB('news');
this.replicate();
return true;
},
replicate: function(){
if (!db) this.init();
db.replicate.from(remote,
{live:true,
retry:true})
.on('paused',function(changes){
changed(changes);
});
},
get: function(id){
if (!db) this.init();
return db.get(id);
},
getCats: function(){
if (!db) this.init();
return db.allDocs(
{startkey:'cat_',
endkey:'cat_\uffff',
include_docs:true});
},
getNews: function(catId){
if (!db) this.init();
if (catId)
return db.query('news/topic',
{key:[catId],
include_docs:true,
descending:true});
else return db.allDocs({startkey:'news_\uffff',
endkey:'news_',
descending: true,
include_docs:true});
}
}
};

  • Cuiden la replicación.
    • En la línea 20 verán que iniciamos la replicación  y con el atributo “live” y esto nos garantiza que los cambios se pasan casi instantáneamente pero como todo en la vida, tiene costo, así que consideren tenerlo apagado y encenderlo cuando haga falta. El siguiente atributo “retry” se refiere a que la replicación continúe luego de una falla en la red, tenganlo siempre en true, pero no se confíen.
    • Al encender la replicación podemos especificar eventos. En nuestro caso en la línea 23 encendemos el soporte del evento “paused” que se refiere a que la replicación “live” ya no recibe nuevos cambios, así que podemos decir que la base de datos remota y local son iguales. En nuestro caso, este es un buen momento para refrescar las vistas. Hay otro evento que es “change” que se genera con cada cambio que se recibe, lo cual es bueno pero no tanto porque si refrescamos aquí, tendríamos ejecutando el refresco muchas veces, sobre todo para la corrida inicial y nuestra base de datos local está vacía. Luego utilizaremos estos eventos para crear notificaciones locales.
  • Cuídense de los procesos asíncronos. Todos los métodos en el servicio “db” son iguales en una cosa: todos son asíncronos. Tal como les recomendé, usen los estados en el archivo app.js para llamar a estos métodos y se librarán de la pesadilla de javascript llamada “Promesas”. Ya luego veremos como manejar promesas.

Y listo, todos los demás archivos van iguales y en lineas generales podrán ver que el código está más limpio y ordenado, además, estamos llamando todos los datos directamente desde la base de datos.

Nota final: en lo que se refiere a performance, el consejo básico es que diseñen su app para que no tenga que refrescar automáticamente nada de nada, a menos que sea absolutamente necesario por la simple razón que si bien PouchDB es una buena base de datos, puede volverse bastante pesada si tienen que lidiar con una gran cantidad de datos. Refrescar datos por eventos es la mejor estrategia.

Como podrán ver, no han habido nuevas funciones que hayamos incluido, pero al menos hemos mejorado el código y espero que los consejos y el estilo de programación mostrado aquí les sirva para organizar su código en algo que puedan mantener y entender con facilidad.


Esta es una continuación de la serie sobre desarrollo en móviles que comencé aquí. Les recomiendo comenzar a escribir el código fuente desde la parte 1 ya que no se publica el código para descarga.

En la parte 8 dejamos el app con menus que permiten mostrar las noticias por categorías, pero faltaba cargar las categorías directamente de la base de datos, de tal forma que se vayan mostrando automáticamente las categorías nuevas.

Las categorías serán extraídas de la base de datos en una consulta bastante simple y debemos considerar lo siguiente:

  • Debemos poder establecer el orden en que se muestran las categorías. Para eso deberíamos agregar un campo que nos permita establecer el orden.
  • Debemos poder desactivar las categorías. Para eso también agregaremos un campo que indique el estado

Ok, hagamos esto primero entonces, felizmente tenemos pocas categorías. Para ejemplo, veamos como quedaría “cat_01”:


{
"_id": "cat_01",
"_rev": "2-df694a2ed157b1403baa2ca9214d801b",
"nombre": "Nacional",
"descripcion": "Noticias del país",
"tipo": "topic",
"orden":1,
"estado":1
}

Lo mismo para todas las demás categorías. Con estos cambios, consultaremos las categorías, las ordenamos por el campo orden y las filtramos por el campo estado.

Ahora para hacer la consulta tenemos dos alternativas:

  • Usando la clave principal: Todas las categorías tienen un id que comienza con “cat_” entonces podemos usar la clave por defecto. El orden será el orden de las claves: cat_01 luego cat_02 y así, igual podemos ordenarlo al mostrarlo.
  • Creando una vista: estaremos creando un índice solamente para recuperar categorías, ¿valdrá la pena? pues son pocas categorías así que aún no vale la pena ir por este camino.

Listo, ahora empieza lo complicado. Necesitamos que la lista de categorías sea pasado al estado inicial para que pueda ser mostrado. Recordemos que hemos definido un estado abstracto para incluir el sidemenu. Como en el ejemplo anterior, pasar datos a un estado requiere el uso de “resolve”, eso es fácil, lo  complicado es que tenemos que hacer la consulta a la base de datos para obtener esas categorias. Si revisan el archivo app.js, por ninguna parte hago alguna llamada al factory “db” que contiene todas nuestras operaciones por lo que si llamamos alguna operación de datos en el “app.js” simplemente fallarán porque acuerdense que inicializamos la factory db con el método db.init()  en el controlador “newsController” en la línea 8 del archivo “controllers.js” como pueden ver aquí:


.controller('newsController',function($scope,db){
db.init();
$scope.notas=[];
$scope.$on('refrescar',function(event,news){
$scope.$apply(function(){
$scope.notas = news;
})
})
})

Esto nos funcionó en su momento, y como ya podrán darse cuenta, es una mala práctica inicializar las cosas en un controlador. Incluso llamar a un inicializador puede resultar malo. Así que comenzaremos modificando el archivo “services.js” para dos cosas:

  • Ejecutar la rutina init() cuando haga falta y no tener que llamarla en ningún lugar. De esta manera simplemente llamaremos a las consultas y siempre tendremos la db lista.
  • Agregar el método para consultar las categorias

El primer paso es fácil: simplemente agregamos estas líneas al comienzo de cada método:


if (!db) {
this.init();
}

Simple, preguntamos si la variable interna es nula o no, y ejecutamos el método init() cuando haga falta. Agregamos eso a todos los métodos del factory.

El segundo paso también es simple, al igual que el método “mostrar” usamos el método allDocs así:


getCats: function(){
if (!db) {
this.init();
}
return db.allDocs({startkey:'cat_',endkey:'cat_\uffff',include_docs:true})
}

Con tanto cambio, el archivo services.js. quedará así:


angular.module('services',[])
.factory('db',function($rootScope){
var key = 'bentareadyessharyinessee';
var pass = 'OnEixgKgpt8LyEtl0S5DkAon';
var remote = 'https://'+key+':'+pass+'@supermio.cloudant.com/news';
var db;
var cat;
var mostrar = function(){
db.allDocs({startkey:'news_\uffff',endkey:'news_',descending: true,include_docs:true})
.then(function(result){
$rootScope.$broadcast('refrescar',result.rows);
});
};
var mostrarCat = function(catId){
if (catId)
db.query('news/topic',{key:[catId],include_docs:true,descending:true}).then(function(result){
$rootScope.$broadcast('refrescar',result.rows);});
else mostrar();
};
return {
init: function(){
if (!db) {
db = new PouchDB('news');
}
mostrarCat(cat);
this.replicate();
},
replicate: function(){
if (!db) {
this.init();
}
db.replicate.from(remote,{live:true,retry:true})
.on('paused',function(info){
mostrarCat(cat);
});
},
get: function(id){
if (!db) {
this.init();
}
return db.get(id);
},
setCat: function(id){
if (!db) {
this.init();
}
cat = id;
mostrarCat(cat);
},
getCats: function(){
if (!db) {
this.init();
}
return db.allDocs({startkey:'cat_',endkey:'cat_\uffff',include_docs:true})
}
}
});

Y listo, ahora vamos al archivo “app.js” donde vamos a agregar el resolve al estado abstracto:


.state('app',{
abstract: true,
views:{
"home":{
templateUrl:"templates/menu.html",
controller:"menuController as menu"
}
},
resolve:{
cats: function(db){
return db.getCats();
}
}
})

Agregamos la llamada al nuevo método y la pasamos al controlador a través de la variable “cats”. No se olviden de que, ya que estamos llamando a un factory definido en el modulo “services”, tenemos que agregarlo como dependencia. Así que la primera línea de nuestro “app.js” será ahora así.


angular.module('starter', ['ionic','controllers','services','ui.bootstrap'])

Y listo, terminamos con este archivo. Ya terminamos actualizando el modelo, ahora toca el controlador. Aquí simplemente tenemos que recoger la variable cats con las categorias y quitar cualquier llamada a “db.init()”. El controlador “menuController” quedaría así:


.controller('menuController',function($state,$scope,db,cats){
$scope.cats = cats.rows;
$scope.setCat = function(id){
$state.go('app.news');
db.setCat(id);
};
})

Acuérdense que cuando llamamos a la base de datos obtenemos un objeto y dentro de ese objeto el array “rows” nos trae los registros que queremos.

Ahora así quedaría el controlador “newsController”:


.controller('newsController',function($scope){
$scope.notas=[];
$scope.$on('refrescar',function(event,news){
$scope.$apply(function(){
$scope.notas = news;
})
})
})

Como ven, sólo le hemos quitado la llamada a “db.init()”. Y con esto hemos acabado con el controlador y vamos a arreglar la vista en el archivo “menu.html”. Si se fijan en la línea 25 comienza una lista donde todos los valores son estáticos y va hasta la línea 38. Reemplazamos todo eso por un iterador y quedaría así:


<ion-list>
<ion-item collection-repeat="cat in cats | orderBy: 'doc.orden'| filter:{'doc':{'estado':1}}" nav-clear menu-close ng-click='setCat(cat.id)'>
{{cat.doc.nombre}}
</ion-item>
</ion-list>

Cosas interesantes aqui:

  • Collection-repeat es propio de Ionic y es más eficiente en listas largas
  • El array cats es el que metimos en el $scope desde el controlador y tiene todas nuestras categorías.
  • Mediante “orderBy” las ordenamos por el campo “orden” que agregamos.
  • Y mediante “filter” mostramos solamente los que tienen estado =1

Todo lo demás viene del template anterior, incluso para llamar al método “setCat” llamamos al cat.id.

Y listo!, Si prueban el código hasta aquí funcionará, pero hay algunas limitaciones:

  • Si agregan una categoría en el servidor o se cambia el orden de alguna, no se refresca en la lista del menú automáticamente
  • Al inicio se mostrarán todas las noticias, incluidas las que pertenezcan a categorías inactivas

Esas las veremos en el siguiente post. Lo que si es urgente es corregir el estilo del código, siendo el más urgente el que figura en el controlador “detailController” donde verán que tenemos una llamada a db.get y esperamos por la respuesta. Si se habrán dado cuenta, para las categorías es lo mismo pero no esperamos por respuesta en ningún lado, simplemente esperamos valores y ya, así que usemos ese patrón aquí y corrijamos el controlador para que se vea así:


.controller('detailController',function($scope,db,noticia){
$scope.event=doc;
});

y tendríamos que cambiar el resolve de la ruta en el archivo “app.js” para que se vea asi:


resolve:{
noticia: function($stateParams,db){
return db.get($stateParams.id);
}

Y listo. La explicación para este cambio es que como casi todo en JavaScript es asíncrono, tenemos que usar un modelo llamado “promesas” para esperar por la respuesta y hacer algo con eso. Es por esto que usamos objeto.then() y dentro del then() incluimos una función con una operación. Pues bien, al momento de utilizar estados, el componente detrás de toda esa magia (UI-Router!!!) hace muchas cosas bien, y una de esas cosas es manejar la resolución de las promesas y es tan inteligente que esperará hasta que la promesa sea resuelta y podamos recibir los documentos en el controlador. Con eso nuestro código queda mas limpio.

Promises es todo un tema y siempre es un motivo de confusión así que mejor dejar que sea UI-Router quien las maneje. Así que para que se diviertan notarán que para cargar las noticias para la categoría seleccionada podemos usar el mismo esquema anterior. A ver si pueden completarlo por su cuenta.

NOTA: Ya un lector notó que todo es sobre leer datos en el cliente y tiene una razón. Grabar datos es bien simple pero supone que tengamos mucho cuidado durante el diseño, porque si dejamos escribir a un usuario, debemos tener en cuenta que cualquier usuario podrá hacerlo a menos que pongamos un control. Establecer un control requiere de cierto manejo adicional que aún falta explicar. Para que lo vean en perspectiva: 

  • Cualquiera puede escribir:
    • No hace falta establecer usuario y pass al inicio
    • Se debe tener cuidado de no duplicar el id del documento que se vaya a grabar
    • La replicación debe modificarse, porque si hay muchos usuarios, estaremos recibiendo muchos cambios y datos en nuestra db local.
  • Todos usuarios registrados pueden escribir:
    • Hay que incluir usuario y pass
    • También la replicación se tiene que ajustar para controlar los datos entrantes.
  • Sólo algunos usuarios registrados pueden escribir:
    • Usuario y clave es obligatorio
    • Hay que determinar quien puede escribir y quien no. Podemos integrar la autentication propia de CouchDB para determinar esto, pero esto aplica a todos los documentos, lo cual no necesariamente es bueno.
    • La replicación también se modifica pero ya es más simple

Recuerden que más importante que empezar a construir es sentarse a diseñar.





%d bloggers like this: