
Introducción
En esta parte vamos a crear una nueva funcionalidad para nuestro sistema de login y registro, esta funcion es la de recuperar contraseña por medio de un email, la idea es poder recibir un link en nuestar casilla de correo el cual nos sirva para recuperar la contraseña perdida u olvidada.

El primer paso es crear nuestra layout y actividad que va a utilizar esta nueva funcion, en este caso creamos una nueva actividad llamada PasswordRecoverActivity.kt dentro de nuestro paquete auth - passwordrecover
activity_password_recover.xml
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".presentation.auth.passwordrecover.view.PasswordRecoverActivity"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textEmailAddress" android:ems="10" android:id="@+id/etxt_recover_pw" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp" android:layout_marginTop="340dp" app:layout_constraintTop_toTopOf="parent"/> <Button android:text="Send recover email" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btn_recover_pw" android:layout_marginTop="40dp" app:layout_constraintTop_toBottomOf="@+id/etxt_recover_pw" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp" app:layout_constraintHorizontal_bias="0.498"/> <ProgressBar style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/progress_recover_pw" android:elevation="3dp" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp" android:layout_marginTop="40dp" app:layout_constraintTop_toBottomOf="@+id/etxt_recover_pw" app:layout_constraintHorizontal_bias="0.498"/> <TextView android:text="Password Recover" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="30sp" android:textStyle="bold" android:id="@+id/textView" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp" android:layout_marginTop="176dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintHorizontal_bias="0.498"/> </androidx.constraintlayout.widget.ConstraintLayout>
El próximo paso es crear la interfaz que va a sostener los metodos de nuestra vista como de nuestro presenter, a esto lo llamaremos
PasswordRecoverContract.kt
package com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover /** * Created by Gastón Saillén on 14 June 2019 */ interface PasswordRecoverContract { interface PasswordRecoverView{ fun showError(msgError:String?) fun showProgress() fun hideProgress() fun recoverPassword() fun navigateToLogin() } interface PasswordRecoverPresenter{ fun attachView(passwordRecoverView:PasswordRecoverView) fun detachView() fun detachJob() fun isViewAttached():Boolean fun sendPasswordRecover(email:String) } }
Estas interfaces las vamos a implementar tanto en nuestra vista como en nuestro presenter, en nuestra vista PasswordRecoverActivity implementamos la interfaz y sus metodos, adema extendemos BaseActivity() ya que como habiamos mensionado en tutoriales anteriores va a ser nuestro base para cada actividad que creemos y que cumpla con las condiciones que estan planteadas en el.
PasswordRecoverActivity.kt
package com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.view import android.content.Intent import android.os.Bundle import android.view.View import com.gaston.cleanfirestorelogin.R import com.gaston.cleanfirestorelogin.base.BaseActivity import com.gaston.cleanfirestorelogin.domain.interactor.auth.passwordrecoverinteractor.PasswordRecoverImpl import com.gaston.cleanfirestorelogin.presentation.auth.login.view.LoginActivity import com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.PasswordRecoverContract import com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.presenter.PasswordRecoverPresenter import kotlinx.android.synthetic.main.activity_password_recover.* class PasswordRecoverActivity : BaseActivity(), PasswordRecoverContract.PasswordRecoverView { lateinit var presenter: PasswordRecoverPresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) presenter = PasswordRecoverPresenter(PasswordRecoverImpl()) presenter.attachView(this) btn_recover_pw.setOnClickListener { recoverPassword() } } override fun getLayout(): Int { return R.layout.activity_password_recover } override fun showError(msgError: String?) { toast(this, msgError) } override fun showProgress() { progress_recover_pw.visibility = View.VISIBLE btn_recover_pw.visibility = View.GONE } override fun hideProgress() { progress_recover_pw.visibility = View.GONE btn_recover_pw.visibility = View.VISIBLE } override fun recoverPassword() { val email:String = etxt_recover_pw.text.trim().toString() if(!email.isEmpty()) presenter.sendPasswordRecover(email) else toast(this,"E-mail is empty") } override fun navigateToLogin() { startActivity(Intent(this,LoginActivity::class.java)) toast(this,"Recover E-mail sent") finish() } override fun onDestroy() { super.onDestroy() presenter.detachView() presenter.detachJob() } override fun onDetachedFromWindow() { super.onDetachedFromWindow() presenter.detachView() presenter.detachJob() } }
Ahora nos queda implementa el presenter con el cual esta actividad se va a comunicar para poder realizar distintas acciones, en este caso , una de ellas es la mas importante, y es la de recuperar contraseña.
Solo deberemos pasarle un E-mail al metodo sendPasswordRecover(email) , este E-mail es el que recogemos de nuestro edittext
PasswordRecoverPresenter.kt
package com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.presenter import com.gaston.cleanfirestorelogin.domain.interactor.auth.passwordrecoverinteractor.PasswordRecover import com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.PasswordRecoverContract import com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.exception.PasswordRecoverException import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext /** * Created by Gastón Saillén on 14 June 2019 */ class PasswordRecoverPresenter(passwordRecoverInteractor: PasswordRecover) : PasswordRecoverContract.PasswordRecoverPresenter, CoroutineScope { var view: PasswordRecoverContract.PasswordRecoverView? = null var passwordRecoverInteractor: PasswordRecover? = null var job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job init { this.passwordRecoverInteractor = passwordRecoverInteractor } override fun attachView(passwordRecoverView: PasswordRecoverContract.PasswordRecoverView) { this.view = passwordRecoverView } override fun detachView() { view = null } override fun detachJob() { coroutineContext.cancel() } override fun isViewAttached(): Boolean { return view != null } override fun sendPasswordRecover(email: String) { launch { try { view?.showProgress() passwordRecoverInteractor?.sendPasswordResetEmail(email) view?.hideProgress() view?.navigateToLogin() } catch (e:PasswordRecoverException){ view?.hideProgress() view?.showError(e.message) } } } }
Vamos a necesitar implementar una excepcion personalizada para poder retornar el error en caso de que el email no exista o que ocurra algun error en la red a la hora de tratar de recuperar la contraseña.
Para eso implementamos una nueva clase llamada
PasswordRecoverException.kt
package com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.exception /** * Created by Gastón Saillén on 14 June 2019 */ class PasswordRecoverException(message:String): Exception(message)
Por último vamos a implementar nuestro interactor (caso de uso) que nos va a servir para hacer la logica que envia el email y recibe dos responses, una exitosa y otra de error.
Primero creamos la interfaz de este interactor
PasswordRecover.kt
package com.gaston.cleanfirestorelogin.domain.interactor.auth.passwordrecoverinteractor /** * Created by Gastón Saillén on 14 June 2019 */ interface PasswordRecover { suspend fun sendPasswordResetEmail(email: String) }
y por ultimo implementamos esa interfaz con su logica
PasswordRecoverImpl.kt
package com.gaston.cleanfirestorelogin.domain.interactor.auth.passwordrecoverinteractor import com.gaston.cleanfirestorelogin.presentation.auth.passwordrecover.exception.PasswordRecoverException import com.google.firebase.auth.FirebaseAuth import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException /** * Created by Gastón Saillén on 14 June 2019 */ class PasswordRecoverImpl: PasswordRecover { override suspend fun sendPasswordResetEmail(email: String): Unit = suspendCancellableCoroutine { continuation -> FirebaseAuth.getInstance().sendPasswordResetEmail(email).addOnCompleteListener { if(it.isSuccessful){ continuation.resume(Unit) }else{ continuation.resumeWithException(PasswordRecoverException(it.exception?.message !!)) } } } }
Para despejar dudas y ver la implementación concreta de este código podes seguir el siguiente video
Links útiles
Link GitHub del código completo: https://github.com/gastsail/CleanFirestoreLogin