I have a completable method for authentication and I want to retrieve the authenticated user details on login success.
After retrieving them, I want to call an onUserAuthenticated method.
I am doing this with a nested completable (2 levels deep), as I want to sent both the authorization token received on login and the user details to the onUserAuthenticated method.
The problem is that onUserAuthenticated never gets invoked.
class LoginViewModel(val emailAuthentication: EmailAuthentication) : ViewModel() {
val email = MutableLiveData<String>()
val password = MutableLiveData<String>()
fun login() {
emailAuthentication.login(email = email.value!!, password = password.value!!)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Timber.d("User $email logged in")
}, { error ->
Timber.e(error, "Error logging in $email")
})
}
}
class EmailAuthenticationImpl(private val authentication: Authentication,
private val userRepository: UserRepository,
private val authRepository: AuthenticationRepository
) : EmailAuthentication {
override fun register(email: String, password: String): Completable {
return userRepository.register(email, password)
}
override fun login(email: String, password: String): Completable {
// some missing fields validation
return authRepository.login(email, password)
.flatMapCompletable { token ->
userRepository.getCurrentUser()
.flatMapCompletable {
Completable.defer {
// FIXME this never gets invoked
authentication.onUserAuthenticated(AuthType.EMAIL, it, token)
Completable.complete()
}
}
}
}
I tried putting Completable.defer also only before userRepository.getCurrentUser() and both before userRepository.getCurrentUser() and before authentication.onUserAuthenticated(AuthType.EMAIL, it, token), but the code is never reached.
What am I doing wrong?
Related
I've been stuck with this problem for some time now, and I can't seem to find the problem especially when all I did was following a guide online.
I'm trying to make a POST request, and receive a response in exchange:
Request body:
{
"Email":"test#gmail.com",
"firebaseUid":"Test_UID",
"IsBanned":1
}
Response body:
`
{
"Email": "test#gmail.com",
"UserId": 7
}
So basically whenever I submit a request to /users/ to create an account, I get both the email and UserId returned.
data class UserLogin(
#SerializedName("Email") val Email: String,
#SerializedName("UserId") val UserId: Int?,
#SerializedName("IsBanned") val IsBanned: Boolean?,
#SerializedName("firebaseUid") val firebaseUid: String?
)
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2/8000/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T{
return retrofit.create(service)
}
}
class RestApiService {
fun addUser(userData: UserLogin, onResult: (UserLogin?) -> Unit){
val retrofit = ServiceBuilder.buildService(RestApi::class.java)
retrofit.addUser(userData).enqueue(
object : Callback<UserLogin> {
override fun onFailure(call: Call<UserLogin>, t: Throwable) {
Log.d("Failed retrofit",t.message.toString())
onResult(null)
}
override fun onResponse( call: Call<UserLogin>, response: Response<UserLogin>) {
val addedUser = response.body()
onResult(addedUser)
}
}
)
}
}
onFailure doesn't seem to be printing anything on the console. I'm calling the API from a button like this and both Email and UserId keep returning null for some reason:
`
val apiService = RestApiService()
val userInfo = UserLogin(UserId = null,firebaseUid = "TestTestTest", IsBanned = false, Email = "test#gmail.com");
apiService.addUser(userInfo){
Log.d("Retrofit user added", it?.Email.toString())
}
`
I tried to:
Set default values for the data class members.
Tried to check if response is successfull, and then print the errorBody if not. That didn't help either. I'm getting unreadable errors like $1#9afe35d instead.
Everything seem to be working fine when I do requests manually with POSTMAN.
It turned out there was nothing with the code. I just typed the port the wrong way and used a / instead of :
private val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:8000/") // this
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
I'm new to Kotlin and having trouble to change the existing POST request parameters to body instead. I looked at other answers but none of them have the similar code as mine for the request part. I don't know how to change it just getting a lot of syntax errors. Thanks!
import retrofit2.Call
import retrofit2.http.*
interface PostInterface {
#POST("signin")
fun signIn(#Query("email") email: String, #Query("password") password: String): Call<String>
}
class BasicRepo #Inject constructor(val postInterface: PostInterface) {
fun signIn(email: String, password: String): MutableLiveData<Resource> {
val status: MutableLiveData<Resource> = MutableLiveData()
status.value = Resource.loading(null)
postInterface.signIn(email, password).enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
if (response.code() == 200 || response.code() == 201) {
// do something
} else {
// do something
}
}
}
}
}
class User constructor(
email: String,
password: String
)
#POST("signin")
suspend fun signIn(
#Body body: User,
): ResponseBody
Btw, You can use body instead of query params only if your API supports it.
Also, I recommend using a ResultWrapper. Handling errors with Retrofit and Coroutines in a single place
im trying to implement what said in this article:
https://blog.coinbase.com/okhttp-oauth-token-refreshes-b598f55dd3b2
im working on an android app using kotlin
for a coibase wallet.
I was able to get the authorization code, and then an authorization token with retrofit. i have used the token to get user information and also refresh a token. But when it came to create address i am getting wrong token response even if im using a newly created token by using the refresh token with the correct scope create wallet address
so as a suggestion , mentor on my course asked me to use that article implementation, so that tokens are refreshed automatically with correct headers and all.
so i can't find a way to implement correctly and cant find an example that uses code from the article.
ill share my code tomorrow, hope someone can help with this. Thank you for your time, i appreciate.
This is the code im trying to implement based on the article:
object UserNetwork {
//private val logger = HttpLoggingInterceptor()
// .setLevel(HttpLoggingInterceptor.Level.BODY )
private val accessTokenProvider = AccessTokenProviderImp()
private val accessTokenInterceptor = AccessTokenInterceptor(accessTokenProvider)
val client = OkHttpClient.Builder()
.addNetworkInterceptor(accessTokenInterceptor)
.authenticator(AccessTokenAuthenticator(accessTokenProvider))
.build()
val coinBaseClienApiCalls:CoinBaseClienApiCalls
get(){
return Retrofit.Builder()
.baseUrl("https://api.coinbase.com/")
.client(client)
.addConverterFactory(MoshiConverterFactory.create())
.build()
.create(CoinBaseClienApiCalls::class.java)
}
private class UserCallBack(
private val onSuccess:(UserData.Data) -> Unit): Callback<UserData> {
override fun onResponse(call: Call<UserData>, response: Response<UserData>) {
Log.e("ON Response User:"," ${response.body()?.data?.name}")
val newClient = UserData.Data(
name = response.body()?.data?.name?:"",
avatarUrl = response.body()?.data?.avatarUrl?:"",
id = response.body()?.data?.id?:"",
profileBio = response.body()?.data?.profileBio?:"",
profileLocation = response.body()?.data?.profileLocation?:"",
profileUrl = response.body()?.data?.profileUrl ?:"",
resource = response.body()?.data?.resource?:"",
resourcePath = response.body()?.data?.resourcePath?:"",
username = response.body()?.data?.username?:""
)
Log.e("RESPONDED WITH:","Client: ${newClient.name},${newClient.id} ${response.isSuccessful}")
onSuccess(newClient)
}
override fun onFailure(call: Call<UserData>, t: Throwable) {
Log.e("On Failure Address:","$t")
}
}
fun getUser (onSuccess: (UserData.Data) -> Unit){
var token = if(Repository.accessToken != ""){
Repository.accessToken
}else{
""
}
if(token != ""){
coinBaseClienApiCalls.getUser("Bearer $token").enqueue(UserCallBack(onSuccess)) //getUser(token).enqueue(AddressCallBack(onSuccess))
}else{
Log.e("ACCESS TOKEN IN REPOSITORY","${Repository.accessToken}")
}
}
}
class AccessTokenInterceptor(
private val tokenProvider: AccessTokenProvider
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = tokenProvider.token()
return if (token == null) {
chain.proceed(chain.request())
} else {
val authenticatedRequest = chain.request()
.newBuilder()
.addHeader("Authorization", "Bearer $token")
.build()
chain.proceed(authenticatedRequest)
}
}
}
interface AccessTokenProvider {
/**
* Returns an access token. In the event that you don't have a token return null.
*/
fun token(): String?
/**
* Refreshes the token and returns it. This call should be made synchronously.
* In the event that the token could not be refreshed return null.
*/
fun refreshToken(): String?
}
class AccessTokenAuthenticator(
private val tokenProvider: AccessTokenProvider
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// We need to have a token in order to refresh it.
val token = tokenProvider.token() ?: return null
synchronized(this) {
val newToken = tokenProvider.token()
Log.e("NEW TOKEN AUTHENTICATOR","$newToken")
// Check if the request made was previously made as an authenticated request.
if (response.request.header("Authorization") != null) {
// If the token has changed since the request was made, use the new token.
if (newToken != token) {
Log.e("Testing Authenticator1","Testing1")
return response.request
.newBuilder()
.removeHeader("Authorization")
.addHeader("Authorization", "Bearer $newToken")
.build()
}
val updatedToken = tokenProvider.refreshToken() ?: return null
Log.e("Testing Authenticator2","Testing2")
// Retry the request with the new token.
return response.request
.newBuilder()
.removeHeader("Authorization")
.addHeader("Authorization", "Bearer $updatedToken")
.build()
}
}
return null
}
}
class AccessTokenProviderImp():AccessTokenProvider {
var token = Repository.accessToken
override fun token(): String? {
return token
}
override fun refreshToken(): String? {
return token
}
}
I fixed the way the AccessTokenProviderImp() requested the existing token or the newly refreshed token, remove the check if (newToken != token) from Authenticator. Works great.
I need to make a sync call to reauthenticate the user and get a new token, but I haven't found a way that works. The code below blocks the thread and it is never unblocked, ie. I have an infinite loop
class ApolloAuthenticator(private val authenticated: Boolean) : Authenticator {
#Throws(IOException::class)
override fun authenticate(route: Route, response: Response): Request? {
// Refresh your access_token using a synchronous api request
if (response.request().header(HEADER_KEY_APOLLO_AUTHORIZATION) != null) {
return null //if you've tried to authorize and failed, give up
}
synchronized(this) {
refreshTokenSync() // This is blocked and never unblocked
val newToken = getApolloTokenFromSharedPreference()
return response.request().newBuilder()
.header(HEADER_KEY_APOLLO_AUTHORIZATION, newToken)
.build()
}
private fun refreshTokenSync(): EmptyResult {
//Refresh token, synchronously
val repository = Injection.provideSignInRepository()
return repository
.signInGraphQL()
.toBlocking()
.first()
}
fun signInGraphQL() : Observable<EmptyResult> =
sharedPreferencesDataSource.identifier
.flatMap { result -> graphqlAuthenticationDataSource.getAuth(result) }
.flatMap { result -> sharedPreferencesDataSource.saveApolloToken(result) }
.onErrorReturn { EmptyResult() }
}
---------- Use of it
val apollAuthenticator = ApolloAuthenticator(authenticated)
val okHttpBuilder =
OkHttpClient.Builder()
.authenticator(apollAuthenticator)
I haven't found a way to make a sync call using RxJava, but I can make it by using kotlin coutorine runBlocking, which will block the thread until the request is finished:
synchronized(this) {
runBlocking {
val subscription = ApolloReauthenticator.signInGraphQl() // await until it's finished
subscription.unsubscribe()
}
}
fun signInGraphQl(): Subscription {
return repository.refreshToken()
.subscribe(
{ Observable.just(EmptyResult()) },
{ Observable.just(EmptyResult()) }
)
}
When I was try to login on my service via retrofit. When my service is off, 10 seconds after clicking the button I got an SocketTimeoutException exception.
So far everything is normal but again, I clicked the button again after the error gave the same error immediately. What's wrong?
interface LoginService {
#FormUrlEncoded
#POST("/login")
fun login(#Field("id") id: String, #Field("pw") pw: String): Deferred<Response<User>>
}
class LoginViewModel : ViewModel() {
private var job: Job = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + job)
private val service by lazy { RetrofitApiFactory().create(LoginService::class.java) }
private val excHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable);
}
fun doLogin(id: String, pw: String) {
scope.launch(excHandler) {
val response = service.login(id, pw).await()
if (response.isSuccessful) {
response.body()
?.let { user -> doOnSuccess(user) }
?: doOnError(InvalidUserException())
} else doOnError(Exception())
}
}
private fun CoroutineScope.doOnError(e: Throwable) {
excHandler.handleException(coroutineContext, e)
}
private fun doOnSuccess(user: User) {
...
}
override fun onCleared() {
job.cancel()
}
}
You need to change your CoroutineScope to not re-use the same Job. It's already considered as failed, so it will not even begin executing.
See related issue on github.