Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement login feature using Arrow #1

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,21 @@ dependencies {
implementation Dependencies.retrofitGsonConverter
implementation Dependencies.okHttpLogger
implementation Dependencies.ok2curl
implementation Dependencies.arrowCore
implementation Dependencies.arrowTypeclasses
implementation Dependencies.arrowEffects
implementation Dependencies.arrowEffectsKotlinxCoroutines
implementation Dependencies.kotlinxCoroutinesCore
implementation Dependencies.kotlinxCoroutinesAndroid

testImplementation Dependencies.jUnit
testImplementation Dependencies.mockito
testImplementation Dependencies.mockitoKotlin
testImplementation Dependencies.kotlinTestJunit
testImplementation Dependencies.kluent
testImplementation Dependencies.wiremock
testImplementation Dependencies.mockwebserver

androidTestImplementation Dependencies.AndroidTestRunner
androidTestImplementation Dependencies.espresso
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.jcminarro.authexample.internal.interactor

import arrow.core.Either
import arrow.effects.DeferredK
import arrow.effects.unsafeRunSync

abstract class FPAsyncInteractor<I, O, E> {

fun execute(input: I, onError: (E) -> Unit = {}, onSuccess: (O) -> Unit): Unit =
DeferredK{run(input)}.unsafeRunSync().fold(onError, onSuccess)

protected abstract fun run(input: I): Either<E, O>
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.jcminarro.authexample.internal.network

import arrow.core.Try
import retrofit2.Call
import java.io.IOException

open class ApiClient<T>(protected val endpoint: T) {

@Throws(IOException::class)
protected fun <U> evaluateCall(call: Call<U>): U {
val response = call.execute()
if (!response.isSuccessful) {
throw APIIOException(response.raw())
}
return response.body()
}

protected fun <U> evaluateCallFP(call: Call<U>): Try<U> = Try {evaluateCall(call)}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jcminarro.authexample.internal.network.login

import arrow.core.Try
import com.jcminarro.authexample.internal.network.APIIOException
import com.jcminarro.authexample.internal.network.ApiClient
import com.jcminarro.authexample.internal.network.OAuth
Expand All @@ -10,5 +11,10 @@ constructor(endpoint: LoginEndpoint) : ApiClient<LoginEndpoint>(endpoint) {

@Throws(APIIOException::class)
fun login(username: String, password: String): OAuth =
map(evaluateCall(endpoint.login(LoginBody(username, password))))
map(evaluateCall(loginCall(username, password)))

fun loginFP(username: String, password: String): Try<OAuth> =
evaluateCallFP(loginCall(username, password)).map {map(it)}

private fun loginCall(username: String, password: String) = endpoint.login(LoginBody(username, password))
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.jcminarro.authexample.internal.repository

import arrow.core.Try
import com.jcminarro.authexample.internal.localdatasource.SessionDatasource
import com.jcminarro.authexample.internal.network.APIIOException
import com.jcminarro.authexample.internal.network.login.LoginApiClient
import com.jcminarro.authexample.internal.network.refresh.RefreshApiClient
import javax.inject.Inject

class SessionRepository @Inject
constructor(
private val loginApiClient: LoginApiClient,
private val refreshApiClient: RefreshApiClient,
private val sessionDatasource: SessionDatasource) {

@Throws(APIIOException::class)
fun login(username: String, password: String): Boolean {
sessionDatasource.storeOAuthSession(loginApiClient.login(username, password))
return true
}

fun loginFP(username: String, password: String): Try<Boolean> =
loginApiClient.loginFP(username, password).map {
sessionDatasource.storeOAuthSession(it)
true
}

fun refreshSession(): Boolean {
val oAuth = sessionDatasource.oAuthSession
if (oAuth != null) {
try {
sessionDatasource.storeOAuthSession(refreshApiClient.refresh(oAuth.refreshToken))
return true
} catch (e: Exception) {
e.printStackTrace()
}
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ protected void initDI() {
}

private void onLogin() {
presenter.login(username.getText().toString(),
presenter.loginFP(username.getText().toString(),
password.getText().toString());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.jcminarro.authexample.login

import arrow.core.Either
import arrow.core.Left
import arrow.core.Right
import com.jcminarro.authexample.internal.interactor.FPAsyncInteractor
import com.jcminarro.authexample.internal.repository.SessionRepository
import javax.inject.Inject

class LoginFPInteractor @Inject constructor(private val sessionRepository: SessionRepository)
: FPAsyncInteractor<Input, Boolean, CredentialError>() {
override fun run(input: Input): Either<CredentialError, Boolean> = with(input) {
sessionRepository.loginFP(username, password).fold({Left(CredentialError)}, {Right(it)})
}
}

data class Input(val username: String, val password: String)
object CredentialError

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.jcminarro.authexample.login

import com.jcminarro.authexample.internal.interactor.InteractorExecutor
import com.jcminarro.authexample.internal.navigator.Navigator
import com.jcminarro.authexample.internal.presenter.BasePresenter
import javax.inject.Inject

class LoginPresenter @Inject constructor(
private val loginInteractor: LoginInteractor,
private val loginFPInteractor: LoginFPInteractor,
private val navigator: Navigator) : BasePresenter<LoginPresenter.View>() {

override fun update() {
super.update()
showLoginStatus()
}

private fun showLoginStatus() {
view.showLogin()
view.hideLoading()
}

fun login(username: String, password: String) =
performLoginIfNotEmptyCredential(username, password, this::performLogin)

fun loginFP(username: String, password: String) =
performLoginIfNotEmptyCredential(username, password, this::performLoginFP)

private fun performLoginFP(username: String, password: String) {
showLoadingStatus()
loginFPInteractor.execute(Input(username, password), {onLoginError()}, this::onLoginSuccess)
}

private fun performLogin(username: String, password: String) {
showLoadingStatus()
execute<LoginInteractor.Input, Boolean, Exception>(loginInteractor,
LoginInteractor.Input(username, password),
object : InteractorExecutor.Callback<Boolean, Exception> {
override fun onSuccess(isLoggedIn: Boolean) = onLoginSuccess(isLoggedIn)
override fun onError(error: Exception) = onLoginError()
})
}

private fun onLoginSuccess(isLoggedIn: Boolean) = if (isLoggedIn) {
onLoggedIn()
} else {
onLoginError()
}

private fun performLoginIfNotEmptyCredential(
username: String,
password: String,
loginFunction: (String, String) -> Unit) =
if (username.isBlank() || password.isBlank()) {
onLoginError()
} else {
loginFunction(username, password)
}

private fun showLoadingStatus() {
view.showLoading()
view.hideLogin()
}

private fun onLoginError() {
showLoginStatus()
view.showError()
}

private fun onLoggedIn() {
view.close()
navigator.navigateToQuote()
}

interface View : BasePresenter.View {

fun close()
fun showError()
fun showLogin()
fun hideLogin()
fun showLoading()
fun hideLoading()
}
}
Loading