diff --git a/README.md b/README.md new file mode 100644 index 0000000..56c9370 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Android Avanzado + +## :wave: Bienvenid@s + +## :dart: Objetivo + +Construir una aplicación Android con funciones inyectadas por dependencias y librerías, con simplificación de autenticación, reporte y seguimiento de errores, servicios de terceros, elementos de realidad aumentada, y publicable en tiendas alternativas a Google Play y/o en un servidor propio. + +## Datos relevantes + +- Firebase es una plataforma de Google para desarrollar y facilitar la creación de apps con alta calidad de una forma rápida. +- ArCore es un kit de desarrollo de software para dispositivos móviles que permite la creación de aplicaciones de realidad aumentada. +- Jetpack es un conjunto de bibliotecas que ayuda a los desarrolladores a seguir las prácticas recomendadas, reducir el código estándar y escribir código que funcione de manera coherente en los dispositivos y las versiones de Android, para que puedan enfocarse en el código que les interesa. + +## :gear: Requerimientos + +- Android Studio +- Cuenta de Google +- Teléfono físico (Recomendado) +- SDK ArCore + +## 💻 Proyecto + +Desarrollar una app Android con Kotlin como principal lenguaje de desarrollo, con funciones agregadas mediante Firebase Authentication, Firebase Crashlytics, Dependencias, Navigation, SDK´s, Deploys, Librerías y RA. + +## :bookmark_tabs: Sesiones + +1. [Firebase Authentication ](./Sesion-01)(Simplifica el inicio de sesión y el registro) +2. [Firebase Crashlytics ](./Sesion-02)(Reportes y seguimiento a errores) +3. [Dependencias ](./Sesion-03)(Simplifica el código) +4. [Navigation ](./Sesion-04)(Mejora el flujo de datos) +5. [Terceros ](./Sesion-05)(Implementar SDK's de terceros) Facebook, Spotify, Conekta +6. [Deploys ](./Sesion-06)(Publica en tiendas altenativas a Google Play o en un servidor externo) +7. [Librerías ](./Sesion-07)(Ahorra código y mejora el flujo) +8. [RA ](./Sesion-08)(Implementaciones más utilizadas de RA) diff --git a/Sesion-01/Ejemplo-01/README.md b/Sesion-01/Ejemplo-01/README.md new file mode 100644 index 0000000..344f64a --- /dev/null +++ b/Sesion-01/Ejemplo-01/README.md @@ -0,0 +1,58 @@ +# Ejemplo 01: Implementar Firebase Authentication + +## Objetivo + +* Implementar y configurar Firebase Authentication en un proyecto base. + +## Desarrollo + +A partir de un proyecto de Android previamente creado se implementará el BaaS de Firebase Authentication para integrar sus servicios de registro e inicio de sesión. + +Usaremos el [Proyecto base](./base) y le modificaremos lo que se requiera. Para hacerlo realiza los siguientes pasos: + +1. Ejecutamos el proyecto base con Android Studio. Este desplegará la siguiente interfaz. + + + + +2. Ahora, agregaremos las librerías de Firebase. Para ello hacemos clic en la siguiente ruta: **Tools -> Firebase** + + + +3. El resultado será la siguiente ventana. Es necesario identificar la opción de Authentication, hacer clic en ella, y después hacer clic en la tercera opción, enlistada como **Authentication using a custom authentication system**. + + + +4. Aparecerá la siguiente ventana. Se debe hacer clic en **Connect to Firebase**. + + + + + +5. El resultado del botón abrirá el navegador. En la siguiente ventana debe seleccionarse el proyecto (el cual denominamos Bedu), y posteriormente es necesario hacer clic en **Conectar**. + + + +6. Listo, hasta este paso la app ya se encuentra conectada a Firebase. + + + +7. Ahora debe seguirse la ruta hasta el paso 3. Ahí debe seleccionarse **Add the Firebase Authentication SDK to your app** y luego se aceptan los cambios. + + + + Esto Agrega *Play Services y Auth* + + + +8. Ahora debemos dirigirnos a Firebase Console y activar **Authentication**. + + + + + +¡Felicidades! Ya implementaste Firebase Authentication en tu proyecto Android. + +
+ +[Siguiente ](../Ejemplo-02/README.md)(Ejemplo 2) diff --git a/Sesion-01/Ejemplo-01/assets/01.png b/Sesion-01/Ejemplo-01/assets/01.png new file mode 100644 index 0000000..024d413 Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/01.png differ diff --git a/Sesion-01/Ejemplo-01/assets/02.png b/Sesion-01/Ejemplo-01/assets/02.png new file mode 100644 index 0000000..8e48756 Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/02.png differ diff --git a/Sesion-01/Ejemplo-01/assets/03.png b/Sesion-01/Ejemplo-01/assets/03.png new file mode 100644 index 0000000..c91a0b6 Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/03.png differ diff --git a/Sesion-01/Ejemplo-01/assets/04.png b/Sesion-01/Ejemplo-01/assets/04.png new file mode 100644 index 0000000..416ffca Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/04.png differ diff --git a/Sesion-01/Ejemplo-01/assets/05.png b/Sesion-01/Ejemplo-01/assets/05.png new file mode 100644 index 0000000..b283196 Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/05.png differ diff --git a/Sesion-01/Ejemplo-01/assets/06.png b/Sesion-01/Ejemplo-01/assets/06.png new file mode 100644 index 0000000..d30f55c Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/06.png differ diff --git a/Sesion-01/Ejemplo-01/assets/07.png b/Sesion-01/Ejemplo-01/assets/07.png new file mode 100644 index 0000000..bfcc45c Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/07.png differ diff --git a/Sesion-01/Ejemplo-01/assets/08.png b/Sesion-01/Ejemplo-01/assets/08.png new file mode 100644 index 0000000..bd8b4a4 Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/08.png differ diff --git a/Sesion-01/Ejemplo-01/assets/09.png b/Sesion-01/Ejemplo-01/assets/09.png new file mode 100644 index 0000000..9b65d14 Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/09.png differ diff --git a/Sesion-01/Ejemplo-01/assets/10.png b/Sesion-01/Ejemplo-01/assets/10.png new file mode 100644 index 0000000..d0a5fa1 Binary files /dev/null and b/Sesion-01/Ejemplo-01/assets/10.png differ diff --git a/Sesion-01/Ejemplo-01/assets/README.md b/Sesion-01/Ejemplo-01/assets/README.md new file mode 100644 index 0000000..a46b26b --- /dev/null +++ b/Sesion-01/Ejemplo-01/assets/README.md @@ -0,0 +1 @@ +## Aquí puedes sustituir este archivo por imagenes o código que requiera el ejemplo \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/.gitignore b/Sesion-01/Ejemplo-01/base/.gitignore new file mode 100644 index 0000000..426a199 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/.gitignore b/Sesion-01/Ejemplo-01/base/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/build.gradle b/Sesion-01/Ejemplo-01/base/app/build.gradle new file mode 100644 index 0000000..4b5678c --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/build.gradle @@ -0,0 +1,48 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.bedu.auth" + minSdkVersion 19 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.5.0' + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'com.google.android.material:material:1.3.0' + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/proguard-rules.pro b/Sesion-01/Ejemplo-01/base/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/androidTest/java/com/bedu/auth/ExampleInstrumentedTest.kt b/Sesion-01/Ejemplo-01/base/app/src/androidTest/java/com/bedu/auth/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..b5e9233 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/androidTest/java/com/bedu/auth/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.bedu.auth + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.bedu.auth", appContext.packageName) + } +} \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/AndroidManifest.xml b/Sesion-01/Ejemplo-01/base/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..10c2861 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/utils/Utility.kt b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/utils/Utility.kt new file mode 100644 index 0000000..40146eb --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/utils/Utility.kt @@ -0,0 +1,33 @@ +package com.bedu.auth.utils + +import android.content.Context +import android.view.View +import android.view.inputmethod.InputMethodManager +import androidx.annotation.ColorInt +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import com.bedu.auth.R +import com.google.android.material.snackbar.Snackbar + +object Utility { + + //display SnackBar + fun displaySnackBar(view: View, s: String, context: Context, @ColorRes colorRes: Int) { + + Snackbar.make(view, s, Snackbar.LENGTH_LONG) + .withColor(ContextCompat.getColor(context, colorRes)) + .setTextColor(ContextCompat.getColor(context, R.color.white)) + .show() + + } + + fun View.hideKeyboard() { + val inputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + inputManager.hideSoftInputFromWindow(windowToken, 0) + } + + private fun Snackbar.withColor(@ColorInt colorInt: Int): Snackbar { + this.view.setBackgroundColor(colorInt) + return this + } +} \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/EmailActivity.kt b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/EmailActivity.kt new file mode 100644 index 0000000..91ef1fc --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/EmailActivity.kt @@ -0,0 +1,92 @@ +package com.bedu.auth.view + +import android.app.Activity +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.View +import androidx.core.widget.doAfterTextChanged +import com.bedu.auth.databinding.ActivityEmailBinding +import com.bedu.auth.utils.Utility.hideKeyboard + +class EmailActivity : Activity() { + + private lateinit var binding: ActivityEmailBinding + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityEmailBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) + + handleClick() + } + + private fun handleClick() { + + binding.btnLogin.setOnClickListener { + it.hideKeyboard() + + binding.btnLogin.visibility = View.GONE + binding.loading.visibility = View.VISIBLE + + val email = binding.editText.text.toString() + val password = binding.editText2.text.toString() + + signIn(email, password) + } + binding.btnRegister.setOnClickListener { + it.hideKeyboard() + + binding.btnLogin.visibility = View.GONE + binding.loading.visibility = View.VISIBLE + + val email = binding.editText.text.toString() + val password = binding.editText2.text.toString() + + createAccount(email, password) + } + + binding.editText.doAfterTextChanged { + val email = binding.editText.text.toString() + val password = binding.editText2.text.toString() + + binding.btnLogin.isEnabled = email.isNotEmpty() && password.isNotEmpty() + binding.btnRegister.isEnabled = email.isNotEmpty() && password.isNotEmpty() + } + + binding.editText2.doAfterTextChanged { + val email = binding.editText.text.toString() + val password = binding.editText2.text.toString() + + binding.btnLogin.isEnabled = email.isNotEmpty() && password.isNotEmpty() + binding.btnRegister.isEnabled = email.isNotEmpty() && password.isNotEmpty() + } + } + + private fun createAccount(email: String, password: String) { + updateUI() + } + + private fun signIn(email: String, password: String) { + updateUI() + } + + private fun sendEmailVerification() { + } + + private fun updateUI() { + Handler(Looper.getMainLooper()).postDelayed({ + binding.loading.visibility = View.GONE + binding.btnLogin.visibility = View.VISIBLE + }, 2000) + } + + private fun reload() { + + } + + companion object { + private const val TAG = "EmailPassword" + } +} \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/MainActivity.kt b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/MainActivity.kt new file mode 100644 index 0000000..eff0193 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/MainActivity.kt @@ -0,0 +1,51 @@ +package com.bedu.auth.view + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import com.bedu.auth.databinding.ActivityOptionsBinding + +class MainActivity : Activity() { + + private lateinit var binding: ActivityOptionsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityOptionsBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) + + handleClick() + } + + override fun onStart() { + super.onStart() + + } + + private fun handleClick() { + + binding.btnEmail.setOnClickListener { + val intent = Intent(this, EmailActivity::class.java) + startActivity(intent) + } + binding.btnPhone.setOnClickListener { + val intent = Intent(this, PhoneActivity::class.java) + startActivity(intent) + } + binding.btnGoogle.setOnClickListener { + } + } + + + + private fun updateUI() { + + } + + companion object { + private const val TAG = "GoogleActivity" + private const val RC_SIGN_IN = 9001 + } + +} \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/PhoneActivity.kt b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/PhoneActivity.kt new file mode 100644 index 0000000..63d28e2 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/java/com/bedu/auth/view/PhoneActivity.kt @@ -0,0 +1,90 @@ +package com.bedu.auth.view + +import android.app.Activity +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.View +import androidx.core.widget.doAfterTextChanged +import com.bedu.auth.databinding.ActivityPhoneBinding +import com.bedu.auth.utils.Utility.hideKeyboard + + +class PhoneActivity : Activity() { + + private lateinit var binding: ActivityPhoneBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityPhoneBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) + + binding.btnContinue.setOnClickListener { + it.hideKeyboard() + + val phone = binding.edtPhone.text.toString() + + when { + binding.btnContinue.text == "Resend code" -> { + binding.btnContinue.visibility = View.GONE + binding.loading.visibility = View.VISIBLE + binding.edtCode.text.clear() + resendVerificationCode(phone, "resendToken") + binding.btnContinue.text = "Continue" + } + binding.edtPhone.visibility == View.VISIBLE -> { + binding.btnContinue.visibility = View.GONE + binding.loading.visibility = View.VISIBLE + binding.edtPhone.visibility = View.INVISIBLE + + startPhoneNumberVerification(phone) + } + binding.edtCode.text.isNotEmpty() -> { + binding.btnContinue.visibility = View.GONE + binding.loading.visibility = View.VISIBLE + binding.edtCode.visibility = View.GONE + + val code = binding.edtCode.text.toString() + + verifyPhoneNumberWithCode("verificationId", code) + } + } + } + + binding.edtPhone.doAfterTextChanged { + val phone = binding.edtPhone.text.toString() + + binding.btnContinue.isEnabled = phone.isNotEmpty() + } + } + + private fun startPhoneNumberVerification(phoneNumber: String) { + updateUI() + } + + private fun verifyPhoneNumberWithCode(verificationId: String, code: String) { + updateUI() + } + + private fun resendVerificationCode( + phoneNumber: String, + token: String + ) { + updateUI() + } + // [END resend_verification] + + private fun updateUI() { + Handler(Looper.getMainLooper()).postDelayed({ + binding.btnContinue.visibility = View.VISIBLE + binding.loading.visibility = View.GONE + binding.edtPhone.visibility = View.VISIBLE + }, 2000) + } + + companion object { + private const val TAG = "PhoneAuthActivity" + } + +} \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Sesion-01/Ejemplo-01/base/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/res/drawable/ic_launcher_background.xml b/Sesion-01/Ejemplo-01/base/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sesion-01/Ejemplo-01/base/app/src/main/res/layout/activity_email.xml b/Sesion-01/Ejemplo-01/base/app/src/main/res/layout/activity_email.xml new file mode 100644 index 0000000..c6c847c --- /dev/null +++ b/Sesion-01/Ejemplo-01/base/app/src/main/res/layout/activity_email.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + +