1. Instalación del entorno
    1. ¿Qué es?
    2. Guía de instalación
    3. Activar opciones de desarrollo
    4. Ejecutar aplicaciones desde el móvil
    5. Configuración de Android Studio
    6. Gradle
  2. Layout
    1. Android API
    2. Primer proyecto
    3. Activity
    4. Hola mundo - layout
    5. Unidades de medida
    6. Ejercicio Layout
    7. Crear una interfaz de usuario por código
    8. Tipos de layout
    9. Ejercicio - landscape
    10. Ejercicio – strings.xml e internalizacion
    11. Contenido Scrollable
    12. AndroidManifest.xml
    13. Resources
    14. Imágenes
    15. Background repeat
    16. Consola de trazas
    17. Ejercicios estilos
    18. OnClick
    19. Añadir un nuevo Activity
    20. Pasar información de una activity a otra
    21. Menus
    22. Problema al cambiar la orientación de la pantalla
  3. Animaciones
    1. Convertir una imagen en otra
    2. Animación con clase Tween
    3. Set de animaciones
    4. Detectar el fin de una animación
    5. Animación FrameByFrame
  4. Almacenar info
    1. Preferencias
    2. Guardar preferencias
    3. XML de preferencias
    4. Fragmento
    5. Ejercicio preferencias
    6. SQLite
    7. SQLite read
    8. SQLite - precarga de la base de datos
    9. Instalar la base de datos con cada actualización de la app
    10. OnChange de un spinner
    11. Ejercicio
  5. Multimedia
    1. Audio
    2. Video
    3. Hacer Streaming
    4. Usar cámara fotos
  6. Dialogs, notifications
    1. Alert dialog
    2. Simple Progress Dialog
    3. Ejercicio - Personal dialog
    4. Toast - notificaciones instantáneas
    5. Notification
    6. Notificación abre un Activity
  7. Servicios
    1. Temporizadores
    2. Ejercicio yoga con notificaciones
    3. Broadcaster Receiver
    4. Solicitar el servicio de wifi y comprobar su estado
    5. Otros controles
    6. Network connection
  8. ItemList
    1. ¿Qué es una lista?
    2. Id's de una lista
    3. Extendiendo ListActivity
    4. Content Provider
    5. Lista sin extender de ListActivity
    6. Adapter
    7. BaseAdapter
    8. Optimizaciones
    9. Ejercicio - plantas
  9. XML y JSON
    1. Abrir XML
    2. Parsear XML
    3. Parsear JSON
  10. AsynTask
    1. Petición por GET
    2. Petición por POST
    3. Ejercicio search UPC
  11. Mapas
    1. Recomendación
    2. Teoría geolocalización
    3. Location Manager
    4. onResume, onPause
    5. Proximity Alert
    6. Criteria
    7. Obtener una Map API Key
    8. Añadir un mapa
    9. Centrar el mapa en un punto
    10. Geolocalización con mapas
    11. Pintar marker
    12. Obtener la ubicación de cierto lugar
    13. Rutas
    14. Servicios
  12. Login
    1. Login con google
    2. Login con facebook
  13. Dibujar
  14. Publicar
    1. Publicar la aplicación en Google Play
    2. Certificado de aplicaciones
    3. Error típico – conflicto con el nombre del paquete repetido
    4. Eliminar app
    5. Publicar y debugar una aplicación en el móvil
    6. Desinstalar una aplicación del teléfono móvil desde consola

Activar opciones de desarrollo en Android 4.0 y superiores

Ajustes → Acerca del teléfono → Pulsamos 7 veces sobre el número de compilación.

Problema con la versión de Gradle

The version of Gradle you are using (Gradle installation '...') does not support ... Support for this is available in Gradle ... and all later versions.

Habrá que instalar la última versión de Gradle: gradle.org. Si somos usuarios de linux, debemos instalar el gestor de versiones sdkman.

Todos los ejercicios de este curso

Descargar la aplicación con todos los ejercicios en este enlace.

Ejecutar aplicaciones desde el móvil

Esto será mucho más rápido y veraz que ejecutar desde el emulador.

En la pestaña Devices veremos el ordenador está reconociendo el dispositivo. Si no lo reconoce (muestra ???????????), es probable que debamos instalar los drivers.

Conectar dispositivo con Android > 7.0 a Linux

  1. Ignora:
    • todo lo que hayas leído sobre el fichero /etc/udev/rules.d/51-android.rules
    • mtp-tools. No están documentadas y jmtpfs es mucho más fácil
  2. sudo apt-get install jmtpfs
  3. sudo mkdir /media/myphone
  4. Conecta el móvil por ordenador mediante USB
  5. Acepta la notificación que aparece y marca "Use USB to... -> Transfer files"

Si el ordenador detecta el móvil que dispositivo no autorizado:

  1. sudo apt-get install android-tools-adb
  2. desconecta el móvil
  3. adb kill-server
  4. adb start-server
  5. conecta el dispositivo

Configuración de Android Studio

Navegar en el explorador de ficheros a la ubicacion del fichero activo: En el panel "Android", en la columna izuierda -> Engranaje (opciones) -> Autoscroll From Source

Fichero gradle

apply plugin: 'com.android.application' //Esto nos indica que estamos haciendo una aplicación Androdid

android {
//Los siguientes dos valores van a la par, el primero es la versión de android que estamos usando y el segundo nos indica dentro de esa versión, en que revisión del código estamos
    compileSdkVersion 26 
    buildToolsVersion '26.0.2'
    defaultConfig {
//Este parámetro es un valor único dentro de la PlayStore. Suele coincidir con el package name (el primer paquete creado en la app)
        applicationId "com.example.elamorhallegado.dansu"
        minSdkVersion 19 //Cuanto mayor es, con menos dispositivos será compatible nuestra app
        targetSdkVersion 26

        versionCode 1 //es un numero entero que indica la versión de mi app
        versionName "1.0" //puede ser una palabra que indica la versión de mi app
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false // decidimos si queremos ofuscar nuestro código para evitar que alguien lo pueda decompilar. Es un embrollo para depurar
            ...
        }
    }
}
dependencies {
    ...
}

Error típico

All libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions 8.3.0, 11.4.2. Examples include com.google.android.gms:play-services-auth:8.3.0 and com.google.android.gms:play-services-base:11.4.2

Esto significa que debo actualizar las versiones de las librerías que son 8.3.0 a la versión 11.4.2, que es más actual. Además debo incluir una librería que falta, y que se cita en el error: com.google.android.gms:play-services-base:11.4.2

Las versiones siempre serán 3 cifras: 11.6.0

Layouts

Primer proyecto

File → new → Android → Android Proyect → Create new proyect in Workspace → Lo llamamos a_hola_mundo

Esto crea un proyecto Android con un Activity que será el punto de inicio de la aplicación.

Error típico: An SDK Target must be specified → Para solventarlo → window → preferences → Android

Activity

Es una clase java. Presenta su propia interfaz de usuario (ventana).

Es cada una de las pantallas que componen una aplicación.

Uno de los activities es el que lanza la aplicación (lanzador)

Hola Mundo - Activity

Este es el código del Activity que se genera por defecto al crear nuestro proyecto.

		
public class A_holaMundoActivity extends Activity{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}
		
	

R (resources) es una clase java (ruta gen/R.java) que contiene constantes que apuntan a la ubicación de los recursos utilizados en nuestra aplicación.

R.layout.main contiene la ubicación del XML que contiene la apariencia de nuestra activity. Está accesible desde res/layout/main.xml. En eclipse, será posible modificar dicha ubicación mediante un editor gráfico o mediante código.

Hola Mundo - Layout

Por defecto, la propiedad android:text del main.xml apunta a una variable hello, definida en res/values/string.xml que contiene su valor. También podría haber puesto directamente el texto “Hola Mundo”, en vez de una constante.

	
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
	android:text="@string/hello"

    />
</LinearLayout>

string.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, A_holaMundoActivity!</string>
    <string name="app_name">A_holaMundo</string>
</resources>
	

Unidades de medida

  • sp: scale-independent pixels. Es similar a la unidad dp, pero su tamaño variará dependiendo de las preferencias del usuario y de la densidad de la pantalla. Lo utilizarás para los tamaños de textos.
  • dp: density-independent pixels. Lo utilizarás para todo lo demás. Un dip es lo mismo que un dp, habitualmente utilizamos el término dp, porque se parece más al término sp. Estas unidades son relativas a una pantalla de 160 dpi(dots per inch); luego un dp equivale a un px en este tipo de pantalla.
  • px: pixel
  • in: inches (pulgadas). Basadas en el tamaño físico de la pantalla.
  • mm: milímetros. Basados en el tamaño físico de la pantalla.
  • pt: points. 1/72 de una pulgada.

Ejercicio Layout

Hacer una aplicación con un activity que llame a un layout llamado nuevo_layout.xml que tenga dos cuadros de texto.

En el primero se muestra el texto “Hola Mundo”. Dicho texto será una variable definida en strings.xml. Se mostrará en un tamaño superior al normal.

En el segundo se muestra el texto “Adios Mundo”. Dicho texto estará escrito directamente en la propiedad android:text. Se mostrará en rojo y en negrita.

Probar a introducir otros componentes, modificar sus propiedades y ver el efecto que esto tiene

Notas:

  • Para crear un nuevo layout: botón derecha sobre la carpeta res/layout en el panel de estructura de proyecto → new → Layout Resource File

Crear una interfaz de usuario por código

		
public class InterfazUSuarioActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView texto = new TextView(this);
        texto.setText("Hola Mundo");
        setContentView(texto);
    }
}
		
	

El contexto es una variable que almacena el estado del teléfono (brillo, conectividad, batería...). Un activity y un servicio heredan de Context. Por tanto, también son contextos.

La clase Activity es una subclase de Context.

Al instanciar un objeto de la clase TextView le pasamos como parámetro el Activity que se va a encargar de gestionarlo.

Al instanciar un objeto de la clase TextView le pasamos como parámetro el Activity que se va a encargar de gestionarlo.

Como la clase A_interfazUSuarioActivity es una subclase de Activity, también es de tipo context. Por ello, puedes pasar this como contexto del TextView.

Tipos de layout

  • Linear Layout
    Para definir si los elementos se irán apilando verticalmente u horizontalmente:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation=vertical android:layout_width="fill_parent" android:layout_height="fill_parent">
    </LinearLayout>
  • Nota: Si tenemos algún elemento con su alto o ancho ocupando toda la pantalla o unas dimensiones demasiado grandes, los elementos desaparecerán por la derecha o el fondo de la pantalla.
  • RelativeLayout
    Los elementos se posicionan en función de los que tienen al lado.
  • FrameLayout
    Los elementos se superponen unos con otros. No es un layout típico.
  • TableLayout
    Coloca los elementos siguiendo una distribución de tabla
  • AbsoluteLayout
    Deprecated

Linear Layout (el más frecuente)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
	<AnalogClock 
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" />
	<CheckBox
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="un checkbox" />
	<Button
		android:layout_width="wrap_content"
		android:layout_height="wrap_content" 
		android:text="un botón" />
	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="un TextView" />
</LinearLayout>
primer layout

Table Layout

Se utiliza la etiqueta TableRow cada vez que queremos insertar una nueva línea.
Utilizaré el atributo android:stretchColumns="*" para que las celdas ajusten su tamaño hasta alcanzar los límites de la pantalla.

<?xml version="1.0" encoding="utf-8"?>
<TableLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
<TableRow>
	<AnalogClock 
	android:layout_width="wrap_content"
	android:layout_height="wrap_content" />
	<CheckBox
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="un checkbox" />
</TableRow>
<TableRow>
	<Button
	android:layout_width="wrap_content"
	android:layout_height="wrap_content" 
	android:text="un botón" />
	<TextView
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="un TextView" />
</TableRow>
</TableLayout>
ejemplo table layout

GridLayout

<GridLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
      android:columnCount="2">
<AnalogClock 
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/un_checkbox" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content" 
android:text="@string/un_boton" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/un_textview" />
</GridLayout>
layout grid layout

Relative Layout
permite comenzar a situar los elementos en cualquiera de los cuatro lados del contenedor e ir añadiendo elementos pegados a estos

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
<AnalogClock 
android:id="@+id/AnalogClock01"
android:layout_width="wrap_content"
android:layout_height="wrap_content" 
android:layout_alignParentTop="true"/>
<CheckBox
android:id="@+id/CheckBox01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="un checkbox" 
android:layout_below="@+id/AnalogClock01"/>
<Button
android:id="@+id/Button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" 
android:text="un botón" 
android:layout_below="@+id/CheckBox01"/>
<TextView
android:id="@+id/TextView01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="un TextView" />
</RelativeLayout>
relative layout

Frame Layout (posiciona todos los elementos usando todo el contenedor, sin distribuirlos espacialmente)

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
<AnalogClock 
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="un checkbox" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content" 
android:text="un botón" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="un TextView" />
</FrameLayout>

Ejercicio Linear Layout

Utilizaremos un Linear Layout y setearemos su propiedad gravity.

El fichero res/strings.xml ha de tener el siguiente contenido:


<?xml version="1.0" encoding="utf-8"?>
<resources>
	<string name="app_name">Ejercicio Layout</string>
	<string name="pablomonteserin">Pablo Monteserín</string>
	<string name="menu1">Acerca de mí</string>
	<string name="menu2">Galería de videos</string>
	<string name="menu3">Galería de fotos</string>
	<string name="menu4">Contacto</string>
</resources>
ejercicio layout

Ejercicio TableLayout

Android permite diseñar una vista diferente para la configuración horizontal y vertical.

A partir del ejercicio anterior, crea la carpeta res/layout-land y crea en ella una vista con el mismo nombre que la que está en la carpeta layout.

Para ver la carpeta land recién creada: panel Project → Packages.

Habrá que usar un TableLayout dentro de un LinearLayout. Para que los elementos del TableLayout ocupen todo el ancho de la celda, utilizar el siguiente atributo: android:stretchColumns="*".

Todos los elementos visuales del layout (los layouts, los Buttons y el TextView) deben tener un layout_width y un layout_height.

ejercicio layout 2

Ejercicio – strings.xml e internalizacion

Android utiliza una lista de sufijos para expresar recursos alternativos. Estos sufijos pueden hacer referencia a la orientación del dispositivo, al lenguaje, la región, la densidad de pixeles, la resolución, etc.

Crea el siguiente fichero en el que traducirás todas las cadenas al inglés:
res/values-en/strings.xml

Para cambiar el idioma en el dispositivo:
Ajustes → Idioma y texto → Seleccionar idioma

Contenido scrollable

Bastará con envolver el Layout dentro de nuestro activity_main.xml por un ScrollView. El ScrollView sólo puede tener un hijo.

Nota:
No pondremos un ListView dentro de un ScrollView salvo que no nos quede mas remedio, ya que ambos componentes son engorrosos de integrar.

AndroidManifest.xml

Describe las funcionalidades y los permisos que serán necesarios, etc. de la aplicación.
Para editarlo recurriremos a las vistas xml y no a las vistas gráficas que nos da eclipse.
Las siguientes líneas estarán contenidas en el nodo del activity que se lanzará en primer lugar al arrancar la aplicación:

	
<intent-filter>
	<action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
	

Cuando en la consola de trazas obtengo un "Class Not Found Exception":
o el nombre de la clase o el nombre del paquete están mal en el manifest. Lo ideal es que en el manifest la definición del paquete coincida con el paquete raíz de todos los activities.

Si tenemos el siguiente error en las trazas:
android.content.ActivityNotFoundException: Unable to find explicit activity class {com.pablomonteserin/com.pablomonteserin.Second}; have you declared this activity in your AndroidManifest.xml?

Falto declarar la segunda activity. Ponemos la siguiente línea en el AndroidManifest.xml

	
<activity android:name=".Second" />
	

R

Todos los recursos (xml, drawable, etc.) se empaquetan en un archivo R que es una especie de índice de recursos.

android:id="@+id/item2"

La arroba significa que nos estamos refiriendo al archivo de recursos R.
El + significa que estamos añadiendo un nuevo recurso.

Cada vez q realizo un cambio en la estructura de mi proyecto (carpetas, documentos xml, atributos de dichos documentos, etc) android lo comprueba. Si el cambio arrojase un error es probable que dicho error apareciese reflejado en la pestaña Console

Imágenes

Tamaños de imágenes y layouts en función de la pantalla dónde serán reproducidos

res/drawable-mdpi/my_icon.png 		//bitmap for medium density
res/drawable-hdpi/my_icon.png 		//bitmap for high density
res/drawable-xhdpi/my_icon.png 		//bitmap for extra high density

res/layout/my_layout.xml 			//layout for normal screen size (“default”)
res/layout-small/my_layout.xml 		//layout for small screen size
res/layout-large/my_layout.xml 		//layout for large screen size
res/layout-xlarge/my_layout.xml 		//layout for extra large screen size
res/layout-xlarge-land/my_layout.xml 	//layout for extra large in landscape orientation

Ponemos los recursos en estas carpetas y será el sistema el que decida cuál utilizar.

Ejercicio - Cargar una imagen

Debemos copiar la imagen en la carpeta drawable-hdpi.

Cargar la imagen en un ImageView:
Introducimos un objeto de tipo ImageView en el xml y lo vinculamos a la imagen guardada en drawable.

Cargar la imagen en un ImageButton:
Introducimos un elemento de tipo ImageButton en el xml y lo vinculamos a la imagen guardada en drawable.

Cambiar dinámicamente la foto del ImageView anterior:

	
ImageView iv = (ImageView) findViewById(R.id.imageView1);
iv.setImageResource(R.drawable.cara);
	

Background repeat

Por defecto una imagen se deformará para ocupar las dimensiones del ImageView, en el caso de que la imagen sea mas pequeña. Si lo que queremos es que la imagen se repita:

activity_main.xml<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@drawable/background_repeat"
...
background_repeat.xml<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android" 
   android:src="@drawable/icon"
   android:tileMode="repeat"
    />

Consola de trazas

Poner nuestras propias trazas:

		
Log.d("total", cadenaTexto);
	
	

Si quiero mostrar por las trazas un número:

	
Log.d("total", ""+numeroRegistros);
	

Para ver las trazas:
Window → show view → other → logcat, devices (debo seleccionar el dispositivo del que quiero ver las trazas)

AndroidManifest.xml → Debbuggable: true

Ejercicio - Hacer que un radio button pertenezca a un RadioGroup para que no pueda haber dos seleccionados simultáneamente.

	
<RadioGroup android:layout_width="fill_parent" android:layout_height="wrap_content">
	<RadioButton android:text="RadioButton" android:id="@+id/radioButton1" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton>
	<RadioButton android:text="RadioButton" android:id="@+id/radioButton2" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton>
	<RadioButton android:text="RadioButton" android:id="@+id/radioButton3" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton>
	<RadioButton android:text="RadioButton" android:id="@+id/radioButton4" android:layout_width="wrap_content" android:layout_height="wrap_content"></RadioButton>
</RadioGroup>
	

Radiogroup es un layout, y como ocurre con todos los layouts, habrá que definir su ancho y alto.

Ejercicios estilos

Ejercicio - Cambiar dinámicamente el color o la imagen de fondo de un layout

milayout= findViewById(R.id.miLayout); 
	milayout.setBackgroundColor(Color.rgb(151, 19, 19)); 
//milayout.setBackgroundResource(R.drawable.background_img);

Ejercicio - Poner un degradado a un layout

res/drawable/degradado.xml
<?xml version="1.0" encoding="utf-8"?>
<shape
  xmlns:android="http://schemas.android.com/apk/res/android">
	<gradient android:startColor="#FFFFFF"
	android:endColor="#0000FF"
	android:angle="270"/>    
</shape>
	
En el .java
View mlayout = findViewById(R.id.main_layout);
mlayout.setBackgroundResource(R.drawable.degradado);

Ejercicio - Maquetar un botón

res/drawable/rect.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
	android:shape="rectangle">
	<gradient
		android:startColor="#FFFF0000"
		android:endColor="#80FF00FF"
		android:angle="45"/>
	<!--  Este es el padding para colocar el texto -->
	<padding
		android:left="7dp"
		android:top="7dp"
		android:right="7dp"
		android:bottom="7dp" />		
	<corners android:radius="10dp" />
</shape>
main.xml<Button android:text="Button"
 android:id="@+id/button1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:background="@drawable/rect" />
<Button android:text="Button"
 android:id="@+id/button1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" android:background="@drawable/rect" />

Ejercicio – usar colores predefinidos

rest/layout/main.xml<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    android:textSize="30dp"
    android:textColor="@color/red"
    />
res/values/colors.xml (también podría modificar un fichero llamado loquesea.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<color name="red">#ff0000</color>
	<color name="green">#00ff00</color>
	<color name="blue">#0000ff</color>
</resources>

Ejercicio - Cargar las propiedades de un elemento desde un xml

Creamos el xml:
New → Android XML file → what type of resource would you like to create? → Values

res/layout/main.xml
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello" 
    style="@style/code"/>
	

res/values/styles.xml (también podría modificar un fichero llamado loquesea.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<style name="code" parent="@android:style/TextAppearance">
		<item name="android:textSize">40sp</item>
		<item name="android:typeface">monospace</item>
		<item name="android:textColor">#0000FF</item>
	</style>    
</resources>
	

Ejercicio - crear un estilo que sobrescriba el anterior.

res/layout/loquesea.xml
<TextView 
    android:text="TextView" 
    android:id="@+id/textView1" 
    style="@style/code.red" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"></TextView>
	
res/values/styles.xml (también podría modificar un fichero llamado loquesea.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
	<style name="code" parent="@android:style/TextAppearance">
			<item name="android:textSize">40sp</item>
			<item name="android:typeface">monospace</item>
			<item name="android:textColor">#0000FF</item>
	</style>
	<style name="code.red">
		<item name="android:textColor">#FF0000</item>
	</style>    
</resources>	
	

Ejercicio – Cargar un theme de los que vienen por defecto en android

Para aplicar un theme a toda la aplicación añado la línea en rojo en el AndroidManifest.xml:

<application android:icon="@drawable/icon" android:label="@string/app_name"
android:theme="@style/Base.Theme.AppCompat.Light">

Para aplicarlo a un activity en concreto:

<activity android:name=".AcercaDe"
	android:label="@string/app_name"
	android:theme="@style/Base.Theme.AppCompat.Light"/>

Lo que no hacemos es aplicar el tema en el xml del layout.

No todos los temas son compatibles con todos los activities.

Ejercicio → Cargar mi propio theme

New → Android XML file → what type of resource would you like to create? → Values

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style parent="@android:style/Theme.Dialog" name="myTheme">
    	<item name="android:textColor">#FF0000</item>
    </style>
</resources>

Cargamos el theme creado por nosotros utilizando el código pintado de rojo en el AndroidManifest.xml:


<application android:icon="@drawable/icon" android:label="@string/app_name"
android:theme="@style/myTheme">

Ejercicio

Crear un theme que herede de Theme.Dialog y hacer que toda la aplicación lo implemente.

El tema tendrá los siguientes estilos:

	
android:textColor="#00FF00"
android:typeface="monospace"
	

Fragments

Es una porción de la interfaz de usuario que puede añadirse o eliminarse forma independiente al resto de elementos de la actividad,

Esto nos permite dividir la interfaz en varias porciones con el fin de diseñar diversas configuraciones de pantalla.

Todo fragment debe tener asociado, además del layout, su propia clase java, que debe extender de la clase Fragment.

onCreateView(), es el “equivalente” al onCreate() de las actividades, y dentro de él es donde normalmente asignaremos un layout determinado al fragment. En este caso tendremos que “inflarlo” (convertir el XML en la estructura de objetos java equivalente) mediante el método inflate() pasándole como parámetro el ID del layout correspondiente, en nuestro caso fragment_listado.

activity_contiene_fragment.xml<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.pablomonteserin.layouts.fragments.RecipienteFragment">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Esto no es un fragment"
    />

    <fragment class="com.pablomonteserin.layouts.fragments.BlankFragment"
        android:id="@+id/FrgListado"
        android:layout_weight="40"
        android:layout_width="match_parent"
        android:layout_height="60dp" />

</LinearLayout>
BlankFragmentpublic class BlankFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.layout_del_fragment, container, false);
    }
    @Override
    public void onActivityCreated(Bundle state) {
        super.onActivityCreated(state);
        TextView tv = getView().findViewById(R.id.elTextViewDelFragment);
        tv.setText("Esto si lo es");
    }
}
layout_del_fragment.xml<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.pablomonteserin.layouts.fragments.BlankFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment"
        android:id="@+id/elTextViewDelFragment"/>

</FrameLayout>

Tabs

layout_tabs.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <TabHost android:id="@android:id/tabhost"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

            <TabWidget android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@android:id/tabs" />

            <FrameLayout android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@android:id/tabcontent" >

                <LinearLayout android:id="@+id/tab1"
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" >

                    <TextView android:id="@+id/textView1"
                        android:text="Contenido Tab 1"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>

                <LinearLayout android:id="@+id/tab2"
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" >

                    <TextView android:id="@+id/textView2"
                        android:text="Contenido Tab 2"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />
                </LinearLayout>
            </FrameLayout>
        </LinearLayout>
    </TabHost>
</LinearLayout>
TabsActivitypublic class TabsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_tabs);

        Resources res = getResources();

        TabHost tabs=findViewById(android.R.id.tabhost);
        tabs.setup();

        TabHost.TabSpec spec=tabs.newTabSpec("mitab1");
        spec.setContent(R.id.tab1);
        spec.setIndicator("",
                ContextCompat.getDrawable(this,android.R.drawable.ic_btn_speak_now));
        tabs.addTab(spec);

        spec=tabs.newTabSpec("mitab2");
        spec.setContent(R.id.tab2);
        spec.setIndicator("mi casa es tu casa",ContextCompat.getDrawable(this, android.R.drawable.ic_dialog_email));
        tabs.addTab(spec);

        tabs.setCurrentTab(0);
    }
}

OnClick

	
MainActivity.java
public class MainActivity extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}
	public void btnPulsado(View v){
		TextView tv = (TextView)findViewById(R.id.textView1);
		tv.setText("botón pulsado");
	}
}

main.xml
…
<Button android:text="Button"
 android:id="@+id/button1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:onClick="btnPulsado"/>
...
	

Esta forma de gestionar los eventos fue introducida en la API 7 (Android 2.1)

Implementación de la interfaz OnClickListener()

Con esto conseguiremos tener solamente una única clase que gestione todos los eventos de click.

	
public class Main extends Activity implements View.OnClickListener{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Button b = (Button) findViewById(R.id.button1);
        b.setOnClickListener(this);
    }
	@Override
	public void onClick(View v) {
		if(v.getId() ==  R.id.button1){
			Log.d("traza", "llega");
		}
	}
}
	

Clase OnClickListener (lo utilizaremos para el siguiente ejercicio)

	
Button b = (Button) findViewById(R.id.button1);
b.setOnClickListener(new View.OnClickListener() {
@Override
	public void onClick(View arg0) {
		Toast.makeText(getApplicationContext(), "Boton Pulsado",Toast.LENGTH_LONG).show();
	}
});
	

Añadir un nuevo Activity

Botón derecho sobre el paquete → new → class → Android Activity

Esto crea una clase Activity con su método onCreate y su correspondiente referencia en el AndroidManifest.xml.

¿Para qué sirve un Intent?

Para invocar componentes (un Activity, código ejecutándose en segundo plano, et.)

Crear un botón en la primera activity para que cuando pulsemos sobre él vayamos a la segunda

startActivity(new Intent(this, Second.class));
        	}
   });}}

Activity que hace la llamada. Si sólo ponemos this, se pensará que estamos haciendo referencia a la clase OnClickListener, en lugar de a la clase Main

Hay que registrar el Activity en el AndroidManifest.xml

	
<activity android:name=".AcercaDe" 
	android:label="@string/app_name">
</activity>
	

error android

Ejercicio

Hacer que al pulsar el botón Acerca de se lance un activity con un cuadro de texto informativo.

El activity con el cuadro informativo utilizará un theme propio de diálogos:

	
android:theme="@android:style/Theme.Dialog"
	

Dicho Activity deberá extender de la clase Activity, en lugar de la clase ActionBarActivity.

Pasar información de una activity a otra

Main.java
	public final static String TEXTO = "texto";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

	public void pasarInfo(View view) {
		final EditText et = (EditText) findViewById(R.id.editText1);
		Intent intent = new Intent(Main.this, Second.class);
		intent.putExtra(TEXTO, et.getText().toString());
		startActivity(intent);
	}

Second.java
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.second);
		TextView tv = (TextView)findViewById(R.id.textView1);
		tv.setText(getIntent().getExtras().getString(Main.TEXTO));
	}

Ejercicio - Crear un menú

Un menú contextual es lanzado al pulsar la tecla menú del teléfono.

res/menu/menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
	<item android:id="@+id/item1" android:title="Option 1" android:icon="@android:drawable/ic_menu_compass" />
	<item android:id="@+id/item2" android:title="Option 2" android:icon="@android:drawable/ic_menu_call" />    
</menu>
MenuActivity.java
public class MenuActivity extends Activity {
	...
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.menu, menu);
		return true;
	}

Al ejecutar la aplicación pulsamos el botón de menú para ver los menús.

menu android

Ejercicio - Detectar la pulsación de un menú

Añadimos el siguiente código al java anterior:

	
public boolean onOptionsItemSelected(MenuItem item){
	if(item.getItemId() == R.id.item1){
    	Log.d("opcion", "opcion 1 pulsada");
    }
	return super.onOptionsItemSelected(item);
}
	

Problema al cambiar la orientación de la pantalla

Al cambiar la orientación se destruyen los componentes visuales y se vuelven a crear, llamando al onCreate.

Podemos solventar los problemas que esto pueda acarrear modificando el nodo <activity> correspondiente del AndroidManifest.xml:

- Para limitar el giro de la aplicación:
android:screenOrientation="portrait"

- Para que no se destruya el activity:
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|smallestScreenSize"

Ejercicio – Al pulsar sobre un botón, convertir una imagen en otra

MainActivity.javaImageView iv = (ImageView) findViewById(R.id.imageView1);
TransitionDrawable td = (TransitionDrawable) iv.getDrawable();
td.startTransition(1000);
res/drawable-hdpi/transition.xml<?xml version="1.0" encoding="utf-8"?>
<transition
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/steve1"></item>
    <item android:drawable="@drawable/steve2"></item>
</transition>

En el layout, el ImageView deberá tener el siguiente atributo: android:src="@drawable/transition"

Ejercicio

Al pulsar sobre un botón, animarlo utilizando la clase Tween, que se encargará de deformarlo.

MainActivity.java
Animation anim = AnimationUtils.loadAnimation(AnimationTweenActivity.this, R.anim.animation);
Button b = (Button)findViewById(R.id.button1);
b.startAnimation(anim);
res/anim/animation.xml
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
	android:fromXScale="0.5"
	android:toXScale="2.0"
	android:fromYScale="0.5"
	android:toYScale="2.0"
	android:pivotX="50%"
	android:pivotY="50%"
	android:duration="4000"
	  >
</scale>

Si quiero iniciar las dos animaciones a la vez, lo pondre a 0.

Ejercicio - set de animaciones

	
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
	<scale xmlns:android="http://schemas.android.com/apk/res/android"
	 	android:fromXScale="0.5"
	 	android:toXScale="2.0"
	 	android:fromYScale="0.5"
	 	android:toYScale="2.0"
	 	android:pivotX="50%"
	 	android:pivotY="50%"
	 	android:duration="4000" />
	<rotate android:fromDegrees="0"
		android:toDegrees="180"
		android:pivotX="50%"
		android:pivotY="50%"
		android:duration="20000"
		android:startOffset="2000" />
</set>
	

Detectar el fin de una animación

El activity debe implementar la interfaz AnimationListener.

	
...
questionsPanel_anim.setAnimationListener(this);
questionsPanel.startAnimation(questionsPanel_anim);	
...


public void onAnimationEnd(Animation arg0) {
		if(arg0==questionsPanel_anim){
			pintarPreguntasYRespuestas();
		}
}
	

Animación FrameByFrame

MainActivity.java…
ImageView iv = (ImageView) findViewById(R.id.imageView1);
iv.setBackgroundResource(R.drawable.animation);
AnimationDrawable animation = (AnimationDrawable) iv.getBackground();
animation.start();
res/layout/activity_main.xml<ImageView
    android:background="@drawable/animation"
    ...
/>
res/drawable/animation.xml<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/animation_00031" android:duration="30" />
<item android:drawable="@drawable/animation_00032" android:duration="30" />
<item android:drawable="@drawable/animation_00033" android:duration="30" />
…

Almacenar info

Preferencias

Las usamos para guardar pequeñas cantidades de información.

Suelen ser datos que una aplicación debe guardar para personalizar la experiencia del usuario, por ejemplo información personal, opciones de presentación, etc.

La información es almacenada en forma clave-valor en ficheros xml, en el dispositivo móvil:
/data/data/paquetejava/shared_prefs/nombre_coleccion.xml

Ver las preferencias del emulador

Para acceder a las shared preferences del dispositivo pulsamos en el Android Device Monitor (robot verde de la toolbar) → pestaña File Explorer

/data/data/com.pablomonteserin/shared_prefs/
com.pablomonteserin_preferences.xml

Guardar preferencias

		
public class Almacenamiento_Pref_Activity extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
		String txt = sp.getString("texto", "valor por defecto");
		Toast.makeText(getApplicationContext(), txt, Toast.LENGTH_LONG).show();
	}
	
	//El siguiente código se ejecuta cuando la aplicación está a punto de terminar
	@Override 
	protected void onPause() {
		super.onPause();
		EditText et = (EditText)findViewById(R.id.editText);
		SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
		SharedPreferences.Editor sharedPreferencesEditor = sp.edit();
		sharedPreferencesEditor.putString("texto", et.getText().toString());
		sharedPreferencesEditor.commit();
	}
		
	

Si pulso el botón de retroceso volveré al menú principal, pero la información introducida estará disponible la próxima vez que ejecute la aplicación.

XML de preferencias

res/xml/preferences.xml<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <CheckBoxPreference android:key="primero" android:title="Opción 1" android:summary="Descripción opción 1" />
    <CheckBoxPreference android:key="segundo" android:title="Opción 2" android:summary="Descripción opción 2" />
</PreferenceScreen>
xml de preferencias

¿Qué es un fragmento?

Es una parte de una actividad, que tiene su propio ciclo de vida, recibe sus propios eventos de entrada, y que se puede añadir o quitar dinámicamente.

Preferencias con Fragment

public class MainActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getFragmentManager().beginTransaction()
				.replace(android.R.id.content, new PreferenciasFragment()).commit();
	}
	public static class PreferenciasFragment extends PreferenceFragment {
		@Override
		public void onCreate(Bundle savedInstanceState) {
			super.onCreate(savedInstanceState);

			// Make sure default values are applied. In a real app, you would
			// want this in a shared function that is used to retrieve the
			// SharedPreferences wherever they are needed.
			PreferenceManager.setDefaultValues(getActivity(), R.xml.preferences, false);
			// Load the preferences from an XML resource
			addPreferencesFromResource(R.xml.preferences);
		}
	}
}
res/xml/preferences.xmlNos sigue sirviendo el xml de la diapositiva anterior.

Ejercicio – recuperar las preferencias marcadas en la diapositiva anterior en dos textview en un nuevo Activity

SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);

//Dónde "primero" el atributo "key" del elemento <CheckBoxPreference> en el XML.
tv.setText(""+sp.getBoolean("primero", false));

SQLite

ejecutaremos una vez el siguiente código para que la base de datos aparezca en el file explorer (data → data → nombreDelPaquete → databases)

	
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
        
	//La siguiente línea busca la base de datos. Si la encuentra la abre. si no, la crea
	SQLiteDatabase db = openOrCreateDatabase("nombre_base_datos", MODE_PRIVATE, null);
	db.close();
}
	
  • Para editar un fichero del file explorer (por ejemplo el archivo correspondiente a la base de datos sqlite) debo pulsar el icono correspondiente dentro del panel file explorer:
  • Pull para sacar el fichero seleccionado.
  • Push para introducir el fichero seleccionado.

Sqlite - operaciones

//La siguiente línea busca la base de datos. Si la encuentra la abre. si no, la crea
SQLiteDatabase db = openOrCreateDatabase("nombre_base_datos", MODE_PRIVATE, null);
Cursor c = db.rawQuery("SELECT * FROM MyTable", null);
c. moveToFirst();
Log.d("LEE", c.getString(c.getColumnIndex("campo1")));
c.close();
db.close();

Después de hacer una consulta, si quiero leer los resultados de la misma debo llevar el cursor al punto a partir del cual quiero leer. De lo contrario no funcionará.

Operaciones con parámetros

//INSERT
ContentValues nuevoRegistro = new ContentValues();
nuevoRegistro.put("nombre_campo1", "6");
nuevoRegistro.put("nombre_campo2","Pepe");
db.insert("Usuarios", null, nuevoRegistro);

----------------------------------------------------------------------------------------------

//UPDATE
ContentValues valores = new ContentValues();
valores.put("nombre","usunuevo");
db.update("Usuarios", valores, "codigo=6", null);

----------------------------------------------------------------------------------------------

//DELETE
db.delete("Usuarios", "codigo=6", null);
db.execSQL("DELETE FROM invitado WHERE nombre=' pp'");

----------------------------------------------------------------------------------------------

//CONSULTA
Cursor c = db.rawQuery("SELECT * FROM MyTable", null);
//Para recorrer un cursor:
while(c.moveToNext()){
	//No debemos hacer el c.moveToFirst(); o nos saltaremos el primer registro.
	Log.d("LEE", c.getString(c.getColumnIndex("campo1")));
	...

SQLite – precarga base de datos

public class MyApp extends Application {
	@Override
	public void onCreate() {
		super.onCreate();
		File file = this.getDatabasePath("nombre_base_datos");
		if (!file.exists()) {
			file.getParentFile().mkdirs();// Esto crea todas las carpetas para que la ruta a la bd exista. 
			Log.d("paso1", "copiamos la base de datos si no existía antes");
			OutputStream os = null;
			InputStream is = null;
			try {
				is = this.getResources().openRawResource(R.raw.nombre_base_datos);
				os = new FileOutputStream(file);
				byte[] buffer = new byte[1024];
				int length;
				while ((length = is.read(buffer)) > 0) {
					os.write(buffer, 0, length);
				}
				os.flush();
			} catch (Throwable t) {//e.printStackTrace();
			} finally {
				try {
					if (os != null)
						os.close();
				} catch (IOException e) {//e.printStackTrace();
				}
				if (is != null) {
					try {
						is.close();
					} catch (IOException e) {//e.printStackTrace();
}}}}}} //cierre de llaves..	

Para cargarlo desde el manifest debemos modificar el nodo application y añadir el atributo: android:name:

<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:name=".MyApp"
android:debuggable="false">

Instalar la base de datos con cada actualización de la app

public class MyApp extends Application {
	private boolean hayActualizacion;
	@Override
	public void onCreate() {
		super.onCreate();
		SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
    		int version = prefs.getInt("version", -1);
		try {
			PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
			int posible_nueva_version = pInfo.versionCode;
			if(version<posible_nueva_version){
				hayActualizacion = true;
				SharedPreferences.Editor editor = prefs.edit();
				editor.putInt("version", posible_nueva_version);
				editor.commit();
			}else{
				hayActualizacion = false;
			}	
		} catch (NameNotFoundException e1) {
			e1.printStackTrace();
		}
		if(hayActualizacion){
			...	

Cargar un spinner con datos

Para cargar un listView con los datos de una lista (objeto de tipo List):


listView.setAdapter(new ArrayAdapter(this,android.R.layout.simple_list_item_1,lista));

Para cargar un spinner, sería un código similar.

OnChange de un spinner

Spinner sp = (Spinner) findViewById(R.id.spinner1);
		
sp.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
	public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
		recalcular ();
	
	@Override
	public void onNothingSelected(AdapterView<?> arg0) {
		// TODO Auto-generated method stub
	}
	// add some code here
});

Ejercicio

Hacer el alta, baja, modificación y consulta de una entidad. Para mostrar los datos usaremos un listview. Para borrar o modificar, usaremos un spinner.

Multimedia

Cargar un sonido

MediaPlayer mp = MediaPlayer.create(Audio.this, R.raw.hakunamatata);

Cargar un video

getWindow().setFormat(PixelFormat.TRANSLUCENT);
        VideoView videoHolder = new VideoView(this);
        //if you want the controls to appear
        videoHolder.setMediaController(new MediaController(this));
        Uri video = Uri.parse("android.resource://" + getPackageName() + "/"
                + R.raw.leon);
        videoHolder.setVideoURI(video);
        setContentView(videoHolder);
        videoHolder.start();

Hacer streaming de un video

Habrá que dar permisos de conexión a internet en el:

AndroidManifest.xml<uses-permission android:name="android.permission.INTERNET"/>
String stringUrl = "https://pablomonteserin.com/curso/html5/ex/HakunaMatataItMeansNoWorries.mp3";
try {
	final MediaPlayer mediaPlayer = new MediaPlayer();
	mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
	mediaPlayer.setDataSource(stringUrl);
	mediaPlayer.prepareAsync();
	mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
		@Override
		public void onPrepared(MediaPlayer mp) {
			mediaPlayer.start();
		}
	});
} catch (Exception e) {
	e.printStackTrace();
}

Usar cámara de fotos

public void sacaFoto(View v){
	Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
	startActivityForResult(intent,0);
}

protected void onActivityResult(int requestCode, int resultCode, Intent data){
	super.onActivityResult(requestCode, resultCode, data);
	Bitmap bm = (Bitmap) data.getExtras().get("data");
	iv.setImageBitmap(bm);
}

Dialogs, notifications

Alert dialog

AlertDialog.Builder builder = new AlertDialog.Builder(Main.this);
builder.setMessage("Are you sure you want to exit?");
//setCancelable determina si el cuadro de diálogo será cancelable utilizando la tecla de retroceso (BACK key)
builder.setCancelable(false);
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
		Main.this.finish();
	}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
	public void onClick(DialogInterface dialog, int which) {
		dialog.cancel();
	}
});
AlertDialog alert = builder.create();
alert.show();
pantallazo de un Activity con Java

ProgressBar Dialog

layout.xml<ProgressBar
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:id="@+id/progressBar"
	style="?android:attr/progressBarStyleHorizontal"/>

<ProgressBar
	android:layout_width="match_parent"
	android:layout_height="wrap_content"/>
 ProgressBar pb = (ProgressBar) findViewById(R.id.progressBar);
pb.setIndeterminate(false);
pb.setProgress(50);
progress bar en android

Ejercicio

La barra de progreso que inicialmente estaba a cero debe ir aumentando progresivamente según hacemos click.

Ejercicio - Personal dialog

Cuando haga click sobre un botón, deberá aparecer un cuadro de diálogo personalizado.

Dialog d = new Dialog(PersonalDialog.this);
d.setContentView(R.layout.personal_dialog);
d.setTitle("This is important");
d.show();

Creamos un layout llamado personal_dialog.xml para el dialog.

Toast - notificaciones instantáneas

Ejercicio: al pulsar el botón se muestra el mensaje mergente

Toast.makeText(this, "El texto emergente",Toast.LENGTH_SHORT).show();

Notificaciones

Básica

El código para programar notifiaciones ha cambiado a partir de Android 8.

if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
	// only for Oreo(8) and newer versions
	mNotificationUtils = new NotificationUtils(this, true);
}else{
	mNotificationUtils = new NotificationUtils(this, false);
}

Notification.Builder nb = mNotificationUtils.getChannelNotification("Titulo", "Mensaje", R.raw.hakunamatata);
mNotificationUtils.getManager().notify(101, nb.build());
public class NotificationUtils extends ContextWrapper {

    private NotificationManager mManager;
    private boolean isAndroid8;
    public static final String ANDROID_CHANNEL_ID = "com.pablomonteserin.ANDROID";
    public static final String ANDROID_CHANNEL_NAME = "ANDROID CHANNEL";

    public NotificationUtils(Context base, boolean isAndroid8) {
        super(base);
        this.isAndroid8 = isAndroid8;
        if(isAndroid8){
           createChannels();
        }
    }

    @TargetApi(26)
    public void createChannels() {
        // create android channel
        NotificationChannel androidChannel = new NotificationChannel(ANDROID_CHANNEL_ID,
                ANDROID_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
        // Sets whether notifications posted to this channel should display notification lights
        androidChannel.enableLights(true);
        // Sets whether notification posted to this channel should vibrate.
        androidChannel.enableVibration(true);
        // Sets the notification light color for notifications posted to this channel
        androidChannel.setLightColor(Color.GREEN);
        // Sets whether notifications posted to this channel appear on the lockscreen or not
		// androidChannel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        androidChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);

        getManager().createNotificationChannel(androidChannel);
    }

    public NotificationManager getManager() {
        if (mManager == null) {
            mManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        }
        return mManager;
    }

    public Notification.Builder getChannelNotification(String title, String body,int sound) {
        Notification.Builder builder;

        if(isAndroid8){
            builder  = new Notification.Builder(getApplicationContext(), ANDROID_CHANNEL_ID);
        }else{
            builder = new Notification.Builder(getApplicationContext());
        }
        builder .setContentTitle(title)
                .setContentText(body)
                .setSmallIcon(android.R.drawable.stat_notify_more)
                .setAutoCancel(true);
        return builder;
    }
}
notificacion

Ejerccio

Hacer que la notificación muestre una imagen

...
Bitmap aperturaabductoresBitmap = BitmapFactory.decodeResource(getResources(),
                            R.drawable.aperturaabductores);
...
.setStyle(new Notification.BigPictureStyle().bigPicture(bitmap));

Notificación que abre un activity

PendingIntent

Es un envoltorio de un Intent que puede necesitar ciertos permisos para ser ejecutado. Si la aplicación dónde se está ejecutando ya tiene esos permisos, envolviendo el Intent con un PendingIntent, podremos hacer uso de dichos permisos.

Intent notificationIntent = new Intent(this, NotificacionesActivityAbierto.class);

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
...
.setContentIntent(pendingIntent);

Servicios

Son códigos que se ejecutan en segundo plano, aunque la aplicación no esté abierta.

Temporizadores

Son aquellas clases que nos permiten programar tareas que se ejecutarán al cabo de un tiempo.

Recogemos 3 grandes tipos:

  • CounterTimeDown: nos permite ejecutar un evento cada cierto tiempo (siempre que la aplicación esté abierta, si la app está en segundo plano, dará problemas).
    CountDownTimer timer = new CountDownTimer(3000, 1000) {
    	private int counter;
    
    	public void onTick(long millisUnistilFinished) {
    		Log.d("traza", "Se ejecuta el timer");
    	}
    }.start();
    Para cancelarlo:if(isMyServiceRunning(		Servicio.class)){
    	CountDownTimerServiceMovil.timer.cancel();
    }
    
    private boolean isMyServiceRunning(Class<?> serviceClass) {
    	ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    	for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
    		if (serviceClass.getName().equals(service.service.getClassName())) {
    			return true;
    		}
    	}
        return false;
    }
  • AlarmManager: para ejecutar un evento en un momento concreto. Si la invocamos desde un servicio, podremos usarla aunque la applicación no esté abierta.
    Ejemplo de uso de AlarmManagerIntent myIntent = new Intent(this, AlarmReceiver.class);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this,id, myIntent, 0);
    
    AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
    alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + delay, pendingIntent);
    //alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), delay,pendingIntent);
    AlarmReceiver.javapublic class AlarmReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("alarma: ", "MEHH");
            Toast.makeText(context, "ALARM!! ALARM!!", Toast.LENGTH_SHORT).show();
        }
    }
  • JobScheduler: para ejecutar un evento aproximadamente en cierto momento. Supuestamente es evento se producirá más o menos pronto. Si queremos atenernos a un calendario, el AlarmManager será más apropiado. Consume menos recursos que el Alarm Manager, pero a cambio debemos especificar un rango de tiempo en el que se ejecutará cada evento. Si la invocamos desde un servicio, podremos usarla aunque la applicación no esté abierta.
    public class Util {
        public static void scheduleJob(Context context,int time) {
            ComponentName serviceComponent = new ComponentName(context, MovilJobService.class);
            JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
            builder.setMinimumLatency(time * 1000); // wait at least
            builder.setOverrideDeadline(time*1000+5000); // maximum delay
            //builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); // require unmetered network
            //builder.setRequiresDeviceIdle(true); // device should be idle
            //builder.setRequiresCharging(false); // we don't care if the device is charging or not
            JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
            jobScheduler.schedule(builder.build());
        }
    }
    public class MovilJobService extends JobService {
        private static final String TAG = "SyncService";
    
        @Override
        public boolean onStartJob(JobParameters params) {
    }

Ejercicio yoga con notificaciones

Hacer una aplicación que cada 10 segundos nos vaya indicando un cambio de postura de yoga. Estos avisos serán hechos utilizando notificaciones en las que se oirá el sonido de la postura de yoga y se verá su fotografía. Además, al ir al activity de la aplicación, veremos la misma imagen que se mostraba en la notificación. Las notificaciones se ejecutarán aunque el móvil esté con la pantalla apagada o no estemos el el Activity de la aplicación.

Para ejecutar las notificaciones utilizaremos un servicio:

CountDownTimerServicepublic class CountDownTimerService extends Service {
	NotificationManager mNotificationManager;
	public static CountDownTimer timer;

	@Nullable
	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public void onCreate() {
	super.onCreate();
		mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		final Service service = this;
		timer = new CountDownTimer(30000, 10000) {
			private int counter;
			public void onTick(long millisUntilFinished) {
			...

Cuando pulsemos un botón con el texto "terminar servicio" en el MainActivity, dejarán de mostrarse notifiaciones. Además, para poder modificar la imagen del Activity principal, haremos declararemos la variable vinculada a su ImageView pública y estática:

MainActivity.javapublic class MainActivity extends AppCompatActivity {
	public static ImageView iv;
	public static Activity contexto;
	...
	public void cerrarServicio(View v){
		CountDownTimerService.timer.cancel();
		stopService(new Intent(this, CountDownTimerService.class));
	}

Habrá que registrar el servicio en el AndroidManifest.xml

<service android:name="com.pablomonteserin.notifications.ejercicio_yoga.CountDownTimerService"/>

Descargar recursos

Broadcaster Receiver

Nos permiten enviar información a un Activity desde un servicio.

Servicio.javaIntent bc_intent = new Intent();
bc_intent.setAction("actualizar_foto");
bc_intent.putExtra("idDeLaFoto",R.drawable.torsion);
sendBroadcast(bc_intent);
Activity.java//Es necesario registrar un escuchador del broadcasterpublic void onResume(){
	super.onResume();
	IntentFilter intentFilter = new IntentFilter();
	intentFilter.addAction("actualizar_foto");
	registerReceiver(mMessageReceiver, intentFilter);
}
//Cuando el usuario cierre el Activity, dejaremos de escuchar el BroadcastReceiver
public  void onPause(){
	super.onPause();
	unregisterReceiver(mMessageReceiver);
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
	@Override
	public void onReceive(Context context, Intent intent) {
		ImageView iv = findViewById(R.id.imagen);
		iv.setImageResource(intent.getExtras().getInt("idDeLaFoto"));
	}
};

Solicitar el servicio de wifi y comprobar su estado


public class Main extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
        TextView tv = (TextView) findViewById(R.id.textView1);
        boolean wifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnectedOrConnecting();
        if(wifi)tv.setText("El wifi está activo");
        else tv.setText("El wifi no está activo");
    }
}

Habrá que añadir un permiso al AndroidManifest.xml:
Doble click en AndroidManifest.xml → Pestaña Permissions → Add → Uses permission → android.permission.ACCESS_NETWORK_STATE

Otros controles

Desde el emulador → Applications → API Demos

Network connection

public class MainActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        final EditText et = (EditText) findViewById(R.id.editText1);
        final Button b = (Button) findViewById(R.id.button1);
        final TextView tv = (TextView) findViewById(R.id.textView1);
        
        b.setOnClickListener(new OnClickListener(){

			@Override
			public void onClick(View arg0) {
				try{
					URL url = null;
					url = new URL(et.getText().toString());
					URLConnection conn = url.openConnection();
					BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
					String line = "";
					while((line = reader.readLine()) != null){
						tv.append(line);
					}
				}catch(Exception e){
					
				}
			}
        	
        });
    }
}

ItemList

Qué es una Lista

Una lista en el contexto de Activity es la interfaz gráfica que nos permite navegar por una serie de datos.

lista android

ID's de una lista

android:id="@android:id/list"
→ Utilizo una id ya existente en la API de android. Lo utilizo cuando extiendo de list activity.

android:id="@+id/miLista"
Crea una nueva id

Extendiendo ListActivity

Para ello la activity debe heredar de la clase ListActivity y la lista debe ser identificada en el layout (main.xml) de la siguiente forma:

activity_main.xml<ListView android:id="@android:id/list" android:layout_height="wrap_content" android:layout_width="match_parent">
</ListView>
public class Main extends ListActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
       setListAdapter(new ArrayAdapter<String>(this, 			
	android.R.layout.simple_list_item_1,
	getResources().getStringArray(R.array.countries)));
    }
}
res/values/countries.xml<?xml version="1.0" encoding="UTF-8"?>
<resources>
	<string-array name="countries">
		<item name="usa">United States</item>
		<item name="brazil">Brazil</item>
		<item name="russia">Russia</item>
		<item name="egypt">Egypt</item>
		<item name="japan">Japan</item>
	</string-array>
</resources>

Mediante el método setListAdapter definimos cuál será el layout de cada uno de los items de la lista

El segundo parámetro carga un layout que traen las librerías de android

El tercer parámetro indica cuál es la información que cargará la lista.

lista banderas paises

El arrayAdapter está pensado para tener en cada posición una sola cosa, un texto, una foto, etc.

Content Provider

Nos permiten acceder a la información de otras aplicaciones incluida en un dispositivo, por ejemplo, a la agenda telefónica.

En un content provider los datos pueden estar almacenados:

  • en una base de datos.
  • en archivos.
  • en una red.

Cómo acceder a los contactos

Para leer los contactos, primero habrá que añadir uno:
Símbolo del teléfono → botón de menú → Add contact.

Añadimos el permiso justo antes del cierre del Manifest:
<uses-permission android:name="android.permission.READ_CONTACTS" />

ContentResolver cr = getContentResolver();
Cursor contactos = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
…
String name = contactos.getString(contactos.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
  • FROM table_name
  • WHERE col = value
  • ORDER BY col,col,..
  • El array de columnas que será incluído en cada fila recuperada
  • Remplazo de las ? En la clausula anterior

Obtener los teléfonos

private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 0;
...
//A partir de Android 6 es necesario crear un aviso solicitando acceder a los contactos en tiempo de ejecución
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
	if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
		ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS);
	}else{
		gestionarContactos();

	}
}else{
	gestionarContactos();
}

ContentResolver cr = getContentResolver();
Cursor contactos = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
String phoneNo = "";
if (contactos.getCount() > 0) {
	while (contactos.moveToNext()) {
		String id = contactos.getString(contactos.getColumnIndex(ContactsContract.Contacts._ID));
		String name = contactos.getString(contactos.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
		if (Integer.parseInt(contactos.getString(contactos.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
			Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,
				ContactsContract.CommonDataKinds.Phone.CONTACT_ID+ " = ?", new String[] { id }, null);
			while (pCur.moveToNext()) {
				phoneNo = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
				String user = "Name: " + name + ", Phone No: "+ phoneNo;
				Log.d("user", user);
			}
			pCur.close();
		}
	}
}
...
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
	switch (requestCode) {
		case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    gestionarContactos();

                } else {
                    // permission denied, boo! Disable the
                    // functionality that depends on this permission.
                }
                return;
            }
            // other 'case' lines to check for other
            // permissions this app might request	
	}
}

Ejercicio

Recuperar los contactos del teléfono y mostrarlos en un ListView

Lista sin extender de ListActivity

public class Main extends Activity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		ListView lv = (ListView) findViewById(R.id.miLista);
		lv.setAdapter(new ArrayAdapter<String>(this, 			
				android.R.layout.simple_list_item_1,
				getResources().getStringArray(R.array.countries)));
	}

Mediante el método setAdapter definimos cuál será el layout de cada uno de los items de la lista

El segundo parámetro carga el layout personalizado que he hecho.

El tercer parámetro indica cuál es la información que cargará la lista.

Adapter

Escogeremos el tipo de Adapter a usar en función de los datos que queremos cargar.

  • Si tenemos un ArrayList de Strings usaremos ArrayAdapter.
  • Si tenemos un cursor de la base de datos, usaremos CursorAdapter.
  • BaseAdapter puede usarse para cualquier cosa, es muy versátil, aunque requiere algo mas de código.

Haciendo que cada row cargue un layout personalizado
para ello hay que sobrescribir el método getView del Adapter

  1. Creamos un nuevo layout que utilizará una de las filas de la lista, llamado list_item_layout.xml.
  2. Añado un textview y un imageview al layout.
  3. Me aseguro de que el layout sea horizontal y no vertical (vamos a ir poniendo filas, no columnas).
  4. En la siguiente diapositiva, el código de la clase Main.java.

Utilizando BaseAdapter

ListView lv = (ListView) findViewById(R.id.miLista);
lv.setAdapter(new MyAdapter(getPaises()));
...
public ArrayList<Pais> getPaises() {
	ArrayList<Pais> paises = new ArrayList<Pais>();
	paises.add(new Pais("United States", "flag_usa"));
	paises.add(new Pais("Brazil", "flag_brasil"));
	paises.add(new Pais("Russia", "flag_russia"));
	paises.add(new Pais("Egypt", "flag_egypt"));
	paises.add(new Pais("Japan", "flag_japan"));
	return paises;
}
...
private class MyAdapter extends BaseAdapter {
	ArrayList<Pais> paises;
	public MyAdapter(ArrayList <Pais>paises) {
		super();
		this.paises = paises;
	}
	@Override
	public int getCount() {
		return paises.size();
	}
//MyAdapter llama automáticamente al método getView para cargar la lista con información
//Dicho método se va llamar muchas veces (una cada vez que tenga que pintar una posición de la lista)
	public View getView(int position, View convertView, ViewGroup parent) {
		View row = View.inflate(MainActivity.this,R.layout.list_item, null);
		TextView tv = (TextView) row.findViewById(R.id.textView1);
		ImageView iv = (ImageView) row.findViewById(R.id.imageView1);
		tv.setText(paises.get(position).getNombre());
		//getPackageName() me devuelve el nombre del paquete que identifica a nuestra aplicación
		iv.setImageResource(getResources().getIdentifier(paises.get(position).getImg(), "drawable", getPackageName()));
		return row;
	}
Descargar imágenes.

Optimizaciones

Optimización fundamental del adapter

@Override
public View getView(int position, View convertView, ViewGroup parent) {
	View row;
	//convertView almacena temporalmente cada una de las filas de la lista.
	// Cuando la fila deja de ser visible tendré un convertView con la fila ya inflada no estoy utilizando. 
	//Puedo reutilizarlo para almacenar la siguiente fila, en vez de crear uno nuevo
	if(convertView == null)row = View.inflate(ItemListConImgActivity.this, R.layout.list_item, null);
	else row = convertView;

//Antes de optimización, para una lista de 100 filas tendría que inflar las 100, utilizando apropiadamente el convertView, me basta con inflar sólo las que se están viendo en pantalla

Optimización útil para listas grandes

private class MyAdapter extends BaseAdapter{
…
	public View getView(int position, View convertView, ViewGroup parent) {
		View row;
		//Con esta optimización conseguimos llamar sólo 10 veces al findViewById, en vez de 100. 			
		//Ideal para listas largas.
		if(convertView == null){
			row = View.inflate(ItemListConImgActivity.this, R.layout.list_item, null);
			ViewHolder vh = new ViewHolder();
			vh.iv = (ImageView) row.findViewById(R.id.imageView1);
			vh.tv = (TextView) row.findViewById(R.id.textView1);
			row.setTag(vh);
		}else row = convertView;
		ViewHolder vh = (ViewHolder) row.getTag();
		vh.tv.setText(items[position]);
		vh.iv.setImageResource(getResources().getIdentifier(itemsImg[position], "drawable", getPackageName()));
		return row;
	}
…
	private static class ViewHolder{
		TextView tv;
		ImageView iv;
	}
}

Ejercicio - plantas

ChachiConsejos:

Haremos que el AppCompatActivity implemente la interfaz OnItemClickListener, de tal forma que al hacer click sobre un Item iremos a otro activity pasándole los datos de la planta seleccionada.

startActivity(new Intent(this,InfoActivity.class).putExtra(PLANTA,plantas.get(position)));

Para recoger un Pojo, este debe implementar la interfaz serializable y debemos usar:

Planta planta = (Planta) getIntent().getExtras().getSerializable(MisPlantasActivity.PLANTA);
ejercicio plantas Descargar imágenes.

Ejercicio - plantas II

Para hacer la siguiente ampliación del ejercicio, deberemos utilizar una base de datos.

Añadir un ToggleButton al segundo activity. Al pulsarlo, la planta correspondiente en el activity principal se mostrará pintada de verde. Para ello cambiaremos un campo llamado “is_checked” definido en la base de datos.

Ejercicio plantas 2

XML y JSON

Abrir fichero XML

res/raw/libros.json<?xml version="1.0" encoding="utf-8"?>
<libros>
    <libro>Viaje la luna</libro>
    <libro>Buscando a Wally</libro>
</libros>
InputStream  inputStream = this.getResources().openRawResource(R.raw.libros);

Parsear XML

Modificaremos la siguiente clase para adaptarla al documento que queremos parsear.

XMLParserpublic class XMLParser {
    private static final String ns = null;

    public List parse(InputStream in) throws XmlPullParserException, IOException {
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
            parser.setInput(in, null);
            parser.nextTag();
            return readFeed(parser);
        } finally {
            in.close();
        }
    }

    private List readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
        List entries = new ArrayList();

        parser.require(XmlPullParser.START_TAG, ns, "libros");
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) {
                continue;
            }
            String name = parser.getName();
            if (name.equals("libro")) {
                entries.add(readEntry(parser));
            } else {
                skip(parser);
            }
        }
        return entries;
    }

    private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException {
        parser.require(XmlPullParser.START_TAG, ns, "libro");
        String titulo = null;
        String autor = null;
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) {
                continue;
            }
            String name = parser.getName();
            if (name.equals("titulo")) {
                titulo = readTitulo(parser);
            } else if (name.equals("autor")) {
                autor = readNombre(parser);
            } else {
                skip(parser);
            }
        }
        return new Entry(titulo, autor);
    }

    private String readTitulo(XmlPullParser parser) throws IOException, XmlPullParserException {
        parser.require(XmlPullParser.START_TAG, ns, "titulo");
        String titulo = readText(parser);
        parser.require(XmlPullParser.END_TAG, ns, "titulo");
        return titulo;
    }

    private String readNombre(XmlPullParser parser) throws IOException, XmlPullParserException {
        String nombre = "";
        parser.require(XmlPullParser.START_TAG, ns, "autor");
        String tag = parser.getName();
        String relType = parser.getAttributeValue(null, "tipo");
        if (tag.equals("autor")) {
            if (relType.equals("bueno")){
                nombre = parser.getAttributeValue(null, "nombre");
                parser.nextTag();
            }else{
                nombre ="No merece la pena recordarle";
                parser.nextTag();
            }
        }
        parser.require(XmlPullParser.END_TAG, ns, "autor");
        return nombre;
    }

    private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
        String result = "";
        if (parser.next() == XmlPullParser.TEXT) {
            result = parser.getText();
            parser.nextTag();
        }
        return result;
    }

    private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
        if (parser.getEventType() != XmlPullParser.START_TAG) {
            throw new IllegalStateException();
        }
        int depth = 1;
        while (depth != 0) {
            switch (parser.next()) {
                case XmlPullParser.END_TAG:
                    depth--;
                    break;
                case XmlPullParser.START_TAG:
                    depth++;
                    break;
            }
        }
    }
    public static class Entry {
        public final String titulo;
        public final String nombre;

        private Entry(String titulo, String nombre) {
            this.titulo = titulo;
            this.nombre = nombre;
        }
    }

}
res/raw/libros.xml<?xml version="1.0" encoding="utf-8"?>
<libros>
    <libro>
        <titulo>Viaje la luna</titulo>
        <autor tipo="bueno" nombre="Monteserín" />
    </libro>
    <libro>
        <titulo>Buscando a Wally</titulo>
        <autor tipo="malo" nombre="García" />
    </libro>
</libros>

Parsear JSON

Modificaremos la siguiente clase para adaptarla al documento que queremos parsear.

public class JSONParser {
    public List<Libro> readJsonStream(InputStream in) throws IOException {
        JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
        try {
            return readMessagesArray(reader);
        } finally {
            reader.close();
        }
    }

    public List<Libro> readMessagesArray(JsonReader reader) throws IOException {
        List<Libro> messages = new ArrayList<Libro>();

        reader.beginArray();
        while (reader.hasNext()) {
            messages.add(readMessage(reader));
        }
        reader.endArray();
        return messages;
    }

    public Libro readMessage(JsonReader reader) throws IOException {
        long id = -1;
        String titulo = null;
        Autor autor = null;
        List<Double> geo = null;

        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals("id")) {
                id = reader.nextLong();
            } else if (name.equals("titulo")) {
                titulo = reader.nextString();
            } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
                geo = readDoublesArray(reader);
            } else if (name.equals("autor")) {
                autor = readAutor(reader);
            } else {
                reader.skipValue();
            }
        }
        reader.endObject();
        return new Libro(id, titulo, geo, autor);
    }

    public List<Double> readDoublesArray(JsonReader reader) throws IOException {
        List<Double> doubles = new ArrayList<Double>();

        reader.beginArray();
        while (reader.hasNext()) {
            doubles.add(reader.nextDouble());
        }
        reader.endArray();
        return doubles;
    }

    public Autor readAutor(JsonReader reader) throws IOException {
        String nombre = null;
        int seguidores = -1;

        reader.beginObject();
        while (reader.hasNext()) {
            String name = reader.nextName();
            if (name.equals("nombre")) {
                nombre = reader.nextString();
            } else if (name.equals("seguidores")) {
                seguidores = reader.nextInt();
            } else {
                reader.skipValue();
            }
        }
        reader.endObject();
        return new Autor(nombre, seguidores);
    }

    public static class Libro {
        public final long id;
        public final String titulo;
        public final List<Double> geo;
        public final Autor autor;

        private Libro(long id, String titulo, List<Double> geo, Autor autor) {
            this.id = id;
            this.titulo = titulo;
            this.geo = geo;
            this.autor = autor;
        }
    }

    public static class Autor {
        public final String nombre;
        public final int seguidores;

        private Autor(String nombre, int seguidores) {
            this.nombre = nombre;
            this.seguidores = seguidores;
        }
    }
}
res/raw/libros.json[
	{
    	"id": 912345678901,
    	"titulo": "El amor prevalecerá sobre las imprecisiones",
    	"geo": null,
    	"autor": {
    		"nombre": "android_newb",
    		"seguidores": 41
    	}
    },
    {
    "id": 912345678902,
    "titulo": "El amor como medio de vida",
    "geo": [50.454722, -104.606667],
    "autor": {
    	"nombre": "jesse",
		"seguidores": 2
    }
	}
]

AsynTask

Petición por GET

new MiAsyncTask().execute("http://pablomonteserin.com/curso/java/rec/login.php");

//El primer parámetro es lo que recibe doInBackground
//El segundo parámetro es lo que recibe un método que no estamos utilizando
//El tercer parámetro es lo que devuelve doInBackground (una cookie es un String)

private class MiAsyncTask extends AsyncTask<String, Integer, String> {
	//doInBackground es un método que se ejecuta en el background. 
	//Aquí realizamos operaciones pesadas que pueden durar mucho tiempo dando lugar a una excepción
	//doInBackground no debe tocar nada de la interfaz visual. Estas operaciones serán hechas en onPostExecute
	protected String doInBackground(String... urls) {
		URL url = new URL(urls[0]);
		URLConnection urlConnection = url.openConnection();
		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
		String line;
		while ((line = bufferedReader.readLine()) != null){
			content.append(line + "\n");
		}
		bufferedReader.close();
		return content.toString();
	}

	//El método onPostExecute debe recibir lo que devuelve el doInBackground (en este caso, un String)
	protected void onPostExecute(String txt) {
	}
}

Petición por POST

Ejercicio searchUPC

El código UPC que escribimos se lo mandaremos a la cuenta
https://pablomonteserin.com/curso/web/servidor/php/fundamentos/res/miUPCsearch.php?upc_code=upcCode

Para probar podremos utilizar el código:
upcCode=1234

Usaremos el permiso:

<uses-permission android:name="android.permission.INTERNET" />

Mientras que se está realizando el envió y la recepción de la información solicitada, veremos en pantalla una progressBar.

El layout del listview será un linear layout. Para bloquear el listener del imageview y del textview que tiene dentro y escuchar sólo cuando hago click sobre una fila del listview, añadiré el atributo android:descendantFocusability="blocksDescendants" al linearlayout.

Al pulsar sobre la foto, vamos al siguiente Activity. En el segundo activity mostraremos el resto de los datos y si pulsamos sobre el botón de ir a página web iremos a la web indicada en el bea.

ejercicio resuelto

Petición POST con Retrofit

Subida de una imagen de la galería al servidor con Retrofit

SubidaImagen.javapublic class SubirImagen extends AppCompatActivity {

	private boolean isAndroid6;
	private final int MY_PERMISSIONS_REQUEST_CAMARA = 1;

	TextView image_name_tv;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_subir_imagen);
		image_name_tv = findViewById(R.id.nombreFoto);

		if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
			isAndroid6 = true;
			askPermissionsIfneeded();
		}
	}
	private void askPermissionsIfneeded(){
		if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
			ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, MY_PERMISSIONS_REQUEST_CAMARA);
		}
	}

	@Override
	public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
		switch (requestCode) {
			case MY_PERMISSIONS_REQUEST_CAMARA: {
			// If request is cancelled, the result arrays are empty.	
				if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
					// permission was granted, yay! Do the
					// contacts-related task you need to do.
				}else {
					// permission denied, boo! Disable the
					// functionality that depends on this permission.
				}
				return;
			}
            // other 'case' lines to check for other
            	// permissions this app might request
		}
	}

    public void sacarFoto(View v){
        Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
        photoPickerIntent.setType("image/*");
        startActivityForResult(photoPickerIntent, 1);

        /*Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        	if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        	    startActivityForResult(takePictureIntent, 1);
        	}*/
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 1)
            if (resultCode == Activity.RESULT_OK) {
                Uri selectedImage = data.getData();

                String filePath = getPath(selectedImage);
                String file_extn = filePath.substring(filePath.lastIndexOf(".") + 1);
                image_name_tv.setText(filePath);

                if (file_extn.equals("img") || file_extn.equals("jpg") || file_extn.equals("jpeg") || file_extn.equals("gif") || file_extn.equals("png")) {
                    File file = new File(filePath);
                    RequestBody requestFile =
                            RequestBody.create(
                                    MediaType.parse(getContentResolver().getType(selectedImage)),
                                    file
                            );
                    MultipartBody.Part body =
                            MultipartBody.Part.createFormData("sampleFile", file.getName(), requestFile);
                    String descriptionString = "hello, this is description speaking";
                    RequestBody description =
                            RequestBody.create(
                                    okhttp3.MultipartBody.FORM, descriptionString);
                    RetrofitService retrofitService = RetrofitService.getInstance();
                    PabloAPI api = retrofitService.getApiProxyServer();
                    api.uploadFile(description,body).enqueue(new Callback<ResponseBody>() {
                        @Override
                        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                            Toast.makeText(SubirImagen.this, "Imagen subida", Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onFailure(Call<ResponseBody> call, Throwable t) {
                            Toast.makeText(SubirImagen.this, "Error", Toast.LENGTH_SHORT).show();
                        }
                    });

                } else {
                    //NOT IN REQUIRED FORMAT
                }
            }
    }

    public String getPath(Uri uri) {
        String[] filePathColumn = { MediaStore.Images.Media.DATA };
        Cursor cursor = getContentResolver().query(uri,filePathColumn, null, null, null);
        cursor.moveToFirst();
        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
        String picturePath = cursor.getString(columnIndex);
        cursor.close();
        return picturePath;
    }
}
PabloAPIpublic interface PabloAPI {
    @Multipart
    @POST("subida/upload")
    Call<ResponseBody> uploadFile(@Part("description") RequestBody description, @Part MultipartBody.Part file);
}
public class RetrofitService {
	private static RetrofitService INSTANCE = null;
	private static final String BASE_URL = "http://pablomonteserin.com:19139";
	private PabloAPI apiService;

	// Private constructor suppresses    
	private RetrofitService(){
		Retrofit builder = new Retrofit.Builder()
			.baseUrl(BASE_URL)
			.addConverterFactory(GsonConverterFactory.create())
			.build();

		apiService = builder.create(PabloAPI.class);
	}

	// creador sincronizado para protegerse de posibles problemas  multi-hilo
	// otra prueba para evitar instanciación múltiple
	private synchronized static void createInstance() {
		if (INSTANCE == null) {
			INSTANCE = new RetrofitService();
		}
	}

	public static RetrofitService getInstance() {
		if (INSTANCE == null) createInstance();
		return INSTANCE;
	}

	public PabloAPI getApiProxyServer(){
		return apiService;
	}
}
build.gradle (module)// Retrofit
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
Servidor nodejs con Expressrouter.post('/upload', function(req, res) {
  console.log(req.files);
  console.log("amor");
  if (!req.files)
    return res.status(400).send('No files were uploaded.');
  // The name of the input field (i.e. "sampleFile") is used to retrieve the uploaded file
  let sampleFile = req.files.sampleFile;
 
  // Use the mv() method to place the file somewhere on your server
  sampleFile.mv('/usr/home/pablomonteserin/termine/public/subida/filename.jpg', function(err) {
    var path = require("path");
      console.log("./ = %s", path.resolve("./"));
      console.log("__dirname = %s", path.resolve(__dirname));

    if (err){
      console.log("adasda")
      return res.status(500).send(err);
    }
    res.send('File uploaded!');
  });
});

router.get('/', function(req, res){
	res.write(`<html>
	<body>
	<form ref='uploadForm' 
		id='uploadForm' 
		action='http://pablomonteserin.com:19139/subida/upload' 
		method='post' 
		encType="multipart/form-data">
		<input type="file" name="sampleFile" />
		<input type='submit' value='Upload!' />
		</form>     
		</body>
	</html>`);
  	res.end();
});
app.jsconst fileUpload = require('express-fileupload');
app.use(fileUpload());

Mapas

Recomendación

No probar ejercicios que impliquen geolocalización en el emulador. Unos funcionan, otros no, otros funcionan mal, etc.

Geolocalización

La podemos obtener por 3 medios:

  • GPS: (el más preciso y lento (puede tardar incluso más de un minuto)). 2 metros de error.
  • Red móvil: El móvil detecta 3 antenas, hace un triángulo (el método se llama triangulación) y detecta la posición (con un margen de error de 20, 50 m...) depende de la distancia con las antenas. En ciudad hay menos error que en el campo. Tarda unos segundos
  • Wi-Fi: Funciona si estoy conectado a una red WI-FI. El móvil detecta el SSID (nombre de la red) de la wi-fi y accede a una base de datos donde tiene registrada su ubicación.

La geolocalizacion del emulador no funciona correctamente pq no tiene ni gps, ni red movil ni han integrado la geolocalización por wi-fi.
Para falsear la geolocalización del emulador: window-> show view -> other -> emulator control
Habrá que asegurarse de tener el device seleccionado en el momento de pulsar el botón de send.

Habrá que añadir los siguientes permisos:<!--Localización usando la red WI-FI: –>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--Localización usando el GPS –>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Location Manager

Mostrar coordenadas en un textview

Tendremos que solicitar permisos al usuario de forma similar a como lo hicimos cuando queríamos acceder a los contactos.

LocationManager lm = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
LocationListener listener = new LocationListener(){
	...
	public void onLocationChanged(Location location) {
		textView.setText("Latitud: "+location.getLatitude()+"Longitud: "+location.getLongitude());	
	}
};

//La siguiente linea activa la geolocalizacion cada 0 milisegundos y cada 0 metros que me desplace : lo q hará realmente será darme la info con la mayor constancia posible
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
//GPS_PROVIDER actualizaciones del GPS, NETWORK_PROVIDER: actualizaciones usando la red móvil

implementando LocationListener)

public class TrackingActivity extends Activity implements LocationListener {
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);
	LocationManager lm = (LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
	lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,this);
	//El NETWORK_PROVIDER sólo funciona en el teléfono móvil, no en el emulador
	//lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0,this);
}

public void onLocationChanged(Location arg0) {
	Toast toast = Toast.makeText(this, "Latitud: "+ arg0.getLatitude()+" Longitude: "+ arg0.getLongitude(), Toast.LENGTH_LONG);
	toast.show();
}
//Esto ocurre cuando desactivas el gps, la red móvil, etc
public void onProviderDisabled(String arg0) {}

//Esto ocurre cuando activas el gps, la red móvil, etc.
public void onProviderEnabled(String arg0) {}

OnResume, OnPause

public void onResume() {
	super.onResume();
	locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
	locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
	locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
}

public void onPause() {
	super.onPause();
	// La siguiente línea hace que la ubicación deje de actualizarse, con lo cual se ahorra batería
	locationManager.removeUpdates(this);
}

Proximity Alert

Cuando esté cerca del punto, se lanzará una activity

LocationManager lm;

public void onCreate(){
	lm = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
	lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
	lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0,this);	
}
public void onResume() {
	…
	Intent intent = new Intent(this, LanzadaAlEstarCercaDelPunto.class);
	//El requestCode debe ser único para cada código de proximidad
	//FLAG_CANCEL_CURRENT: si hay una alerta igual a esta (tiene el mismo requestCode) la cancela
	PendingIntent pi = PendingIntent.getActivity(this, 0, intent,PendingIntent.FLAG_CANCEL_CURRENT);
	//latitud y longitud (de Madrid), radio en metros, tiempo de expiración de la alerta en milisegundos, pending intent
	lm.addProximityAlert(40.4656199, -3.6892187, 1000000, -1, pi);
}

public void onPause() {
	super.onPause();
	lm.removeUpdates(this);//Esto hace que deje de actualizarse, con lo cual se ahorra batería
}

PendingIntent
Es un envoltorio de un Intent que puede necesitar ciertos permisos para ser ejecutado. Si la aplicación dónde se está ejecutando ya tiene esos permisos, envolviendo el Intent con un PendingIntent, podremos hacer uso de dichos permisos.

Criteria

Criteria sólo funciona a partir de la API 9.

Nos permitirá determinar las características de la geolocalizacion que vamos a tratar de realizar a continuación.

Criteria crit = new Criteria();
crit.setAccuracy(Criteria.ACCURACY_COARSE);//exactitud de las mediciones. Para el método setAccuracy sólo funcionan 2 constantes ACCURACY_COARSY Y ACCURACY_FINE
crit.setPowerRequirement(Criteria.POWER_LOW); //cuanta batería va a utilizar
crit.setSpeedRequired(false); // no estamos recuperando la velocidad, así que lo podemos especificar. Es true por defecto
lm.requestLocationUpdates(0, 0, crit, pendingIntent);

Obtener una Map API Key (modo debug)

https://cloud.google.com/console → Seleccionamos en la barra horizontal superior el proyecto actual y se abrirá un panel que nos permitirá crear un nuevo proyecto → Create Project → APIs → go to the api's overview → enable API → Google Maps Android API → Enable → Create Credentials → Create new Key → SHA1*;nombre del paquete dónde estará la clase que cargará el mapa en nuestro proyecto final → anotamos la API key.

Añadir un mapa

¿Qué es un fragmento?

Es una parte de una Activity, que tiene su propio ciclo de vida, recibe sus propios eventos de entrada, y que se puede añadir o quitar dinámicamente.

Añadir un mapa I

  1. Vista project → app → build.gradle → en dependencias añado lo siguiente: compile 'com.google.android.gms:play-services:+'
  2. Además, en el layout habrá que añadir el siguiente código que representará el mapa:
    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/map"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            class="com.google.android.gms.maps.SupportMapFragment"/>

En el caso de que esté actualizando un proyecto viejo es probable que necesite ejecutar-> Botón derecho sobre el proyecto → Android Tools → Add Support Library (para poder usar fragmentos)

*Google Maps API v3 – es para navegadores web.

Añadir un mapa II como quedaría el AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.pablomonteserin.mapas"  android:versionCode="1" android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <!-- lo que sigue es la versión del opengl mínimo que necesita el dispositivo para instalar la aplicación. Si no la tiene no le aparecerá en la google play -->
    <uses-feature android:glEsVersion="0x00020000"  android:required="true" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <uses-library android:name="com.google.android.maps" />
        <activity
            android:name="com.pablomonteserin.mapas.GeolocalizacionCapasActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="AIzaSyDKH-mnui5hMmO3wnTdrmXy4V5IVs6KTR8" />
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    </application>
</manifest>

Añadir un mapa III

public class MainActivity extends android.support.v4.app.FragmentActivity {
private GoogleMap mapa;

	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.acitivity_main);

Centrar el mapa en un punto

public class MainActivity extends android.support.v4.app.FragmentActivity {
private GoogleMap mapa;
…
 mapa = ((SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map)).getMap();
LatLng madrid = new LatLng(40.417325, -3.683081);
CameraPosition camPos = new CameraPosition.Builder()
                    .target(madrid)   //Centramos el mapa en Madrid
                    .zoom(19)          //Establecemos el zoom en 19
                    .bearing(45)       //Establecemos la orientación con el noreste arriba
                    .tilt(70)       	//Bajamos el punto de vista de la cámara 70 grados
                    .build();
        
CameraUpdate camUpd3 = CameraUpdateFactory.newCameraPosition(camPos);
        
mapa.animateCamera(camUpd3);

Geolocalización con mapas

…
myTracker= new MyTracker(this);
myTracker.setListener(this);
...

@Override
    protected void onStart() {
        super.onStart();
        myTracker.encender();
    }

    @Override
    protected void onStop() {
        myTracker.apagar();
        super.onStop();
    }

    @Override
    public void nuevaPosicion(Location loc) {
        myLatLng = new LatLng(loc.getLatitude(),loc.getLongitude());
        camPos = new CameraPosition.Builder()
                .target(myLatLng)	//Centramos el mapa en Madrid
                .zoom(11)			//Establecemos el zoom en 19
                .build();
        camUpd3 =  CameraUpdateFactory.newCameraPosition(camPos);
        mapa.animateCamera(camUpd3);
    }
Descargar ficheros necesarios.

Pintar puntos

mapa.addMarker(new MarkerOptions().position(myLatLng).icon(BitmapDescriptorFactory.fromResource(R.drawable.icon)));

Ejercicio

Hacer una aplicación que utilizando una base de datos SqlLite situe en pantalla 3 puntos, identificados por el logo suministrado. Al pulsar sobre el logo, iremos a una pantalla dónde se mostrará información del enclave pulsado.

Recursos:

Notas:
Marker marker = mapa.addMarker(new MarkerOptions()
	.position(new LatLng(lat, lon))
	.snippet(monumento.getDescripcion())
	.title(monumento.getNombre())
	.icon(BitmapDescriptorFactory.fromResource(R.drawable.escudo)));			
tabla.put(marker, monumento.getId());

monumentos es un HashMap de objetos Monumento.

Para pasar los datos del enclave pulsado de un Activity a otro:
startActivity(new Intent(GeolocalizacionCapasActivity.this,
LugarActivity.class).putExtra(MONUMENTO, monumentos.get(arg0)));

Para recibirlo:
Monumento monumento = (Monumento) getIntent().getExtras().getSerializable(GeolocalizacionCapasActivity.MONUMENTO);

Obtener la ubicación de cierto lugar

String direccion = "Camino de Rubin 2, Gijón";

Geocoder geocoder = new Geocoder(this);  
List<Address> addresses;
addresses = geocoder.getFromLocationName(direccion, 1);
if(addresses.size() > 0) {
	double latitude= addresses.get(0).getLatitude();
	double longitude= addresses.get(0).getLongitude();
	tv.setText("Latitud="+latitude+" Longitud="+longitude);
}

Rutas

Rutas I

LatLng Barcelona = new LatLng(41.387917,2.169918);
String url = makeURL(Madrid.latitude, Madrid.longitude, Barcelona.latitude, Barcelona.longitude);
new connectAsyncTask().execute(url);

Rutas II

private class connectAsyncTask extends AsyncTask<String, Void, String> {
	private ProgressDialog progressDialog;

	@Override
	protected void onPreExecute() {
		super.onPreExecute();
		progressDialog = new ProgressDialog(MainActivity.this);
		progressDialog.setMessage("Calculando ruta, por favor espere...");
		progressDialog.setIndeterminate(true);
		progressDialog.setCancelable(false);
		progressDialog.show();
	}
	@Override
	protected String doInBackground(String... urls) {
		JSONParser jParser = new JSONParser();
		String json = jParser.getJSONFromUrl(urls[0]);
		return json;
	}
	@Override
	protected void onPostExecute(String result) {
		super.onPostExecute(result);
		progressDialog.hide();
		if (result != null) {
			drawPath(result);}}}

Rutas III

private void drawPath(String result) {
		try {
			// Tranform the string into a json object
			final JSONObject json = new JSONObject(result);
			JSONArray routeArray = json.getJSONArray("routes");
			JSONObject routes = routeArray.getJSONObject(0);
			JSONObject overviewPolylines = routes.getJSONObject("overview_polyline");
			String encodedString = overviewPolylines.getString("points");
			List<LatLng> list = decodePoly(encodedString);
			for (int z = 0; z < list.size() - 1; z++) {
				LatLng src = list.get(z);
				LatLng dest = list.get(z + 1);
				Polyline line = mapa.addPolyline(new PolylineOptions()
						.add(new LatLng(src.latitude, src.longitude),
								new LatLng(dest.latitude, dest.longitude))
						.width(2).color(Color.BLUE).geodesic(true).width(6f));
				listaPolylineas.add(line);
			}
		} catch (JSONException e) {}
	}

Rutas IV

private List<LatLng> decodePoly(String encoded) {
	List<LatLng> poly = new ArrayList<LatLng>();
	int index = 0, len = encoded.length();
	int lat = 0, lng = 0;
	while (index < len) {
		int b, shift = 0, result = 0;
		do {
			b = encoded.charAt(index++) - 63;
			result |= (b & 0x1f) << shift;
			shift += 5;
		} while (b >= 0x20);
		int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
		lat += dlat;
		shift = 0;
		result = 0;
		do {
			b = encoded.charAt(index++) - 63;
			result |= (b & 0x1f) << shift;
			shift += 5;
		} while (b >= 0x20);
		int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
		lng += dlng;
		LatLng p = new LatLng((((double) lat / 1E5)),(((double) lng / 1E5)));
		poly.add(p);
	}
	return poly;
}

Login

Login con Google

Instalación

  1. Tendremos que loguearnos en la consola de Firebase
  2. Crear nuevo proyecto
  3. google-services.json
    1. Engranaje de settings -> Project Settings -> Download google-services.json
    2. En Android Studio cambiamos el modo de visualización del proyecto a Proyecto.
    3. Arrastramos el fichero creado (google-services.json) sobre la carpeta APP.
  4. Pegamos en el build.gradle del módulo y proyecto las líneas que nos indica el asistente de configuración.
  5. Abrimos el fichero Build.gradle del módulo y copiamos el valor del applicationID. Nos vamos a la página de firebase y lo pegamos en: Engranaje de settings -> Project Settings -> Package Name.
  6. Habrá que añadir los siguientes repositorios:
    build.gradle (project)allprojects {
        repositories {
            maven { url 'https://maven.google.com'  }
        }
    }
  7. build.gradledependencies {
    	compile 'com.google.firebase:firebase-auth:11.4.2'
    	compile 'com.google.android.gms:play-services-auth:11.4.2'
    	...
  8. Para poder recuperar datos del usuario como la contraseña, debemos ejecutar el siguiente código en nuestro ordenador:
    $ keytool -genkey -v -keystore my-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
    y copiar el codigo SHA que genera en Consola de Firebase -> tuerquita de configuración -> Configuración del proyecto -> huella digital del certificado

Configuración

layout.xml<com.google.android.gms.common.SignInButton
	android:id="@+id/sign_in_button"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
/>
Login.javapublic class MainActivity extends AppCompatActivity implements GoogleApiClient.OnConnectionFailedListener{ 
	private GoogleApiClient googleApiClient;
	private SignInButton signInButton;
	private int SIGN_IN_CODE;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.login_google);

		GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
			.requestEmail() //Esta línea es opcional, por si quiero el mail del usuario
			.build();

		googleApiClient = new GoogleApiClient.Builder(this)
			.enableAutoManage(this, this)
			.addApi(Auth.GOOGLE_SIGN_IN_API)
			.build();

		signInButton = (SignInButton) findViewById(R.id.sign_in_button);
		signInButton.setOnClickListener(new View.OnClickListener(){
			@Override
			public void onClick(View view) {
				Intent intent = Auth.GoogleSignInApi.getSignInIntent(googleApiClient);
				startActivityForResult(intent,SIGN_IN_CODE);
			}
		});
	}

	@Override
	public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);
		if(requestCode == SIGN_IN_CODE){
			GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
			manejaResultado(result);
		}
	}

	private void manejaResultado( GoogleSignInResult result ){
		if(result.isSuccess()){
			Toast.makeText(this, "Exito en el Login",Toast.LENGTH_SHORT).show();
		}else{
			Toast.makeText(this, "Error en el Login",Toast.LENGTH_SHORT).show();
		}
	}

Recuperar los datos del usuario logueado con google

SecondActivity.java@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login_google_second_activity);

        photoimageView1 = findViewById(R.id.photoImageView1);
        nombreTextView = findViewById(R.id.nombre);
        emailTextView = findViewById(R.id.email);
        id = findViewById(R.id.texto);

        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestEmail()
                .build();

        googleApiClient = new GoogleApiClient.Builder(this)
                .enableAutoManage(this, this)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();
    }

    public void desloguearse(View v){

    }

    @Override
    protected void onStart() {
        super.onStart();

        OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(googleApiClient);
        if(opr.isDone()){
            GoogleSignInResult result = opr.get();
            manejarResult(result);
        }else {
            opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {

                @Override
                public void onResult(@NonNull GoogleSignInResult googleSignInResult) {
                    manejarResult(googleSignInResult);
                }
            });
        }
    }
    private void manejarResult(GoogleSignInResult result){
        if(result.isSuccess()){
            GoogleSignInAccount account = result.getSignInAccount();
            nombreTextView.setText(account.getDisplayName());
            emailTextView.setText(account.getEmail());
            id.setText(account.getId());
        }else{
            startActivity(new Intent(this, SecondActivity.class));
        }
    }

