@lvbernal

Desarrollo // Telecomunicaciones // Colombia

2018/02/01

Xamarin - Lecciones aprendidas 2

Esta es la segunda parte de las lecciones aprendidas con Xamarin. Aquí está la primera parte.

API Keys, URLs y otras variables importantes:

Las API Keys, App URLs, Client IDs, etc. se deben manejar con mucho cuidado, especialmente para no publicarlas en repositorios de git, svn o tfs. Ese error es todo un dolor de cabeza.

Todavía estoy buscando la forma de utilizar variables de entorno en proyectos Xamarin desde Visual Studio for Mac. La idea es definir variables por consola (ej. export MY_AZURE_URL=https://myazureurl.azurewebsites.net) y luego usarlas en la aplicación (ej. var myAzureUrl = Environment.GetEnvironmentVariable("MY_AZURE_URL");), como en cualquier framework web decente.

En Xamarin es distinto porque Environment.GetEnvironmentVariable() lee variables locales, en este caso del teléfono, en lugar de cargarlas durante la compilación. Y Visual Studio for Mac todavía no tiene funciones "tan avanzadas".

Por ahora hago lo siguiente:
  1. Creo una clase estática como DemoApp.Infrastructure.AzureConfig para poner las variables, inicialmente con valores falsos.
  2. Subo el archivo al VCS (git en este caso).
  3. Ignoro todos los cambios futuros usando el comando git update-index --skip-worktree AzureConfig.cs. Aquí está la documentación del comando y aquí un ejemplo rápido.
Es importante que esas variables queden en una clase separada que se modifique muy de vez en cuando. Al principio las tenía directamente en el servicio de acceso a datos (ej. DataService.cs), que por supuesto editaba todo el tiempo... eso alcanzó a generarme un par de problemas.

2018/01/27

Xamarin - Lecciones aprendidas 1

Llevaba un tiempo haciendo aplicaciones nativas para Android y hace más o menos un año empecé a utilizar Xamarin (Forms) para casi todos mis desarrollos móviles. Aquí voy a documentar algunas lecciones aprendidas de cosas que quizá sean muy básicas y a veces obvias, pero que me pusieron en apuros. No es mi intención hablar de soluciones ideales ni de buenas prácticas, sino dar pistas de la forma en que abordé cada situación.

Ponerle atención al tamaño de las imágenes:

Para una app con muchos íconos y botones redondos, utilicé el plugin Xam.Plugins.Forms.ImageCircle. En algún ejemplo vi que usaban imágenes de 500x500px dentro de un marco de 700x700px, que da un borde de 200px por cada lado, así:

Parking by Arthur Shlain from the Noun Project

Esa relación de aspecto es ideal para las imágenes, porque al ponerles fondo transparente y ubicarlas en el ImageCircle, se ven muy bien:

check in by Chinnaking from the Noun Project
checkout by Chinnaking from the Noun Project

El problema está en el tamaño; 700x700px es demasiado grande para una Tablet Android de bajo costo (ej. Lenovo Tab3 7 Essential) y en general para cualquier ícono, incluso en grandes resoluciones. Todo parecía funcionar bien porque estaba probando en un iPhone y en un emulador acelerado por hardware, pero cuando probé en la Tablet, el problema de rendimiento fue evidente.

144x144 es más que suficiente para que los botones se vean bien tanto en iPhone como en Android, sin sacrificar el rendimiento.

Tener muy claro MVVM:

MVVM es un patrón arquitectural muy usado en Xamarin.Forms, que permite separar la interfaz de usuario (View) y el modelo (Model) utilizando una capa intermedia (ViewModel). Este video me ayudó mucho a entender el concepto.

Los que no entendemos bien MVVM tendemos a complicarnos con la implementación (OCD, que llaman), agregando complejidad innecesaria y haciéndonos preguntas un tanto existenciales como esta:

¿Si asigno el BindingContext (ViewModel) en el xaml (ej. BindingContext="{Binding Main, Source={StaticResource Locator}}"), entonces mi code behind debería estar siempre limpio, es decir, es una mala práctica invocar métodos del ViewModel desde el code behind (ej. ((MainViewModel)BindingContext).DoSomething();)?

Es totalmente válido interactuar con el ViewModel desde el code behind, de hecho, eso es lo que hace el xaml todo el tiempo "tras bambalinas". Lo que no está bien es que el ViewModel invoque métodos de la vista. Esto es importante para manejar métodos del ciclo de vida como OnAppearing() y OnDisappearing(); muchos desarrolladores utilizan el MessagingCenter, pero creo que esa herramienta se creó para tareas más interactivas.

Solía decir que el que entendía los Intents, entendía Android. Ahora digo que el que entiende MVVM, entiende Xamarin.Forms. Yo todavía no llego a ese punto.

Estar pendiente de liberar la memoria ociosa:

Una práctica común en Xamarin es crear un MainViewModel que controla la navegación y que tiene referencias de los otros ViewModels, que se van inicializando en la medida en que el usuario usa la aplicación. De nuevo, esto puede generar problemas de rendimiento cuando se usan aparatos de bajo costo.

Cuando la navegación es por Tabs, esos ViewModels permanecen en la memoria casi todo el tiempo. No sé cómo manejar ese caso porque nunca he hecho una aplicación así. Pero si la aplicación tiene navegación "tradicional" por páginas, algo que suelo hacer es poner en null los ViewModels que no estoy utilizando.

Esta no es una "buena práctica", pero es un remedio efectivo contra la frustración del usuario. Siempre depende del manejo de los datos, de qué tan frecuente navega el usuario de una página a otra, de qué tan livianos son los ViewModels, etc. Si se realizan muchas tareas en background cada que se inicializa un ViewModel, esto seguramente va a ser contraproducente.

La idea al final es ofrecerle al usuario una experiencia libre de frustración. Cualquier mejora en el rendimiento siempre será bien recibida.

2016/10/01

Tareas automatizadas con Visual Studio Code

Visual Studio Code es el editor que uso para programar. Una de las características que más me gusta es que se pueden integrar herramientas externas utilizando tareas. Las tareas permiten ejecutar comandos para deployment, revisión de estilo, pruebas unitarias, entre otros, directamente desde el editor.

Hago muchos programas en Python, y tres tareas que siempre configuro son: run para ejecutar scripts, pep8 para revisar estilo y test para correr pruebas unitarias. Este es mi archivo tasks.json:

{
    "version": "0.1.0",
    "osx": {
        "command": "sh",
        "args": ["-c"]
    },
    "windows": {
        "command": "cmd",
        "args": ["/C"]
    },
    "isShellCommand": true,
    "suppressTaskName": true,
    "showOutput": "always",
    "tasks": [
        {
            "taskName": "run",
            "args": ["python ${file}"]
        },
        {
            "taskName": "pep8",
            "args": ["pep8 ${file}"]
        },
        {
            "taskName": "test",
            "args": ["python tests.py"]
        }
   ]
}


Como las tareas se escriben por cada proyecto, hice un snippet JSON que me permite agregarlas rápidamente:

{
     "python_tasks": {
        "prefix": "pytasks",
        "body": [
            "{",
            "    \"version\": \"0.1.0\",",
            "    \"osx\": {",
            "        \"command\": \"sh\",",
            "        \"args\": [\"-c\"]",
            "    },",
            "    \"windows\": {",
            "        \"command\": \"cmd\",",
            "        \"args\": [\"/C\"]",
            "    },",
            "    \"isShellCommand\": true,",
            "    \"suppressTaskName\": true,",
            "    \"showOutput\": \"always\",",
            "    \"tasks\": [",
            "        {",
            "            \"taskName\": \"run\",",
            "            \"args\": [\"python $\\{file\\}\"]",
            "        },",
            "        {",
            "            \"taskName\": \"pep8\",",
            "            \"args\": [\"pep8 $\\{file\\}\"]",
            "        },",
            "        {",
            "            \"taskName\": \"test\",",
            "            \"args\": [\"python tests.py\"]",
            "        }",
            "   ]",
            "}"
        ],
        "description": "Creates python tasks"
    }
}


Hay muchas formas de personalizar Visual Studio Code y cientos de extensiones ya desarrolladas.