Commit 4058b11c authored by Lucio Maciel's avatar Lucio Maciel

Better organize authentication classes, use LifecycleObserver to cancel Jobs on onDestroy

parent 43391e73
......@@ -79,4 +79,10 @@ dependencies {
repositories {
mavenCentral()
}
kotlin {
experimental {
coroutines "enable"
}
}
\ No newline at end of file
package chat.rocket.android
import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
abstract class BaseActivity : AppCompatActivity() {
protected fun addFragment(tag: String, layoutId: Int, block: (Unit) -> Fragment) {
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: block(Unit)
supportFragmentManager.beginTransaction().replace(layoutId, fragment, tag).commit()
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
class AuthenticationModule {
......@@ -19,4 +20,9 @@ class AuthenticationModule {
fun provideAuthTokenRepository(): AuthTokenRepository {
return AuthTokenRepository()
}
@Provides
fun provideJob(): Job {
return Job()
}
}
package chat.rocket.android.authentication.di
import chat.rocket.android.authentication.presentation.LoginView
import chat.rocket.android.authentication.ui.LoginFragment
import dagger.Module
import dagger.Provides
@Module
class LoginFragmentModule {
@Provides
fun loginView(frag: LoginFragment): LoginView {
return frag
}
}
package chat.rocket.android.authentication.di
import chat.rocket.android.authentication.presentation.SignupView
import chat.rocket.android.authentication.ui.SignupFragment
import dagger.Module
import dagger.Provides
@Module
class SignupFragmentModule {
@Provides
fun signupView(frag: SignupFragment): SignupView {
return frag
}
}
package chat.rocket.android.authentication.di
import chat.rocket.android.authentication.presentation.LoginView
import chat.rocket.android.authentication.presentation.TwoFAView
import chat.rocket.android.authentication.ui.LoginFragment
import chat.rocket.android.authentication.ui.TwoFAFragment
import dagger.Module
import dagger.Provides
@Module
class TwoFAFragmentModule {
@Provides
fun loginView(frag: TwoFAFragment): TwoFAView {
return frag
}
}
package chat.rocket.android.authentication.login.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.login.presentation.LoginView
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class LoginFragmentModule {
@Provides
fun loginView(frag: LoginFragment): LoginView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: LoginFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
package chat.rocket.android.authentication.di
package chat.rocket.android.authentication.login.di
import chat.rocket.android.authentication.ui.LoginFragment
import chat.rocket.android.authentication.login.ui.LoginFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......
package chat.rocket.android.authentication.presentation
package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.common.RocketChatAuthException
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.RocketChatTwoFactorException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject
class LoginPresenter @Inject constructor(private val view: LoginView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
var job: Job? = null
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
......@@ -31,31 +31,23 @@ class LoginPresenter @Inject constructor(private val view: LoginView,
fun authenticate(username: String, password: String) {
// TODO - validate input
job = launch(UI) {
view.showProgress()
launchUI(strategy) {
view.showLoading()
try {
val token = client.login(username, password)
view.hideProgress()
navigator.toChatList()
} catch (ex: RocketChatException) {
view.hideProgress()
when(ex) {
is RocketChatAuthException ->
if (ex.error?.contentEquals("totp-required") == true) {
navigator.toTwoFA(navigator.currentServer!!, username, password)
}
is RocketChatTwoFactorException ->
navigator.toTwoFA(navigator.currentServer!!, username, password)
else ->
view.onLoginError(ex.message)
}
view.onLoginError(ex.message)
} finally {
view.hideLoading()
}
}
}
fun unbind() {
job?.let {
it.cancel()
}.also { null }
}
fun signup() {
......
package chat.rocket.android.authentication.login.presentation
import chat.rocket.android.core.behaviours.LoadingView
interface LoginView : LoadingView {
fun onLoginError(message: String?)
}
\ No newline at end of file
package chat.rocket.android.authentication.ui
package chat.rocket.android.authentication.login.ui
import DrawableHelper
import android.app.ProgressDialog
......@@ -10,8 +10,8 @@ import android.widget.ScrollView
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.app.KeyboardHelper
import chat.rocket.android.authentication.presentation.LoginPresenter
import chat.rocket.android.authentication.presentation.LoginView
import chat.rocket.android.authentication.login.presentation.LoginPresenter
import chat.rocket.android.authentication.login.presentation.LoginView
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_log_in.*
import javax.inject.Inject
......@@ -42,11 +42,6 @@ class LoginFragment : Fragment(), LoginView {
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
}
override fun onDestroy() {
presenter.unbind()
super.onDestroy()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_log_in, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......@@ -201,12 +196,13 @@ class LoginFragment : Fragment(), LoginView {
}, 1500)
}
override fun showProgress() {
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating", "Verifying user credentials")
progress = ProgressDialog.show(activity, "Authenticating",
"Verifying user credentials", true, true)
}
override fun hideProgress() {
override fun hideLoading() {
progress?.apply {
cancel()
}
......
......@@ -4,9 +4,9 @@ import android.content.Intent
import chat.rocket.android.R
import chat.rocket.android.app.MainActivity
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.authentication.ui.LoginFragment
import chat.rocket.android.authentication.ui.SignupFragment
import chat.rocket.android.authentication.ui.TwoFAFragment
import chat.rocket.android.authentication.login.ui.LoginFragment
import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.util.addFragmentBackStack
class AuthenticationNavigator(internal val activity: AuthenticationActivity) {
......
package chat.rocket.android.authentication.presentation
interface LoginView {
fun showProgress()
fun hideProgress()
fun onLoginError(message: String?)
}
\ No newline at end of file
package chat.rocket.android.authentication.presentation
interface ServerView
\ No newline at end of file
package chat.rocket.android.authentication.presentation
interface SignupView {
fun showProgress()
fun hideProgress()
fun onSignupError(message: String? = "Unknown error")
}
\ No newline at end of file
package chat.rocket.android.authentication.presentation
interface TwoFAView : LoginView
\ No newline at end of file
package chat.rocket.android.authentication.di
package chat.rocket.android.authentication.server.di
import chat.rocket.android.authentication.presentation.ServerView
import chat.rocket.android.authentication.ui.ServerFragment
import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.authentication.server.ui.ServerFragment
import dagger.Module
import dagger.Provides
......
package chat.rocket.android.authentication.di
package chat.rocket.android.authentication.server.di
import chat.rocket.android.authentication.ui.ServerFragment
import chat.rocket.android.authentication.server.ui.ServerFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......
package chat.rocket.android.authentication.presentation
package chat.rocket.android.authentication.server.presentation
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import javax.inject.Inject
class ServerPresenter @Inject constructor(private val view: ServerView,
......
package chat.rocket.android.authentication.server.presentation
interface ServerView
\ No newline at end of file
package chat.rocket.android.authentication.ui
package chat.rocket.android.authentication.server.ui
import android.os.Bundle
import android.support.v4.app.Fragment
......@@ -7,8 +7,8 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import chat.rocket.android.R
import chat.rocket.android.authentication.presentation.ServerPresenter
import chat.rocket.android.authentication.presentation.ServerView
import chat.rocket.android.authentication.server.presentation.ServerPresenter
import chat.rocket.android.authentication.server.presentation.ServerView
import chat.rocket.android.util.ifEmpty
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_server.*
......
package chat.rocket.android.authentication.signup.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.authentication.signup.ui.SignupFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class SignupFragmentModule {
@Provides
fun signupView(frag: SignupFragment): SignupView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: SignupFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
package chat.rocket.android.authentication.di
package chat.rocket.android.authentication.signup.di
import chat.rocket.android.authentication.ui.SignupFragment
import chat.rocket.android.authentication.signup.ui.SignupFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......
package chat.rocket.android.authentication.presentation
package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import chat.rocket.core.internal.rest.signup
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import timber.log.Timber
import javax.inject.Inject
class SignupPresenter @Inject constructor(private val view: SignupView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
var job: Job? = null
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
......@@ -32,8 +32,8 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
fun signup(email: String, name: String, username: String, password: String) {
// TODO - validate input
job = launch(UI) {
view.showProgress()
launchUI(strategy) {
view.showLoading()
try {
val user = client.signup(email, name, username, password)
......@@ -42,18 +42,12 @@ class SignupPresenter @Inject constructor(private val view: SignupView,
val token = client.login(username, password)
Timber.d("Logged in: $token")
view.hideProgress()
navigator.toChatList()
} catch (ex: RocketChatException) {
view.hideProgress()
view.onSignupError(ex.message)
} finally {
view.hideLoading()
}
}
}
fun unbind() {
job?.let {
it.cancel()
}.also { null }
}
}
\ No newline at end of file
package chat.rocket.android.authentication.signup.presentation
import chat.rocket.android.core.behaviours.LoadingView
interface SignupView : LoadingView {
fun onSignupError(message: String? = "Unknown error")
}
\ No newline at end of file
package chat.rocket.android.authentication.ui
package chat.rocket.android.authentication.signup.ui
import DrawableHelper
import android.app.ProgressDialog
......@@ -9,8 +9,8 @@ import android.view.*
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.app.KeyboardHelper
import chat.rocket.android.authentication.presentation.SignupPresenter
import chat.rocket.android.authentication.presentation.SignupView
import chat.rocket.android.authentication.signup.presentation.SignupPresenter
import chat.rocket.android.authentication.signup.presentation.SignupView
import chat.rocket.android.util.content
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_sign_up.*
......@@ -41,11 +41,6 @@ class SignupFragment : Fragment(), SignupView {
serverUrl = arguments?.getString(SERVER_URL) ?: "https://open.rocket.chat"
}
override fun onDestroy() {
presenter.unbind()
super.onDestroy()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_sign_up, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......@@ -100,12 +95,13 @@ class SignupFragment : Fragment(), SignupView {
}
}
override fun showProgress() {
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating", "Registering user")
progress = ProgressDialog.show(activity, "Authenticating",
"Registering user", true, true)
}
override fun hideProgress() {
override fun hideLoading() {
progress?.apply {
cancel()
}
......
package chat.rocket.android.authentication.twofactor.di
import android.arch.lifecycle.LifecycleOwner
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.dagger.scope.PerFragment
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.experimental.Job
@Module
@PerFragment
class TwoFAFragmentModule {
@Provides
fun loginView(frag: TwoFAFragment): TwoFAView {
return frag
}
@Provides
fun provideLifecycleOwner(frag: TwoFAFragment): LifecycleOwner {
return frag
}
@Provides
fun provideCancelStrategy(owner: LifecycleOwner, jobs: Job): CancelStrategy {
return CancelStrategy(owner, jobs)
}
}
package chat.rocket.android.authentication.di
package chat.rocket.android.authentication.twofactor.di
import chat.rocket.android.authentication.ui.TwoFAFragment
import chat.rocket.android.authentication.twofactor.ui.TwoFAFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
......
package chat.rocket.android.authentication.presentation
package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.infraestructure.AuthTokenRepository
import chat.rocket.android.authentication.presentation.AuthenticationNavigator
import chat.rocket.android.core.lifecycle.CancelStrategy
import chat.rocket.android.util.launchUI
import chat.rocket.common.RocketChatException
import chat.rocket.common.util.PlatformLogger
import chat.rocket.core.RocketChatClient
import chat.rocket.core.internal.rest.login
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Inject
class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
private val strategy: CancelStrategy,
private val navigator: AuthenticationNavigator,
private val okHttpClient: OkHttpClient,
private val logger: PlatformLogger,
private val repository: AuthTokenRepository) {
var job: Job? = null
val client: RocketChatClient = RocketChatClient.create {
httpClient = okHttpClient
restUrl = HttpUrl.parse(navigator.currentServer)!!
......@@ -30,25 +30,18 @@ class TwoFAPresenter @Inject constructor(private val view: TwoFAView,
fun authenticate(username: String, password: String, pin: String) {
// TODO - validate input
job = launch(UI) {
view.showProgress()
launchUI(strategy) {
view.showLoading()
try {
val token = client.login(username, password, pin)
view.hideProgress()
navigator.toChatList()
} catch (ex: RocketChatException) {
view.hideProgress()
view.onLoginError(ex.message)
} finally {
view.hideLoading()
}
}
}
fun unbind() {
job?.let {
it.cancel()
}.also { null }
}
fun signup() {
......
package chat.rocket.android.authentication.twofactor.presentation
import chat.rocket.android.authentication.login.presentation.LoginView
interface TwoFAView : LoginView
\ No newline at end of file
package chat.rocket.android.authentication.ui
package chat.rocket.android.authentication.twofactor.ui
import DrawableHelper
import android.app.ProgressDialog
......@@ -11,8 +11,8 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.widget.Toast
import chat.rocket.android.R
import chat.rocket.android.authentication.presentation.TwoFAPresenter
import chat.rocket.android.authentication.presentation.TwoFAView
import chat.rocket.android.authentication.twofactor.presentation.TwoFAPresenter
import chat.rocket.android.authentication.twofactor.presentation.TwoFAView
import chat.rocket.android.util.content
import dagger.android.support.AndroidSupportInjection
import kotlinx.android.synthetic.main.fragment_authentication_two_fa.*
......@@ -52,12 +52,6 @@ class TwoFAFragment : Fragment(), TwoFAView {
password = arguments?.getString(PASSWORD) ?: ""
}
override fun onDestroy() {
presenter.unbind()
super.onDestroy()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_authentication_two_fa, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......@@ -84,12 +78,13 @@ class TwoFAFragment : Fragment(), TwoFAView {
}
}
override fun showProgress() {
override fun showLoading() {
// TODO - change for a proper progress indicator
progress = ProgressDialog.show(activity, "Authenticating", "Verifying user credentials")
progress = ProgressDialog.show(activity, "Authenticating",
"Verifying user credentials", true, true)
}
override fun hideProgress() {
override fun hideLoading() {
progress?.apply {
cancel()
}
......
......@@ -5,6 +5,7 @@ import android.support.v4.app.Fragment
import android.support.v7.app.AppCompatActivity
import chat.rocket.android.R
import chat.rocket.android.app.LayoutHelper
import chat.rocket.android.authentication.server.ui.ServerFragment
import chat.rocket.android.util.addFragment
import dagger.android.AndroidInjection
import dagger.android.AndroidInjector
......
package chat.rocket.android.core.behaviours
interface LoadingView {
fun showLoading()
fun hideLoading()
}
\ No newline at end of file
package chat.rocket.android.core.lifecycle
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleObserver
import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.experimental.Job
import javax.inject.Inject
class CancelStrategy @Inject constructor(owner: LifecycleOwner, val jobs: Job) : LifecycleObserver {
init {
owner.lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy() {
jobs.cancel()
}
}
\ No newline at end of file
......@@ -2,6 +2,10 @@ package chat.rocket.android.dagger.module
import chat.rocket.android.app.MainActivity
import chat.rocket.android.authentication.di.*
import chat.rocket.android.authentication.login.di.LoginFragmentProvider
import chat.rocket.android.authentication.server.di.ServerFragmentProvider
import chat.rocket.android.authentication.signup.di.SignupFragmentProvider
import chat.rocket.android.authentication.twofactor.di.TwoFAFragmentProvider
import chat.rocket.android.authentication.ui.AuthenticationActivity
import chat.rocket.android.dagger.scope.PerActivity
import dagger.Module
......
package chat.rocket.android.util
import chat.rocket.android.core.lifecycle.CancelStrategy
import kotlinx.coroutines.experimental.CoroutineScope
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch
/**
* Launches a coroutine on the UI context.
*
* @param strategy a CancelStrategy for canceling the coroutine job
*/
fun launchUI(strategy: CancelStrategy, block: suspend CoroutineScope.() -> Unit): Job {
return launch(context = UI, parent = strategy.jobs, block = block)
}
\ No newline at end of file
......@@ -7,7 +7,7 @@ ext {
targetSdk : 27,
buildTools : '27.0.0',
kotlin : '1.2.0',
coroutine : '0.19.3',
coroutine : '0.20',
dokka : '0.9.15',
// Main dependencies
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment