1

"Resumen" Curso Definitivo de Android - Parte 2

Arquitectura de Software

  • Nos permite entender la estuctura, funcionamiento y forma de ensamblaje de los módulos de nuestra app.

  • Módulos

    • Vistas
    • Intercambios de datos - Capas de datos
  • Arquitecturas más comunes:

  1. MVC (Model - View- Controler) - Este es el modelo que hemos estado implementando hasta ahora.
  2. MVP (Model - View - Presenter)
  3. MVVM (Model - View - View - Model)
  • Nuestro código normalmente debe seguir estas tres características:
  1. Organizado de tal forma que todo el equipo pueda entenderlo.
  2. Debe hablar por si solo. La lógica del negocio debe tener relevancia para que pueda ser fácilmente entendible.
  3. Los componentes deben poder ser probados.

Principios SOLID

  • Cinco principios basados en la POO y el diseño de Software. Su intención es permitirnos crear aplicaciones fáciles de mantener y escalar a lo largo del tiempo.

    • S = Single Responsability Principle = Una clase solo debe tener una responsabilidad.
    • O = Open/closed principle = No debemos modificar código existente, sino crear un nuevo módulo a partir de este.
    • L = Liskov substitution principle = si una clase hereda de una clase padre, la hija debe ser lo suficientemente poderosa como para poder substituir a su padre tantas veces se quiera.
    • I = Interface segregation principle = Si tienes una interfaz con muchos métodos, muy grande, lo mejor es dividirla y crear más interfaces. No podemos permitir que una interfaz sea muy gorda.
    • D = Dependency inversion principle = No inyectar clases, objetos, (como parámetros para un método constructor) porque de esa forma la clase depende MUCHO de lo que el objeto hace. Lo mejor es inyectar algo más genérico como una interfaz que finalmente puede tener muchos comportamientos.

MVP - Model View Presenter

Tendrémos 3 módulos principales:

  1. Modelo : Se enfoca a toda la lógica de negocio de la app. Tiene mucho que ver con la base de datos o las entidades que juegan un papel en la app (Todos nuestros POJOS, ENTIDADES, etc).
  2. Vista : Se enfoca a las vistas. En este caso, la vista comprende nuestro Activity (.java) y nuestro Layout (.xml).
  3. Presentador : Será el coordinador, decide a quien enviar para cada tipo de operación. Para que este tenga sentido, debe trabajar con un interactor. Este será quien ejecute las acciones, con los actores recibidos desde el view, mediante el presentador (Consulte la base de datos, vaya a un API REST, etc)

Estructura de archivos

  • Package
    • view
    • interactor
    • presenter
  • Application.java

Flujo de datos

  • view -> presenter -> interactor (Hace el trabajo)
  • interactor -> presenter -> view (muestra resultados)

Clean Architecture

  • Se enfoca al dominio del problema, es decir, al modelo de datos ya que esto está expuesto. El dominio del problema será el centro de todo el proyecto. Esto hace que el acceso a los distintos componentes sea sencillo y hace más fácil su testeo.
  • La parte visual, la interfaz de usuario, se mantiene independiente de los componentes lo que permite un mejor control del proyecto para diferentes equipos de trabajo.
  • La capa de datos estará completamente aislada. Es fácil sustituir una base de datos por otra sin afectar las reglas del negocio.

Modelo de capas

  • Entities = Domain Logic
  • Use Cases, Interactors = Business Rules
  • Controllers, Gateways, Presenters = Interface Adapters
  • Devices, DB, Web, UI, External Interfaces = Frameworks and drivers

Estructura de archivos

  • Package
    • entity
      • view
      • interactor
      • presenter
      • repository
    • entity
      • view
      • interactor
      • presenter
      • repository
    • Application.java

Flujo de datos

  • view -> presenter -> interactor (Hace el trabajo) -> repository (Hace consultas a fuentes de datos)
  • interactor (trabajo hecho) -> presenter -> view (muestra resultados)
  • repository (consulta finalizada) -> presenter -> view (muestre resultados)

Consejos

  • Las validaciones deben hacerse en la view, de esta manera evitamos procesar datos no necesarios.

Accediendo al Hardware

  • El hardware de nuestro teléfono, internet, contactos, entre otros; son elementos privados del usuario. Por esta razón, es necesario que nuestra app solicite permisos a los usuarios para acceder a este tipo de elementos.
  • Hay dos formas de acceder a permisos:
    • Permisos dinámicos: Se introdujo a partir de Android 6.0. La solicitud de permiso se ejecuta justo cuando el usuario quiere acceder a un elemento privado o hardware, es decir, de forma dinámica.
    • Permisos en AndroidManifest: Son los permisos que se solicitan al momento de instalar la aplicación.

