https://kotlinlang.org logo
#arrow
Title
# arrow
i

Iurysza

08/26/2022, 1:37 PM
Hey everyone, I’ve just started using arrow in a personal project and I was looking to get some feedback on my first try of it:
Copy code
@Singleton
class AuthUseCase @Inject constructor(
    private val networkDataSource: NetworkDataSource,
    private val storage: AuthStorage,
) {

    suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
        val token = storage.getToken().bind()
        ensure(token.expirationDate.isInTheFuture()) {
            TokenExpired
        }
    }.handleErrorWith { error ->
        when (error) {
            is TokenNotFound,
            TokenExpired,
            -> fetchNewTokenAndSaveIt()
            else -> error.left()
        }
    }

    private suspend fun fetchNewTokenAndSaveIt(): Either<DomainError, Unit> = either {
        networkDataSource.getAccessToken().bind()
    }.flatMap { (accessToken, expiresIn) ->
        catch {
            AuthToken(
                accessToken,
                expirationDate = nowPlusMillis(expiresIn)
            )
        }.mapLeft { InvalidExpirationDate }
    }.map { storage.putToken(it) }
}
Is the code above
arrow
idiomatic? Anything that can be improved or that is completely off? Thanks!
q

qohatpp

08/26/2022, 3:29 PM
I would do something like this, It is not tested in the compiler but could give you a clue.
Copy code
@Singleton
class AuthUseCase @Inject constructor(
    private val networkDataSource: NetworkDataSource,
    private val storage: AuthStorage,
) {
    suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
        storage.getToken()
        .ensure({ TokenNotExpired }, { !token.expirationDate.isInTheFuture() })
        .bind() //This should interrupt with a DomainError if the old token is still valid and it is not necessary to refresh
        val (accessToken, expiresIn) = networkDataSource.getAccessToken().bind()
        val newToken = buildNewToken(accessToken, expiresIn).bind()
        storage.putToken(newToken) //Would be great if you can map this sideeffect in an Either as well and call .bind()
    }

    private fun buildNewToken(accessToken: String, expiresIn: WhatEverExpiresInTypeIs): Either<DomainError, AuthToken> =
        Either.catch {
            AuthToken(
                accessToken,
                expirationDate = nowPlusMillis(expiresIn)
            )
        }.mapLeft { InvalidExpirationDate }

}
You also can handle the
refreshTokenIfNeeded()
errors doing an extension function like this
Copy code
inline fun <reified A : Any> Either<DomainError, A>.handleErrors(): Unit =
    when (this) {
        is Either.Left -> println("Was not necessary refresh")
        is Either.Right -> println("The token was refreshed")
    }
so you could do:
Copy code
suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
        ...
    }.handleErrors()
Note that the
Copy code
storage.getToken()
        .ensure({ TokenNotExpired }, { !token.expirationDate.isInTheFuture() })
        .bind()
Is oriented to the right side of the either. I would do like that, because I can avoid that the
bind()
interrupts the continuation by doing
shift<R>
in the case you get a
left
in the
ensure
.
i

Iurysza

08/26/2022, 9:03 PM
Hey, those are great tips! Thanks!!!
q

qohatpp

08/26/2022, 9:38 PM
Hey I think this is another way to do it. This supports your
TokenNotFound
when calling the
storage.getToken()
I mean, you can play with all of these ways and even find a better way to do it. Good luck!!
Copy code
@Singleton
class AuthUseCase @Inject constructor(
    private val networkDataSource: NetworkDataSource,
    private val storage: AuthStorage,
) {
    suspend fun refreshTokenIfNeeded(): Either<DomainError, Unit> = either {
        val token = storage.getToken().ensure({ TokenExpired }, { token.expirationDate.isInTheFuture() })
        when(token) {
            is Either.Left -> fetchNewToken().bind()
            is Either.Right -> println("Token is still valid")
        }
    }
    
    private suspend fun fetchNewToken(): Either<DomainError, Unit> {
        val (accessToken, expiresIn) = networkDataSource.getAccessToken().bind()
        val newToken = buildNewToken(accessToken, expiresIn).bind()
        storage.putToken(newToken).bind()
    }

    private fun buildNewToken(accessToken: String, expiresIn: WhatEverExpiresInTypeIs): Either<DomainError, AuthToken> =
        Either.catch {
            AuthToken(
                accessToken,
                expirationDate = nowPlusMillis(expiresIn)
            )
        }.mapLeft { InvalidExpirationDate }

}
4 Views