tag:blogger.com,1999:blog-67782649659524701582024-03-06T06:23:51.770+01:00Xurxo DeveloperBlog con el objetivo de compartir conocimientos sobre desarrollo de softwarexurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.comBlogger93125tag:blogger.com,1999:blog-6778264965952470158.post-62857580338441502212015-10-29T06:47:00.000+01:002015-10-29T06:47:58.283+01:00Mi nuevo blog<br />
Este post es para informar que me he creado un blog nuevo, la url es <a href="http://www.xurxodev.com/" target="_blank">www.xurxodev.com</a>, como se puede apreciar en el nombre, es muy parecido a este blog. En mi nuevo blog no pretendo escribir sobre nada diferente de lo que vengo escribiendo aquí, la idea ha sido dar el salto de blogger a un blog con dominio y hosting propio.<br />
<br />
<a name='more'></a><br />
Este blog lo doy por cerrado, se quedará a modo de histórico.<br />
<h3>
Infraestructura del nuevo blog</h3>
Para el nuevo blog me decidí por <a href="https://ghost.org/" target="_blank">https://ghost.org/</a> como CMS. El motivo es que Wordpress nunca me ha terminado de convencer, me parece un PHPsaurio, desde la distancia de no haberlo utilizando nunca. No conozco PHP y no tengo ningún interés en este momento. Estuve investigando si había algún CMS basado en <a href="https://nodejs.org/" target="_blank">https://nodejs.org</a> porque me parece una plataforma moderna a la que preveo mucho recorrido en los próximos años.<br />
<br />
El dominio lo he cogido con <a href="https://www.namecheap.com/" target="_blank">https://www.namecheap.com/</a>, no creo que haya mucha diferencia con otros proveedores en este caso.<br />
<br />
En cuanto al hosting me he decidido por <a href="https://www.digitalocean.com/" target="_blank">https://www.digitalocean.com/</a>, aquí no había mucho donde elegir porque hay pocos proveedores de hosting que tenga soporte para nodejs.<br />
<br />
Si alguno estáis interesado en adquirir un hosting, accediendo a DigitalOcean desde este <a href="https://www.digitalocean.com/?refcode=a0e8b058cba8" target="_blank">enlace</a> os dan un crédito de 10$, que viene a ser dos meses gratis.xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com2tag:blogger.com,1999:blog-6778264965952470158.post-15102669299698472962015-10-08T16:01:00.001+02:002023-07-11T16:43:34.462+02:00Código legibleEste blog no esta mantenido, suscribite a la newsletter del nuevo
<div style="height: 40vmin;min-height: 360px"><script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-label-1="xurxodeveloper.blogspot.com" data-background-color="#F1F3F4" data-text-color="#000000" data-button-color="#4169e1" data-button-text-color="#FFFFFF" data-title="XurxoDev" data-description="Descubre el conocimiento de desarrollo de software que te lleva al siguiente nivel, no es efímero y puedes aplicar en cualquier tecnología." data-icon="https://xurxodev.com/content/images/size/w192h192/size/w256h256/2022/08/256506a9078afeaf026655924497deb6.jpeg" data-site="https://xurxodev.com" data-locale="es" async></script></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixqDm_feYytrKfSNNQFwbSVbAVF7TboMWDoVeG0yrt2CO3kcTNhzdfkphDCU6oKxDV_QvhG8a1y0LrS8YjFBC9dkJ8_76ZNwChgHU8hb-NBM2xIxNe0GgrUQb62-0r2M3tX1aRHF0yHos/s1600/Readable.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixqDm_feYytrKfSNNQFwbSVbAVF7TboMWDoVeG0yrt2CO3kcTNhzdfkphDCU6oKxDV_QvhG8a1y0LrS8YjFBC9dkJ8_76ZNwChgHU8hb-NBM2xIxNe0GgrUQb62-0r2M3tX1aRHF0yHos/s1600/Readable.png" /></a></div>
<br />
Según la Wikipedia la legibilidad del código es:
<br/>
<i>In computer programming, readability refers to the ease with which a human reader can comprehend the purpose, control flow, and operation of source code. It affects the aspects of quality above, including portability, usability and most importantly maintainability.</i>
<br/><br/>
Explicado con mis palabras, <b>un código legible es un código que puede ser leido fácilmente por una persona que no lo ha escrito</b>, que pueda seguir el flujo aunque no comprenda todo lo que se hace en él.
<a name='more'></a>
<br/><br/>
Yo puedo leer un artículo científico sin entender muchas cosas de las que dice porque no tengo el conocimiento científico adecuado para entender todo el contexto, pero soy capaz de leerlo y de seguir el flujo del artículo de principio a fin.
<br/><br/>
La legibilidad del código es algo a lo que yo le doy mucha importancia. Me parece fundamental que uná persona pueda coger un código y sea capaz de leerlo, no tiene porque comprender todo, ni porque esta hecho así, pero si tiene que poder leerlo como si fuera un libro o un artículo.
<br/><br/>
¿Os imagináis un libro, donde las páginas estan desordenadas, con abreviaturas que solo el escritor entiende o páginas de infinidad de lineas que nunca acaban, sin comas, parrafos, sin capítulos etc..?, claro que no. Pues tampoco deberíamos escribir código que no pueda leer otra persona.
<br/><br/>
Hay una serie de caraterísticas que van a hacer que nuestro código se entienda mejor como son: tamaño de los ficheros, una correcta indentación, una estructura homogenea, un correcto nombre para variables, funciones etc.. y sobre todo tener claro que el único fin no es que el código funcione. Este es un objetivo pero no el único, nos debería preocupar tambén escribir un código lo mejor estructurado posible y aplicando buenas prácticas consiguiendo asi que sea más legible y más escalable. <b>Lo más gratificante que le puede pasar a un programador es estar orgulloso de su código</b>.
<br/><br/>
<b>Un código que no es legible lleva asociado que va a ser más dificil de mantener</b>.
En el mundo empresarial cualquier trozo de código como puede ser un método, función, clase, etc. va a ser repasado y leído en más ocasiones que las veces que hay que modificarlos. Si no es legible vamos a tardar más tiempo en entenderlo con la perdida de tiempo, el cabreo que conlleva y es más facil que hagamos una mala interpretación de lo que hace.
<br/><br/>
<b>Lo más triste que le puede pasar a un programador es que ese código poco legible que le esta costando entender sea un código suyo.</b>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-37677401668946041412015-09-24T07:13:00.000+02:002015-09-24T07:13:59.506+02:00Code Tip: Mi experiencia con la marca de orden de bytes (BOM)<div class="separator" style="clear: both; text-align: center;">
<a href="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" height="200" width="133" /></a></div>
<br />
En este artículo no voy a contar nada muy puntero ni novedoso, además seguramente mucho de vosotros ya conocéis, pero yo no conocía hasta hace unos días. El objetivo es compartir mi experiencia con la marca de orden de bytes y poder ayudar a alguién que se encuentre en la misma situación que yo hace unos días.
<br/><br/>
Por unas cosas o por otras, pocas veces desde que llevo programando, y van ya 13 años, he tenido la necesidad de tener que enfrentarme a creación de ficheros manualmente utilizando c#. Esta semana ha tocado y me he encontrado con algo que desconocia hasta ahora, la marca de orden de bytes.
<br/><br/>
En este artículo voy a contar los problemas que me he encontrado y lo que he aprendido en el camino.
<a name='more'></a>
<h3>Requerimientos y problema detectado</h3>
Sin entrar en muchos detalles, que no son necesarios para entender el problema, en mi empresa teniamos la necesidad de generar un fichero separado por tabuladores que después se iba a importar en una herramienta de terceros.
<h3>Clase que se encargará de crear los bytes</h3>
En nuestro proyecto tenemos una clase que se encarga de crear los bytes. Tiene métodos para añadir campos, lineas y para generar los bytes del fichero.
<br/><br/>
<pre class="brush:c#">
public class DelimitedFile : IDelimitedFile
{
private string output = string.Empty;
private Queue<byte> fifo = null;
private string delimitedCharacter;
public DelimitedFile(string delimitedCharacter)
{
fifo = new Queue<byte>();
this.delimitedCharacter = delimitedCharacter;
}
public void Empty()
{
fifo = new Queue<byte>();
}
public void InsertField(string field)
{
if(!String.IsNullOrEmpty(field))
{
// First, sanitize the data to clean characters
// new line
field = Regex.Replace(field, @"\r\n?|\n", String.Empty);
// delimitator
field = Regex.Replace(field, delimitedCharacter, String.Empty);
}
output = string.Concat(output, field + delimitedCharacter);
}
public void InsertLine()
{
output = string.Concat(output, "\n");
Enqueue();
}
public void Enqueue()
{
byte[] bytes = Encoding.UTF8.GetBytes(output);
foreach (var b in bytes)
fifo.Enqueue(b);
output = string.Empty;
}
public byte[] CreateFile()
{
if (output != string.Empty)
Enqueue();
byte[] bytes = fifo.ToArray();
Empty();
return bytes;
}
}
</pre>
<h3>Problemas con herramienta externa y excel</h3>
El problema detectado es que uno de los campos tiene una longitud máxima en la herramienta de terceros de 120 caracteres. Nos estaba dando error por superar esta longitud, pero aparentemente estaba bien controlado esta longitud en nuestro código antes de generar el fichero. Una de las pruebas que hice fue abrir el fichero con excel mediante el asistente de ficheros de texto y mi sorpresa fue ver que dentro de ese campo habia una palabra que debería ser nüvi que el asistente interpretaba como nüvi, añadiendo así un carácter más y superando la longitud. Además me doy cuenta de que en el origen del fichero en el asistente indica Windows (Ansi) y no UTF-8 como debería.
<h3>Probando con otras herramientas</h3>
Decido probar a abrir el fichero con otras herramientas para ver si entendían la códificación utf-8 correctamente. Pruebo con Notepad++ y con UltraEdit. Con estás herramientas se interpreta correctamente la palabra nüvi y reconoce la codificación como utf-8. Por lo tanto la herramienta de terceros debe utilizar excel o algo que se comporta de forma similar.
<h3>Analizando código heredado</h3>
Viendo la clase que se encarga de generar el fichero se observa claramente que la codificación utilizada para generar los bytes es utf-8, sin embargo se producía este problema. Así que me decido a investigar por la red acabando como siempre en el querido Stackoverflow. Es en esta web donde empiezo a leer en algunos post y respuestas la palabra BOM, que significa <a href="https://es.wikipedia.org/wiki/Marca_de_orden_de_bytes_(BOM)">marca de orden de bytes</a>.
<h3>Creando test que me ayudan a entender el problema</h3>
Me decido a crear un test sobre DelimitedFile que genere los bytes, para después verificar la codificación. Fijaros que en el test me creo un StreamReader y en el constructor le indico al segundo parámetro Encoding.Unicode y al segundo true. Con esto le estoy indicando que quiero que busque la marca de orden de bytes y si no la encuentra que me devuelva unicode como codificación, para después verificar que sea utf-8.
<br/><br/>
<pre class="brush:c#">
[TestMethod]
public void CreateFileBytes_EncodingShouldBeUTF8()
{
DelimitedFile delimitedFile = new DelimitedFile("\t");
delimitedFile.InsertField("nüvi");
byte[] fileBytes = delimitedFile.CreateFile();
Stream stream = new MemoryStream(fileBytes);
StreamReader sr = new StreamReader(stream, Encoding.Unicode,true);
//detect encoding
sr.ReadToEnd();
Encoding currentEncoding = sr.CurrentEncoding;
Assert.AreEqual(Encoding.UTF8, currentEncoding);
}
</pre>
<br/><br/>
El test no pasa.
<h3>Entendiendo la marca de bytes</h3>
Después de que el test me falle me centro en entender mejor el BOM, son unos bytes que deben preceder a los bytes del fichero y ayuda al que lee los bytes a interpretar mejor la codificación. Buscando de nuevo por internet acabo en este <a href="https://msdn.microsoft.com/es-es/library/system.text.utf8encoding.getpreamble(v=vs.110).aspx">artículo</a> de la documentación de Microsoft donde ya entiendo los que debo de hacer. La marca de orden de bytes no se inclue por defecto, debo incluirla yo y la clase encoding tiene una propiedad GetPreamble que da esos bytes que necesito.
<h3>Modificando la clase que se encargará de crear los bytes</h3>
Modifico la clase que crea los bytes para que al inicio escriba el preamble de bytes (BOM).
<pre class="brush:c#">
public class DelimitedFile : IDelimitedFile
{
private string output = string.Empty;
private Queue<byte> fifo = null;
private string delimitedCharacter;
private Encoding encoding = new UTF8Encoding(true);
public DelimitedFile(string delimitedCharacter)
{
fifo = new Queue<byte>();
EnqueueBOM();
this.delimitedCharacter = delimitedCharacter;
}
public void Empty()
{
fifo = new Queue<byte>();
}
public void InsertField(string field)
{
if(!String.IsNullOrEmpty(field))
{
// First, sanitize the data to clean characters
// new line
field = Regex.Replace(field, @"\r\n?|\n", String.Empty);
// delimitator
field = Regex.Replace(field, delimitedCharacter, String.Empty);
}
output = string.Concat(output, field + delimitedCharacter);
}
public void InsertLine()
{
output = string.Concat(output, "\n");
Enqueue();
}
public void Enqueue()
{
byte[] bytes = encoding.GetBytes(output);
foreach (var b in bytes)
fifo.Enqueue(b);
output = string.Empty;
}
public void EnqueueBOM()
{
//https://en.wikipedia.org/wiki/Byte_order_mark
byte[] bytes = encoding.GetPreamble();
foreach (var b in bytes)
fifo.Enqueue(b);
output = string.Empty;
}
public byte[] CreateFile()
{
if (output != string.Empty)
Enqueue();
byte[] bytes = fifo.ToArray();
Empty();
return bytes;
}
}
</pre>
<h3>Pruebas finales</h3>
Ahora el test pasa y las pruebas con la herramientas de terceros también funcionan.
<h3>Concusiones</h3>
La marca de orden de bytes ayuda a entender la codificación de unos bytes y es algo que se tiene que añadir manualmente. En este artículo no se puesto la cantidad de vueltas reales que di hasta encontrar la solución porque el artículo quedaría demasiado largo :-). También he leido que la marca de orden de bytes no es recomendable añadirla en algunos entornos como unix porque puede confundirse con algún otro tipo de codificación pero para mi en este caso ha sido la solución.xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-39262532816093286202015-09-18T11:00:00.001+02:002023-07-11T16:43:55.039+02:00Code Tip: test parametrizados con xUnitEste blog no esta mantenido, suscribite a la newsletter del nuevo
<div style="height: 40vmin;min-height: 360px"><script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-label-1="xurxodeveloper.blogspot.com" data-background-color="#F1F3F4" data-text-color="#000000" data-button-color="#4169e1" data-button-text-color="#FFFFFF" data-title="XurxoDev" data-description="Descubre el conocimiento de desarrollo de software que te lleva al siguiente nivel, no es efímero y puedes aplicar en cualquier tecnología." data-icon="https://xurxodev.com/content/images/size/w192h192/size/w256h256/2022/08/256506a9078afeaf026655924497deb6.jpeg" data-site="https://xurxodev.com" data-locale="es" async></script></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" height="200" width="133" /></a></div>
<br />
Hay veces que cuando estamos haciedo test, nos encontramos duplicando test simplemente cambiando los valores a probar. Existe un concepto que se llama test parametrizados que ayuda a reducir el volumen de test a crear porque elimina la duplicación de test donde solo cambian los valores.
<br/><br/>
No todos los frameworks de test para .Net tienen esta carácteristica. Por ejemplo MSTest no lo tiene, se puede llegar a hacer algo parecido con un componente externo como un excel o algo así pero demasiado complejo para mi gusto. NUnit y xUnit si trae esta carácteristica, vamos a ver como se hace con xUnit.
<a name='more'></a>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6EYw5_PSvShqgpu1XSzBkyI9qGWwbSVYRdkZyK5b8Jb_bbdHokKN5l7qeuvf9EUI0JLe4PwdoyvJtq4nJvGgvWgAXe2Pgg3-CU9Ja4sI7WYAfBo96GFMPlyxfOxZ8HmqA5fFksZF_GdM/s1600/XUnit.net_Logo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh6EYw5_PSvShqgpu1XSzBkyI9qGWwbSVYRdkZyK5b8Jb_bbdHokKN5l7qeuvf9EUI0JLe4PwdoyvJtq4nJvGgvWgAXe2Pgg3-CU9Ja4sI7WYAfBo96GFMPlyxfOxZ8HmqA5fFksZF_GdM/s400/XUnit.net_Logo.png" /></a></div>
<h3>Creando el proyecto</h3>
Cuando creamos un proyecto de pruebas con MSTest debe ser un tipo de proyecto especial de tipo Unit Test Project pero para xUnit un proyecto de tipo Class Library estándar es suficiente.
<h3>Añadiendo referencias</h3>
Añadimos las referencias necesarias para funcionar con xUnit.
<h4>Añadiendo xUnit</h4>
Añadimos xUnit a través de NuGet.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcsTdmYvazaGRqb1bz-_vSkAxuqNHTpm41TO57vxAbcZOYlnQ1HkpJAsasMeZmvkOx5BysYBjnxivcEMSYy9ZeqDzpptQ355fqMEFCiB9xSd-Ivp5D3KQXDW_Hx2Hftbz-h-IhDJpjaHU/s1600/NuGet-xUnit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="177" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcsTdmYvazaGRqb1bz-_vSkAxuqNHTpm41TO57vxAbcZOYlnQ1HkpJAsasMeZmvkOx5BysYBjnxivcEMSYy9ZeqDzpptQ355fqMEFCiB9xSd-Ivp5D3KQXDW_Hx2Hftbz-h-IhDJpjaHU/s400/NuGet-xUnit.png" width="400" /></a></div>
<h4>Añadiendo el runner de xUnit para Visual Studio</h4>
Para poder ejecutar test desde Visual Studio necesitamos añadir, a través de NuGet también, el runner de Visual Studio xunit.runner.visualstudio.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiG2bqHylNspIWgY7JGCJxfmOwYGh5bNzdi8B9MZ-8-RsRfUvd4FxsfPkGtvuih_N0frU3R2gLalN6McRK2tG06jSvRqNjfSVc8ngh93XFIVJZ9r6EYBVRbmWG5Tai3BSk7_nUC33-7LI/s1600/NuGet-xunit.runner.visualstudio.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiG2bqHylNspIWgY7JGCJxfmOwYGh5bNzdi8B9MZ-8-RsRfUvd4FxsfPkGtvuih_N0frU3R2gLalN6McRK2tG06jSvRqNjfSVc8ngh93XFIVJZ9r6EYBVRbmWG5Tai3BSk7_nUC33-7LI/s400/NuGet-xunit.runner.visualstudio.png" /></a></div>
<h3>Test duplicados</h3>
Vamos a crear unos test que pueban la función string.IsNullOrWhiteSpace, necesitamos 3 test uno por cada valor posible para que devuelva true la función. Los test normales se decoran con el atributo Fact.
<br/><br/>
<pre class="brush:c#">
[Fact]
public void IsNullOrWhiteSpace_forEmptyString_ShouldReturnTrue()
{
string value = "";
Assert.True(string.IsNullOrWhiteSpace(value));
}
[Fact]
public void IsNullOrWhiteSpace_forWhiteSpaceString_ShouldReturnTrue()
{
string value = " ";
Assert.True(string.IsNullOrWhiteSpace(value));
}
[Fact]
public void IsNullOrWhiteSpace_forNull_ShouldReturnTrue()
{
string value = null;
Assert.True(string.IsNullOrWhiteSpace(value));
}
</pre>
<br/><br/>
Tenemos 3 test muy parecidos donde lo único que cambia son los valores que se testean. El resultado de los test se ve así en el test explorer:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcbGvVCDIe7TlWfIj8FUcIviz5o61AiVv6pYBepEumlEZyppaX8u221JqR7-Dp6yNm3mG7FatPTp-AvORI5-e5Rhm3IIKPHpX0v33ndFfM9ZyfbHeJEjLpVO7-cRyQzajW7usFgLMhg6I/s1600/VS-Test-Explorer_duolicate_tests.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="85" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcbGvVCDIe7TlWfIj8FUcIviz5o61AiVv6pYBepEumlEZyppaX8u221JqR7-Dp6yNm3mG7FatPTp-AvORI5-e5Rhm3IIKPHpX0v33ndFfM9ZyfbHeJEjLpVO7-cRyQzajW7usFgLMhg6I/s400/VS-Test-Explorer_duolicate_tests.png" width="400" /></a></div>
<h3>Test parametrizado</h3>
La idea es crear un solo test parametrizado para probar los 3 casos de prueba de antes. Los test parametrizados en xUnit se decoran con el atributo Theory y se le añade un atributo InlineData por cada caso de prueba a realizar. El test es necesario que reciba los casos de prueba por parámetro.
<br/><br/>
<pre class="brush:c#">
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void IsNullOrWhiteSpace_ShouldReturnTrue(string value)
{
Assert.True(string.IsNullOrWhiteSpace(value));
}
</pre>
<br/><br/>
Ahora con un solo método realizamos los 3 casos de prueba y eliminamos la duplicación de código. El resultado de los test se ve así en el test explorer:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ53DilYcim8swRXFIAx1pIpUPN4i36UQaeXPKEaI-aFyFdTHlH30vaNo6moz81h_-diHP38pHmTnEvn1tH4ooOgzS3U6TWLQTx-NMEyXD9l1FH46ucbqRALJ7FC-z6cag3JGk_NjbxU8/s1600/VS-Test-Explorer_parametrized-test-xunit.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="122" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQ53DilYcim8swRXFIAx1pIpUPN4i36UQaeXPKEaI-aFyFdTHlH30vaNo6moz81h_-diHP38pHmTnEvn1tH4ooOgzS3U6TWLQTx-NMEyXD9l1FH46ucbqRALJ7FC-z6cag3JGk_NjbxU8/s400/VS-Test-Explorer_parametrized-test-xunit.png" width="400" /></a></div>
<br/><br/>
Fijaros que genera 3 test y al final de cada test especifica el valor del parámetro aplicado en el test.
<h3>Concusiones</h3>
Mediante los test parametrizados se puede ahorrar bastante código y test duplicados. MStest no tiene esta característica, xUnit y NUnit si. Características como esta pueden hacer que te decantes por un framework o por otro.xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-11976006701068004942015-09-10T09:52:00.001+02:002023-07-11T16:44:07.353+02:00Page Object Pattern en AndroidEste blog no esta mantenido, suscribite a la newsletter del nuevo
<div style="height: 40vmin;min-height: 360px"><script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-label-1="xurxodeveloper.blogspot.com" data-background-color="#F1F3F4" data-text-color="#000000" data-button-color="#4169e1" data-button-text-color="#FFFFFF" data-title="XurxoDev" data-description="Descubre el conocimiento de desarrollo de software que te lleva al siguiente nivel, no es efímero y puedes aplicar en cualquier tecnología." data-icon="https://xurxodev.com/content/images/size/w192h192/size/w256h256/2022/08/256506a9078afeaf026655924497deb6.jpeg" data-site="https://xurxodev.com" data-locale="es" async></script></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihhZLwBICOMS4SSGylJkrdI2_4W84LQnRZd4c7X6MbwCz9yY6zL12oHl-65-x_HZh0wcswOXVDkQvguR4nxEHffoBLGzmMRyUW9jqY4X1CmXEOeVUsnGZ5EIb8rmwJDySN4FopC5Y4Nuk/s1600/Page-Object-Pattern-in-Android.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihhZLwBICOMS4SSGylJkrdI2_4W84LQnRZd4c7X6MbwCz9yY6zL12oHl-65-x_HZh0wcswOXVDkQvguR4nxEHffoBLGzmMRyUW9jqY4X1CmXEOeVUsnGZ5EIb8rmwJDySN4FopC5Y4Nuk/s200/Page-Object-Pattern-in-Android.png" width="182" /></a></div>
<br/><br/>
Parge Object Pattern es una patrón de diseño que se utiliza en los test automatizados end-to-end, surgió como un patrón para testear aplicaciones web, de ahí el nombre. Pero en realidad puede aplicar a cualquier tipo de interfaz de usuario como puede ser aplicaciones móviles nativas como Android. Cuando lo utilizo en Android o iOS me gusta llamarlo Screen Object Pattern porque me parece un nombre más apropiado, pero la esencia es la misma.
<br/><br/>
En este artículo vamos a ver como aplicar este patrón en una aplicación Android.
<a name='more'></a>
<h3>Introducción al Patrón</h3>
Cuando creamos test funcionales o de aceptación contra una aplicación Android, lo habitúal es utilizar un framework para test automatizados como Robotium o Espresso de Google, generalmente mediante estos frameworks acatacamos a la interfaz de usuario basandonos en el tipo del control como Toolbar, TextView o también accedemos a los controles a través de su Id (R.Id.one_button).
<br/><br/>
Esto convierte a este tipo de test en frágiles ante cualquier cambio en la interfaz de usuario. ¿Que sucede si cambiamos el id de un control? ¿Y si cambiamos un Listview por un RecyclerView?, vamos a tener que modificar muchos de test.
<br/><br/>
Para solucionar este problema aparece el patrón Page Object, o Screen Object como me gusta llamarlo cuando no trabajamos con páginas, el objetivo es abstraer a los test de la forma de acceder a los controles de la pantalla. Creamos una capa de objetos entre los test y el framework de automatización de test para conseguirlo.
Vamos a tener un objeto por pantalla y vamos a tener métodos o funciones para representar las acciones dentro de la pantalla, podemos tener getter que expogan la información que se encuentra en la pantalla o podemos tener métodos de validación (assertions) dentro del propio screen object.
<br/><br/>
Adaptación del diagrama de <a href="http://martinfowler.com/bliki/images/pageObject/pageObject.png">Martin Fowler</a> al mundo Android.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5jQVnGRw7L368TU5vDUTBK10K2-JsodZO77P9hZcvmao0P7-4-vrAycjZZq4lK835i5Ns2XOQtxwUOk91K_752JMPdd7rkEl-TeLNGOOiRlKa1s9d8hTkjFUsBbM37Zd6I4zWubbas8Y/s1600/Screen-Object-Pattern-diagram.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="342" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5jQVnGRw7L368TU5vDUTBK10K2-JsodZO77P9hZcvmao0P7-4-vrAycjZZq4lK835i5Ns2XOQtxwUOk91K_752JMPdd7rkEl-TeLNGOOiRlKa1s9d8hTkjFUsBbM37Zd6I4zWubbas8Y/s400/Screen-Object-Pattern-diagram.png" width="400" /></a></div>
<br/><br/>
Respecto donde debería estar las validaciones (assertions) hay dos vertientes, lo que piensan que los page object no deberían validar nada porque no es su responsabilidad, como opina Martin Fowler, las validaciones en este caso son responsabilidad del propio test. Y los que piensan que la validaciones deben estar dentro de los screen objects para no incumplir el principio <a href="http://martinfowler.com/bliki/TellDontAsk.html" >Tell dont Ask</a>
y porque así se evita duplicación de ciertas validaciones quedando esta lógica encapsulada dentro del screen object.
<br/><br/>
Yo sinceramente todavía me encuentro esperimentando con el tema y no tengo una posición concreta de momento, y no se si la tendré. Si me he dado cuenta que según el framework parece que te invita a una cosa o a otra. Por ejemplo Espresso tiene sus propios assertions entonces lo mas sencillo es que el screen object valide encapsulando dichas assertions. Pero como os digo de momento no tengo una posición determinada al respecto.
<h3>Ejemplo</h3>
Nada mejor que un ejemplo para entender el concepto.
<br/><br/>
La aplicación sobre la que vamos a hacer pruebas es una calculadora.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://raw.githubusercontent.com/xurxodev/AndroidTestingCalculator/master/screenshots/Android-testing-calculator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="533" src="https://raw.githubusercontent.com/xurxodev/AndroidTestingCalculator/master/screenshots/Android-testing-calculator.png" width="300" /></a></div>
<br/><br/>
Vamos a tener un test que verifica una combinacion de cálculos
<h3>Test frágiles</h3>
Primero vamos a ver como sería el test sin screen object utilizando Espresso como framework.
<br/><br/>
<pre class="brush:java">
@Rule
public ActivityTestRule<MainActivity> mainActivityRule = new ActivityTestRule<>(MainActivity.class,true,false);
@Test
public void PressThree_PressAdd_PressFour_PressAdd_PressFive_PressAdd_PressSix_PressEqual_DisplayShouldShowEighteen(){
mainActivityRule.launchActivity(null);
onView(withId(R.id.three_button))
.perform(click());
onView(withId(R.id.add_button))
.perform(click());
onView(withId(R.id.four_button))
.perform(click());
onView(withId(R.id.add_button))
.perform(click());
onView(withId(R.id.five_button))
.perform(click());
onView(withId(R.id.add_button))
.perform(click());
onView(withId(R.id.six_button))
.perform(click());
onView(withId(R.id.equal_button))
.perform(click());
onView(withId(R.id.display_TextView))
.check(matches(withText("18")));
}
</pre>
<h3>Test con Screen Object</h3>
Tendremos un CalculatorScreen con acciones para iniciar el activity, pulsar botones y otro para validar el texto del display:
<br/><br/>
<pre class="brush:java">
public class CalculatorScreen extends ScreenObject {
@Rule
public ActivityTestRule<MainActivity> mainActivityRule = new ActivityTestRule<>(MainActivity.class,true,false);
public CalculatorScreen start(){
mainActivityRule.launchActivity(null);
return this;
}
public CalculatorScreen pressNumber(String number){
onView(withText(number))
.perform(click());
return this;
}
public CalculatorScreen pressOperation(String operator){
onView(withText(operator))
.perform(click());
return this;
}
public CalculatorScreen pressEqual(){
onView(withId(R.id.equal_button))
.perform(click());
return this;
}
public CalculatorScreen verifyTextDisplay(String text){
onView(withId(R.id.display_TextView))
.check(matches(withText(text)));
return this;
}
}
</pre>
<br/><br/>
Fijaros que todos los métodos devuelven el objeto es si mismo, proporcionando asi una fluent interface que va a permitir escribit test más legibles.
Ahora el test sería asi:
<br/><br/>
<pre class="brush:java">
@Test
public void PressThree_PressAdd_PressFour_PressAdd_PressFive_PressAdd_PressSix_PressEqual_DisplayShouldShowEighteen(){
CalculatorScreen calculatorScreen = new CalculatorScreen();
calculatorScreen
.start()
.pressNumber("3")
.pressOperation("+")
.pressNumber("4")
.pressOperation("+")
.pressNumber("5")
.pressOperation("+")
.pressNumber("6")
.pressEqual()
.verifyTextDisplay("18");
}
</pre>
<br/><br/>
Ahora el test tiene menos lineas de código y es mucho más legible. Si tenemos 50 tests con combinaciones de cálculos y cambiamos el id de los botones no hay que modificar 50 tests, solo CalculatorScreen y los test funcionarían.
<h3>Conclusiones</h3>
El patrón Page Object (aka Screen Object) nos permite que los test end to end no sean tan frágiles ante cambios en la interfaz de usuario o incluso a cambiar el framework que utilizamos para test funcionales.
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-88495290542080667062015-09-03T09:51:00.001+02:002023-07-11T16:44:31.408+02:00Code Tip: No devolver nullEste blog no esta mantenido, suscribite a la newsletter del nuevo
<div style="height: 40vmin;min-height: 360px"><script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-label-1="xurxodeveloper.blogspot.com" data-background-color="#F1F3F4" data-text-color="#000000" data-button-color="#4169e1" data-button-text-color="#FFFFFF" data-title="XurxoDev" data-description="Descubre el conocimiento de desarrollo de software que te lleva al siguiente nivel, no es efímero y puedes aplicar en cualquier tecnología." data-icon="https://xurxodev.com/content/images/size/w192h192/size/w256h256/2022/08/256506a9078afeaf026655924497deb6.jpeg" data-site="https://xurxodev.com" data-locale="es" async></script></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" height="200" width="133" /></a></div>
<br /><br/>
Devolver null en una función es una mala práctica, vamos a ver en este artículo porque es una mala práctica y como podemos evitarlo.
<br/><br/>
<a name='more'></a>
Devolver null es una mala práctica porque estamos obligando al llamador a verificar si el objeto devuelto es null antes de realizar cualquier acción. Vamos a ver casos concretos.
<h3>Cuando se devuelve una colección</h3>
Fijaros en el siguiente código, tenemos una función que devuelve una lista, si no encuentra datos a devolver el valor devuelto es null.
<br/><br/>
<pre class="brush:c#">
public List<User> GetUsers()
{
if (...no hay usuarios...)
return null;
}
</pre>
<br/><br/>
Esta situación obliga al llamador a verificar si es null la lista antes de hacer nada.
<br/><br/>
<pre class="brush:c#">
List<User> users = GetUsers()
if (users != null)
{
foreach (User user in users)
{
....hace lo que sea....
}
}
</pre>
<br/><br/>
Ensuciando el código, imagina if anidados con llamadas a funciones que pueden devolver null, el código quedaria bastante terrible.
<br/><br/>
Una mejor solución es en caso de no tener datos que devolver, crear una lista vacía como valor de retorno
<br/><br/>
<pre class="brush:c#">
public List<User> GetUsers()
{
if (...no hay usuarios...)
return new List<User>();
}
</pre>
<br/><br/>
Ahora el código del llamador se simplifica
<br/><br/>
<pre class="brush:c#">
List<User> users = GetUsers()
foreach (User user in users)
{
....hace lo que sea....
}
</pre>
<br/><br/>
Quedando más sencillo.
<h3>Cuandose devuelve un objeto</h3>
Otro escenario es cuando tenemos una función que devuelve un objeto pero en función de cada caso la solución puede ser diferente.
<h4>Lanzar una excepción</h4>
Por ejemplo el siguiente código, tenemos una función que devuelve una objeto, si no encuentra el objeto a devolver, el valor devuelto es null.
<br/><br/>
<pre class="brush:c#">
public User GetUserById(int id)
{
if (...no existe ese usuario...)
return null;
}
</pre>
<br/><br/>
Volvemos a tener que verificar si es null el resultado de la función antes de hacer nada.
<br/><br/>
<pre class="brush:c#">
User user = repository.GetUserById(id)
if (user != null)
{
....hace lo que sea....
}
</pre>
<br/><br/>
Volvemos a ensuciar el código y además si nos olvidamos de validar algún null, es facil caer en un NullReferenceException.
<br/><br/>
En este escenario lo más apropiado es lanzar una excepción si no existe ningún usuario con ese identificador
<br/><br/>
<pre class="brush:c#">
public User GetUserById(int id)
{
if (...no existe ese usuario...)
throw new Exception("User not found with id "+ id);
}
</pre>
<br/><br/>
Ahora el código del llamador se simplifica
<br/><br/>
<pre class="brush:c#">
User user = repository.GetUserById(id)
....hace lo que sea....
</pre>
<br/><br/>
En la pila de llamadas debería existir en las capas más altas alguien manejando esta posible excepción, pero en las partes intermedias el código se simplifica al no tener que validar nulos.
<h4>Utilizar Null Object Pattern</h4>
Hay otros escenariós ante una situación parecida, donde la solución más apropiada pueda ser utilizar el patrón Null Object.
<br/><br/>
Imaginemos que tenemos una factoría de logger
<br/><br/>
<pre class="brush:c#">
public Logger GetLogger(string type)
{
if (...no existe ese tipo...)
return null;
}
</pre>
<br/><br/>
Mismo problema en el llamador, hay que verificar que el logger no sea nulo antes de hacer nada.
<br/><br/>
<pre class="brush:c#">
Logger logger = factory.GetLogger(type)
.
.
.
if (logger != null)
{
logger.Debug("escribimos traza");
}
</pre>
<br/><br/>
Volvemos a ensuciar el código, en este caso podemos mejorar el código creando una función Debug que verifica si logger es null antes de invocar el método Debug del logger, pero una mejor solución es utilizar Null Object Pattern.
<br/><br/>
Abstraemos el logger con una interfaz, y tenemos la implementación real y otra vacía.
<br/><br/>
<pre class="brush:c#">
public ILogger
{
void Debug(string message);
}
public RealLogger:ILogger
public void Debug(string message)
{
//hace lo que sea
}
}
public NullLogger:ILogger
{
public void Debug(string message)
{
//No hace nada
}
}
</pre>
<br/><br/>
Ahora la factoría en lugar de devolver null devuelve NullLogger si no existe el tipo.
<br/><br/>
<pre class="brush:c#">
public Logger GetLogger(string type)
{
if (...no existe ese tipo...)
return new NullLogger();
}
</pre>
<br/><br/>
Ahora cada vez que utilicemos logger no es necesario verificar si es null.
<br/><br/>
<pre class="brush:c#">
Logger logger = factory.GetLogger(type)
.
.
.
logger.Debug("escribimos traza");
</pre>
<br/><br/>
Un poco de historia: Sir Charles Antony Richard Hoare (aka C.A.R. Hoare) fue el primero que introdujo la referencia nula, os dejo unas palabras suyas donde reconoce que fue un error.
<br/><br/>
<i>I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. In recent years, a number of program analysers like PREfix and PREfast in Microsoft have been used to check references, and give warnings if there is a risk they may be non-null. More recent programming languages like Spec# have introduced declarations for non-null references. This is the solution, which I rejected in 1965.</i>
<br/><br/>
Y para terminar un poco de actualidad: los lenguajes funcionles como pueden ser F# y Kotlin por defecto no permiten asignar null para evitar este tipo de situaciones.
<h3>Conclusiones</h3>
Devolver null es una mala práctica que nos puede llevar a muchas situaciones de error y tenemos a nuestro alcance soluciones en lenguajes como java o c#. Es cierto que si llamamos a librerías externas o apis tendremos que protegernos de excepciones provocadas porque pueden devolver null, pero si en nuestro código evitamos ponernos piedras en el camino, mucho mejor.xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com3tag:blogger.com,1999:blog-6778264965952470158.post-243782673208826932015-08-13T07:04:00.000+02:002015-08-13T07:04:10.205+02:00Soporte para test unitarios con JUnit 4 en Android Studio <div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIdxPkBHf2AHOGCKy7nhUm7-gQEsimmXEkoGeMPLDkt9DsuJJxXAe-gNaCvYPN0VwAgvO99cwhnQToVluh5aKMJpyDyUJ9YVPF6dr5Hp-uIw6OxaHKiGq2e_xUkgqqmJtqzBAc6HF5ZE0/s1600/Android-JUnit4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiIdxPkBHf2AHOGCKy7nhUm7-gQEsimmXEkoGeMPLDkt9DsuJJxXAe-gNaCvYPN0VwAgvO99cwhnQToVluh5aKMJpyDyUJ9YVPF6dr5Hp-uIw6OxaHKiGq2e_xUkgqqmJtqzBAc6HF5ZE0/s200/Android-JUnit4.png" width="159" /></a></div>
<br /><br/>
Desde la versión 1.1 de Android Studio existe, lo que han llamado desde google, soporte para test unitarios. Esto quiere decir que podemos ejecutar test unitarios sin necesidad de desplegar en un dispositivo o emulador, se van a ejecutar en la maquina virtual de java.
<br/><br/>
Estos test untarios se conocen también como test locales o test de jvm (java virtual machine).
<br/><br/>
Cuando creamos un proyecto mediante una de las plantillas de Android Studio, por defecto los test unitarios que vienen son de JUnit 3, vamos a ver que pasos debemos seguir para ejecutar test de JUnit 4.
<a name='more'></a>
<h3>Configuración de dependencias</h3>
Para poder crear test unitarios con junit 4 tenemos que añadir las dependencias de JUnit al fichero gradle del módulo de la aplicación. Cómo es habitual utilizar objetos dobles, lo normal es añadir también una dependencia de una librería de mocks como Mockito, que es muy popular en Android.
<br/><br/>
<pre>
dependencies {
//Tests
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mockito-core:1.10.19'
}
</pre>
<h3>Plugin de Gradle</h3>
Para que todo funcione correctamente necesitamos utilizar el plugin de gradle 1.1 o superior configurado en el fichero gradle del proyecto.
<br/><br/>
<pre>
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.2.3'
}
}
allprojects {
repositories {
jcenter()
}
}
</pre>
<h3>Carpeta para los test unitarios</h3>
El siguiente paso es crear una carpeta para los test unitarios con la siguiente ruta: src/test/java/[nombre del paquete]
<h3>Creando test unitarios</h3>
Ya podemos crear test unitarios con la sintaxis de junit 4, utilizando anotaciones etc..
<br/><br/>
<pre class="brush:java">
@Test
public void text_is_expected() {
String text = "test1";
assertThat(text, is("test1"));
}
</pre>
<h3>Android.jar para test unitarios</h3>
Cuando se ejecutan test unitarios se arranca un android.jar que no tiene el código real de la librería como cuando se ejecuta en dispositivos o emuladores, en su lugar todos los métodos devuelven una excepción. De esta forma Android Studio se asegura que no se va a invocar ningún método de android en un test de JVM.
<br/><br/>
Vamos a ver que pasa si lo hacemos, modificamos el test y accedemos a la clase Log dentro del test.
<br/><br/>
<pre class="brush:java">
@Test
public void text_is_expected() {
String text = "test1";
Log.d("ExampleTest","executing test");
assertThat(text, is("test1"));
}
</pre>
<br/><br/>
Si ahora ejecutamos el test obtenemos el siguiente error:
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_0OyBHn_MMffqB-6wY_wArgqjgP8XVA8oT-tByWBqzizwFtk9RkS5-k_SH-Ipo-SLj8FjzQfD_dTKB-iXswuXLZ2lnb9r_ftniXjhdv3FVXEvZPKEBM5CsxM7LvTo0iFZBRW7Oy3OU58/s1600/Android-Studio-error-using-anroid-framework-in-unit-test.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="135" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_0OyBHn_MMffqB-6wY_wArgqjgP8XVA8oT-tByWBqzizwFtk9RkS5-k_SH-Ipo-SLj8FjzQfD_dTKB-iXswuXLZ2lnb9r_ftniXjhdv3FVXEvZPKEBM5CsxM7LvTo0iFZBRW7Oy3OU58/s400/Android-Studio-error-using-anroid-framework-in-unit-test.png" width="400" /></a></div>
<br/><br/>
No podemos utilizar el framework de android en nuestras clases que luego vamos a verificar con test unitarios, de esta forma es problemático utilizar clases como Log y TestUtils.
<br/><br/>
Las clases sobre las que deberiamos de crear test unitarios son clases de tipo POJO sin ninguna dependencia, dependiendo de la arquitectura de nuestra aplicación las clases sobre las que haremos este tipo de test unitarios pueden ser por ejemplo modelos de la capa de dominio, si estamos utilizando el patrón MVP, los presenters es otro buen ejemplo.
<br/><br/>
Como hemos visto el comportamiento por defecto del android.jar que arranca con test untarios es lanzar una excepción, este es su comportamiento por defecto. Sin embargo existe una configuración que podemos añadir al fichero gradle para cambiar el comportamiento por defecto y que no se lance la excepción.
<pre>
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}
</pre>
<br/><br/>
Pero hay que tener cuidado con esta configuración ya que lo deseable es que en nuestras clases, donde vamos a verificar su comportamiento con test unitarios, no tengamos dependencias de android. Siempre podemos utilizar abstracciones que hagan de wrappers de estas clases del framework de android y en los test unitarios utilizar objetos dobles con mockito.
<h3>Test Artifact</h3>
Para poder ejecutar test unitarios tenemos que ir a la pestaña build variants y en el combo test artifact debemos seleccionar unit test. Desde este momento cuando se ejecute un test lo será en modo test de la maquina virtual de java.
<h3>Ejecutando los test desde el menu</h3>
Ya tenemos la configuración necesaria y ejecutar el test.
<br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAS15GI5KMCvVyS2Dr7HkhccLiozeZvFCW261FlJTn0_kh09nC6ix8cxGp4JcSKXlcZZ3CS-EBcHLKTzQnV0diDgWCgqRP55PNYSafO_s0q1ZEJoGKcsd8NOqb3M6BfrpoDu2q4kCRp3k/s1600/Android-Studio-run-unit-test.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="171" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAS15GI5KMCvVyS2Dr7HkhccLiozeZvFCW261FlJTn0_kh09nC6ix8cxGp4JcSKXlcZZ3CS-EBcHLKTzQnV0diDgWCgqRP55PNYSafO_s0q1ZEJoGKcsd8NOqb3M6BfrpoDu2q4kCRp3k/s400/Android-Studio-run-unit-test.png" width="400" /></a></div>
<h3>Ejecutando los test desde el terminal</h3>
Para poder ejecutar los test unitarios del proyecto desde el terminal podemos utilizar el comando gradlew test.
<br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bWdkI-Jq4V1_xXVXJAMFhghy7OcFWgO1DusHRnyrcsSuI4DVhl188V3eRICKY3fu-uvtrLe59pZOTkEjBKGuuwkQJxTIOVYxDvhxQZOvEQVPRhGnmKeBIUZ-SAjqwQgZTD5UlcMgrNQ/s1600/Android-Studio-running-unit-test-from-terminal.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6bWdkI-Jq4V1_xXVXJAMFhghy7OcFWgO1DusHRnyrcsSuI4DVhl188V3eRICKY3fu-uvtrLe59pZOTkEjBKGuuwkQJxTIOVYxDvhxQZOvEQVPRhGnmKeBIUZ-SAjqwQgZTD5UlcMgrNQ/s400/Android-Studio-running-unit-test-from-terminal.png" /></a></div>
<br/><br/>
Esto nos genera un informe en la ruta app\build\reports\tests\debug\index.html.
<br/>
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT7p-pmqrO2vd2-3hM_-qfhpTimwS3mJXwFENbs99vL3_ZeOWYxBV_JJeBTY0W7X06YEZT6FbrAupiSkRqAok7va64UiWv_opnNQQy2ZafzpwZ7sc495a8zlLi_RttBgWziHER8fwnges/s1600/Android-Studio-unit-test-report.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjT7p-pmqrO2vd2-3hM_-qfhpTimwS3mJXwFENbs99vL3_ZeOWYxBV_JJeBTY0W7X06YEZT6FbrAupiSkRqAok7va64UiWv_opnNQQy2ZafzpwZ7sc495a8zlLi_RttBgWziHER8fwnges/s400/Android-Studio-unit-test-report.png" /></a></div>
<h3>Libros relacionados</h3>
Mockito Cookbook
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/1783982748/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1783982748&linkCode=as2&tag=xurxodevelo09-20&linkId=C36O53JH7B6MGJD7" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/1783982748/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1783982748&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/1783982748/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1783982748&linkCode=as2&tag=xurxodevelope-21&linkId=QXUDG6AC46OEBELD" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
Practical Unit Testing with JUnit and Mockito
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/8393489393/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=8393489393&linkCode=as2&tag=xurxodevelo09-20&linkId=7YUHYDSBRKVPXR2O" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/8393489393/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=8393489393&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/8393489393/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=8393489393&linkCode=as2&tag=xurxodevelope-21&linkId=LIGPFP2NPLVSUUPK" target="_blank">Amazon.co.uk</a>
</li>
</ul>
Mastering Unit Testing Using Mockito and JUnit
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/1783982500/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1783982500&linkCode=as2&tag=xurxodevelo09-20&linkId=AUYVUB4Y2VQUVIRD" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/1783982500/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1783982500&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/1783982500/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1783982500&linkCode=as2&tag=xurxodevelope-21&linkId=IDIJLO74W5GTFXSQ" target="_blank">Amazon.co.uk</a>
</li>
</ul>xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-88314616930032028462015-06-25T07:20:00.001+02:002023-07-11T16:44:43.771+02:00Developer: El despertar de la fuerzaEste blog no esta mantenido, suscribite a la newsletter del nuevo
<div style="height: 40vmin;min-height: 360px"><script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-label-1="xurxodeveloper.blogspot.com" data-background-color="#F1F3F4" data-text-color="#000000" data-button-color="#4169e1" data-button-text-color="#FFFFFF" data-title="XurxoDev" data-description="Descubre el conocimiento de desarrollo de software que te lleva al siguiente nivel, no es efímero y puedes aplicar en cualquier tecnología." data-icon="https://xurxodev.com/content/images/size/w192h192/size/w256h256/2022/08/256506a9078afeaf026655924497deb6.jpeg" data-site="https://xurxodev.com" data-locale="es" async></script></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3dHLJRH_XOrNhpoD9eXVtoOy9_M4vubfGDjiYr0qd6pxDtbHG0aW7BEMiLaR1hjSamfeW39wIMABt4JB1pfQm3zPukDibnX4JEdkjOGTE7xRELwxYYcObn24am9x88fA-dBzQBKFvDuU/s1600/Developer-el-despertar-de-la-fuerza.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="201" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3dHLJRH_XOrNhpoD9eXVtoOy9_M4vubfGDjiYr0qd6pxDtbHG0aW7BEMiLaR1hjSamfeW39wIMABt4JB1pfQm3zPukDibnX4JEdkjOGTE7xRELwxYYcObn24am9x88fA-dBzQBKFvDuU/s400/Developer-el-despertar-de-la-fuerza.png" width="400" /></a></div>
<br />
A estas alturas todos conocemos la película de Star Wars y sabemos que existe el poder de la fuerza y el poder del lado oscuro, bueno pues yo creo que estos conceptos encajan muy bien en la profesión de developer, en este artículo voy a hacer unas reflexiones haciendo un símil con la película.
<a name='more'></a>
<h3>Nacimiento en el lado oscuro</h3>
Se puede decir que todos nacemos en la profesión de developer perteneciendo al lado oscuro, no en cuanto a personalidad, pero si en cuanto a las malas prácticas de desarrollo que llevan a problemas, cuando nacemos no tenemos experiencia en como se deben hacer las cosas bien, no tenemos malas experiencias con las que haber aprendido un camino que conduce a problemas. No conocemos lo que es la duplicación de código, código espagueti, exceso de comentarios en el código, una mala arquitectura de aplicación, no hacer test unitarios etc...
<h3>El primer despertar</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBNFIB2wU2Lt2HPO2bODlsfRLW3S0lx-s3DRJBQ4paJgTwfaLChmy9_AL2dW3S1zkfC_9UzOqxXPSD0dA_UE_HdC-8drDcuTHU9BFH3afx8KD7ybx4Azf__KPRUATFU_ELsQLLIyR3_bI/s1600/Primer-despertar.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="284" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBNFIB2wU2Lt2HPO2bODlsfRLW3S0lx-s3DRJBQ4paJgTwfaLChmy9_AL2dW3S1zkfC_9UzOqxXPSD0dA_UE_HdC-8drDcuTHU9BFH3afx8KD7ybx4Azf__KPRUATFU_ELsQLLIyR3_bI/s320/Primer-despertar.jpg" width="320" /></a></div>
<br />
Hay un momento, que con la experienia llega, donde empezamos a comprender un paradigma como es la programación orientada a objetos, lo conocemos a través de un compañero, en una conferencia o en internet, pero al principio no lo comprendemos, esa forma rara de programar todo con clases no tiene sentido, pero un día el despertar se produce, empezamos a programar utilizando clases, empezamos a entender lo que es un objeto y los beneficios de su existencia.
<br/><br/>
También es el momento donde comprendemos el polimorfismo mediante las abstracciones y pensamos cómo he podido programar de otra forma hasta ahora.
<br/><br/>
En ese momento solo vemos objetos, cada vez que necesitamos algo pensamos en objetos, sabemos que necesito un servicio que haga tal cosa, o un objeto usuario que haga otra. Si tenemos objetos parecidos nos empezamos a dar cuenta de que tal vez sea bueno tener una abstracción común en forma de interface o de clase base para esos objetos con cosas en común.
<h3>Un nuevo despertar</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqms_joRurZQ4FaoPG0Bn9YW_5-5pLbG2vjdDZEh3h21ea_dwJOf5qlO57z6VQ34IGd3nzNhcNNMaNRbzLTMqVta9xAojhKuoALWNYDrZ7Ec0mndUbs3jm9bP4xV9mZiZkiRrjquiEPIk/s1600/Un-nuevo-despertar.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqms_joRurZQ4FaoPG0Bn9YW_5-5pLbG2vjdDZEh3h21ea_dwJOf5qlO57z6VQ34IGd3nzNhcNNMaNRbzLTMqVta9xAojhKuoALWNYDrZ7Ec0mndUbs3jm9bP4xV9mZiZkiRrjquiEPIk/s320/Un-nuevo-despertar.jpg" /></a></div>
<br/>
Lo bonito que me he encontrado en esta profesion es que siempre hay cosas por aprender, nuevos despertares nos esperan. Cuando ya dominas o crees que dominas la programación orientada a objetos, de repente empiezas a oir conceptos como patrones de diseño y empiezas a leer a señores como Martin Fowler, Uncle Bob, Gang of Four, Kent Beck, Eric Evans y te das cuenta de todo lo que tienes que aprender y la cantidad de caminos que hay en el lado de la fuerza.
<br/><br/>
Es momento en el que empiezas a ser un Senior Developer donde ya no solo te dedicas a tirar lineas de código, empiezas a hacer algo más, empiezas a pensar de otro modo, piensas antes de escribir código, razonas la mejor forma de implementar una solución, piensas en la mejor arquitectura para una aplicación que vas a desarrollar. También en ese momento escribes test unitarios que te ayudan a definir tus clases, empiezas a entender los que son buenas prácticas y lo que son malas prácticas, empiezas a dominar conceptos como dominio, negocio, inyección de dependencias, abstracciones, código limpio, refactorización, principios SOLID etc.
<h3>Las tentaciones del lado oscuro</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVjJnGlPvDHgwKR19cVCTZnpFPKe9XjDn8PJaMNGw0liZqJxX94CTaE-s1qmrsoN_rD_ia5J5AaWgU6rKy81Md8DZ9RYfJjqdis0FkOT4BbN64FCsgurKuiE_RiSVDk2g9uai5UEeTfr4/s1600/Dark-Side.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVjJnGlPvDHgwKR19cVCTZnpFPKe9XjDn8PJaMNGw0liZqJxX94CTaE-s1qmrsoN_rD_ia5J5AaWgU6rKy81Md8DZ9RYfJjqdis0FkOT4BbN64FCsgurKuiE_RiSVDk2g9uai5UEeTfr4/s320/Dark-Side.png" /></a></div>
<br/>
Al igual que en Star Wars, es más fácil hacer las cosas mal que hacerlas bién, lo realmente difícil es hacer código sólido, código poco acoplado que permita escalar adecuadamente, hacer refactorizaciones constantes, mantener los test unitarios, en definitiva programar bien requiere de más tiempo y constancia, tiempo para analizar las cosas y siempre habrá tentaciones que nos intentarán llevar al lado oscuro.
<br/><br/>
Siempre habrá un proyecto que va con retraso, un project manager que añade tareas en mitad de una versión queriendo mantener la misma fecha, código que heredas de un compañero que no sigue las mismas buenas prácticas que tu intentas seguir. Todas estas tentaciones hacen fácil acabar cayendo en el lado oscuro y haciendo alguna que otra ñapa o mala práctica, que a largo plazo o incluso a corto acabará en problemas.
<br/><br/>
Pero esta en nosotros mismos y solo en nosotros el mantenernos en el buen camino, en el lado de la fuerza. ¿Cómo lo hacemos?, hay que intentar buscar un equilibrio y saber que si puntualmente es necesario hacer una ñapa, esa parte hay que refactorizarla en cuanto se pueda, si nos añaden tareas en mitad de una versión la versión se tiene que retrasar para que se mantenga la calidad, es de sentido común. Si heredamos un código que no sigue buenas prácticas debemos intentar dejarlo mejor de lo que nos lo encontramos, no hay porque dejarlo perfecto pero si todo el equipo que trabaja en un proyecto tiene esa mentalidad al final el código siempre irá evolucionando a mejor.
<br/><br/>
Es fácil que conozcas a alguién bastante bueno ya sea otro developer, un jefe, el dueño de la empresa que son bastante válidos y estan bien considerados dentro de tu empresa pero que pertenecen al lado oscuro.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHPdKWZ-e4MvR51en-hNgUqlyXkB8cTC_l0hML9o3WuUwtZLWOzHwMk9nI2QGeXWnBs3rgNOuLFFuEUqJ9x0cUONJAGTCLb9KTJRi3oIlnTgtPWkffHbZ-vf0OKdzZU1UWfzo-DYYQaX4/s1600/anakin.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHPdKWZ-e4MvR51en-hNgUqlyXkB8cTC_l0hML9o3WuUwtZLWOzHwMk9nI2QGeXWnBs3rgNOuLFFuEUqJ9x0cUONJAGTCLb9KTJRi3oIlnTgtPWkffHbZ-vf0OKdzZU1UWfzo-DYYQaX4/s320/anakin.jpg" /></a></div>
<br/><br/>
<h3>Maestros Jedi y Padawan</h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht4DnGN1QmnJ2s5okSy2nLZmVTDMaG7e1M-6KCGmzvUgYvW47WF0CF7AnaVubxFQU-rrTdjhzb6WPqGl9ftOgBW4uu1C8Aq5FaNjlrZ3dD1hdJGBHzyIBZN63AhANw-W0oM5_UUxa6WAU/s1600/Jedi.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEht4DnGN1QmnJ2s5okSy2nLZmVTDMaG7e1M-6KCGmzvUgYvW47WF0CF7AnaVubxFQU-rrTdjhzb6WPqGl9ftOgBW4uu1C8Aq5FaNjlrZ3dD1hdJGBHzyIBZN63AhANw-W0oM5_UUxa6WAU/s320/Jedi.jpg" /></a></div>
<br/><br/>
Los developers que son maestros Jedi son personas que son tranquilas, reflexivas, con bastante experiecia, con buen criterio para decidir, humilde, que siguen buenas prácticas, gente importate dentro de un equipo, personas que no dejan nunca de aprender y que tienen una experiencia bastante amplia en el poder de la fuerza. Padawan son personas con el potencial y personalidad para ser un maestro Jedi pero que carecen de la experiencia necesaria, con el tiempo lo normal es que acaben siéndolo.
<br/><br/>
Todos los Jedi y Padawan se llevan bien, rodeate de este tipo de personas, no he conocido ni uno solo que no este dispuesto a ayudar, a enseñarte o a aprender contigo. Conocen bien que tienen que mucho que aprender y siempre estan dispuestos a aprender con una actitud humilde.
<br/><br/>
Si alguién con apariencia de Jedi tiene una actitud poco humilde o prepotente, recela, seguro que esta más cerca del lado oscuro de lo que aparenta.
<br/><br/>
Si tu empresa esta llena de este tipo de personajes cercanos al lado oscuro, será el momento de pensar si no estarías mejor en otra empresa, si tienes un puesto de responsabilidad y tienes developers a tu cargo con este perfil vivirás mejor si los despides, no suelen aportar nada bueno y seguramente si un compañero del lado de la fuerza coge su código seguramente no sea una buena época. A todos nos ha pasado algo parecido en las empresas donde hemos estado.
<br/><br/>
La mejor elección es rodearse de Jedi y Padawan, cuando los encuentras notas el poder de la fuerza en su interior y se produce una afinidad natural desde el primer momento.
<h3>Conclusiones</h3>
Es importante conocer que hay dos caminos como developers, seguir el camino de la fuerza, intentar hacer las cosas bien, con buenas prácticas buscando un equilibrio y hay otro camino, el del lado oscuro, que es más fácil pero que a la larga da problemas y hace que no nos sintamos orgullosos del código que escribimos.
<br/><br/>
Nosotros y solo nosotros somos responsables de no caer en el lado oscuro.
<br/><br/>
Que la fuerza te acompañe.xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-33269549358181966712015-06-18T07:12:00.000+02:002015-06-18T07:12:35.335+02:00Code Tip: Extrae los bloques Try/Catch en su propia función<div class="separator" style="clear: both; text-align: center;">
<a href="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://upload.wikimedia.org/wikipedia/commons/3/3c/Tip-of-the-day-bulb-(png).png" height="200" width="133" /></a></div>
<br />
Los bloques Try/Catch son utilizados en lenguajes como Java y C# para manejar excepciones, el problema que suelen tener es que ensucian el código no pudiendo ver claramente el código real del que controla la excepción, vamos a ver como podemos extraer la gestión de errorres en una función dejando el código mucho mas limpio y legible.
<a name='more'></a>
Veamos este ejemplo código extraido de la MSDN que he modificado para el artículo.
<br/><br/>
<pre class="brush:c#">
public void CreateFile(string path, string content)
{
try
{
if (File.Exists(path))
{
File.Delete(path);
}
using (FileStream fs = File.Create(path))
{
Byte[] info = new UTF8Encoding(true).GetBytes(content);
fs.Write(info, 0, info.Length);
}
}
catch (Exception ex)
{
_logger.Error("A Error has ocurred creating file", ex);
}
}
</pre>
<br/><br/>
En el mismo método crea el fichero y también maneja la excepción, esto tiene varios problemas:
<br/><br/>
Ensucia el código, esta mezclado el manejo de la excepción con la creación del fichero, además la función se esta encargando de dos cosas y las funciones quedan más limpias cuando se encargan de solo una cosa, en caso de tener que hacer varias cosas, es mejor dividir en pequeñas funciones para una mejor legibilidad del código. En este caso la mejor solución es crear una función que se encargue solo de crear el fichero y otra que invoca a esta y además realiza el manejo de la excepción.
<br/><br/>
<pre class="brush:c#">
public void CreateFile(string path, string content)
{
try
{
CreateFileDeleteIfExists(path, content);
}
catch (Exception ex)
{
_logger.Error("A Error has ocurred creating the file", ex);
}
}
public void CreateFileDeleteIfExists(string path, string content)
{
if (File.Exists(path))
{
File.Delete(path);
}
using (FileStream fs = File.Create(path))
{
Byte[] info = new UTF8Encoding(true).GetBytes(content);
fs.Write(info, 0, info.Length);
}
}
</pre>
<br/><br/>
<h3>Concusiones</h3>
Los bloques Try/Catch es recomensable extraerlos en su propia función añadiendo así una mejor legibilidad al código y creando dos funciones, cada una con una funcionalidad concreta, una para el manejo de excepciones y otra con la funcionalidad principal.
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-3520813839892829632015-06-04T09:43:00.000+02:002015-06-04T09:43:44.412+02:00Lollipop new features: Floating Action Button<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" /></a></div>
<br/>
Vamos a continuar con las nuevas características que aparecieron en Lollipop con la aparición de Matrial Design, ya van unos cuantos <a href="http://xurxodeveloper.blogspot.com.es/search?q=Lollipop%20new%20features">artículos</a>. En este artículo vamos a ver el Float Action Button, al que llamaremos FAB a partir de ahora.
<a name='more'></a>
<h3>Introducción a Floating Action Button</h3>
Según las epecificaciones de <a href="http://www.google.com/design/spec/components/buttons-floating-action-button.html#buttons-floating-action-button-floating-action-button">Material Design</a>, FAB es un tipo de botón cuyo objetivo es recomendar la acción principal de una pantalla, pero no todas las pantallas tienen que tener un FAB. Es un botón que se situa elevado por encima del layout de la ventana y se visualiza de forma clara.
<br/><br/>
Solo debe existir un FAB por pantalla que representa la acción principal.
<br/><br/>
Debe representar una acción positiva como añadir, enviar mensaje, no negativas como borrar o de menor importancia como copiar, cortar etc..
<br/><br/>
Personalmente me esperaba por parte de Google darnos el trabajo más hecho porque para ser una especificación suya para crear un FAB y además compatible con versiones anteriores requieres de varios pasos manuales y bastante repetitivos entre apps, de ahí que hayan surgido en GitHub librerías que proporcionan botón FAB ya creado y compatible con versiones anteriores.
<br/><br/>
Parece que esta pega la han solucionado en la siguente versión con una librería anunciada en el pasado I/O, donde ya tendremos un widget FloatActionButton.
<h3>Ejemplo</h3>
Vamos a añadir un botón FAB en la ventana detalle de item de la aplicación que estamos creando, voy a explicar como hacerlo de forma manual, alguna carácteristica habitual de un FAB voy a renunciar a ella para versiones anteriores porque requiere de más pasos manuales y creo que hay que buscar un equilibrio y prefiero prescinfdir de que algo se vea exactamente como en Lollipop en versiones anteriores y que el código quede más limpio y más compacto.
<br/><br/>
El FAB que voy a crer en la ventana de detalle va a representar la acción de ver la imagen a pantalla completa.
<br/><br/>
En el layout del fragement de los datos añadimos un image button que será nuestro FAB.
<pre class="brush:xml">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
.
.
.
<ImageButton
android:id="@+id/detailItemfab"
android:layout_width="@dimen/fab_size"
android:layout_height="@dimen/fab_size"
android:src="@drawable/fullscreen"
android:background="@drawable/fab_background"
android:elevation="4dp"
android:stateListAnimator="@anim/fab_animation"
android:layout_gravity="center"
/>
.
.
.
</LinearLayout>
</pre>
<br/><br/>
Las propiedades android:elevation="4dp" y android:stateListAnimator="@anim/fab_animation" solo se van a ejecutar si la versión Android del dispositivo es API 21 o superior.
<br><br>
Como background creamos fab_background.xml en la carpeta drawable, se compone de un shape ovalado y le ponemos un color por defecto.
<br/><br/>
<pre class="brush:xml">
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/holo_red_light"/>
</shape>
</pre>
<br/><br/>
Ahora creamos una carpeta anim con el sufijo de versión 21, de forma que solo aplica en versión 21 o superior. Dentro creamos el fichero de animación fab_animation.xml para realizar una traslacion en el eje z y así dar la sesación de presionar el botón.
<br/><br/>
<pre class="brush:xml">
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="100"
android:valueFrom="1"
android:valueTo="6"
android:valueType="floatType"/>
</set>
</item>
<item>
<set>
<objectAnimator
android:propertyName="translationZ"
android:duration="100"
android:valueFrom="6"
android:valueTo="1"
android:valueType="floatType"/>
</set>
</item>
</selector>
</pre>
<br/><br/>
Vamos a tener un nuevo activity donde vamos a tener la imagen a pantalla completa, este activity va a contener el fragment de la iamgen del item.
<br/><br/>
Entonces ahora solo nos falta abrir el activity de pantalla completa cuando se pulsa sobre el FAB.
<br/><br/>
Definimos un interface y un listener en el fragment donde se encuentra el FAB para notificar al activity del item detail que se quiere abrir el activity de pantalla completa. Invocaremos el método del listenr cuando se pulse el FAB.
<br/><br/>
<pre class="brush:java">
public class ItemDataFragment extends Fragment {
.
.
.
private OpenItemFullScreenListener openItemFullScreenListener;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_item_data, container, false);
detailItemfab =(ImageButton)rootView.findViewById(R.id.detailItemfab);
//Click fullscreen
detailItemfab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (openItemFullScreenListener != null)
openItemFullScreenListener.openItemFullScreen(item);
}
});
return rootView;
}
.
.
.
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof OpenItemFullScreenListener) {
openItemFullScreenListener = (OpenItemFullScreenListener) activity;
}
}
public static interface OpenItemFullScreenListener {
public void openItemFullScreen(Item item);
}
}
</pre>
<br/><br/>
En el item detail activity, que a su vez esta haciendo de OpenItemFullScreenListener, abrimos el activity de la imagen a pantalla completa utilizando la transición de elemento compartido.
<br/><br/>
<pre class="brush:java">
public class ItemDetailActivity extends ActionBarActivity
implements ItemImageFragment.OnPaletteGeneratedListener,
ItemDataFragment.OpenItemFullScreenListener{
.
.
.
@Override
public void openItemFullScreen(Item item) {
View sharedView = findViewById(R.id.detailItemImage);
String transitionName = getString(R.string.image_transition_name);
ActivityOptionsCompat options =
ActivityOptionsCompat.makeSceneTransitionAnimation(
this, sharedView, transitionName);
Intent intent = new Intent(this, ItemFullScreenActivity.class);
intent.putExtra("item", item);
ActivityCompat.startActivity(this, intent, options.toBundle());
}
}
</pre>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW9R8yZR3rCLJCdg9IxHMUBU3HD79dsVqJhXNQM-_wAD13qqAmAWGUmA_A-myGwTQq8Cd0qJPOdlKO8GqtEO1KRNGLurtNUVskT0bzjMixIJIA5ssJ19b-kznA6YLNTNA4THTAIEv9cmc/s1600/Lollipop_FloatActionButton.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgW9R8yZR3rCLJCdg9IxHMUBU3HD79dsVqJhXNQM-_wAD13qqAmAWGUmA_A-myGwTQq8Cd0qJPOdlKO8GqtEO1KRNGLurtNUVskT0bzjMixIJIA5ssJ19b-kznA6YLNTNA4THTAIEv9cmc/s320/Lollipop_FloatActionButton.gif" width="179" /></a></div>
<h3>Concusiones</h3>
El Float Action Button es un botón que representa la acción principal de la pantalla, su implementación requiere de varios pasos manuales. Esto es algo que parece que va a mejorar en la próxima versión donde tendremos un widget FloatActionButton que implementará toda la funcionalidad.
<br/><br/>
<a href="https://github.com/xurxodeveloper/LollipopNewFeatures">Código fuente en GitHub - tag 0.5</a>
<h3>Libros relacionados</h3>
Android Programming: The Big Nerd Ranch Guide.
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321804333&linkCode=as2&tag=xurxodevelo09-20&linkId=PSV4N3BJAXFZ75KS" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321804333&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/B00C893P8U/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=B00C893P8U&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
Android Programming In a Day!: The Power Guide for Beginners In Android App Programming
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelo09-20&linkId=7TO3TQDCVZFCW26R" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1507893744&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-27875741703683351172015-05-28T06:54:00.000+02:002015-05-28T06:54:54.974+02:00Lollipop new features: extrayendo colores de una imagen con Palette<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" /></a></div>
<br/>
En este artículo vamos a continuar con las novedades de Lollipop, seguimos ampliando la aplicación que empezamos en <a href="http://xurxodeveloper.blogspot.com.es/search?q=Lollipop%20new%20features">anteriores artículos</a>. En este artículo vamos a ver como extraer los colores de una imagen utilizando la librería Palette y que uso podemos darle.
<a name='more'></a>
<h3>Introducción a Palette</h3>
Palette es una nueva librería disponible con Lollipop, solo esta disponible en la librería support AppCompat.
<br><br/>
Para utilizar Palette es necesario añadir la dependencia Gradle.
<br/><br/>
<pre>
dependencies {
compile 'com.android.support:palette-v7:21.0.0'
}
</pre>
<br/><br/>
Para extraer los colores existen dos formas, una síncrona y otra asíncrona.
<br/><br/>
<h4>Métodos síncronos</h4>
<ul>
<li>generate(Bitmap)
</li>
<li>generate(Bitmap, int)
</li>
</ul>
El primer método genera 16 colores por defecto, en el segundo podemos especificar el número de colores que queremos generar, cuantos más colores más tiempo tarda el proceso. El valor de retorno de ambos métodos es un objeto Palette.
<h4>Métodos asíncronos</h4>
<ul>
<li>generateAsync(Bitmap, PaletteAsyncListener)
</li>
<li>generateAsync(Bitmap, int, PaletteAsyncListener)
</li>
</ul>
Los métodos son similares, la unica diferencia es que se le pasa a cada método un listener al que se informa cuando con colores se han generado. La implementación de listener es bastante sencilla, contiene un método onGenerated donde recibe el objeto Palette generado.
<br/><br/>
<pre class="brush:java">
Palette.PaletteAsyncListener listener = new Palette.PaletteAsyncListener() {
public void onGenerated(Palette palette) {
}
}
</pre>
<br/><br/>
La librería intenta generar 16 colores pero estos son los colores que se suelen usar.
<ul>
<li>Vibrant</li>
<li>Vibrant Dark</li>
<li>Vibrant Light</li>
<li>Muted</li>
<li>Muted Dark</li>
<li>Muted Light</li>
</ul>
<h3>Ejemplo</h3>
Vamos a seguir con nuestra aplicación, en esta ocasión dandole funcionalidad con Palette. Antes de este post he hecho una pequeña refactorización sobre la pantalla de detalle. A nivel visual ahora se muestra un título una descripción del item además de la imagen, y a nivel técnico he dividido el contenido del activity en dos fragments al estilo de lista detalle clásico, de forma que tengo un fragment para visualizar la imagen del item y otro con los datos del item. En portrait la imagen se muestra encima de los datos del item y en landscape a la izquierda.
<br/><br/>
Cuando utilizamos Palette podemos hacer cosas como establecer el color de fonfo de algún control en base a los colores de la foto que se visualiza en la misma pantalla. Vamos a hacer eso, vamos a colorear el fondo del título del item en función de la paleta y además vamos a mostrar también los 6 colores básicos extraidos de la imagen en una leyenda.
<br/><br/>
Necesitamos que los dos fragments se comuniquen de forma que el fragment de la imagen cuando esta se haya cargado comunique al fragment de los datos la paleta de colores extraida de la imagen. Vamos a ver los pasos para conseguirlo.
<br/><br/>
En el fragment de la imagen definimos un interface y un listener, también sobreescribimos el método onAttach para asignar el listener en caso de que el activity lo implemente. Entonces cuando se ha cargado la imagen invocamos el método Palette.generateAsync, cuando la paleta se ha generado invocamos el método onPaletteGenerated del listener.
<br/><br/>
<pre class="brush:java">
public class ItemImageFragment extends Fragment {
.
.
.
OnPaletteGeneratedListener onPaletteGeneratedListener;
.
.
.
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Item item = (Item) getActivity().getIntent().getSerializableExtra("item");
Picasso.with(imageView.getContext())
.load(item.getImageUrl())
.into(imageView, new Callback() {
@Override
public void onSuccess() {
Palette.generateAsync(((BitmapDrawable)imageView.getDrawable()).getBitmap(), new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
if (onPaletteGeneratedListener != null)
onPaletteGeneratedListener.onPaletteGenerated(palette);
}
});
}
@Override
public void onError() {
}
});
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof OnPaletteGeneratedListener) {
onPaletteGeneratedListener = (OnPaletteGeneratedListener) activity;
}
}
public static interface OnPaletteGeneratedListener {
public void onPaletteGenerated(Palette palette);
}
}
</pre>
<br/><br/>
En el fragment de los datos definimos un método updatePalette donde coloreamos el fondo del título y los colores de la leyenda en base a la paleta de colores recibida.
<br/><br/>
<pre class="brush:java">
public class ItemDataFragment extends Fragment {
TextView titleTextView;
TextView descriptionTextView;
TextView vibrantTextView ;
TextView vibrantDarkTextView;
TextView vibrantLightTextView;
TextView mutedTextView;
TextView mutedDarkTextView;
TextView mutedLightTextView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_item_data, container, false);
titleTextView = (TextView)rootView.findViewById(R.id.detailItemTitle);
descriptionTextView = (TextView)rootView.findViewById(R.id.detailItemDescription);
//Colors
vibrantTextView = (TextView)rootView.findViewById(R.id.vibrant_color);
vibrantDarkTextView = (TextView)rootView.findViewById(R.id.vibrant_dark_color);
vibrantLightTextView = (TextView)rootView.findViewById(R.id.vibrant_light_color);
mutedTextView = (TextView)rootView.findViewById(R.id.muted_color);
mutedDarkTextView = (TextView)rootView.findViewById(R.id.muted_dark_color);
mutedLightTextView = (TextView)rootView.findViewById(R.id.muted_light_color);
return rootView;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Item item = (Item) getActivity().getIntent().getSerializableExtra("item");
titleTextView.setText(item.getTitle());
descriptionTextView.setText(item.getDescription());
}
public void updatePalette(Palette palette){
int defaultColor = Color.WHITE;
int titleColor = palette.getVibrantColor(defaultColor);
titleTextView.setBackgroundColor(titleColor);
vibrantTextView.setBackgroundColor(palette.getVibrantColor(defaultColor));
vibrantDarkTextView.setBackgroundColor(palette.getDarkVibrantColor(defaultColor));
vibrantLightTextView.setBackgroundColor(palette.getLightVibrantColor(defaultColor));
mutedTextView.setBackgroundColor(palette.getMutedColor(defaultColor));
mutedDarkTextView.setBackgroundColor(palette.getDarkMutedColor(defaultColor));
mutedLightTextView.setBackgroundColor(palette.getLightMutedColor(defaultColor));
}
}
</pre>
<br/><br/>
El activity va a hacer de mediador entre los fragments de forma que va a ser el listener del fragment de la imagen y cuando el fragment comunica que se ha generado la paleta, se lo comunicará al fragment de los datos pasandole la paleta generada.
<br/><br/>
<pre class="brush:java">
public class ItemDetailActivity extends ActionBarActivity
implements ItemImageFragment.OnPaletteGeneratedListener {
private static final String TAG_IMAGE_FRAGMENT = "image_fragment";
private static final String TAG_DATA_FRAGMENT = "data_fragment";
ItemImageFragment itemImageFragment;
ItemDataFragment itemDataFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_detail);
itemImageFragment = (ItemImageFragment) getSupportFragmentManager().findFragmentByTag(TAG_IMAGE_FRAGMENT);
itemDataFragment = (ItemDataFragment) getSupportFragmentManager().findFragmentByTag(TAG_DATA_FRAGMENT);
if (itemImageFragment == null) {
itemImageFragment = new ItemImageFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.itemImageContainer, itemImageFragment,TAG_IMAGE_FRAGMENT)
.commit();
}
if (itemDataFragment == null) {
itemDataFragment = new ItemDataFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.itemDataContainer, itemDataFragment,TAG_DATA_FRAGMENT)
.commit();
}
}
//OnPaletteGeneratedListener
@Override
public void onPaletteGenerated(Palette palette) {
if (itemDataFragment != null)
itemDataFragment.updatePalette(palette);
}
}
</pre>
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG_lIVpHHRkbbzDj6CNq3Hxv9UI_2HZJMcXoDC_dGWNve25ydb-_jSGgxYmco5_RR6YLiFYjjeL3NEok-AhBKPHoSxobM5PUyStf_UumlOx3H6wun9YPvs8O2SXmsSdl0KTtfHrN502OA/s1600/Lollipop_Palette.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhG_lIVpHHRkbbzDj6CNq3Hxv9UI_2HZJMcXoDC_dGWNve25ydb-_jSGgxYmco5_RR6YLiFYjjeL3NEok-AhBKPHoSxobM5PUyStf_UumlOx3H6wun9YPvs8O2SXmsSdl0KTtfHrN502OA/s320/Lollipop_Palette.gif" width="179" /></a></div>
<h3>Concusiones</h3>
Mediante Palette podemos extraer los colores de una imagen y dar una variabilidd al look & feel de nuestra app que en ciertos casos puede ser muy interesante. Hemos visto los sencillo que es implementar esta carácteristica de Lollipop.
<br/><br/>
<a href="https://github.com/xurxodeveloper/LollipopNewFeatures">Código fuente en GitHub - tag 0.4</a>
<h3>Libros relacionados</h3>
Android Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321804333&linkCode=as2&tag=xurxodevelo09-20&linkId=PSV4N3BJAXFZ75KS" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321804333&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/B00C893P8U/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=B00C893P8U&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
Android Programming In a Day!: The Power Guide for Beginners In Android App Programming
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelo09-20&linkId=7TO3TQDCVZFCW26R" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1507893744&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-43245540358528222252015-05-21T06:58:00.000+02:002015-05-26T21:12:19.921+02:00Lollipop new features: transiciones de elementos compartidos<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" /></a></div>
<br/>
En este artículo vamos a continuar con las novedades de Lollipop, seguimos ampliando la aplicación que empezamos en <a href="http://xurxodeveloper.blogspot.com.es/2015/05/lollipop-new-features-manejando-el-click-en-los-elementos-del-recyclerview.html">anteriores artículos</a>. En este artículo vamos a ver transiciones de Lollipop y concretamente transiciones de elementos compartidos.
<a name='more'></a>
<h3>Introducción</h3>
Con la versión 5 de Android Lollipop llegó también Material Design, que es un lenguage de diseño nuevo creado por Google. MaterialDesign tiene una filosofía que cubre muchas areas pero en este post nos vamos a centrar en las transiciones.
<br/><br/>
Según las especificaciones de <a href="http://www.google.com/design/spec/material-design/introduction.html" target="_blank">Material Design</a>, las transiciones entre dos estados visuales debe ser clara, suave y sin esfuerzo.
<br/><br/>
Hay varios tipos de transiciones pero en este artículo nos vamos a centrar en las transiciones de elementos compartidos. Este tipo de transición da una continuidad visual en el paso de una ventana a otra.
<br/><br/>
En versiones anteriores a Lollipop ya existían transiciones entre activities y fragments pero ahora las transiciones pueden animar vistas hijas dentro de un activity o fragment como pueden ser imágenes, botones, textos etc..
<br/><br/>
Si dos ventanas comparten un mismo elemento como puede ser una imagen o un texto, en lugar de hacer una transición brusca entre pantallas, lo que conseguimos con este tipo de transición es sensación de que la imagen se traslada desde la posición y tamaño de la vista inicial hasta las posición y tamaño de la vista secundaria.
<br/><br/>
Este tipo de transición solo esta disponible en Lollipop (API 21), veremos en el ejemplo como podemos controlarlo para que no se produzca error en versiones anteriores.
<h3>Ejemplo</h3>
En la aplicación que estamos desarrollando en esta serie, tenemos una lista de imágenes y al hacer click en una de ellas abrimos una vista de detalle con la imagen más grande. Lo que vamos a hacer es una transición de esta imagen compartida entre las dos vistas.
<br/><br/>
Hacer este tipo de transición se consigue con poco código, vamos a ver los pasos que tenemos que dar para hacerlo. Utilizaremos la librería Compat de forma que sea esta librería la que controle si estamos en version 21 (Lolliopop) para realizar la transición o no.
<h4>Habilitando transiciones en el Theme de la aplicación</h4>
Las transiciones deben ser habilitadas en el theme de la apliación, pero solo para la API 21, entonces creamos una carpeta de recursos values-21 y creamos una carpeta styles dentro. Definimos el theme para la aplicación habilitando transiciones.
<br/><br/>
<pre class="brush:xml">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"><br />
<item name="android:windowContentTransitions">true</item><br />
<item name="android:windowAllowEnterTransitionOverlap">true</item><br />
<item name="android:windowAllowReturnTransitionOverlap">true</item><br />
<item name="android:windowSharedElementEnterTransition">@android:transition/move</item><br />
<item name="android:windowSharedElementExitTransition">@android:transition/move</item><br />
</style>
</pre>
<br/><br/>
<h4>Asignando al elemento compartido del activity secundarío el nombre de transición</h4>
Para poder hacer la transición hay que identificar la vista compartida en la activity secundaria, en nuestro caso una imagen, así que tenemos que asignarle valor a la propiedad TransitionName.
<br/><br/>
<pre class="brush:xml">
.
.
.
<ImageView<br />
android:id="@+id/detailItemImage"<br />
android:transitionName="@string/image_transition_name"<br />
android:layout_width="match_parent"<br />
android:layout_height="match_parent"<br />
android:scaleType="fitStart" />
.
.
.
</pre>
<h4>Abrir activity secundaria indicando elementos compartidos</h4>
Al abrir el activity secundaria tenemos que hacer unos cambios. Primero nos creamos un ActivityOptionsCompat, es un objeto que contiene la información de los objetos que intervienen en la transición.
<br/><br/>
Después abrimos el activity secundario, no con el método startActivity del propio activity sino del objeto ActivityCompat, este objeto gestiona la versión en la que estamos para abrir el activity de una forma o de otra, le indicamos el activity origen, el elemento compartido de la vista origen y el transitionName, que es el mismo que hemos asignado a la imagen del activity secundario.
<br/><br/>
<pre class="brush:java">
((ItemsAdapter) adapter).setOnItemClickListener(new ItemsAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Item item = items[position];
View sharedView = view.findViewById(R.id.itemImage);
String transitionName = getString(R.string.image_transition_name);
ActivityOptionsCompat options =
ActivityOptionsCompat.makeSceneTransitionAnimation(
getActivity(), sharedView, transitionName);
Intent intent = new Intent(getActivity(), ItemDetailActivity.class);
intent.putExtra("item", item);
ActivityCompat.startActivity(getActivity(), intent, options.toBundle());
}
});
</pre>
<br/><br/>
Con estos pasos ya tenemos lista la transición de elemento compartido.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEKmWMyPn5tsyEitWYJTdKEI37KVq8stpPZSJmmwRso7JP8364x_elZQivB8DzaOBLb8qmbXF91BuuDBkrkrxkfKDOOYZH2A3qM4gWeM0BW5OroSASoaP5KPPVNTstTTHvOZUKVaiJriA/s1600/Lollipop_Shared_Element_Transition.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgEKmWMyPn5tsyEitWYJTdKEI37KVq8stpPZSJmmwRso7JP8364x_elZQivB8DzaOBLb8qmbXF91BuuDBkrkrxkfKDOOYZH2A3qM4gWeM0BW5OroSASoaP5KPPVNTstTTHvOZUKVaiJriA/s320/Lollipop_Shared_Element_Transition.gif" width="179" /></a></div>
<h3>Concusiones</h3>
Con Lollipop también ha llegado Material Design que es un lenguaje de diseño, existe una especificación creada por Google con los detalles del lenguaje. Las transiciones es un punto importante de la especificación y en en este artículo hemos visto lo sencillo que es crear transiciones de elementos compartidos para dispositivos Lollipop y pre-Lollipop.
<br/><br/>
<a href="https://github.com/xurxodeveloper/LollipopNewFeatures">Código fuente en GitHub - tag 0.3</a>
<h3>Libros relacionados</h3>
Android Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321804333&linkCode=as2&tag=xurxodevelo09-20&linkId=PSV4N3BJAXFZ75KS" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321804333&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/B00C893P8U/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=B00C893P8U&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
Android Programming In a Day!: The Power Guide for Beginners In Android App Programming
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelo09-20&linkId=7TO3TQDCVZFCW26R" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1507893744&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-14189635162059625212015-05-14T07:00:00.000+02:002015-05-26T21:12:05.844+02:00Lollipop new features: manejando el click en los elementos del RecyclerView<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" /></a></div>
<br/>
En este artículo vamos a continuar con las novedades de Lollipop, vamos a continuar con la aplicación que empezamos en el anterior artículo sobre <a href="http://xurxodeveloper.blogspot.com.es/2015/05/lollipop-new-features-recyclerview.html">RecyclerView</a>. El control RecyclerView a diferencia de ListView no tiene un OnClickListener y vamos a tener que implementarlo nosotros.
<a name='more'></a>
<h3>Introducción</h3>
Ahora debe ser la propia celda la que comunique cuando se ha hecho click sobre ella, a mi me gusta añadir un interface OnItemClickListener en el Adapter, con un método onClick invocado cuando se hace click en la vista del elemento desde el ViewHolder. De esta forma la responsabilidad de que hacer cuando se produce el click en un elemento queda ajeno al ViewHolder y el Adapter. Será el activity o el fragment el que decidirá lo que hay que hacer, en arquitecturas tipo Clean notificaría al ViewPresenter la acción a realizar.
<br/><br/>
El método onClick del listener va a tener dos parámetros, uno la vista donde se ha hecho click y la posición que ocupa el elemento en el adapter, de forma que se pueda acceder al modelo que representa la vista si se necesita.
<br/><br/>
El método onClick podría llevar solo un parámetro directamente con el modelo, de hecho veréis este tipo de solución por internet, pero a mi me gusta dejarlo más abierto y menos dependiente del modelo de la aplicacón por si se necesitará hacer algo con la vista sobre la que se hace click como cambiar el layout o hacer una transición como veremos en artículos posteriores.
<h3>Ejemplo</h3>
Bueno vamos al lío.
<h4>Un listener en el adapter</h4>
Añadimos un interface para el listener y el objeto listener en el adapter.
<br/><br/>
<pre class="brush:java">
public class ItemsAdapter extends RecyclerView.Adapter<ItemsAdapter.ViewHolder> {
.
.
.
private static OnItemClickListener onItemClickListener;
.
.
.
public static interface OnItemClickListener {
public void onItemClick(View view, int position);
}
.
.
.
}
</pre>
<br/><br/>
<h4>Se invoca el listener cuando se hace click en la vista</h4>
Capturamos el click de la vista root del item y cuando se dispara el callback invocamos el onClick del listener del adapter.
<br/><br/>
<pre class="brush:java">
public class ItemsAdapter extends RecyclerView.Adapter<ItemsAdapter.ViewHolder> {
.
.
.
private static OnItemClickListener onItemClickListener;
.
.
.
public static interface OnItemClickListener {
public void onItemClick(View view, int position);
}
.
.
.
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public ViewHolder(View itemRootView) {
super(itemRootView);
imageView = (ImageView) itemRootView.findViewById(R.id.itemImage);
itemRootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int position = ViewHolder.super.getAdapterPosition();
onItemClickListener.onItemClick(view,position);
}
});
}
}
}
</pre>
<h4>Asignamos el onItemClickListener al adapter </h4>
Desde el activity o fragment, en nuestro caso fragment, asignamos un listener al adapter y en el callback onClick vamos a obtener el item seleccionado mediante la posición y abrimos un activity de detalle del item.
<br/><br/>
<pre class="brush:java">
public class ItemsFragment extends Fragment {
.
.
.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
.
.
.
((ItemsAdapter) adapter).setOnItemClickListener(new ItemsAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Item item = items[position];
Intent intent = new Intent(getActivity(), ItemDetailActivity.class);
intent.putExtra("item", item);
startActivity(intent);
}
});
.
.
.
}
.
.
.
}
</pre>
<h3>Concusiones</h3>
El nuevo widget RecyclerView que viene en la version Lollipop, no tiene un listener para cuando se hace click en un elemento, tenemos que implementar una solución manual. Hemos visto una forma de hacerlo que consiste en añadir el listener al adapter de forma que no sobrecargamos de responsabilidades el adapter ya que notifica mediante su listener cuando se hace click en un elemento pero no se responsabiliza de la acción a realizar en ese momento.
<br/><br/>
<a href="https://github.com/xurxodeveloper/LollipopNewFeatures">Código fuente en GitHub - tag 0.2</a>
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLBRsHwXdgyR0VEJpGbVx4uzd3r8SaqDSMcNsO3-NTgiCScN4StEjJJ3eBVlFrA999w0Ryu-Lc3ZC1hza-XdxPnGBJtaS0T9dAxSkwxCbW8ciDCjive_XDNlyOONfywL9t8sUPAdfMnjo/s1600/Lollipop_RecyclerView_OnClickItemListener.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLBRsHwXdgyR0VEJpGbVx4uzd3r8SaqDSMcNsO3-NTgiCScN4StEjJJ3eBVlFrA999w0Ryu-Lc3ZC1hza-XdxPnGBJtaS0T9dAxSkwxCbW8ciDCjive_XDNlyOONfywL9t8sUPAdfMnjo/s320/Lollipop_RecyclerView_OnClickItemListener.gif" width="179" /></a></div>
<h3>Libros relacionados</h3>
Android Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321804333&linkCode=as2&tag=xurxodevelo09-20&linkId=PSV4N3BJAXFZ75KS" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321804333&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/B00C893P8U/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=B00C893P8U&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
Android Programming In a Day!: The Power Guide for Beginners In Android App Programming
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelo09-20&linkId=7TO3TQDCVZFCW26R" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1507893744&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-80966862513607456872015-05-07T07:20:00.000+02:002015-05-26T21:11:50.034+02:00Lollipop new features: RecyclerView <div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHMNlnM7OKC4TTiwez8odQLS4dNsvwwYJrh5NUVgvEJ3AnuoJ3JjDdCxMZhhwLxEl5oum1JpwH8GYq6W-kTTrrlOa7ITtncZ_Anrkkil4uZq1HHoiwgvljRx0VHsNK2RHFsMCLZz0h_m8/s1600/ic_launcher.png" /></a></div>
<br/>
Con la llegada de Android 5 Lollipop llegaron una serie de novedades a la plataforma que voy a intentar repasar en este y próximos artículos. En vamos a empezar viendo el nuevo widget ReciclerView.
<a name='more'></a>
El widget ReciclerView viene incluido dentro de la nueva versión actualizada de la libreria de soporte para versiones antiguas appcompat v7. Viene para sustituir a los widget ListView y GridView.
<h3>Introducción a ReciclerView</h3>
Es un contenedor de colección elementos en forma de lista pero es más flexible y oferce mejor rendimiento que ListView. Permite configurar una serie de animaciones para la eliminación, desplazamiento y creación de nuevos elementos en tiempo real.
<br/><br/>
La creación y administración de un RecyclerView es similar a la de un ListView. Se necesita un origen datos y un adaptador que los lea, interprete e infle, pero también es necesario un nuevo elemento llamado LayoutManager.
<br/><br/>
El LayoutManager es el encargado de reutilizar los views y pedir el nuevo contenido al adapter, se encarga de reciclar los elementos que dejan de ser visibles para el usuario al hacer scroll por el contenido de otros que son visibles.
<br/><br/>
En Lollipop vienen 3 LayoutManager :
<ul>
<li>LinearLayoutManager muestra los items en forma de lista horizontal o vertical</li>
<li>GridLayoutManager muestra los items en forma de grid</li>
<li>StaggeredGridLayoutManager muestra los items en forma de grid con celdas no uniformes.</li>
</ul>
El adapter tiene que extender de la clase RecyclerView.Adapter y viene integrado con el patrón ViewHolder, vamos a ver un ejemplo de cómo se implementa.
<h3>Ejemplo</h3>
Vamos a ver un ejemplo de un ReciclerView que va a ser un grid de imagenes.
<h4>Dependencias</h4>
En el fichero build.gradle del modulo de la aplicación tenemos que añadir las dependencias que vamos a necesitar.
<pre>
dependencies {
...
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.android.support:recyclerview-v7:22.0.+'
compile 'com.squareup.picasso:picasso:2.5.2'
}
</pre>
<br/><br/>
Picasso es una libería para descargar imágenes pero se queda fuera del alcance de este artículo, asi que no entraré en detalles.
<h4>Layouts</h4>
Vamos a tener 3 layouts:
<br/><br/>
activity_items.xml:
<br/><br/>
Nuestro ejemplo es sencillo a si que solo consta de un Framelayout donde visualizaremos despues el fragment con el contenido. Es una buena práctica en Android utilizar fragments para reutilización de vistas entre diferentes resoluciones.
<br/><br/>
<pre class="brush:xml">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container"<br />
android:layout_width="match_parent" android:layout_height="match_parent"<br />
tools:context=".ItemsActivity" tools:ignore="MergeRootFrame" />
</pre>
<br/><br/>
fragment_items.xml:
<br/><br/>
El layout del fragment va a ser donde vamos a tener realmente el RecyclerView.
<br/><br/>
<pre class="brush:xml">
&lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;<br />
xmlns:tools=&quot;http://schemas.android.com/tools&quot;<br />
android:layout_width=&quot;match_parent&quot;<br />
android:layout_height=&quot;match_parent&quot;<br />
tools:context=&quot;.ItemsActivity$ItemsFragment&quot;&gt;<br />
&lt;android.support.v7.widget.RecyclerView<br />
android:id=&quot;@+id/items_recycler_view&quot;<br />
android:scrollbars=&quot;vertical&quot;<br />
android:layout_width=&quot;match_parent&quot;<br />
android:layout_height=&quot;match_parent&quot;/&gt;<br />
&lt;/RelativeLayout&gt;
</pre>
<br/><br/>
items_item.xml:
<br/><br/>
El layout para cada elemento del RecyclerView, que consta de una imagen por el momento.
<pre class="brush:xml">
<?xml version="1.0" encoding="utf-8"?><br />
<LinearLayout<br />
xmlns:android="http://schemas.android.com/apk/res/android"<br />
android:layout_width="match_parent"<br />
android:layout_height="match_parent"><br />
<ImageView<br />
android:id="@+id/itemImage"<br />
android:layout_width="@dimen/image_width"<br />
android:layout_height="@dimen/image_height" /><br />
</LinearLayout>
</pre>
<h4>Modelo</h4>
El modelo es bastante sencillo y de momento para este artículo solo ultilizaremos el getter image.
<br/><br/>
<pre class="brush:java">
public class Item {
String title;
String description;
String imageUrl;
public Item(String title, String description, String imageUrl) {
this.title = title;
this.description = description;
this.imageUrl = imageUrl;
}
}
</pre>
<h4>Activity</h4>
El activity es bastante sencillo también solo visualiza el fragment.
<br/><br/>
<pre class="brush:java">
public class ItemsActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_items);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new ItemsFragment())
.commit();
}
}
}
</pre>
<h4>Fragment</h4>
El fragment, es donde esta realmente esta parte del trabajo, crea los datos a representar, configura el RecyclerView y el adapter.
<br/><br/>
<pre class="brush:java">
public class ItemsFragment extends Fragment {
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private RecyclerView.LayoutManager layoutManager;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_items, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.items_recycler_view);
//poner a true si cambios en el contenido no supone
//cambios en el tamaño del control, esto mejora rendimiento
recyclerView.setHasFixedSize(true);
//usamos un GridLayoutManager con 2 columnas
layoutManager = new GridLayoutManager(rootView.getContext(),2);
recyclerView.setLayoutManager(layoutManager);
adapter = new ItemsAdapter(createItems());
recyclerView.setAdapter(adapter);
return rootView;
}
private Item[] createItems(){
String commonPath = "http://lorempixel.com/400/400/people/";
Item[] items = new Item[]
{ new Item(null,null,commonPath + "1"),
new Item(null,null,commonPath + "2"),
new Item(null,null,commonPath + "3"),
new Item(null,null,commonPath + "4"),
new Item(null,null,commonPath + "5"),
new Item(null,null,commonPath + "6"),
new Item(null,null,commonPath + "7"),
new Item(null,null,commonPath + "8"),
new Item(null,null,commonPath + "9"),
new Item(null,null,commonPath + "10")};
return items;
}
}
</pre>
<h4>Adapter</h4>
El Adapter es el encargado de representar los datos, fijaros la integración que tiene ahora un adapter con el ViewHolder, en versiones anteriores a Lollipop era bastante manual la aplicación de este patrón.
<br/><br/>
<pre class="brush:java">
public class ItemsAdapter extends RecyclerView.Adapter<ItemsAdapter.ViewHolder> {
private Item[] items;
public static class ViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public ViewHolder(View v) {
super(v);
imageView = (ImageView) v.findViewById(R.id.itemImage);;
}
}
public ItemsAdapter(Item[] items) {
this.items = items;
}
// crea una nueva vista(invocado por el layout manager)
@Override
public ItemsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
//creamos la nueva imagen
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_items, parent, false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
// Reemplaza el contenido de una vista (invocado por el layout manager)
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Item item = items[position];
//cargamos la imagen con picasso
Picasso.with(holder.imageView.getContext())
.load(item.imageUrl)
.into(holder.imageView);
}
@Override
public int getItemCount() {
return items.length;
}
}
</pre>
<h3>Concusiones</h3>
A partir de Lollipop existe un nuevo widget llamado RecyclerView que debe ser el que utilicemos para representar datos en forma de lista. Esta más optimizado que el antiguo ListView. Para configurarlo es parecido a ListView la diferencia es que viene integrado con el patrón ViewHolder y existe una nueva clase LatoutManager encargada de la gestion de reciclado.
<br/><br/>
<a href="https://github.com/xurxodeveloper/LollipopNewFeatures">Código fuente en GitHub - tag 0.1</a>
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDCV3bI_pDqi0HaGaz3zyoUtHKthVg6a2O3GZ4nu_2O6eZlvomDIKk7SxwAyshO2rWZgz_lH6OmHP0knYzyCtPYqHevxpYS8ODDj6PVYKFUpGLlLn4R1BdgUmtEir6HWXEHaSJytfdBCI/s1600/device-2015-05-02-235707.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDCV3bI_pDqi0HaGaz3zyoUtHKthVg6a2O3GZ4nu_2O6eZlvomDIKk7SxwAyshO2rWZgz_lH6OmHP0knYzyCtPYqHevxpYS8ODDj6PVYKFUpGLlLn4R1BdgUmtEir6HWXEHaSJytfdBCI/s1600/device-2015-05-02-235707.gif" height="320" width="179" /></a></div>
<h3>Libros relacionados</h3>
Android Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321804333&linkCode=as2&tag=xurxodevelo09-20&linkId=PSV4N3BJAXFZ75KS" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321804333&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/B00C893P8U/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=B00C893P8U&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
Android Programming In a Day!: The Power Guide for Beginners In Android App Programming
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelo09-20&linkId=7TO3TQDCVZFCW26R" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1507893744&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-81781599256096234532015-04-26T22:52:00.000+02:002015-04-26T22:52:46.222+02:00Droidcon Spain 2015 - Madrid<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiei7EDGQEos9wsjmudWEEZWoUoj26hKa2ZNK3ul8lTV3aPHr84yMDCKD9qMjKXc8oWXuRGbnBN7yov5KB7rN_T2fjG5mi6Kq0qbaE1L89byyRbsuyV683ATXgL0zqFVCFo-wfkfxkUu3g/s1600/DroidconMAD2015.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiei7EDGQEos9wsjmudWEEZWoUoj26hKa2ZNK3ul8lTV3aPHr84yMDCKD9qMjKXc8oWXuRGbnBN7yov5KB7rN_T2fjG5mi6Kq0qbaE1L89byyRbsuyV683ATXgL0zqFVCFo-wfkfxkUu3g/s1600/DroidconMAD2015.png" /></a></div>
<br />
Durante los dias 23, 24 y 25 de abril se ha celebrado el evento Droidcon Spain 2015 en la Universidad Autónoma de Madrid. He asistido por primera vez a este evento y en este artículo quiero compartir con vosotros mis impresiones.
<br />
<a name='more'></a><h3>
Impresiones</h3>
Voy dividirlas en dos puntos, organización y charlas.
<br />
<h4>
Organización</h4>
La organización creo que no ha sido del todo acertada, muy bien los speakers que han traído (creo que el nivel ha sido bastante bueno en general), pero en cuanto al aforo ha tenido fallos a mi modo de ver graves para costar 60€ la entrada. No cabíamos todos en el auditorio, había gente en el suelo, de pie, incluso se quedaban fuera en varias charlas. La organización se excusó diciendo que no habían contabilizado bien los alumnos de la Universidad Autónoma de Madrid que entraban gratuitamente por realizarse el evento en su casa y que se habilitaba una sala en streaming. Posteriormente pidieron a los speakers que repitieran su conferencia en un aula taller para que la gente que se quedaba fuera pudiera verles en directo. En fin creo que si pagas 60€ qué menos que ver lo que pagas y en las condiciones que se presupone.
<br />
<h4>
Charlas</h4>
El nivel de las charlas ha sido bastante bueno, como todo, unas te gustan más que otras, pero el nivel medio muy alto. La tónica general han sido arquitectura de las aplicaciones, SOLID, test unitarios y refactorización. En principio puede sorprender que lo más hablado no haya sido sobre el SDK de Android en un evento Droidcon pero bueno, yo he quedado encantando porque son temas que me interesan mucho y sobre los que suelo investigar habitualmente e intento llevar a la práctica. Es grafiticante ver cómo gente de mucho nivel tiene como forma de trabajar éstas buenas prácticas que intento seguir en mi día a día.
<br />
<br />
También hemos tenido charlas sobre lenguages alternativos a Java para Android como Scala y Kotlin, que son lenguajes funcionales. La programación funcional es algo que parece que va a venir cada vez a más ya que resuelve limitaciones de lenguajes como Java.
<br />
<h3>
Material recopilado</h3>
A continuación os dejo el material que he podido encontrar por las redes sociales sobre el evento. Actualizaré el post con más enlaces segun se vayan subiendo a las redes sociales. La organización también dijo que subiría los slides y videos.
<br />
<ul>
<li>
<a href="http://es.slideshare.net/jmoc25/testing-android-security" target="_blank">Testing Android Security – José Manuel Ortega</a>
</li>
<li>
<a href="http://www.slideshare.net/jorgedortiz/7-stages-of-unit-testing" target="_blank">Unit Testing en Android Studio – Jorge Ortiz Fuentes</a>
</li>
<li>
<a href="https://speakerdeck.com/danielgallegovico/droidcon-spain-2105-one-app-to-rule-them-all-methodologies-tools-and-tricks-to-achieve-an-effective-white-label-model" target="_blank">One app to rule them all: Methodologies, Tools & Tricks to achieve an effective white label model – Daniel Gallego</a>
</li>
<li>
<a href="http://es.slideshare.net/eenriquelopez?utm_campaign=profiletracking&utm_medium=sssite&utm_source=ssslideview" target="_blank">Continuous Integration for Android / Keep It Building Baby! – Enrique López</a>
</li>
<li>
<a href="https://speakerdeck.com/guardiola31337/elegant-unit-testing-droidcon-spain-2015" target="_blank">Elegant?? Unit Testing – Pablo Guardiola</a>
</li>
<li>
<a href="http://www.slideshare.net/txusballesteros/annotations-processor-tools-apt" target="_blank">Annotation Processing Tool – Txus Ballesteros</a>
</li>
<li>
<a href="https://speakerdeck.com/saulmm/android-wear-vs-tizen-apr-2015" target="_blank">Android Wear vs Tizen – Saul M.</a>
</li>
<li>
<a href="http://www.slideshare.net/ChristianPanadero/my-way-to-clean-android-v2" target="_blank">My way to clean Android (No more buses) – Christian Panadero</a>
</li>
<li>
<a href="https://speakerdeck.com/borillo/android-refactoring" target="_blank">Android Refactoring – Ricardo Borillo</a>
</li>
<li>
<a href="https://docs.google.com/presentation/d/1bPE0HbKIUQn-SAWfLBFcRz3aJwjgEUr8gKGhLJgDxRo/edit#slide=id.g76bf114f5_2_0" target="_blank">What’s new in Android Testing – Jose Alcérreca</a>
</li>
<li>
<a href="https://speakerdeck.com/cesarvaliente/libre-open-source-software-1" target="_blank">What you can/can’t do with FLOSS – César Valiente</a>
</li>
<li>
<a href="https://speakerdeck.com/malmstein/android-tv-this-is-not-the-idiot-box-you-are-looking-for" target="_blank">Android TV. Ésta no es la caja tonta que estabas buscando – David González</a>
</li>
<li>
<a href="https://speakerdeck.com/antoniolg/developing-android-with-kotlin" target="_blank">Desarrollando en Android con Kotlin – Antonio Leiva</a>
</li>
<li>
<a href="http://es.slideshare.net/everywaretech/weapons-for-boilerplate-destruction" target="_blank">Weapons for Boilerplate Destruction – Tomás Ruiz</a>
</li>
<li>
<a href="https://speakerdeck.com/android10/the-mayans-lost-guide-to-rxjava-on-android" target="_blank">The Mayans Lost Guide To RxJava on Android – Fernando Cejas</a>
</li>
<li>
<a href="http://raycoarana.com/misc/concurrency-with-promise-style-droidcon-2015/" target="_blank">Concurrency with Promise Style – Rayco Araña</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-90518328827954739242015-04-23T06:56:00.000+02:002015-04-23T06:56:52.318+02:00WinRT Tip: Cómo crear imágenes circulares<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgoXe5QO_qxZanvC7-iI-VSUyjrw53vguEixOpPORZYFMtEUButavRaAA_y7QRJNWH3ITjeoBAeqJBZCMKpjSdOB9lrgmu7xnuG-SB2uuYtRk54RWJxn5iL-Te9K9DmfXm8xtu0Iis_WY/s1600/Windows-8.1-and-Windows-phone-8.1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgoXe5QO_qxZanvC7-iI-VSUyjrw53vguEixOpPORZYFMtEUButavRaAA_y7QRJNWH3ITjeoBAeqJBZCMKpjSdOB9lrgmu7xnuG-SB2uuYtRk54RWJxn5iL-Te9K9DmfXm8xtu0Iis_WY/s320/Windows-8.1-and-Windows-phone-8.1.png" /></a></div>
<br />
En el anterior artículo vimos <a href="http://buff.ly/1EJHK3x">cómo crear imagenes circulares en Android</a>, que esta bastante de moda en el diseño de aplicaciones web y móbiles, ahora vamos a ver como podemos realizar lo mismo en WinRT, la solución que vamos a ver aplica tanto para aplicaciones Windows Phone 8.1 como a Windows 8.1 .
<a name='more'></a>
<h3>Ejemplo</h3>
En WinRT a diferencia de Android podemos crear una imagen circular sin escribir ni una sola de linea de código C#, solo escribiendo Xaml.
<br/><br/>
Si quisieramos mostrar una imagen normal sin ningun tipo de redondeo utilizamos un control image asignando la imagen en la propiedad source:
<pre class="brush:xml">
<Page<br />
x:Class="CircularImageExample.MainPage"<br />
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br />
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br />
xmlns:local="using:CircularImageExample"<br />
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"<br />
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"<br />
mc:Ignorable="d"<br />
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><br />
<br />
<Grid><br />
<Image Height="300"<br />
Width="300"<br />
Source="images/image.jpg"<br />
VerticalAlignment="Center"<br />
HorizontalAlignment="Center"><br />
</Image><br />
</Grid><br />
</Page>
</pre>
<br/><br/>
El resultado sería este
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC2KVCLfS89KK24jxrXlPMX0ChogRufEjc0fdmkDj7dsUUR9HlxDr7wnRzWI5iH-6iHVsrk2Q7H-NmDmqAeKaXs5f-poiN5MoB92h51tY_LCCopEucjKwTFi6rC2BrlPdGqp7rGBLpxn8/s1600/Image.WP8.1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC2KVCLfS89KK24jxrXlPMX0ChogRufEjc0fdmkDj7dsUUR9HlxDr7wnRzWI5iH-6iHVsrk2Q7H-NmDmqAeKaXs5f-poiN5MoB92h51tY_LCCopEucjKwTFi6rC2BrlPdGqp7rGBLpxn8/s1600/Image.WP8.1.png" height="320" width="192" /></a></div>
<br />
<br/><br/>
Si quisieramos hacer la imagen circular, en lugar de utilizar una control Image, utilizamos un Ellipse y como propiedad Fill asignamos un ImageBrush con la imagen, si queremos que se vean bien tanto imagenes cuadradas como imagenes rectangulares en la propiedad Stretch debemos asignar el valor UniformToFill:
<pre class="brush:xml">
<Page<br />
x:Class="CircularImageExample.MainPage"<br />
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br />
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br />
xmlns:local="using:CircularImageExample"<br />
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"<br />
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"<br />
mc:Ignorable="d"<br />
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><br />
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"><br />
<Ellipse Height="300"<br />
Width="300" <br />
VerticalAlignment="Center"<br />
HorizontalAlignment="Center"><br />
<Ellipse.Fill ><br />
<ImageBrush ImageSource="images/image.jpg" Stretch="UniformToFill"/><br />
</Ellipse.Fill><br />
</Ellipse><br />
</Grid><br />
</Page>
</pre>
<br/><br/>
El resultado es este
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQvKJRsRwHHU-XbM2_C69agdcwB9UdOeFAt6gamCQXinvyW9YpGslRq4hG0XviXap7OFpomNwC9VwGi7bZOzvm53jb8TGj4f84jMmeiGvQ40EMi3Vm4qc1OUBTZcUvi8XhgE4ENRo-XSs/s1600/Circular-image.WP8.1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiQvKJRsRwHHU-XbM2_C69agdcwB9UdOeFAt6gamCQXinvyW9YpGslRq4hG0XviXap7OFpomNwC9VwGi7bZOzvm53jb8TGj4f84jMmeiGvQ40EMi3Vm4qc1OUBTZcUvi8XhgE4ENRo-XSs/s1600/Circular-image.WP8.1.png" height="320" width="192" /></a></div>
<h3>Resumen</h3>
Hemos visto lo sencillo que es crear imágenes circulares en WinRT para aplicaciones Windows Phone 8.1 y Windows 8.1 utilizando el control Ellipse, además no ha sido necesario escribir código C# como si ocurre en Android que es necesario para hacer lo mismo escribir código Java.
<h3>
Libros relacionados</h3>
Windows 8.1 Apps with XAML and C# Unleashed
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0672337088/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0672337088&linkCode=as2&tag=xurxodevelo09-20&linkId=VVWMCVKIE67C6CHX" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0672337088/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0672337088&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/1430240474/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1430240474&linkCode=as2&tag=xurxodevelope-21&linkId=DM6TPFUZ7TUBZ5WM" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br />
Pro Windows 8.1 Development with XAML and C#
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/1430240474/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1430240474&linkCode=as2&tag=xurxodevelo09-20&linkId=M4ELURWBVTFIBXDO" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/1430240474/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1430240474&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/1430240474/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1430240474&linkCode=as2&tag=xurxodevelope-21&linkId=2547UXASHKNG6ZVD" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-58370713188246980792015-04-16T06:23:00.000+02:002016-05-27T07:50:21.451+02:00Android Tip: Cómo crear imágenes circulares<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJwy17mRmHa-fdVWsEGiM0hDSzqJJJw_8H7YXlD_SuPuO_2WKCplPOxqWyCDDdUkKCFjMSbKS_TT6O0zRNvBiQBw60DSEgGTFwikmxKlPw9HfeBmX16NllWp8RWHhbCcgOatBqOZaUeW0/s1600/Android_Logo.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img alt="Android logo" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJwy17mRmHa-fdVWsEGiM0hDSzqJJJw_8H7YXlD_SuPuO_2WKCplPOxqWyCDDdUkKCFjMSbKS_TT6O0zRNvBiQBw60DSEgGTFwikmxKlPw9HfeBmX16NllWp8RWHhbCcgOatBqOZaUeW0/s1600/Android_Logo.png" title="Android logo" width="140"></a></div>
<br />
En el diseño de aplicaciones web y móbiles esta bastante del moda mostrar imágenes circulares, en Android antes era bastante tedioso poder realizar esta tarea, incluso éxisten librerías de terceros que básicamente suelen consistir en un control que hereda de ImageView, con un nombre tipo CircularImageView o RoundedImageView. Esto supone tener que arrastrar el control de una librería externa por todas los layouts que requieran una imagen circular y tener una dependencia tan fuerte de algo externo no es una de mis opciones favoritas.
<br/><br/>
En este artículo vamos a ver como podemos hacer esta tarea en Android ahora de una forma más sencilla y que viene soportado por el propio framework.
<a name='more'></a>
<h3>RoundedBitmapDrawable</h3>
En la versión 21 de la libreria support appcompat, que es la libreria utilizada en Android para dar soporte de nuevas funcionalidades del framework para versiones anteriores, existe una nueva clase que se llama RoundedBitmapDrawable, hereda de la clase Drawable y el trabajo de redondear los bordes de una imagen o hacerla circular completamente es bastante sencillo.
<h3>Ejemplo</h3>
En el activity tenemos un ImageView
<br/><br/>
<pre class="brush:xml">
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"<br />
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"<br />
android:paddingRight="@dimen/activity_horizontal_margin"<br />
android:paddingTop="@dimen/activity_vertical_margin"<br />
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"<br />
android:background="@android:color/black"><br />
<br />
<ImageView<br />
android:layout_width="300dp"<br />
android:layout_height="300dp"<br />
android:id="@+id/imageView"<br />
android:layout_centerInParent="true" /><br />
</RelativeLayout>
</pre>
<br/><br/>
Si no hicieramos la imagen circular el código seria el siguiente:
<br/><br/>
<pre class="brush:java">
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//obtenemos el drawable original
Drawable originalDrawable = getResources().getDrawable(R.drawable.image);
ImageView imageView = (ImageView) findViewById(R.id.imageView);
imageView.setImageDrawable(originalDrawable);
}
</pre>
<br/><br/>
El resultado sería este
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIcVuKv3GQIo8adBR7xNEUdlcV7XSVcl5vnX5_7aCXBOAcdET6kyZdcjgKp6kj17z5DA5A_I8F5t_89Vq08LJGdrmhR4eBwiHU7uMh6klMTybdX9OkM8TPC5V3Wc7xZAZJh64MwQ_Jj4c/s1600/Android-imagen.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIcVuKv3GQIo8adBR7xNEUdlcV7XSVcl5vnX5_7aCXBOAcdET6kyZdcjgKp6kj17z5DA5A_I8F5t_89Vq08LJGdrmhR4eBwiHU7uMh6klMTybdX9OkM8TPC5V3Wc7xZAZJh64MwQ_Jj4c/s320/Android-imagen.png" height="320" width="179"></a></div>
<br/><br/>
Si quisieramos hacer la imagen circular el código es el siguiente:
<br/><br/>
<pre class="brush:java">
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//extraemos el drawable en un bitmap
Drawable originalDrawable = getResources().getDrawable(R.drawable.image);
Bitmap originalBitmap = ((BitmapDrawable) originalDrawable).getBitmap();
//creamos el drawable redondeado
RoundedBitmapDrawable roundedDrawable =
RoundedBitmapDrawableFactory.create(getResources(), originalBitmap);
//asignamos el CornerRadius
roundedDrawable.setCornerRadius(originalBitmap.getHeight());
ImageView imageView = (ImageView) findViewById(R.id.imageView);
imageView.setImageDrawable(roundedDrawable);
}
</pre>
<br/><br/>
El resultado es este
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjobdy3cgI7tED3uMdhQCijKioZwPX2VwtC7NPy0YMjeP9uvmWHDfs5HwreL9mfWQ7yaP2_s1G70UndiF_QE7yE9S50CzGHfEaAw4DfVBpy2BMAPCb6tx0PfFWoTud2_lHtj7FERKxKB_M/s1600/Android-image-circular.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjobdy3cgI7tED3uMdhQCijKioZwPX2VwtC7NPy0YMjeP9uvmWHDfs5HwreL9mfWQ7yaP2_s1G70UndiF_QE7yE9S50CzGHfEaAw4DfVBpy2BMAPCb6tx0PfFWoTud2_lHtj7FERKxKB_M/s320/Android-image-circular.png"></a></div>
<h3>Resumen</h3>
Hemos visto lo sencillo que es crear imagenes circulares en Android utilizando la clase RoundedBitmapDrawable que existe en la librería support appcompat desde la versión 21.
<h3>Libros relacionados</h3>
Android Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321804333&linkCode=as2&tag=xurxodev-20&linkId=PSV4N3BJAXFZ75KS" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/0321804333/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321804333&linkCode=as2&tag=xurxodev0e-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/B00C893P8U/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=B00C893P8U&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>
Android Programming In a Day!: The Power Guide for Beginners In Android App Programming
<br>
<ul>
<li>
<a href="http://www.amazon.com/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=1507893744&linkCode=as2&tag=xurxodev-20&linkId=7TO3TQDCVZFCW26R" target="_blank">Amazon.com</a>
</li>
<li>
<a href="http://www.amazon.es/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=1507893744&linkCode=as2&tag=xurxodev0e-21" target="_blank">Amazon.es</a>
</li>
<li>
<a href="http://www.amazon.co.uk/gp/product/1507893744/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=1507893744&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.co.uk</a>
</li>
</ul>xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com13tag:blogger.com,1999:blog-6778264965952470158.post-87137438801694131972015-04-09T06:59:00.000+02:002015-04-09T06:59:26.796+02:00Enlaces interesantes sobre programación para iOS<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
En este artículo quiero cerrar la serie de artículos que han acompañado mi iniciación en el mundo iOS con una serie de recursos como libros y artículos de otros blogs en los que me he apoyado desde que inicie una aplicación en mi empresa sobre esta plataforma.
<a name='more'></a>
<h3>Tutoriales básicos</h3>
<ul>
<li>
<a href="http://codehero.co/series/objective-c-desde-cero/" target="_blank">http://codehero.co/series/objective-c-desde-cero/</a>
</li>
<li>
<a href="http://es.slideshare.net/MassimoOliviero/architecting-ios-project" target="_blank">http://es.slideshare.net/MassimoOliviero/architecting-ios-project</a>
</li>
<li>
<a href="https://www.bignerdranch.com/blog/property-values/" target="_blank">https://www.bignerdranch.com/blog/property-values</a>
</li>
<li>
<a href="http://nshipster.com/bool/" target="_blank">http://nshipster.com/bool/</a>
</li>
<li>
<a href="http://nshipster.com/equality" target="_blank">http://nshipster.com/equality</a>
</li>
<li>
<a href="http://nshipster.com/namespacing/" target="_blank"> http://nshipster.com/namespacing/</a>
</li>
</ul>
<h3>Certificates y provisional profiles</h3>
<ul>
<li>
<a href="http://www.cocoamental.com/2012/10/31/provisioning-profiles-developer-certificates-para-torpes" target="_blank">http://www.cocoamental.com/2012/10/31/provisioning-profiles-developer-certificates-para-torpes</a>
</li>
<li>
<a href="http://es.slideshare.net/agbotraining/omo-subir-una-app-al-app-storev1" target="_blank">http://es.slideshare.net/agbotraining/omo-subir-una-app-al-app-storev1</a>
</li>
</ul>
<h3>Buenas prácticas iOS</h3>
<ul>
<li>
<a href="https://github.com/futurice/ios-good-practices" target="_blank">https://github.com/futurice/ios-good-practices</a>
</li>
<li>
<a href="https://github.com/github/objective-c-style-guide" target="_blank">https://github.com/github/objective-c-style-guide</a>
</li>
<li>
<a href="http://www.alexruperez.com/ios-coding-best-practices" target="_blank">http://www.alexruperez.com/ios-coding-best-practices</a>
</li>
</ul>
<h3>Diseño</h3>
<ul>
<li>
<a href="http://iosdesign.ivomynttinen.com/" target="_blank">http://iosdesign.ivomynttinen.com/</a>
</li>
<li>
<a href="http://taybenlor.com/2013/05/21/designing-for-ios.html" target="_blank">http://taybenlor.com/2013/05/21/designing-for-ios.html</a>
</li>
<li>
<a href="https://designcode.io/xcode" target="_blank">https://designcode.io/xcode</a>
</li>
<li>
<a href="http://www.sitepoint.com/building-adaptive-user-interfaces-ios-8/" target="_blank">http://www.sitepoint.com/building-adaptive-user-interfaces-ios-8/</a>
</li>
<li>
<a href="http://mathewsanders.com/designing-adaptive-layouts-for-iphone-6-plus/" target="_blank">http://mathewsanders.com/designing-adaptive-layouts-for-iphone-6-plus</a>
</li>
<li>
<a href=" http://www.migueldiazrubio.com/2013/02/14/desarrollo-ios-manejando-textos-con-formato-utilizando-la-clase-nsattributedstring/" target="_blank"> http://www.migueldiazrubio.com/2013/02/14/desarrollo-ios-manejando-textos-con-formato-utilizando-la-clase-nsattributedstring/</a>
</li>
</ul>
<h3>Pragma mark</h3>
<ul>
<li>
<a href="http://www.zenbrains.com/blog/2010/04/todo-y-pragma-mark-en-xcode/" target="_blank">http://www.zenbrains.com/blog/2010/04/todo-y-pragma-mark-en-xcode</a>
</li>
<li>
<a href="http://nshipster.com/pragma" target="_blank">http://nshipster.com/pragma</a>
</li>
</ul>
<h3>Macros</h3>
<ul>
<li>
<a href="http://matt.coneybeare.me/my-favorite-macros-for-objective-c-development-in-xcode/" target="_blank">http://matt.coneybeare.me/my-favorite-macros-for-objective-c-development-in-xcode/</a>
</li>
</ul>
<h3>Cocopods</h3>
<ul>
<li>
<a href="http://www.raywenderlich.com/64546/introduction-to-cocoapods-2" target="_blank">http://www.raywenderlich.com/64546/introduction-to-cocoapods-2</a>
</li>
</ul>
<h3>Cliente Http</h3>
<ul>
<li>
<a href="http://www.migueldiazrubio.com/2013/10/21/desarrollo-ios-7-introduccion-afnetworking-2" target="_blank">http://www.migueldiazrubio.com/2013/10/21/desarrollo-ios-7-introduccion-afnetworking-2</a>
</li>
<li>
<a href="http://www.raywenderlich.com/59255/afnetworking-2-0-tutorial" target="_blank">http://www.raywenderlich.com/59255/afnetworking-2-0-tutorial</a>
</li>
</ul>
<h3>Errores y excepciones</h3>
<ul>
<li>
<a href="http://rypress.com/tutorials/objective-c/exceptions" target="_blank">http://rypress.com/tutorials/objective-c/exceptions</a>
</li>
<li>
<a href="http://nshipster.com/nserror/" target="_blank">http://nshipster.com/nserror/</a>
</li>
</ul>
<h3>Inyección de dependencias</h3>
<ul>
<li>
<a href="http://www.bignerdranch.com/blog/dependency-injection-ios/" target="_blank">http://www.bignerdranch.com/blog/dependency-injection-ios/</a>
</li>
</ul>
<h3>De C# a Objetive-C</h3>
<ul>
<li>
<a href="http://www.programering.com/a/MTM2IDMwATk.html" target="_blank">http://www.programering.com/a/MTM2IDMwATk.html</a>
</li>
</ul>
<h3>Categorías y extensiones</h3>
<ul>
<li>
<a href="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html" target="_blank">https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html</a>
</li>
<li>
<a href="http://benscheirman.com/2010/04/handy-categories-on-nsstring/" target="_blank">http://benscheirman.com/2010/04/handy-categories-on-nsstring/</a>
</li>
<li>
<a href="http://rypress.com/tutorials/objective-c/categories" target="_blank">http://rypress.com/tutorials/objective-c/categories</a>
</li>
</ul>
<h3>Comentarios para documentación</h3>
<ul>
<li>
<a href="http://blog.dadabeatnik.com/2013/09/25/comment-docs-in-xcode-5/" target="_blank">http://blog.dadabeatnik.com/2013/09/25/comment-docs-in-xcode-5/</a>
</li>
</ul>
<h3>Massive view controller</h3>
<ul>
<li>
<a href="http://khanlou.com/2014/09/8-patterns-to-help-you-destroy-massive-view-controller/" target="_blank">http://khanlou.com/2014/09/8-patterns-to-help-you-destroy-massive-view-controller</a>
</li>
<li>
<a href="http://doing-it-wrong.mikeweller.com/2013/06/ios-app-architecture-and-tdd-1.html" target="_blank">http://doing-it-wrong.mikeweller.com/2013/06/ios-app-architecture-and-tdd-1.html</a>
</li>
</ul>
<h3>Test unitarios</h3>
<ul>
<li>
<a href="http://blog.dadabeatnik.com/2014/07/13/asynchronous-unit-testing-in-xcode-6/" target="_blank">http://blog.dadabeatnik.com/2014/07/13/asynchronous-unit-testing-in-xcode-6/</a>
</li>
<li>
<a href="http://hackazach.net/code/2014/03/03/effective-testing-with-ocmock/" target="_blank">http://hackazach.net/code/2014/03/03/effective-testing-with-ocmock/</a>
</li>
</ul>
<h3>Libros</h3>
NSHipster: Obscure Topics in Cocoa & Objective C
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelo09-20&linkId=YRIEDK656XNDO26F" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0991218205&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelope-21&linkId=J2O7GUPUEBKBL7QS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
iOS Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
Objective-C Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-49012081064949932482015-03-26T07:05:00.001+01:002015-03-26T07:05:14.393+01:00Pruebas funcionales en iOS con Kif<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
Un tipo de test que podemos realizar a una aplicación iOS son pruebas de interfaz de usuario, al igual que en Android en iOS disponemos de librerías de terceros que nos ayudan en esta tarea. En este artículo vamos a ver cómo podemos realizar pruebas funcionales o de interfaz de usuario utilizando Kif.
<a name='more'></a>
<h3>Introducción a Kif</h3>
<a href="https://github.com/kif-framework/KIF" target="_blank">Kif</a> (Keep it Functional) es un framework opensource que permite realizar pruebas de interfaz de usuario sobre aplicaciones nativas en iOS. Se basa en las <a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html" target="_blank">APIs de Accesibilidad de iOS</a>, vamos a poder hacer test que simulan acciones del usuario como pulsar sobre las vistas, realizar gestos como swipe y escribir texto. Vamos a acceder a los controles a través de AccesibilityLabel, todos las vistas que aparecen en un ViewController como botones, TableView etc.. tienen un AccesibilityLabel que en caso de controles con contenido de texto como un label va a coincidir con el texto que contiene el control por defecto, pero por ejemplo para un TableView al no tener un contenido de texto vamos a tener que asignar un AccesibilityLabel, <a href="https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/iPhoneAccessibility/Making_Application_Accessible/Making_Application_Accessible.html" target="_blank">aquí</a> os dejo la guía de Apple.
<br/><br/>
El objeto que se utiliza en Kif como centralizador de todas las acciones simuladas es el objeto Tester.
<br/><br/>
Podemos añadir esta librería mediante Cocoapods y también de forma manual, yo para el ejemplo voy a utilizar Cocoapods porque me resulta más cómodo.
<br/><br/>
Para añadirlo mediante Cocoapods, esta es la configuración que deberemos añadir en el Podfile para el Target de pruebas donde queremos añadir la dependencia.
<br/><br/>
<pre>
target 'ExampleTargetTests', :exclusive => true do
pod 'KIF', '~> 3.0', :configurations => ['Debug']
end
</pre>
<h3>Accesibility Inspector</h3>
Si tenemos dudas de que accesibilityLabel tiene un control en ejecución existe una utilidad en iOS que es el inspector de accesibilidad se puede activar desde Settings > General > Accessibility, aparece un popup que nos indica información de cualquier vista que pulsemos, el primer dato es Label y ese será el accesibility label que tiene el control.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHSfEhJ8JY6Y5wxfsszbYW9HUDvLVg3fkdtAyg0OxBiJBLXcY2hP6gpS5eAeCr30TP8juBV_nKuax7-mBWleVWS_AyKHajtEk9WUBzBi8qYhYEfsA4LmA8jnqAwkhXNILYZl47MfwcDwA/s1600/iOS-Accesibility-Inspector.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHSfEhJ8JY6Y5wxfsszbYW9HUDvLVg3fkdtAyg0OxBiJBLXcY2hP6gpS5eAeCr30TP8juBV_nKuax7-mBWleVWS_AyKHajtEk9WUBzBi8qYhYEfsA4LmA8jnqAwkhXNILYZl47MfwcDwA/s1600/iOS-Accesibility-Inspector.png" height="320" width="179" /></a></div>
<br/><br/>
<h3>Ejemplo</h3>
La aplicación que voy a utilizar para el ejemplo de test de intefaz va a ser el ejemplo del <a href="http://xurxodeveloper.blogspot.com.es/2015/03/menu-de-navegacion-lateral-en-ios-utilizando-swrevealviewcontroller.html" target="_blank">artículo anterior</a> de un menú lateral para navegación. Vamos a crear un test que va a ir navegando por todos los view controllers desde el menú lateral.
<br/><br/>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5037FP8zuUuc8bmvO03b1Xp9pxVFFVE1JT-gJCRnBexT0MnOOSnoxM19Xa8ihHcNhWV8pkos-jKAGPG4dHSExh8rwTr9DS1twA4QB_JIRFuqO2JXvv8bjMVuws7HbAfS530eXK8NpNTI/s1600/Side-Menu-Example.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5037FP8zuUuc8bmvO03b1Xp9pxVFFVE1JT-gJCRnBexT0MnOOSnoxM19Xa8ihHcNhWV8pkos-jKAGPG4dHSExh8rwTr9DS1twA4QB_JIRFuqO2JXvv8bjMVuws7HbAfS530eXK8NpNTI/s1600/Side-Menu-Example.png" height="320" width="220"></a>
</div>
<br/><br/>
<h3>Accediendo a los menús mediante texto estático</h3>
Una opción que tenemos para pulsar sobre los menús de navegación es utilizar el método tapViewWithAccessibilityLabel del objeto tester. En este caso no es necesario que especifiquemos un AccesibilityLabel porque para los controles que intervienen en la prueba nos vale el que asume por defecto, para el botón de menú por defecto será el nombre de la imagen y para pulsar las filas del menu serán los textos que contiene. Cada vez que abrimos un ViewController tenemos verificar que se abre correctamente, para hacerlo utilizamos el método waitForViewWithAccessibilityLabel y buscamos por el título esperado para el ViewController. Este método tiene un timeout de 10 segundos y si en ese tiempo no encuentra una vista con el accesibilty label espeficificado el test fallará.
<br/><br/>
<pre class="">
- (void)testSideNavigationWithStaticText {
[tester tapViewWithAccessibilityLabel:@"Menu icon"];
[tester tapViewWithAccessibilityLabel:@"Red View Controller"];
[tester waitForViewWithAccessibilityLabel:@"Red View"];
[tester tapViewWithAccessibilityLabel:@"Menu icon"];
[tester tapViewWithAccessibilityLabel:@"Yellow View Controller"];
[tester waitForViewWithAccessibilityLabel:@"Yellow View"];
}
</pre>
<h3>Accediendo a los menús mediante índice de la fila</h3>
Otra opción que tenemos para pulsar sobre los menús de navegación es utilizar el método tapRowAtIndexPath:inTableViewWithAccessibilityIdentifier del objeto tester. En este caso necesitamos acceder al UItableView por el identificador y label de accesibilidad. Al no ser una vista accesible, porque las vistas accesibles sobre las que se pueden interactuar son las celdas, no se puede asignar label desde el interface builder (o yo no consigo como hacerlo) y he tenido que asignarselo desde código en el método viewDidLoad. En este método también utilizamos waitForViewWithAccessibilityLabel para esperar a que cargen los controladores de la parte central.
<br/><br/>
<pre class="">
//sideViewController.m
- (void)viewDidLoad{
self.tableView.accessibilityIdentifier = @"menuTableView";
self.tableView.accessibilityLabel =@"menuTableView";
}
//SideMenuExampleTests.m
- (void)testSideNavigationByRowIndex {
[tester tapViewWithAccessibilityLabel:@"Menu icon"];
[tester tapRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] inTableViewWithAccessibilityIdentifier:@"menuTableView"];
[tester waitForViewWithAccessibilityLabel:@"Red View"];
[tester tapViewWithAccessibilityLabel:@"Menu icon"];
[tester tapRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0] inTableViewWithAccessibilityIdentifier:@"menuTableView"];
[tester waitForViewWithAccessibilityLabel:@"Yellow View"];
}
</pre>
<h3>Accediendo a los menús dinámicamente</h3>
Si número de filas del menú se carga dinámicamente y queremos navegar por todos los menús, se puede utilizar el método waitForViewWithAccessibilityLabel para acceder tableView y preguntarle por el número de filas y mediante un bucle for, navegar por todos los menús utilizando el mismo método tapRowAtIndexPath:inTableViewWithAccessibilityIdentifier de la sección anterior y podemos utilizar el método waitForAnimationsToFinishWithTimeout para esperar hasta que termine de cargar la animación de mostrar el nuevo UIViewController.
<pre class="">
- (void)testSideNavigationDynamically {
[tester tapViewWithAccessibilityLabel:@"Menu icon"];
UITableView *menuTableView = (UITableView *)[tester waitForViewWithAccessibilityLabel:@"menuTableView"];
NSInteger numRows = [menuTableView numberOfRowsInSection:0];
for (NSInteger i = 0; i < numRows;i++) {
[tester tapRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0] inTableViewWithAccessibilityIdentifier:@"menuTableView"];
[tester waitForAnimationsToFinishWithTimeout:(2)];
[tester tapViewWithAccessibilityLabel:@"Menu icon"];
}
</pre>
<h3>Resumen</h3>
En este artículo hemos visto como podemos realizar test de interfaz de usuario en iOS utilizando Kif que se basa en las APIs de Accesibilidad para poder realizar gestos, pulsaciones y escribir texto.
<h3>Libros relacionados</h3>
NSHipster: Obscure Topics in Cocoa & Objective C
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelo09-20&linkId=YRIEDK656XNDO26F" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0991218205&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelope-21&linkId=J2O7GUPUEBKBL7QS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
iOS Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
Objective-C Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-76820208657973312462015-03-19T10:02:00.001+01:002015-03-19T10:10:56.810+01:00Menú de navegación lateral en iOS utilizando SWRevealViewController<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
Una característica habitual en la aplicaciones móviles es tener un menú de navegación lateral. La aplicación de Facebook fue la primera que mostró este tipo de menú. Vamos a ver en este artículo un ejemplo de cómo crear un menú lateral utilizando la librería SWRevealViewController.
<a name='more'></a>
<h3>Instalación de la libería </h3>
La libería consta de un fichero .h y otro .m con varias clases definidas en esos ficheros. Para instalar la librería podemos descargarla directamente de <a href="https://github.com/John-Lluch/SWRevealViewController">GitHub</a> y copiamos los ficheros SWRevealViewController.h y SWRevealViewController.m. También existe la opción de añadir la librería a nuestro proyecto mediante Cocoapods.
<h3>Ejemplo</h3>
Para explicar como funciona SWRevealViewController voy a hacer un ejemplo muy sencillo que va a constar de un menú de navegación con 3 menús y al selecionar cada uno de ellos se abre su correspondiente view controller frontal.
<br/><br/>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5037FP8zuUuc8bmvO03b1Xp9pxVFFVE1JT-gJCRnBexT0MnOOSnoxM19Xa8ihHcNhWV8pkos-jKAGPG4dHSExh8rwTr9DS1twA4QB_JIRFuqO2JXvv8bjMVuws7HbAfS530eXK8NpNTI/s1600/Side-Menu-Example.png" imageanchor="1"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5037FP8zuUuc8bmvO03b1Xp9pxVFFVE1JT-gJCRnBexT0MnOOSnoxM19Xa8ihHcNhWV8pkos-jKAGPG4dHSExh8rwTr9DS1twA4QB_JIRFuqO2JXvv8bjMVuws7HbAfS530eXK8NpNTI/s1600/Side-Menu-Example.png" height="320" width="220" /></a>
<br/><br/>
Voy a utilizar en este ejemplo xib y no StoryBoard.
<h3>creando ViewController para el menú lateral</h3>
Para el menú lateral creamos un ViewController que herede de UIViewController y en el xib añadimos un TableView, el controlador tiene que implementar el protocolo de UITableViewDelegate y UITableViewDataSource. Ahora desde el xib pulsando ctrl arrastramos desde el TableView a File's Owner y seleccionamos las opciones de delegate y dataSource para indicar al TableView que el controlador es su datasource y delegate.
<h4>Implementando el protocolo DataSource</h4>
Al indicar que el controlador es el origen de datos, el TableView nos va a pedir información como número de filas o celdas, estos son dos métodos que tenemos que implementar obligatoriamente para cumplir el protocolo DataSource.
<pre>
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
NSInteger row = indexPath.row;
if (nil == cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
NSString *text = nil;
if (row == 0)
{
text = @"Blue View Controller";
}
else if (row == 1)
{
text = @"Red View Controller";
}
else if (row == 2)
{
text = @"Yellow View Controller";
}
cell.textLabel.text = text;
return cell;
}
</pre>
<br/><br/>
En nuestro caso vamos a tener 3 filas a modo de menús para navegar a cada uno de nuestros controllers.
<h4>Implementando el protocolo Delegate</h4>
Como delegado de TableView no es obligatorio implementar nigún método pero en nuestro caso como queremos navegar a una pantalla al pulsar sobre un menú de navegación, necesitamos implementar el método didSelectRowAtIndexPath y como parámetro nos va a venir la fila de la tabla seleccionada.
<br/><br/>
<pre>
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Recuperamos el view controller contenedor reveal
SWRevealViewController *revealController = self.revealViewController;
// selecting row
NSInteger row = indexPath.row;
if ( row == _presentedRow )
{
[revealController setFrontViewPosition:FrontViewPositionLeft animated:YES];
}
else
{
UIViewController *newFrontController = nil;
if (row == 0){
BlueViewController *blue = [[BlueViewController alloc] init];
newFrontController = [[UINavigationController alloc] initWithRootViewController:blue];
}
else if (row == 1){
RedViewController *red = [[RedViewController alloc] init];
newFrontController = [[UINavigationController alloc] initWithRootViewController:red];
}
else if ( row == 2 )
{
YellowViewController *yellow = [[YellowViewController alloc] init];
newFrontController = [[UINavigationController alloc] initWithRootViewController:yellow];
}
[revealController pushFrontViewController:newFrontController animated:YES];
_presentedRow = row;
}
}
</pre>
<br/><br/>
Lo primero que necesitamos hacer es acceder a nuestro parent RevealViewController, esta propiedad es una categoría que añade la librería al tipo UIViewController, será sobre el que tenemos que hacer push del controller al que queremos navegar. La idea es la misma que cuando el parent de un controlador es un UINavigationCotroller.
<br/><br/>
En caso de que la fila seleccionada sea la misma que el controller frontal que ya está abierto, invocamos el método setFrontViewPosition para ocultar el menú lateral y en caso contrario navegamos al controller adecuado según el menú seleccionado.
<h3>creando los ViewControllers frontales</h3>
Los ViewControllers frontales que vamos a tener en este ejemplo son iguales y lo único que cambia es el color de fondo, que se asigna en el xib, y el título.
<br/><br/>
<pre>
#import "BlueViewController.h"
#import "SWRevealViewController.h"
@implementation BlueViewController
- (void)viewDidLoad{
[super viewDidLoad];
self.title = @"Blue View";
[self configureSideMenuButton];
}
- (void)configureSideMenuButton{
SWRevealViewController *revealController = [self revealViewController];
[self.view addGestureRecognizer:revealController.panGestureRecognizer];
UIBarButtonItem *revealButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"Menu-icon.png"]
style:UIBarButtonItemStylePlain target:revealController action:@selector(revealToggle:)];
self.navigationItem.leftBarButtonItem = revealButtonItem;
}
@end
</pre>
<br/><br/>
Básicamente lo único que hace este ViewController es asignar el título y crear un botón para el menú que asignaremos como botón izquierdo de la barra de navegación. Aquí lo importante es utilizar la propiedad revealViewController para acceder al parent y que como ya vimos al crear el menú lateral en la anterior sección esta propiedad es añadida mediante una categoría de la libreria SWRevealViewController. Por un lado configuramos el GestureRecognizer añadiendo el panGestrureRecognizer del parent y al botón le indicamos que su target es el parent revealViewController y el método revealToggle para que se invoque al pulsar el botón. Estas dos configuraciones va a hacer que al pulsar el botón o arrantrar el desde desde la parte izquierda aparezca nuestro menú lateral.
<h3>Creando el contenedor principal SWRevealControler</h3>
Una vez creados los ViewController frontales y el lateral, ahora necesitamos crear el SWRevealControler en el AppDelegate, este controlador va a ser el controlador principal que va a contener el menú lateral y el controlador frontal de inicio o por defecto. Pero no va a contenerlos directamente, como necesitamos navegar a cada uno de los controladores frontales desde el menú lateral necesitamos un UINavigationViewController como root de la parte lateral y también necesitamos un UINavigationViewController como root de la parte frontal porque necesitamos una barra de navegación donde poner nuestro botón menú para visualizar el menú lateral. Este UINavigationViewController además nos va a permitir navegar a otros controladores que no están contenidos dentro del SWRevealControler.
<pre class="">
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
SideViewController *sideMenuViewController = [[SideViewController alloc] init];
BlueViewController *blueViewController = [[BlueViewController alloc] init];
UINavigationController *frontNavigationController = [[UINavigationController alloc] initWithRootViewController:blueViewController];
UINavigationController *rearNavigationController = [[UINavigationController alloc] initWithRootViewController: sideMenuViewController];
SWRevealViewController *mainRevealController = [[SWRevealViewController alloc]
initWithRearViewController:rearNavigationController frontViewController:frontNavigationController];
self.window.rootViewController = mainRevealController;
[self.window makeKeyAndVisible];
return YES;
}
</pre>
<br/>
<br/>
Código fuente disponible en <a href="https://github.com/xurxodeveloper/iOSSideMenu">GitHub</a>.
<h3>Resumen</h3>
En este artículo hemos visto como crear un menú de navegación lateral utilizando la librería SWRevealViewController que nos simplifica bastane la creación de este tipo de menú.
<h3>Libros relacionados</h3>
NSHipster: Obscure Topics in Cocoa & Objective C
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelo09-20&linkId=YRIEDK656XNDO26F" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0991218205&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelope-21&linkId=J2O7GUPUEBKBL7QS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
iOS Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
Objective-C Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-33372849933312258852015-03-05T07:02:00.000+01:002015-03-19T10:11:08.325+01:00Errores y excepciones en iOS<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
Las excepciones es el método común en C# y Java para gestionar los errores, sin emargo en iOS existen además del concepto de excepción (NSException) el concepto de error (NSError) y cada uno tiene un proposito y una utilidad diferente. En este artículo vamos a ver cuando recomienda Apple utilizar cada tipo de error.
<a name='more'></a>
<h3>Programador vs Usuario</h3>
Si quien provoca el un error es un programador o un usuario es la clave para identificar si se debe utilizar un NSError o un NSException.
<br/><br/>
Exepciones son errores de programación y este tipo de errores ocasionarían un crash en la aplicación y son anormales. Por ejemplo intentar acceder a un índice de un array que no existe, pero también provocaria una excepción crear un NSURL con algo que no es una url. En nuestras clases nosotros marcamos nuestras reglas de negocio o invariantes de forma que si tenemos una clase que recibe por parámetro un servicio al que debe llamar y es imprescidible que que no nos lo pasen a nil, deberíamos verificarlo y si nos lo han pasado a nil generamos una excepción.
<br/><br/>
Las exceptiones lo ideal es que sean detectadas y solucionadas en fase de desarrollo mediante test unitarios o en fase de pruebas y no debría ser una situación que deba pasar en producción.
<br/><br/>
Los errores sin embargo son errores originados por el usuario y son situaciones que entra dentro de lo normal que ocurran en producción. Un ejemplo claro es intentar acceder a un servicio externo en un momento donde no tenemos conexión a internet. Estos errores deben ser manejados y debemos informar al usuario de ellos. Este tipo de errores no debe ocasionar un crash de la aplicación porque son esperados.
<h3>Excepciones</h3>
Las excepciones estan representadas por la clase NSException, según esta diseñada esta clase, la idea es que sirva de forma estándar y no necesitemos realizar una clase derivada, pero es algo que vamos a poder hacer sin ningún problema. Una exception se compone de las siguientes propiedades:
<ul>
<li>name, string corto para identificar la excepción.</li>
<li>reason, string largo con texto amigable para identificar la exceptión.</li>
<li>userInfo, diccionario opcional y es usado para indicar información extra sobre la excepción.</li>
</ul>
<h4>Manejando exceptiones</h4>
Para manejar exceptiones en Objective-C existen las directivas de compilación @try @catch @finally .
<br/><br/>
<pre class="">
@try {
// código normal que puede general excepción
}
@catch (NSException* e) {
// manejo de excepción o relanzamiento de la misma
}
@finally {
// bloque finally
}
</pre>
<h4>Excepciones predefinidas</h4>
Cocoa tiene excepciones predefinidas, pero no son clases derivadas de NSException, como hemos visto NSException esta diseñada de forma que se pueda utilizar de forma genérica para todos los casos y lo que las exceptiones predefinidas representan son el name de una clase NSException. Por ejemplo una exceptión predefinida que tiene Cocoa es NSInvalidArgumentException y es una constante almacenada en NSException.h. <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Exceptions/Concepts/PredefinedExceptions.html#//apple_ref/doc/uid/20000057-BCIGHECA" target="_blank">Aquí</a> os dejo un enlace de apple con las excepciones predefinidas.
<br/><br/>
De esta forma cuando estamos capturando la excepción podemos utilizar los nombres de excepción predefinidos para realizar cierta lógica en función de la excepción que se ha producido.
<br/><br/>
<pre class="">
...
} @catch(NSException *exception) {
if (exception.name == NSInvalidArgumentException) {
NSLog(@"Caught an NSInvalidArgumentException");
} else {
NSLog(@"Ignored a %@ exception", theException);
@throw;
}
}
</pre>
<h4>Generando excepciones</h4>
En Objetive-C también podemos generar excepciones o relanzar excepciones que se han producido. Para relanzar una excepción se utiliza la instrucción @throw.
<br/><br/>
Para generar una nueva excepción, lo mas habitual es utilizar el método de factoría exceptionWithName.
<br>/<br/>
<pre class="">
NSException *e = [NSException
exceptionWithName:NSInvalidArgumentException
reason:@"value is required"
userInfo:nil];
@throw e;
</pre>
<h3>Errores</h3>
Los errores como ya hemos visto en la introducción son errores esperados y normales en producción, son originados por el usuario.
Los errores están representadas por la clase NSError, esta clase esta diseñada para que sirva de forma estándar como las Excepciones y no necesitemos realizar una clase derivada. Un error se compone de las siguientes propiedades:
<ul>
<li>domain, string para identificar el error en una jerarquia. Es habitual utilizar la notación de dominio inverso junto con el nombre del proyecto tipo com.xurxo.example.DomainError.</li>
<li>code, integer númerico para identificar el error, lo normal es que este código de error sea único dentro de un dominio.</li>
<li>userInfo, diccionario opcional y es usado para indicar información extra sobre el error.</li>
</ul>
userInfo para errores es habitual que contenga más información que una excepción. Existen unas claves predefinidas para userInfo:
<ul>
<li>NSLocalizedDescriptionKey, string que representa la descripción del error.</li>
<li>NSLocalizedFailureReasonErrorKey, string que representa la razón del error.</li>
<li>NSLocalizedRecoverySuggestionErrorKey, string que informa al usuario de que forma puede recuperarse del error.</li>
<li>NSUnderlyingErrorKey, error origen en un subsistema, a veces puede ser interesante para extraer más información</li>
</ul>
<h4>Manejando errores</h4>
Los métodos que son susceptibles de generar un error, normalmente adeptan un parámetro por referencia indirecta de tipo NSError. Si se produce error la respuesta será NO o nil y el objeto error si se lo hemos pasado vendrá con la información del error.
<br/><br/>
El Error que pasamos por referencia indirecta es un puntero a un puntero, el método parámetro del método se define con la notación de doble puntero NSError ** y se le asigna valor con el <a href="http://rypress.com/tutorials/objective-c/c-basics#pointers" target="_blank">operador de referencia</a>.
<pre class="">
// main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *fileToLoad = @"/path/to/non-existent-file.txt";
NSError *error;
NSString *content = [NSString stringWithContentsOfFile:fileToLoad
encoding:NSUTF8StringEncoding
error:&error];
if (content == nil) {
// Method failed
NSLog(@"Error loading file %@!", fileToLoad);
NSLog(@"Domain: %@", error.domain);
NSLog(@"Error Code: %ld", error.code);
NSLog(@"Description: %@", [error localizedDescription]);
NSLog(@"Reason: %@", [error localizedFailureReason]);
} else {
// Method succeeded
NSLog(@"Content loaded!");
NSLog(@"%@", content);
}
}
return 0;
}
</pre>
<h4>Errores predefinidos</h4>
Al igual que con las excepciones existen dominios y códigos de error predefinidos en Cocoa, estan definidos en la cabecera NSError.h, podemos verlos <a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorObjectsDomains/ErrorObjectsDomains.html#//apple_ref/doc/uid/TP40001806-CH202-CJBGAIBJ" target="_blank">aquí</a>.
<br/><br/>
De esta forma cuando estamos verificando un error podemos utilizar los nombres de dominio y código predefinidos para realizar cierta lógica en función del error que se ha producido.
<br/><br/>
<pre class="">
...
if (content == nil) {
if ([error.domain isEqualToString:@"NSCocoaErrorDomain"] &&
error.code == NSFileReadNoSuchFileError) {
NSLog(@"That file doesn't exist!");
NSLog(@"Path: %@", [[error userInfo] objectForKey:NSFilePathErrorKey]);
} else {
NSLog(@"Some other kind of read occurred");
}
} ...
</pre>
<h4>Generando errores</h4>
Para generar errores propios una buena práctica es tener una cabecera (.h) donde tenemos definido el dominio y un enumerado con los códigos de error.
<br/><br/>
<pre class="">
// XurxoErrors.h
NSString *XurxoErrorDomain = @"com.xurxo.example.ErrorDomain";
enum {
Example1Error,
Example2Error,
};
</pre>
Y entonces para generar un error utilizaremos el método de factoría errorWithDomain y necesitamos el dominio y código que ya hemos definido en la cabecera errors más un diccionario userInfo donde utlizaremos la clave predefinida NSLocalizedDescriptionKey para indicar la descripción del error.
<br/><br/>
<pre class="">
NSString *exampleMethod(NSArray *array, NSError **error) {
int maximum = (int)[array count];
if (maximum == 0) {
if (error != NULL) {
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: @"Could not"
" select a item because there are no items in the array."};
*error = [NSError errorWithDomain:XurxoErrorDomain
code:Example1Error
userInfo:userInfo];
}
return nil;
}
int randomIndex = arc4random_uniform(maximum);
return array[randomIndex];
}
</pre>
<h3>Resumen</h3>
En este artículo hemos visto los dos conceptos de error que existen en iOS, NSError y NSException. También cual es la recomendación de Apple en el uso de cada uno de ellos, donde NSError está más enfocado a errores que es posible que puedan ocurrir en producción y NSException a errores de programación.
<h3>Libros relacionados</h3>
NSHipster: Obscure Topics in Cocoa & Objective C
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelo09-20&linkId=YRIEDK656XNDO26F" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0991218205&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0991218205/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0991218205&linkCode=as2&tag=xurxodevelope-21&linkId=J2O7GUPUEBKBL7QS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
iOS Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
Objective-C Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-10560776841282599772015-02-26T07:00:00.000+01:002015-03-19T10:11:48.437+01:00Pruebas unitarias en iOS con OCMock<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
Ya había escrito un artículo sobre <a href="http://xurxodeveloper.blogspot.com.es/2013/11/pruebas-unitarias-con-moq.html" target="_blank">pruebas unitarias con moq</a> para .Net y <a href="http://xurxodeveloper.blogspot.com.es/2014/11/pruebas-unitarias-en-android-con-mockito.html" target="_blank">pruebas unitarias con Mockito</a> para Android, ahora vamos a ver como podemos hacer pruebas unitarias con mocks en iOS utilizando <a href="http://ocmock.org/">OCMock</a>, que es una de las liberías que existen para iOS para crear mocks. Existen otras como la versión <a href="https://github.com/jonreid/OCMockito">Mockito</a> para iOS, pero OCMock lleva bastante más tiempo.
<br />
<a name='more'></a><h3>
Introducción a OCmock</h3>
<a href="http://ocmock.org/">OCMock</a> es un libería que nos va a permitir crear objetos dobles, pudiendo así realizar pruebas unitarias de objetos con dependencias de forma clara y simple.
<br />
<br />
Podemos añadir OCMock como una libería estática descargandola de <a href="https://www.blogger.com/%3Ca%20href=http://ocmock.org/download/">OCMock</a>"la página de descargas o mediante cocoapods.
<br />
<h3>
Ejemplo</h3>
Vamos a realizar el mismo ejemplo que habíamos visto en los anteriores artículos para .Net y Android para ver que los conceptos se aplican de la misma forma en todas las plataformas.
<br />
<br />
Vamos a tener un BlogService con un método publishPost y va a tener dos dependencías que se le inyectan en el inicializador, un Logger y un HtmlValidator. En caso de validar correctamente el html a publicar, escribe en el log y devuelve true, si el html es incorrecto escribe en el log el error y devuelve un NSError.
<br />
<br />
Veamos los componentes
<br />
<br />
<pre class="">//Protocol dependencies
@protocol XURLogger
-(void) logWithMessage(NSString *)message;
@end
@protocol XURHtmlValidator
-(BOOL) isValidHtml(NSString *)html;
@end
//Class with protocol dependencies
@interface XURBlogService
-(instanceType) initWithLogger(NSObject<Logger> *)logger htmlValidator(NSObject<HtmlValidator> *) htmlValidator;
-(BOOL) publishPostWithHtml:(NSString *)html error(NSError **)error ;
@end
@implementation BlogService
-(instanceType) initWithLogger(NSObject<Logger> *)logger htmlValidator(NSObject<HtmlValidator> *) htmlValidator{
if (self = [super init]) {
_logger = logger;
_htmlValidator = htmlValidator;
}
return self;
}
-(BOOL) publishPostWithThml(NSString *) html error(NSError **)error{
if ([htmlValidator isValidHtml:html]){
NSLog(@"Post has published");
return YES;
}
else{
if (error != nil){
NSMutableDictionary* details = [NSMutableDictionary dictionary];
[details setValue:@"invalid html" forKey:NSLocalizedDescriptionKey];
*error = [NSError errorWithDomain:@"com.xurxo.exampledomain" code:200 userInfo:details];
}
return NO;
}
}
@end</pre>
<h4>
Testeando con un Dummy</h4>
Una primera prueba unitaria es simplemente crearnos las dependencias sin implementación, a modo de objetos dummy para verificar que no hay ningún problema al crear el servicio de blog.
<br />
<br />
<pre class="">- (void)testBlogServiceTest {
id loggerDummy = OCMProtocolMock(@protocol(XURLogger));
id htmlValidatorDummy = OCMProtocolMock(@protocol(XURHtmlValidator));
XURBlogService *blog = [[XURBlogService alloc] initWithLogger:loggerDummy htmlValidator:htmlValidatorDummy]];
XCTAssertNotNil(blog);
}
</pre>
<h4>
Testeando con un Stub</h4>
La siguiente prueba unitaria es comprobar que cuando le pasamos un html incorrecto, el servicio de blog nos devuelve un NSError. Preparamos un stub para el protocolo HtmlValidator que siempre devuelva false en el método isValid y en el test verificamos que nos devuelve un NSError en este caso.
<br />
<br />
<pre class="">- (void)testPublishPostShouldCreateErrorForNotValidHtml {
id loggerDummy = OCMProtocolMock(@protocol(XURLogger));
id htmlValidatorStub = OCMProtocolMock(@protocol(XURHtmlValidator));
OCMStub([htmlValidatorStub isValidHtml:[OCMArg any]]).andReturn(NO);
XURBlogService *blog = [[XURBlogService alloc] initWithLogger:loggerDummy htmlValidator:htmlValidatorStub]];
NSError *error;
BOOL success = [XURBlogService publishPostWithHtml:@"invalid html text" error:&error];
XCTAssertFalse(success);
XCTAssertNotNil(error);
XCTAssertEqualObjects([error localizedDescription], @"invalid html", @"Not expected localized description error");
}
</pre>
<h4>
Testeando con un Mock</h4>
En la siguiente prueba vamos a verificar el caso contrario, que todo va bien y no se produce ninguna excepción, además vamos a verificar que servicio de blog realiza una llamada a nuestro objeto doble HtmlValidator, lo que lo convierte en un mock.
<br />
<br />
<pre class=""> - (void)testPublishPostShouldWork {
id loggerDummy = OCMProtocolMock(@protocol(XURLogger));
id htmlValidatorMock = OCMProtocolMock(@protocol(XURHtmlValidator));
OCMStub([htmlValidatorMock isValidHtml:[OCMArg any]]).andReturn(YES);
XURBlogService *blog = [[XURBlogService alloc] initWithLogger:loggerDummy htmlValidator:htmlValidatorStub]];
NSError *error;
BOOL success = [XURBlogService publishPostWithHtml:@"valid html text" error:&error];
XCTAssertTrue(success);
XCTAssertNil(error);
OCMVerify([htmlValidatorMock isValidHtml:[OCMArg any]]);
}
</pre>
<br />
<br />
En el método verify estamos verificando que se ha invocado la función isValid y con OCMArg any indicamos que el parámero da igual para la verificación, puede ser cualquier valor. Si hubieramos especificado un html concreto lo que estariamos verificando es que se haya llamado a la función isValid con un parámetro concreto.
<br />
<h3>
Libros relacionados</h3>
Test-Driven iOS Development (Developer's Library)
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321774183/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321774183&linkCode=as2&tag=xurxodevelo09-20&linkId=3U6QZXGKVBR4HGQ5" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321774183/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321774183&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/B007RNK0W6/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=B007RNK0W6&linkCode=as2&tag=xurxodevelope-21&linkId=KW4JGH4LT7K3IBGB" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br />
iOS Programming: The Big Nerd Ranch Guide
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br />
Objective-C Programming: The Big Nerd Ranch Guide
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-61626358507126824892015-02-19T06:55:00.000+01:002015-03-19T10:12:00.802+01:00iOS para desarrolladores .NET: categorías y extensiones<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
Una vez que ya hemos visto como se definen las clases en Objetive-C en anteriores artículos, en este artículo vamos a ver de que forma podemos añadir o extender clases existentes y vamos a compararlo con C# donde también se pueden extender clases.
<a name='more'></a>
<h3>Categorias</h3>
Una categoría permite extender una clases existente, puede ser una clase nuestra y tendremos el código fuente o puede ser una clase de la que no tenemos el código fuente.
<br/><br/>
Por ejemplo a la clase de Cocoa NSString podemos añadirle el típico método isNullOrWhiteSpace que tiene string en C#, mediante una categoría podríamos añadirle este método. También es posible conseguirlo mediante la herencia, creándonos una clase que herede de NSString, pero si queremos que el método que añadimos también lo tengan disponible las clases derivadas como NSMutableString, utilizar una categoría es el camino adecuado.
<h4>C#</h4>
En C# el concepto más parecido a Categoría son las extensiones. Por ejemplo Linq hace uso de extensiones para añadir funcionalidades de filtro a las colecciones. Vamos a ver un ejemplo de una clase Person y le añadimos una extension de una función fullName.
<pre class="brush:c#">
public static class PersonExtension {
public static string GetFullName(this Person p) {
return string.Format("%1 %2", p.FirstName, p.LastName);
}
}
</pre>
<br/><br/>
Añadiendo el using del namespace donde esté la clase extension, podemos invocar la función GetFullName sobre un objeto person.
<br/><br/>
<pre class="brush:c#">
Person person = new Person ("Jorge","Sánchez Fernández");
Debug.WriteLine(person.GetFullName());
</pre>
<h4>Objetive-C</h4>
En Objetive-C para crearnos una categoría, el nombre de la clase debe ser nombreclaseaextender (nombrecategoria), Xcode crea el nombre de fichero como nombreclaseeextender+nombrecategoria.h y nombreclaseeextender+nombrecategoria.h.
<br/><br/>
Apple recomienda que la clase a extender y la categoría lleven un prefijo para evitar la situación de tener por ejemplo una categoría que sea NSSring(isEmpty) y que una nueva versión de Foundation por ejemplo traiga justo esa funcionalidad, entonces al existir dos categorias iguales el comportamiento puede ser inexperado. Si la clase a extender también es nuestra también debería ir prefijada.
<br/><br/>
<pre class="">
XURPerson+XURFullNameExtension.h
#import "Person.h"
@interface XURPerson (XURPersonAditions)
-(void)getFullname;
@end
XURPerson+XURFullNameExtension.m
@implementation Person (XURPersonAditions)
-(void)getFullname
{
return [[NSString alloc] initWithFormat:@"%@ %@", self.firstName, self.lastName];
}
@end
</pre>
<br/><br/>
Añadiendo el import de la categoría, podemos invocar la función getFullName sobre un objeto person.
<br/><br/>
<pre class="">
#import "XURPerson+XURFullNameExtension.h"
.
.
.
Person *person = [[Person alloc] initWithFisrtName:@"Jorge" lastName:@"Sánchez"];
NSLog([person getFullName]);
</pre>
<h3>Extensiones</h3>
Otro mecanismo para extender una clase en Objetive-C son las extensiones, para este método de extensión es necesario tener el código fuente de la clase que queremos extender porque se realiza añadiendo código en el fichero .m.
<br/><br/>
En C# no hay nada parecido ya que el uso de extensiones en Objetive-C tiene como proposito encapsular en la implementación de una clase ciertas métodos o propiedadades de la misma, es decir, todo lo que indicamos dentro de una extensión va a ser privado para la implementación de la clase. En C# existen los modificadores de acceso de miembros para variables, métodos y propiedades, de esta forma se oculta hacia fuera de la clase estas definiciones.
<br/><br/>
Para definir una extensión tenemos que añadir un @interface dentro del fichero .m, seguido del nombre de la clase y unos paréntesis, y dentro de esta sección interface en el fochero .m será lo que se entiende como extensión.
<br/><br/>
<pre class="">
//Fichero claseEjemplo.m
@interface NombreClase()
@property (strong, nonanatomic) NSString *propiedadPrivada;
-(void) metodoPrivado;
@end
@implementation NombreClase
@end
</pre>
<br/><br/>
Un uso, que según he visto, es bastante común, es denifir una propiedad de solo lectura en la cabecera y definir la misma propiedad de lectura y escritura en la extensión de la clase para utilizarla en modo escritura desde la implementación y exponerla en modo solo lectura a los clientes.
<br/><br/>
<pre class="">
//Fichero Person.h
@interface Person
@property (strong, nonanatomic,readonly) NSString *firstName;
@end
//Fichero Person.m
@interface Person()
@property (strong, nonanatomic) NSString *firstName;
@end
@implementation Person
@end
</pre>
<br/><br/>
Esto es algo que me choca bastante respecto a C# porque ya que @property crea una variable de instancia se puede utilizar dicha variable siempre desde la implementación, pero bueno he visto que es bastante habitual esta práctica en Objetive-C.
<h3>Resumen</h3>
En este artículo hemos los dos métodos de extender una clase en Objetive C, categorías donde podemos extender clases de las que no tenemos el código fuente y es similar a las extensiones de C# y por otra parte las extensiones propias de Objetive-C donde es necesario tener el código fuente y el objeto es poner en la extensión aquello que queremos que sea privado a la implementación de la clase.
<h3>Libros relacionados</h3>
iOs Programming for .Net Developers
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0985784512&linkCode=as2&tag=xurxodevelo09-20&linkId=5QCTTWL3DMZQY5GZ" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0985784512&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0985784512&linkCode=as2&tag=xurxodevelope-21&linkId=CFCSJ2TSVHAMOW55" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
iOS Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
Objective-C Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-58609547744561059572015-02-12T07:08:00.000+01:002015-03-19T10:12:11.192+01:00iOS para desarrolladores .NET: herencia y protocolos<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
Una vez que ya hemos visto como se definen las clases en Objetive-C en anteriores artículos, en este artículo vamos a ver de que forma podemos realizar herencia y abstraer clases mediante protocolos, que es un concepto similar a interfaces en C#.
<br />
<a name='more'></a><h3>
Herencia</h3>
En Objetive C al igual que en C# podemos realizar herencia simple, vamos a poder crear una clase que herede de otra y así poder reutilizar código. Todos los objetos como mínimo heredan de NSObject.
<br />
<h4>
C#</h4>
En C# para indicar que heredamos de una clase se realiza poniendo dos puntos despues del nombre de clase y a continuación la clase base de la que se hereda.
<br />
<br />
<pre class="brush:c#">public class Person
{
}
public class Employee: Person
{
}
</pre>
<br />
<br />
Si la clase base tiene un constructor con parámetros cuando creamos un objeto de la clase derivada, esta debe invocarlo desde su constructor.
<br />
<br />
<pre class="brush:c#">public class Person
{
public string FirstName {get; private set;}
public string LastNameName {get; private set;}
public Person (string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
public class Employee: Person
{
public double Salary {get; private set;}
public Person (string firstName, string lastName,double salary):base(firstName, lastName)
{
Salary = salary;
}
}
</pre>
<h4>
Objetive-C</h4>
En Objetive-C para indicar que heredamos de una clase base se realiza en el fichero de cabecera, se ponen dos puntos y el nombre de la clase base a continuación del nombre de la clase derivada.
<br />
<br />
<pre class="">XURPerson.h
@interface XURPerson:NSObject
@end
XURPerson.m
@implementation XURPerson
@end
XUREmployee.h
@interface XUREmployee:XURPerson
@end
XUREmployee.m
@implementation XUREmployee
@end
</pre>
<br />
<br />
En objetive C recordad que no tenemos <a href="http://xurxodeveloper.blogspot.com.es/2015/01/ios-para-desarrolladores-net-clases-en-objetive-c-parte-2.html" target="_blank"> constructores si no inicializadores</a>, y además los inicializadores se heredan por lo tanto si defino un constructor en la clase base, puedo utilizarlo para crear una instancia de la clase derivada.
<br />
<br />
Siguiendo el mismo ejemplo que hemos visto para C#, en Objetive C tenemos que invocar el inicializador designado de la clase base desde el inicializador designado de la clase derivada mediante la palabra clave super.
<br />
<br />
<pre class="">//XURPerson.h
@interface XURPerson:NSObject
@property (strong,nonatomic,readonly) NSString *fisrtName;
@property (strong,nonatomic,readonly) NSString *lastName;
-(instancetype) initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
@end
//XURPerson.m
@implementation XURPerson
-(instancetype) initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName{
self = [super init];
if (self) {
_fisrtName = fisrtName;
_lastName = lastName;
}
return self;
}
@end
//XUREmployee.h
@interface XUREmployee:XURPerson
@property (nonatomic,readonly) double salary;
-(instancetype) initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName salary:(double)salary;
@end
//XUREmployee.m
@implementation XUREmployee
-(instancetype) initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName salary:(double)salary{
self = [super initWithFirstName:firstName lastName:lastName];
if (self) {
_salary = salary;
}
return self;
}
@end
</pre>
<h3>
Protocolos</h3>
Así como en C# o Java tenemos interfaces como mecanismo de abstracción y de contrato que deben cumplir las clases que lo implementan, en Objetive-C el concepto que tenemos para el mismo objetivo son los protocolos.
<br />
<h4>
C#</h4>
En C# se define un interface solo con la firma del método y en la clase se indica que una clase implementa un interface en el mismo lugar donde podemos indicar la clase de la que se hereda. Si se hereda de una clase y se implementa un interfacea la vez, primero se pone la clase base y luego el interface separado por una coma.
<br />
<br />
<pre class="brush:c#">public interface IEntity
{
}
public class Person
{
}
public class Employee: Person , IEntity
{
}
</pre>
<h4>
Objetive-C</h4>
En Objetive-C se definen los protocolos solo con la firma como con las cabeceras y se definen en un fichero .h, y hay que indicar que cumple el protocolo NSObject, aparte de tener NSObject como la clase más básica de la que hay que heredar, también existe un protocolo NSObject. Para indicar que una clase cumple un protocolo se pone entre corchetes angulares.
<br />
<br />
<pre class="">
//XUREntity
@protocol XUREntity<NSObject>{
}
//XURPerson.h
@interface XURPerson:NSObject
@end
//XURPerson.m
@implementation XURPerson
@end
//XUREmployee.h
@interface XUREmployee:XURPerson<XUREntity>
@end
//XUREmployee.m
@implementation XUREmployee
@end
</pre>
<br />
<br />
Como principal diferencia entre interfaces de C# y protocolos de Objetive C, es que por defecto todo lo que se define en el protocolo es obligatorio pero podemos indicar mediante @optional métodos que son opcionales y no es obligatorio implementarlos. Si ponemos @optional todos los métodos hasta el final del protocolo o hasta un @required van a ser opcionales.
<br />
<br />
<pre class="">@protocol MyProtocol
- (void)requiredMethod;
@optional
- (void)anOptionalMethod;
- (void)anotherOptionalMethod;
@required
- (void)anotherRequiredMethod;
@end
</pre>
<br />
<br />
En tiempo de compilación podemos verificar si la clase implementa un método y deberiamos utilizarlo para los métodos que el protocolo indica que son opcionales.
<br />
<br />
<pre class="">NSString *thisSegmentTitle;
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
}
</pre>
<h3>
Resumen</h3>
En este artículos hemos comparado como se realiza en C# y ObjetiveC dos de los pilares funcamentales de la programación orientada a objetos como son la herencia y como definir un contrato que la clase debe cumplir, esta abstracción en C# se consigue con interfaces y en Objetive-C con protocolos. La idea es parecida pero tienen alguna diferencia como lo que hemos visto que en los protocolos se pueden definir métodos opcionales.
<br />
<h3>
Libros relacionados</h3>
iOS Programming for .Net Developers
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0985784512&linkCode=as2&tag=xurxodevelo09-20&linkId=5QCTTWL3DMZQY5GZ" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0985784512&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0985784512&linkCode=as2&tag=xurxodevelope-21&linkId=CFCSJ2TSVHAMOW55" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br />
iOS Programming: The Big Nerd Ranch Guide
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br />
Objective-C Programming: The Big Nerd Ranch Guide
<br />
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>
xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0tag:blogger.com,1999:blog-6778264965952470158.post-59822506424153962572015-02-05T08:11:00.000+01:002015-03-19T10:12:20.399+01:00iOS para desarrolladores .NET: Clases en Objetive-C parte 3<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIRNWNjQsXtCsjlR6GxI8FzlwNkOKraD0Zunh3tVWRPi5SGlfWcv4nUYWSerQ5-nWinxr4mYdl6ASyWlQVPhbQwR_iACd42U-g1UPTwarvmcSvD4B2bb4Sn3j3H5eW3K_C4fL8jhIfOf8/s1600/IOS-Xcode.png" height="90" width="200" /></a></div>
<br />
En el anterior <a href="http://xurxodeveloper.blogspot.com.es/2015/01/ios-para-desarrolladores-net-clases-en-objetive-c-parte-2.html">artículo</a> vimos como se definen los inicilizadores en una clase y los tipo de inicilizadores que existen.
<br/><br/>
En este artículo vamos a ver los métodos, como se definen y que tipo de métodos tenemos.
<a name='more'></a>
<h3>Métodos de clase</h3>
Métodos de clase son métodos que no necesitan de una instancia de objeto para poder invocarse, el concepto es igual a los métodos estáticos de C#.
<h4>C#</h4>
En C# existe el modificador static para indica que un método es de clase.
<br/><br/>
<pre class="brush:c#">
public class Builder
{
public static Product BuildProduct (string name)
{
return new Product(name);
}
}
</pre>
<br/><br/>
Y se invoca directamente desde la clase.
<br/><br/>
<pre class="brush:c#">
Product product = Builder.BuildProduct("ejemplo ejemplo");
</pre>
<h4>Objetive-C</h4>
En Objetive-C existe el signo + para indicar cuando un método es de clase
<br/><br/>
<pre class="">
//Fichero de cabecera
@interface Builder : NSObject
+(id)buildProductWithName(NSString *) name;
@end
//Fichero de implementación
@implementation Builder
+(id)buildProductWithName(NSString *) name{
return [Product alloc[ initWithName:name]];
}
</pre>
<br/><br/>
Fijaros que la sintáxis de los métodos es igual que la de los inicializadores que vimos en el anterior <a href="http://xurxodeveloper.blogspot.com.es/2015/01/ios-para-desarrolladores-net-clases-en-objetive-c-parte-2.html">artículo</a>. Los parámetros aparecen intercalados en el nombre del método y este nombre tiene que hacer referencia a todos los parámetros.
<br/><br/>
Y se invoca directamente desde la clase.
<br/><br/>
<pre class="">
Product product = [Builder buildProductWithName:@"ejemplo ejemplo"];
</pre>
<h3>Métodos de instancia</h3>
Métodos de instancia son métodos que necesitan de una instancia de la clase, es decir, se invocan sobre el objeto no sobre la clase, el concepto es igual a los métodos normales de un objeto en C#.
<h4>C#</h4>
En C# si no ponemos el modificador static por defecto es un método de instancia.
<br/><br/>
<pre class="brush:c#">
public class Product
{
public void ChangeName (string name)
{
Name = name;
}
}
</pre>
<br/><br/>
Y se invoca a través de una instancia.
<br/><br/>
<pre class="brush:c#">
Product product = Builder.BuildProduct("ejemplo ejemplo");
product.ChangeName("ejemplo 2");
</pre>
<h4>Objetive-C</h4>
En Objetive-C existe el signo - para indicar cuando un método es de instancia.
<br/><br/>
<pre class="">
//Fichero de cabecera
@interface Product : NSObject
-(void)changeName(NSString *) name;
@end
//Fichero de implementación
@implementation Product
-(void)changeName(NSString *) name{
_name = name;
}
@end
</pre>
<br/><br/>
Y se invoca a través de una instancia.
<br/><br/>
<pre class="">
Product product = [Builder buildProductWithName:@"ejemplo ejemplo"];
[product changeName:@"ejemplo 2"];
</pre>
<h3>Métodos de factoría</h3>
Como ya hemos visto existen en Objetive-C inicializadores para crear instancias de una clase, el concepto es parecido a los constructores de C#. Sin embargo es habitual utilizar métodos de clase como métodos de factoría.
<br/><br/>
En el framework cocoa existen muchos ejemplos de clases que contienen métodos de factoría, la convención es que comience por el nombre de clase sin ningun prefijo.
<pre class="">
//métodos de factoria de la clase NSDate
+ (id)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)secs;
+ (id)dateWithTimeIntervalSince1970:(NSTimeInterval)secs;
//métodos defactoría de la clase NSData
+ (id)dataWithBytes:(const void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length;
+ (id)dataWithBytesNoCopy:(void *)bytes length:(unsigned)length
freeWhenDone:(BOOL)b;
+ (id)dataWithContentsOfFile:(NSString *)path;
+ (id)dataWithContentsOfURL:(NSURL *)url;
+ (id)dataWithContentsOfMappedFile:(NSString *)path;
//métodos de factoria de la clase NSString
+ (id)string;
+ (id)stringWithString:(NSString *)aString;
+ (id)stringWithFormat:(NSString *)format, …;
+ (id)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
+ (id)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc;
</pre>
<h3>Resumen</h3>
En este artículo hemos visto los tipos de métodos que existen en Objetive-C como son de instancia y de clase. También hemos visto como podémos utilizar los métodos de clase para tener métodos de factoría que nos pueden servir para crear un objeto encapsulando así los dos pasos de alloc y de inicilización
<h3>Libros relacionados</h3>
iOs Programming for .Net Developers
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0985784512&linkCode=as2&tag=xurxodevelo09-20&linkId=5QCTTWL3DMZQY5GZ" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0985784512&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0985784512/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0985784512&linkCode=as2&tag=xurxodevelope-21&linkId=CFCSJ2TSVHAMOW55" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
iOS Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelo09-20&linkId=6XLLPOOZ76NV7M7H" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/0321942051/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=0321942051&linkCode=as2&tag=xurxodevelope-21&linkId=2IPFCJDDZGISRLWS" target="_blank">Amazon.co.uk</a>
</li>
</ul>
<br/>
Objective-C Programming: The Big Nerd Ranch Guide
<br>
<ul>
<li>
<a class="amazon-com" href="http://www.amazon.com/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1789&creative=9325&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelo09-20&linkId=PQCN6Q6HOCBA4E3Q" target="_blank">Amazon.com</a>
</li>
<li>
<a class="amazon-es" href="http://www.amazon.es/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=3626&creative=24790&creativeASIN=032194206X&linkCode=as2&tag=xurxode-21" target="_blank">Amazon.es</a>
</li>
<li>
<a class="amazon-co-uk" href="http://www.amazon.co.uk/gp/product/032194206X/ref=as_li_qf_sp_asin_il_tl?ie=UTF8&camp=1634&creative=6738&creativeASIN=032194206X&linkCode=as2&tag=xurxodevelope-21&linkId=Z2VU2M25MAHZUAWA" target="_blank">Amazon.co.uk</a>
</li>
</ul>xurxodevhttp://www.blogger.com/profile/12076376893143638100noreply@blogger.com0