uses-permision vs uses-feature

  • Con uses-permission le notificas al usuario cuando va a instalar la aplicación que usarás ese permiso.

  • Con uses-feature le dices al SO que estas interesado en utilizar un feature específico del hardware, además sirve como filtro en Play Store si el dispositivo no tiene esa característica no aparece la app disponible para instalar.


Ejemplo accediendo a cámara(Permiso por AndroidManifest)

  • En primer lugar debemos solicitar el permiso en AndroidManifest e indicar que nuestra app va a utilizar esta feature:
<uses-permission android:name="android.permission.CAMERA"/>

<uses-feature
	android:name="android.hardware.camera2"
	android:required="false"
	/>
  • Luego, declarámos el método takePicture(), que será el encargado de ejecutar la cámara.
privatevoidtakePicture(){
        Intent intentTakePicture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (intentTakePicture.resolveActivity(getActivity().getPackageManager()) !=null){
            startActivityForResult(intentTakePicture, REQUEST_CAMERA);
        }
    }
  • El if dentro del método takePicture() valida que el destino definido en el objeto intent existe (es decir, que el dispositivo cuenta con lo necesario para tomar una fotografía).
  • En caso de que el dispositivo tenga lo necesario para que el intent llegue a su destino (la cámara), lo ejecutaremos pero esta vez esperando que devuelva un resultado. Para esto es el:
startActivityForResult(intentTakePicture, REQUEST_CAMERA);
  • Este método requiere un parámetro adicional aparte del intent, se trata de un identificador (En mayúsculas como buena práctica), para lo cual crearemos una constante al inicio de la clase:
privatestaticfinalint REQUEST_CAMERA = 1;
  • Luego creamos un método que se ejecutará como respuesta al resultado del startActivityForResult(…):
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intentdata) {
        if (requestCode == REQUEST_CAMERA && resultCode == getActivity().RESULT_OK){
            Log.d("HomeFragent", "CameraOK!! :)");
            Toast.makeText(this.getContext(), "CamaraOK!!", Toast.LENGTH_SHORT).show();
        }
    }
  • En este método, el parámetro int requestCode será el identificador que devuelve el método startActivityForResult, lo utilizamos para validar que estemos recibiendo el resultado del intent correcto. El parámetro int resultCode nos indica el resultado devuelto, en este caso también lo usamos para validar que el resultado se devolviera correctamente. Posteriormente ejecutamos una acción como respuesta a la operación exitosa.

  • Toda app crea en el dispositivo un directorio com.domain.app al cual podemos acceder, escribir y leer.
  • Cuando trabajamos con flujos (por ejemplo esto de crear una imagen o cualquier otro archivo), es ideal capturar las excepciones para hacer que la app siga en ejecución y no se detenga:
try{
	...
}catch (Exception e) {
	e.printStackTrace();
}
  • En el AndroidManifest se pueden crear Custom paths para los archivos.

Gradle

  • Es una herramienta que hace mucho más rápida la construcción de proyectos de desarrollo. Lo que hace es tomar tu código, dependencias y/o librerias, las reúne y construye el archivo ejecutable (.apk).
    • Integra dependencias
    • Crear un sistema de archivos
    • Generar un ejecutable
  • Está basado en un lenguaje de programación Groovy.
  • Usa Domaing Speficied Language - Json.

Ejemplo

static mapping = {
	table'person'
	columns {
		name column:'name'
	}
}
  • Los archivos de configuración de Gradle son de extensión .Json

Archivos de Configuración

  • El archivo de configuración principal es build.gradle. Acá puedes encontrar:
    • Dependencias que se pueden integrar (descarga las librerias y las integra).
    • Compilación del código (genera el apk).
    • Empaquetado.

Estructura del proyecto

MyApp/					*Project*
|
+- build.gradle
|
+- settings.gradle
|
+- app/					*Module* (wear, tv, mobile, etc)
	|
	+- build.gradle
	|
	+- build/
	|
	+- libs/
	|
	+- src/
		|
		+- main/		*Sourceset*
		|
		+- java/
		||
		|	+- com.example.myapp/
		|
		+-res/
		||
		|	+- drawable/
		||
		|	+- layout/
		||
		|	+- ...
		|	
		+- AndroidManifest.xml

Nivel superior

  • settings.gradle está ubicado en el directorio raíz del proyecto, indica a Gradle los módulos que debe incluir al compilar tu app.
  • build.gradle define las configuraciones de compilación que se aplican a todos los módulos de tu proyecto.

Nivel de módulo

  • build.gradle está ubicado en cada directorio de Project/module/. Permite configurar ajustes de compilación para el módulo específico en el que se encuentra.
  • gradle.properties es un archivo donde puedes dar de alta propiedades o variables para todo el proyecto que puedes llamar desde build.gradle. Se pueden definir cosas como el máximo de memoria para el proceso o incluso Proxys.

Application

  • Esta clase se utiliza principalmente para la inicialización del estado global antes de que se mestre la primera actividad.
  • Tiene influencia sobre Activities, Disk Storage y Services.

¿Qué hacer?

  • Crear una clase que herede de Application que será una clase Global que se iniciará antes que cualquier otra cosa (Lo que quieras que se inicie aquí, debes definirlo sobreescribiendo el método onCreate).
public classPlatzigramApplicationextendsApplication{
  • En el AndroidManifest le asignamos el nombre de esta nueva clase que creamos, a la etiqueta application:
<application
	android:name=".PlatzigramApplication"

Firebase Authentication

Toda la información sobre como implementar FirebaseAuth la puedes encontrar en la documentación de Firebase: https://firebase.google.com/docs/auth/android/start/

Almacenamiento en Android

Las aplicaciones en Android se instalan en el directorio /data/data/[package]/ (Para ingresar de forma común a estos directorios debes ser un usuario Root). En este directorio se encontrará el almacenamiento interno de nuestra app.

  • Almacenamiento interno

    • SharedPreferences
    • Internal Storage
      • File I/O
    • SQLiteDatabase
  • Almacenamiento en red

    • Network Connection

La mayor recomendación es trabajar con ambos tipos de almacenamiento, Interno y en la red para proveer a nuestros usuarios de una mayor persistencia.


SharedPreferences

Almacena la información en una ubicación específica, en un archivo .xml, a la que se accesa a través de una Key-Valor.

Este tipo de almacenamiento es útil cuando se trata de datos sencillos. Puntajes, configuraciones, datos de entrada, etc.

Uso

SharedPreferences pref = getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);SharedPreferences.Editor editor = pref.edit();
editor.putString("email", "[email protected]");
editor.putString("nombre", "Carlos");
editor.commit();

Esto se almacena en:

/data/data/package/shared_prefs/MisPreferencias.xml

Para leer la información almacenada en SharedPreferences:

SharedPreferences pref = getSharedPreferences("MisPreferencias",Context.MODE_PRIVATE);pref.getString("email", "mensaje");pref.getString("nombre", "mensaje");

Internal Storage

Almacena la información en la memoria interna del dispositivo File I/O. Esto se hace con código basado completamente en Java.


Almacenamiento como Recursos (raw)

Los archivos también se pueden almacenar como recursos en nuestra carpeta de recursos raw. En este caso comúnmente son propiedades, por ejemplo, en un archivo de texto.

Para obtener los datos:

Resources miRecurso = getResources();InputStreammiarchivo = miRecurso.openRawResource(R.raw.miarchivo);

SQLite Database

Normalmente lo usamos cuando nuestra información tiene una estructura muy compleja y privada. Este es el gestor de base de datos oficial de Android y utiliza el lenguaje SQL.

Por defecto, una base de datos de tu aplicación será almacenada en el directorio:

/data/data/package/databases/miDB

Network Connection

Los datos se almacenan en la nube con la ayuda de web services como firebase o un servidor personalizado.

Menús

La forma mas comun de trabajar los menus en Android es mediante un archivo .xml en la carpeta menu dentro de la carpeta res.

Existen tres tipos de menús con mayor importancia

  • Opciones
  • Contexto
  • Popup

Opciones

De los más comunes, son los tres puntitos que puedes ver normalmente en la toolbar. Más usado para ajustes, configuración de la app.

Implementación

@overridepublicbooleanonCreateOptionsMenu(Menu menu){
	getMenuInflater().inflate(R.menu.menu_opciones, menu);
	returntrue;
}

@overridepublicbooleanonOptionsItemSelected(MenuItem item){

	switch(item.getItemIde()){
	}
}

Contexto

Este menú se ejecuta luego de un long-press sobre algún view, como una lista de opciones.

Implementación

TextView tvNombre = (TextView) findViewById(R.id.tvNombre);
registerForContextMenu(tvNombre);

@overridepublicbooleanonCreateContextMenu(ContextMenu menu, View v, Context...){
	super.onCreateContextMenu(menu, v, menuInfo);
	getMenuInflater().inflate(R.menu.menu_contexto, menu);
}

@overridepublicbooleanonContextItemSelected(MenuItem item){

	switch(item.getItemIde()){
	}
}

Donde registerForContextMenu(miView); lo que hace es definir cuales views seran los que activarán el menú de contexto con el evento long-press.


Popup

Se disparan cuando se da click, tap en algún View (button, cardView, imagen, etc).

Implementación

En primer lugar, se debe asignar un evento onClick al view que ejeutarú el menú popup. En este caso, un onClickListener funciona perfectamente.

ImageView imagen = (ImageView) findViewById(R.id.imgImagen);PopupMenu popupMenu = new PopupMenu(this, imagen);popupMenu.getMenuInflater().inflate(R.menu.menu_popup, popupMenu.getMenu());
popupMenu.setOnMenuClickListener(new PopupMenu.OnMenuItemClickListener() {
	@Override
	public boolean onMenuItemClick(MenuItem item) {
		switch(item.getItemId()){
			...
		}
	}
}

Firebase storage

Usa Google Cloud Storage. Los archivos se estructuran como un sistema de arbol donde el directorio padre es el proyecto, identificado con una URL que puedes encontrar en tu panel de firebase > storage, y cada subdirectorio puede ser llamado y almacenar como quieras.

Todo se manejará en base a referencias mediante la clase principal StorageReference ref; y esta siempre apunta por defecto a nuestro directorio padre (la URL) a partir de ahí puedes acceder a los archivos y nodos mediante .child(“carpeta1”).child(“img3.jpg”);.

Para subir un archivo usarémos UploadTask que funcionará con su Listener.

Logcat de Android y Firebase Crash Reporting

Logcat
Es un sistema de registro de Android que proporciona información sobre la salida del sistema en modo debug o depuración.

Estructura:

  • Fecha/hora del mensaje
  • Criticidad. Nivel de gravedad del mensaje.
    • e() : Error
    • w() : Warning
    • i() : Info
    • d() : Debug
    • v() : Verbose
  • PID. Identificador del proceso que ha producido el mensaje.
  • TAG. Etiqueta identificativa del mensaje. Como buena práctica usamos la clase desde donde viene el mensaje.

Ejemplo

Log.w(TAG, "Mensaje de Warning");

Firebase Crashlytics
Nos informa de errores muy precisos de la aplicación, desde que está siendo desarrollada hasta cuando está ya en producción.

Aprende como implementarlo en tu app siguiendo la documentación oficial.

Uso

Puedes desactivar la recopilación automática con una etiqueta meta-data en tu archivo AndroidManifest.xml:

<meta-data android:name="firebase_crashlytics_collection_enabled"android:value="false" />

Esto sirve, por ejemplo, para habilitar la recopilación para usuarios seleccionados mediante la inicialización de Crashlytics desde una de las actividades de tu app:

Fabric.with(this, new Crashlytics());

Manejarémos tres tipos de errores con Firebase Crashlytics:

  • Excepciones : Emite un reporte de error hacia Firebase.
Crashlytics.logException(e);
  • LogCrashlytics : Emite sólamente un mensaje log a Firebase.
Crashlytics.log(msg);
  • Logcat y LogCrashlytics : Emite un mensaje al logcat de Android y al mismo tiempo a Firebase.
Crashlytics.log(int priority, String tag, String msg);

Agregar claves personalizadas
Puedes asociar pares clave-valor arbitrarios con tus informes de fallos y verlos en Firebase console.


Personalizar mensajes de Firebase Crashlytics

Crashlytics.setString(key, value);

Crashlytics.setBool(Stringkey, boolean value);

Crashlytics.setDouble(Stringkey, double value);

Crashlytics.setFloat(Stringkey, float value);

Crashlytics.setInt(Stringkey, int value);

Ejemplo:

Crashlytics.setInt("current_level", 3);
Crashlytics.setString("last_UI_action", "logged_in");

Configurar ID de usuario
Para agregar los ID de usuario a tus informes, asigna a cada usuario un identificador único con el formato de un número de ID, un token o un valor con hash.

void Crashlytics.setUserIdentifier(String identifier);

Si necesitas borrar un identificador de usuario después de configurarlo, restablece el valor a una string en blanco.

¿Cómo funciona un Servicio web?

Anahí dejó esta clase por escrita, así que sólo dejo el enlace.

¿Qué es un API Rest, Verbos Http y Endpoints?

Anahí dejó esta clase por escrita, así que sólo dejo el enlace.

Guía de estilo para diseñar y desarrollar una API

https://medium.com/m/oauth/authorize?client_id=545fe603e6fd&scope=basicProfile,publishPost,listPublications&state=active&response_type=code&redirect_uri=https://platzi.com/

https://platzi.com/?state=active&code=75b4876b88aa

Usando la API y Retrofit

(Primero debes implementar retrofit de square)

Como buena práctica, en la carpeta de la API que debe estar en el Package deberíamos crear una clase de constantes que contendrá todas las Rutas. Constants

Crearemos una InterfazService que gestionará todos los Endpoints, todas las direcciones de peticiones.

Ej:

@GET(Constants.URL_GET_USER)
call<JsonObject> getDataUser();

//Donde URL_GET_USER contiene "/me"

También crearemos una clase que se encargará de gestionar la conexión a la API, normalmente llamada RestApiAdapter.

OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {
	@OverridepublicResponse intercept(Interceptor.Chain chain)throws IOException {
		Request newRequest = chain.request().newBuilder()
			.addHeader("Authorization", "Bearer" + Constants.Bearer)
			.build);
		
		return chain.proceed(newRequest);
	}
}).build();

//Esto de arriba se utiliza cuando necesitamos pasar Headers. Recuerda que el "Bearer" es algo propio de Medium, normalmente no hace falta mandar "Bearer" sino el token no mas.publicService getClientService(){
	Retrofit retrofit = new Retrofit.Builder()
		.baseURL(Constants.ROOT_URL)
		.client(client)
		.addConverterFactory(GsonConverterFactory.create())
		.build();

	return retrofit.create(Service.class);
}

Para ejecutar, dentro del Repository, en el método que ya definimos como getDataUser() agregamos:

RestApiAdapter restApiAdapter = new RestApiAdapter();
Service service = restApiAdapter.getClientService();
Call<JsonObject> call = service.getDataUser();

call.enqueue(new Callback<JsonObject>() {
	@Override
	public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
		JsonObject userJson = response.body().getAsJsonObject("data").toString();
		User user = new User(
			userJson.get("id").getAsString(),
			userJson.get("name").getAsString(),
			userJson.get("username").getAsString(),
			userJson.get("url").getAsString(),
			userJson.get("imageUrl").getAsString(),
		);

		userPresenter.showDataUser(user);
		
		//Este método es el que, en la vista, le asignará a cada view el dato que le corresponde con
		//objectView.setText(user.getName());
	}

	@Override
	public void onFailure(Call<JsonObject> call, Throwable t) {
		
	}
});

Seguridad en Android ocultando api keys

Normalmente tus api keys estarán almacenadas en el archivo strings.xml o en alguna clase de constantes, si las tienes ahí están vulnerables a ser descubiertas con ingeniería inversa, por ello es buena idea usar Gradle para mantener aislados estos datos del código fuente.

Esta clase es textual, el enlace recuerda que lo tienes aquí.

Esta capa de seguridad sigue siendo muy vulnerable, una mayor seguridad se puede obtener creando una clase que encripte y desencripte los Strings : http://iamvijayakumar.blogspot.com.co/2013/10/android-example-for-encrypt-and-decrypt.html

Animaciones en Android

Android tiene esencialmente 3 formas de crear animaciones:

  • Animaciones Drawable
    • Se enfoca en recursos Drawable que tienen una secuencia de imagenes como un Flip Book
  • Animación de View’s
    • Este tipo de animaciones pueden ser definidas con código Java o con recursos XML (res/anim). Podemos darle los siguientes comportamientos al view:
      • posición
      • tamaño
      • rotación
      • transparencia
  • Animaciones de Propiedades
    • Funcionan gracias a las librerías de Gráficos 2D y 3D que se han implementado en Android, comenzando con OpenGL y un API Framework de Android construdo con el Native Development Kit (NDK).

Toda la clase de @anncode está en este enlace.

Publicando tu aplicación en Google Play

Clase escrita

Notificaciones en Android con Firebase

Clase escrita

La documentación es completamente intuitiva y la puedes encontrar en la misma consola de firebase.

Escribe tu comentario
+ 2