Login con Facebook

  1. Iniciamos sesión en developers.facebook.com. La documentación global está en este enlace.
  2. Mis aplicaciones -> Agregar una aplicación
  3. Panel -> Elegir plataforma -> Android -> Añadimos al build.gradle la dependencia que se nos indica
    compile 'com.facebook.android:facebook-android-sdk:[4,5)'
  4. Pegamos Añadimos el facebook id a nuestra aplicación:
    res/values/strings.xml<string name="facebook_app_id">1508558065902615</string>
  5. Modificamos el AndroidManifest.xml:
    <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
  6. Introducimos en la página web los datos que se nos solicitan y que podemos recuperar de nuestra aplicación Android.
  7. Seguimos las instrucciones que se nos indican apra generar un Key Hash.

Creamos una clase que hereda de Application y que tendrá la siguiente línea en el onCreate:

AppEventLogger.activateApp(this)

Draw

Ejercicio - Dibujar un círculo

res/layout/main.xml...
<com.pablomonteserin.Vista android:layout_width="match_parent" android:layout_height="match_parent" />
...
com.pablomonteserin.Vista.javapublic class Vista extends View{
	Paint pincel;
	int color;
	public Vista(Context context){
		super(context);
		iniciar();
	}
	public Vista(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		iniciar();
	}
	public Vista(Context context, AttributeSet attrs) {
		super(context, attrs);
		iniciar();
	}

	private void iniciar(){
		pincel = new Paint();
		//Hay varias formas de definir los colores	
		//color = Color.argb(127, 0, 255, 0);
		//color = 0x7F00FF00;
		color = Color.BLUE;
		pincel.setColor(color);
		pincel.setStrokeWidth(8);
		pincel.setStyle(Paint.Style.STROKE);
	}
	protected void onDraw(Canvas canvas){
		super.onDraw(canvas);
		canvas.drawCircle(150, 150, 100, pincel);
	}
}

La clase vista debe tener tres constructores:
El primero es para crear la vista desde código. Los otros dos son para inflar la vista llamándola desde un fichero xml.

Hay que procurar no hacer ninguna instancia e incluso tener el mínimo código posible dentro del método onDraw(), ya que será llamado varias veces por segundo y relentizaría la aplicación.

Ejercicio - Dibujar un Path (trazo)

trazo y pincel son dos objetos de las clases Path y Paint respectivamente.

protected void onDraw(Canvas canvas){
	super.onDraw(canvas);
	trazo.addCircle(150, 150, 100, Path.Direction.CCW);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);	
	pincel.setStyle(Paint.Style.STROKE);
	canvas.drawPath(trazo, pincel);
	pincel.setStrokeWidth(1);
	pincel.setStyle(Paint.Style.FILL);
	pincel.setTextSize(20);
	pincel.setTypeface(Typeface.SANS_SERIF);
	canvas.drawTextOnPath("Pablo Monteserín", trazo, 10, 40, pincel);
}
pantallazo resultado circulo

Ejercicio - Dibujar siguiendo un path II

protected void onDraw(Canvas canvas){
	super.onDraw(canvas);
	trazo.moveTo(50, 100);
	strazo.cubicTo(60, 70, 150, 90, 200, 110);
	strazo.lineTo(300, 200);
	canvas.drawColor(Color.YELLOW);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);
	pincel.setStyle(Style.STROKE);
	canvas.drawPath(trazo, pincel);
	pincel.setStrokeWidth(1);
	pincel.setStyle(Style.FILL);
	pincel.setTextSize(20);
	pincel.setTypeface(Typeface.SANS_SERIF);
	canvas.drawTextOnPath("Pablo Monteserín", trazo, 10, 40, pincel);
}

trazo y pincel son dos objetos de las clases Path y Paint respectivamente. Sin embargo, no deben ser instanciados dentro del método onDraw(), ya que este es llamado varias veces por segundo y ralentizaría la aplicación.

resultado final trazo

Ejercicio - Dibujar un rectángulo

protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);
	pincel.setStyle(Paint.Style.STROKE);
	canvas.drawRect(0, 0, 200, 300, pincel);
}

Ejercicio - Dibujar un rectángulo con relleno

protected void onDraw(Canvas canvas) {
	super.onDraw(canvas);
	pincel.setColor(Color.BLUE);
	pincel.setStrokeWidth(8);
	pincel.setStyle(Paint.Style.STROKE);
	canvas.drawRect(10, 10, 200, 50, pincel);
	pincel.setColor(Color.RED);
	pincel.setStyle(Paint.Style.FILL);
	canvas.drawRect(12,12,198,48, pincel);
}

Recuperar atributos del xml

private void iniciar(Context context, AttributeSet attrs){
	pincel = new Paint();
	TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.Monteserin_circle);
	int color = attributes.getColor(R.styleable.Monteserin_circle_monteserin_color, Color.GREEN);
	pincel.setColor(color);
}
res/values/attrs.xml<resources>
    <declare-styleable name="Monteserin_circle">
        <attr name="monteserin_color" format="color" />
    </declare-styleable>
</resources>

Definir el tamaño del Canvas

Por defecto, el tamaño de una vista si no definimos otra cosa, será todo el alto de la pantalla. Por tanto, si ponemos dos elementos seguidos, el segundo no se verá porque el primero ocupará toda la pantalla.

La solución es definir una altura específica al escribir el código en el xml, o sobreescribir el siguiente método en el archivo de la vista:

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

	//Estos valores definirán el tamaño de la vista
	int desiredWidth = 100;
	int desiredHeight = 50;

	int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	int widthSize = MeasureSpec.getSize(widthMeasureSpec);
	int heightMode = MeasureSpec.getMode(heightMeasureSpec);
	int heightSize = MeasureSpec.getSize(heightMeasureSpec);

	int width;
	int height;

	//Measure Width
	if (widthMode == MeasureSpec.EXACTLY) {
		//Must be this size
		width = widthSize;
	} else if (widthMode == MeasureSpec.AT_MOST) {
		//Can't be bigger than...
		width = Math.min(desiredWidth, widthSize);
	} else {
		//Be whatever you want
		width = desiredWidth;
	}

	//Measure Height
	if (heightMode == MeasureSpec.EXACTLY) {
		//Must be this size
		height = heightSize;
	} else if (heightMode == MeasureSpec.AT_MOST) {
		//Can't be bigger than...
		height = Math.min(desiredHeight, heightSize);
	} else {
		//Be whatever you want
		height = desiredHeight;
	}

	//MUST CALL THIS
	setMeasuredDimension(width, height);
}

Ejercicio - Repintar con OnTouchEvent

Al pulsar sobre un rectángulo, este deberá repintarse en una posición diferente.

@Override
public boolean onTouchEvent(MotionEvent event) {
	x = event.getX();
	y = event.getY();
	//Fuerzo el repintado
	this.invalidate();
	return super.onTouchEvent(event);
}

El relleno del rectángulo crece con un click del ratón

		
protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		pincel.setColor(Color.BLUE);
		pincel.setStrokeWidth(8);
		pincel.setStyle(Style.STROKE);
		canvas.drawRect(10, 10, getWidth(), 50, pincel);
		pincel.setColor(Color.RED);
		pincel.setStyle(Style.FILL);
		canvas.drawRect(12,12,getWidth()*this.width-3,48, pincel);
	}

	public void aumentarWidth(){
		if(this.width<1){
			this.width = (float) (this.width+0.1);
			this.postInvalidate();
		}
	}
		
	

Este método fuerza a volver a llamar al método onDraw para que el dibujo se repinte. Si no lo llamamos, el repintado no estará asegurado.

Hacer un juego sencillo

Se trata de un juego en el que un monstruo (una clase que hereda de ImageView) va cambiando de posición en la pantalla y cada vez que pulsamos sobre él aumenta un marcador.

Para realizar una tarea cada x tiempo utilizaremos:

private Handler handler;
...
handler = new Handler();
handler.postDelayed(runnable, 1000);

...
private Runnable runnable = new Runnable() {
	@Override
	public void run() {
		setRandomPosition(iv);
		handler.postDelayed(this, 1000);
	}
};

public void setRandomPosition(ImageView a1){
	int x = random.nextInt(width);
	int y = random.nextInt(height);
	a1.setX(x);
	a1.setY(y);
}

Detectar cuando se ha cargado la vista para recuperar tamaños y posiciones

@Override
public void onWindowFocusChanged(boolean hasFocus) {
	super.onWindowFocusChanged(hasFocus);
	final View content = getWindow().findViewById(Window.ID_ANDROID_CONTENT);

	view_width = content.getWidth()-a1.getWidth();
	view_height = content.getHeight()-a1.getHeight()-getStatusBarHeight();
}

public int getStatusBarHeight(){
        Rect rectangle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
        int statusBarHeight = rectangle.top;
        int contentViewTop = window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
        int titleBarHeight= contentViewTop - statusBarHeight;
        return statusBarHeight+titleBarHeight;
}

Publicar

Publicar la aplicación en Google Play

  1. Eliminamos todas las llamadas a las trazas (Log). Si no lo hacemos, la aplicación funciona pero el usuario de la misma tendría acceso a las trazas mediante logCat.
  2. Android Manifest → Pestaña Application → Debuggable: false
  3. Android Manifest → Pestaña Manifest → Aumentamos “Version code” y “Version Name”, según proceda.
  4. Android Manifest → Pestaña AndroidManifest.xml → Cambiamos el nodo <uses-sdk android:minSdkVersion="8" /> (Buscar infor para saber en función de q)
  5. AndroidManifest.xml → Pestaña Manifest → Exporting → Use the Export Wizard → Next → Create New keyStore Location: ruta en mi equipo dónde almacenaré el certificado.
    Las siguientes veces que quiera subir mi aplicación al AndroidMarket.xml utilizaré el keyStore que se me va a proporcionar. Por tanto, es importante no extraviarlo. → Next → El password requerido ahora será el mismo que el de la pantalla anterior. Validity: 25 years (por ejemplo). → Next → Indico la ruta dónde se guardará el fichero .apk del programa. → Finish → El archivo de certificado y el .apk de la aplicación son salvados en las rutas indicadas.
  6. Hago las fotos (resolución 72 pixeles/inch):
    • feature.png (1024x500) foto que estará en la androidMarket web.
    • promo.png (la misma foto que feature.png, pero con resolución 180x120) foto que estará en la androidMarket en un dispositivo de telefonía móvil.
    • icon.png (512x512). El icono de la aplicación y que sustituirá al icon.png que por defecto coloca Android.
      También habrá que salvar el icono de android en las carpetas drawable-hdpi(72x72), drawable-mdpi(48x48), drawable-ldpi(36x36)
  7. Hago un par de pantallazos de la aplicación funcionando: Arranco el emulador → voy a la pantalla de la que quiero sacar el pantallazo → voy a eclipse → window → show view → devices → selecciono el emulador → icono de screen capture
  8. Vamos a https://market.android.com/publish/ y seguimos los pasos indicados.
  9. Cuando lleguemos a una página con dos pestañas (Product details y APK files), debemos entrar en APK files para activar nuestro .apk subido.

Certificado de aplicaciones

Toda aplicación de Android, para poder ser desplegada debe ir firmada con un certificado.

El SDK de Android crea su propio certificado de debug, que será diferente para cada ordenador.

Por tanto si trato de instalar dos aplicaciones desde el mismo ordenador y estas tienen los mismos nombre de paquetes, para android estaré reinstalando la aplicación primera, en vez de instalando una nueva.

Por otro lado, si los certificados son diferentes pero los paquetes son iguales, obtendré un error al desplegar la aplicación.

Error típico – conflicto con el nombre del paquete repetido

Podemos cambiar el nombre del paquete en el AndroidManifest.xml de nuestro proyecto.

Eliminar una aplicación del Android Market

No es posible. Sólo es posible:

  • Despublicarla (en cuyo caso mantendríamos un conflicto con los nombres de los paquetes en caso de tenerlo con la aplicación que hemos despublicado).
  • Actualizarla con otra aplicación totalmente diferente. Los nombres de paquetes no deben coincidir.

Ejecutar y debugar una aplicación en el móvil

  1. En el móvil: Ajustes → Aplicaciones → Desarrollo → Marco Depuración de USB.
  2. Debemos tener la pestaña Devices activa (menu Window → Preferences → Show View → Other → Devices). Seleccionamos el device correspondiente a nuestro teléfono móvil (que debe estar conectado al ordenador).

No puedo instalar dos aplicaciones que utilicen los mismos nombre de paquetes.
Para poder hacerlo debo desinstalar la que ya tengo instalada, o cambiar el nombre del paquete.

Desinstalar una aplicación del teléfono móvil desde consola

Habrá que hacerlo cuando hayamos instalado la aplicación y queramos volver a instalarla sobrescribiendo la vieja aplicación.


/android-sdk-linux_x86/platform-tools$ ./adb uninstall com.pablomonteserin
icono de mandar un mail¡Contacta conmigo!
contacta conmigoPablo Monteserín

¡Hola! ¿En qué puedo ayudarte?