Retrieve JSON response from rxjava/retrofit POST request - android

I've created a POST request using rxjava and retrofit that successfully hits my backend server and logs the user in (I get a 201 response in my console, all good). However, I want to then retrieve the users access token that is returned, but when I try to access the rxjava result, it just gives me the object I passed to it. Nowhere can I find out how to get the json success response. I have also verified there is in fact a response in Postman, so it's something with how I make this call.
Here is my Retrofit portion
#Headers("Content-Type: application/json")
#POST("api/v1/login")
fun loginTask(#Body credentials: UserLogin)
: Observable<UserLogin>
And the correspoinding API function
class ApiFunctions(val apiService: LunchVoteApi) {
fun provideHello(): io.reactivex.Observable<Hello> {
return apiService.helloMessage()
}
fun loginTask(email: String, password: String): io.reactivex.Observable<UserLogin> {
val credentials: UserLogin = UserLogin(email, password)
return apiService.loginTask(credentials)
}
}
The UserLogin model that is deserialized by Gson
data class UserLogin(
#SerializedName("email") val email: String,
#SerializedName("password") val password: String
)
And finally the call in my LoginActivity
val loginTask = ApiProvider.provideLoginTask()
override fun doInBackground(vararg params: Void): Boolean? {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
// Thread.sleep(2000)
compositeDisposable.add(
loginTask.loginTask(mEmail, mPassword)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe ({
result ->
System.out.println(result.toString())
}, { error ->
System.out.println(error)
})
)
} catch (e: InterruptedException) {
return false
}
The confusion comes when I try to access the result -> portion of the compositeDisposable call. It just prints out the UserLogin object. Am I missing something here? Thanks.

Turns out I was returning my UserLogin type instead of a pojo object with an access token property.
Changing my retrofit call to #Headers("Content-Type: application/json")
#POST("api/v1/login")
fun loginTask(#Body credentials: UserLogin)
: Observable<AccessToken>
And creating a new model
data class AccessToken(
#SerializedName("accessToken") val email: String
)
I am now able to print out the token. Thanks to #john-oreilly

Related

Unable to create call adapter for X model for X method

Guess, I'm already baffled with what I'm doing, so I'm reaching out to the community.
I have the following:
UserModel
data class UserModel(
val id: String,
val name: String
){}
UserService
#Headers("Accept: application/json")
#POST("register")
fun doRegisterUserTest(
#Query("user_id") userName: String,
#Query("password") passWord: String
): Deferred<UserModel>
UserRepository
fun test(username: String, password: String): List<UserModel>{
Network.createNetworkRequest().create(UserService::class.java).doRegisterUserTest(username, password)
val x: UserService by lazy {Network.createNetworkRequest().create(UserService::class.java)}
val y = x.doRegisterUserTest(username, password)
return y
}
UserViewModel
private val _result = MutableLiveData<List<UserModel>>()
val result: LiveData<List<UserModel>> = _result
fun onRegister(username: String, password: String) {
viewModelScope.launch {
// connect to api server
//_registrationStatus.value = RegistrationStatus.LOADING
try {
_result.value = userRepository.test(username, password)
....
}
}
}
Network Client
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/**
* Main entry point for dto access. Call like `Network.devbytes.getPlaylist()`
*/
object Network {
fun createNetworkRequest(): Retrofit {
// Configure retrofit to parse JSON and use coroutines
val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:8081/api/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
return retrofit
}
}
In my api, the above (in ideal scenario) will yield the following:
{
"user": [
{
"user_sys_id": 0,
"name": "Blah blah",
}
]
}
I want to read the values from _result.value = userRepository.test(username, password), I presume that test function will return a List> but I don't know how to access the members.
How can I check _result for the values of user_sys_id and name?
How come I'm getting the following (if I convert the response straight to a List)
Unable to create call adapter for Xmodel for method UserService.doRegisterUserTest
Am I missing something?
Likewise, is it fine to do a Retrofit Call if you were already using Coroutines?
From this Unable to create call adapter for retrofit2.Response<...>, it seems that the function should be suspended, but if I do that I would get a warning
Redundant suspend modifier
PS. Although, if I follow repository pattern, I'm able to see that results where written on a local database the values retrieved from the API, however, I don't think that is correct to store data that aren't supposed to be stored in the first place, say for eg. in a Registration, if the registration fails you just want to read the error message directly from the response

Twitter oauth/request_token 200 code with empty response body

I'm implementing Twitter OAuth flows as per:
https://developer.twitter.com/en/docs/authentication/guides/log-in-with-twitter
I am getting a response back for the first step (oauth/request_token) which has a 200 code, but the response body is completely empty.
I'm using Retrofit to call the API, and have hooked up an interceptor OkHttpClient to debug the response like so:
val client = OkHttpClient.Builder().also { builder ->
builder.addInterceptor { chain ->
val request = chain.request()
val response = chain.proceed(request)
response
}
}.build()
Then setting up Retrofit like so:
Retrofit.Builder()
.baseUrl(TWITTER_AUTH_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(TwitterAuthRetrofit::class.java)
.getRequestToken(
authorizationHeaders
).enqueue(object : Callback<TwitterRequestToken> {
override fun onResponse(call: Call<TwitterRequestToken>, response: Response<TwitterRequestToken>) {
onSuccess(response.body())
}
override fun onFailure(call: Call<TwitterRequestToken>, t: Throwable) {
onFailure()
}
})
When I debug in the interceptor, I can see the response is successful (200) but the response body is empty, which I think is causing my Gson deserialization to fail.
The result of calling response.body.contentLength() in the interceptor is -1.
The result of calling response.code in the interceptor is 200.
Here is the model I am attempting to deserialize the response body to:
data class TwitterRequestToken(
#SerializedName(value = "oauth_token")
val token: String,
#SerializedName(value = "oauth_token_secret")
val tokenSecret: String,
#SerializedName(value = "oauth_callback_confirmed")
val callbackConfirmed: Boolean
)
Note I am using #SerializedName to provide the keys for the response body, whilst the names of my properties are arbitrary to our app (we use camel case). I add a GsonConverterFactory to the Retrofit instance using the builder and have done this in the same way for many other requests before with no issues.
Here is the response I am getting from the API, which I am looking at via debugging in the interceptor above:
Response{protocol=h2, code=200, message=, url=https://api.twitter.com/oauth/request_token}
And here is the cause message from the Throwable I am getting in the onFailure callback from Retrofit:
com.google.gson.stream.MalformedJsonException:
Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
Has anyone got any idea what might cause this?
Finally figured it out, hope this helps someone in future...
The response body from the Twitter API for oauth/request_token isn't encoded as JSON; you will need to read it from the response buffer. Specifically, when implementing the API with Retrofit, you will want your Retrofit interface to return ResponseBody (rather than your custom class), remove GSON from the Retrofit builder and, in the onResponseCallback from Retrofit, write the following code to read the buffer to a string, then split the string on & to get each key val pair, then you can split each of these on = and make sure you have all 3 values before constructing your model:
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
response.body()?.also { body ->
body.source().readString(Charsets.UTF_8).split('&').map { param ->
param.split('=').let { keyVal ->
keyVal[0] to keyVal[1]
}
}.toMap().let { paramMap ->
val oauthToken = paramMap["oauth_token"]
val oauthTokenSecret = paramMap["oauth_token_secret"]
val oauthCallbackConfirmed = paramMap["oauth_callback_confirmed"]?.toBoolean()
if (oauthToken == null || oauthTokenSecret == null || oauthCallbackConfirmed == null) {
onFailure()
} else {
onSuccess(
TwitterRequestToken(
oauthToken,
oauthTokenSecret,
oauthCallbackConfirmed
)
)
}
}
} ?: onFailure()
}

Always getting 401 when using android device but not in Postman

I am sending my token as Authorization in Retrofit but I always get a 401 code. But if I use the same token in Postman, I can get access. I know I am able to access the webapi because I can Login just fine and able to get the token from the Web Api. Please see my code below:
ApiService Interface
#POST("consolidated/sample")
fun sample(#Header("Authorization") token: String): Call<ResponseBody>
Calling the Service
private fun pushTransactionsToWebApi() {
val vApiService = ApiServiceBuillder.buildService(ApiService::class.java)
CoroutineScope(Main).launch {
var token = SharedDataManager.getInstance(context!!).applicationToken
var tokenArr = token!!.split(':')
responseFromApi = tokenArr[1] ==> I use this so I can remove the word "token" at the beginning of the token string
token = "Bearer ${responseFromApi}"
Log.i("TAG", "${token}") ==> ####
val call = vApiService.sample(token)
if(!call.isExecuted) {
call.enqueue(object : Callback<ResponseBody>{
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
responseFromApi = t.message
}
override fun onResponse(
call: Call<ResponseBody>,
response: Response<ResponseBody>
) {
if(response.isSuccessful){
Toast.makeText(context, "We are OK", Toast.LENGTH_LONG).show()
} else {
progressDialog!!.dismiss()
Toast.makeText(context, "We are NOT OK", Toast.LENGTH_LONG).show()
}
}
})
}
}
}
### => Result in my Log.i()
2020-04-08 13:03:09.235 14185-14185/com.kotlin.ambulantlcs I/TAG:
Bearer
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ODgwNSIsInVzZXJJZCI6IjEiLCJmaXJzdE5hbWUiOiJKdWFuIiwibWlkZGxlTmFtZSI6IkEuIiwibGFzdE5hbWUiOiJEZWxhIENydXoiLCJ0cmFuc2FjdGlvbktleSI6IjJkNjZlYzMxLWI5M2ItNDI2ZC1hMzJlLTM0Yjc4OWE4M2E3OCIsInJldmVudWVEYXRlIjoiMjIvMDMvMjAyMCAyOjI0OjM0IFBNIiwic2hpZnQiOiIyIiwic29zSWQiOiIxMjM0NTYiLCJzb2RJZCI6IjY4IiwicGxhemEiOiI4MDMiLCJoYXNEZXBhcnRlZCI6IkZhbHNlIiwianRpIjoiNjhkMDdmNzEtMThiYy00NmQwLTg3YzEtY2MxMjk4YjgxZDkwIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbIlVzZXIiLCJBZG1pbiJdLCJleHAiOjE1ODY0MDg1NzUsImlzcyI6Imh0dHA6Ly8xOTIuMTY4LjEuNDo1MDAwIn0.m1mZw79KLIxq4pZPmBRbN7TjILvhvbUIJOCWDEM8I-k"}
If I paste this in my Postman, I can get access
What do I need to do? Thank you!
From our conversation in the comments, it seems like you're getting a json {"token": "..."} from SharedDataManager.getInstance(context!!).applicationToken. This explains why when you split in : you get printed in the log "..."}.
There are a lot of ways to deserialize json in Android. Here are some options. I think the vanilla way is something like:
val root = JSONObject(SharedDataManager.getInstance(context!!).applicationToken)
val token = root.getString("token")
With this you'll have the token in token.
However, if you already have a json library you could use it. For example, with gson you could do something like:
data class TokenData(
#SerializedName("token")
val token: String)
val token = Gson().fromJson(
SharedDataManager.getInstance(context!!).applicationToken,
TokenData::class.java)
You can now use token.
With Moshi using the kotlin gen library - com.squareup.moshi:moshi-kotlin-codegen - you can define the above model like:
#JsonClass(generateAdapter = true)
data class TokenData(
#Json(name = "token")
val token: String)
// Then get it like:
val token = Moshi.Builder()
.build()
.adapter(TokenData::class.java)
.fromJson(SharedDataManager.getInstance(context!!).applicationToken)
These are just some options. There's also the popular Jackson. Pick the one that suits best your needs. Hope this helps
Remove " " quotes from token
make sure that keys must be same
pass token like as:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ODgwNSIsInVzZXJJZCI6IjEiLCJmaXJzdE5hbWUiOiJKdWFuIiwibWlkZGxlTmFtZSI6IkEuIiwibGFzdE5hbWUiOiJEZWxhIENydXoiLCJ0cmFuc2FjdGlvbktleSI6IjJkNjZlYzMxLWI5M2ItNDI2ZC1hMzJlLTM0Yjc4OWE4M2E3OCIsInJldmVudWVEYXRlIjoiMjIvMDMvMjAyMCAyOjI0OjM0IFBNIiwic2hpZnQiOiIyIiwic29zSWQiOiIxMjM0NTYiLCJzb2RJZCI6IjY4IiwicGxhemEiOiI4MDMiLCJoYXNEZXBhcnRlZCI6IkZhbHNlIiwianRpIjoiNjhkMDdmNzEtMThiYy00NmQwLTg3YzEtY2MxMjk4YjgxZDkwIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbIlVzZXIiLCJBZG1pbiJdLCJleHAiOjE1ODY0MDg1NzUsImlzcyI6Imh0dHA6Ly8xOTIuMTY4LjEuNDo1MDAwIn0.m1mZw79KLIxq4pZPmBRbN7TjILvhvbUIJOCWDEM8I-k

How to read the response data of Apollo Client response/ GraphQL response in Kotlin Andorid

I am developing an Android application using Kotlin. In my application, I am consuming GraphQL API using Apollo Client. What I am trying to do now is that I want to retrieve a response field of the response.
This is my code
protected fun _handleLoginButtonClick(view: View) {
val apolloClient = ApolloClient.builder()
.serverUrl("https://app.herokuapp.com/graphql")
.okHttpClient(OkHttpClient())
.build()
val loginMutation = LoginMutation.builder()
.identity(view.etf_email.text.toString())
.password(view.etf_password.text.toString())
.build()
view.tv_login_error_message.text = "Started making request"
apolloClient.mutate(loginMutation).enqueue(object: ApolloCall.Callback<LoginMutation.Data>() {
override fun onFailure(e: ApolloException) {
view.tv_login_error_message.text = e.message
}
override fun onResponse(response: Response<LoginMutation.Data>) {
//here I dont know how to retrieve a field, accessToken
}
})
}
As you can see the comment in the onResponse callback, I cannot figure out how to retrieve the accessToken field. How can I retrieve it?
OnResponse Contains response Object and it has data object from where you can get your fields.
apolloClient.mutate(loginMutation).enqueue(object: ApolloCall.Callback<LoginMutation.Data>() {
override fun onFailure(e: ApolloException) {
view.tv_login_error_message.text = e.message
}
override fun onResponse(response: Response<LoginMutation.Data>) {
//here you can use response to get your model data like accessToken
response.data.(here you can get data from your model. eg accessToken)
}
})

Singleton that will hold an instance of Retrofit

So I have created a login that will take an username and password input from the user, encode it with Base64 in order to create a token in the format: ("Authorization", AUTH) where AUTH = "Basic " + Base64 encoding of user and password. This is sent via Headers.
So, in the end, it looks like this: Authorization: Basic XXXXXX, where XXXXXX is the user token.
And then it will check whether or not that user exists in the database via an API request.
I am using Retrofit and OkHttp3 in the same class as RetrofitClient and this class is responsible for using the API and adding those Headers.
Later, I use the RetrofitClient class on the Login Activity.
What I need to do now, is make this "token" available to all the other activities by creating a Singleton that will store the data of the Retrofit after a successful login. But I do not know how to do this.
I started learning Kotlin and Android 3 weeks ago.
Here is my code:
GET_LOGIN.kt
interface GET_LOGIN {
#GET("login")
fun getAccessToken() : Call<String>
}
RetrofitClient.kt
class RetrofitClient {
fun login(username:String, password:String){
val credentials = username + ":" + password
val AUTH = "Basic " + Base64.encodeToString(credentials.toByteArray(Charsets.UTF_8), Base64.DEFAULT).trim()
retrofit = init(AUTH)
}
// Initializing Retrofit
fun init(AUTH: String) : Retrofit{
// Creating the instance of an Interceptor
val logging = HttpLoggingInterceptor()
logging.level = HttpLoggingInterceptor.Level.BODY
// Creating the OkHttp Builder
val client = OkHttpClient().newBuilder()
// Creating the custom Interceptor with Headers
val interceptor = Interceptor { chain ->
val request = chain?.request()?.newBuilder()?.addHeader("Authorization", AUTH)?.build()
chain?.proceed(request)
}
client.addInterceptor(interceptor) // Attaching the Interceptor
//client.addInterceptor(logging) // Attaching the Interceptor
// Creating the instance of a Builder
val retrofit = Retrofit.Builder()
.baseUrl("https://srodki.herokuapp.com/") // The API server
.client(client.build()) // Adding Http Client
.addConverterFactory(GsonConverterFactory.create()) // Object Converter
.build()
return retrofit
}
lateinit var retrofit : Retrofit
fun providesGetLogin(): GET_LOGIN = retrofit.create(GET_LOGIN::class.java)
}
LoginActivity.kt
var RetrofitClient : RetrofitClient = RetrofitClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginBtn.setOnClickListener {
val user = userTxt.text.toString()
val pass = passTxt.text.toString()
if (validateLogin(user, pass)){
login(user, pass)
}
}
}
fun validateLogin(user: String, pass: String): Boolean {
if (user == null || user.trim().isEmpty()){
Toast.makeText(this, "Missing Username or Password", Toast.LENGTH_SHORT).show()
return false
}
if (pass == null || pass.trim().isEmpty()){
Toast.makeText(this, "Missing Username or Password", Toast.LENGTH_SHORT).show()
return false
}
return true
}
fun login(user: String, pass: String) {
RetrofitClient.login(user, pass)
val apiLogin = RetrofitClient.providesGetLogin().getAccessToken()
apiLogin.enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
if(response.isSuccessful){
if(response.body()?.code == 0){
Toast.makeText(this#LoginActivity, "Login Successful!", Toast.LENGTH_SHORT).show()
val intent = Intent(this#LoginActivity, List_usersActivity::class.java)
startActivity(intent)
} else {
Toast.makeText(this#LoginActivity, "Login Failed.", Toast.LENGTH_SHORT).show()
}
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
Toast.makeText(this#LoginActivity, "Login Failed.", Toast.LENGTH_SHORT).show()
}
})
}
}
first and foremost, please use camel case on java and kotlin. We have standards in java and kotlin on programming. And i can see that you are trying to do DI, but, thats not how you do it in Android.
Anyways, you could do this a couple of ways without even using a singleton but by saving it on a storage. Options are Shared Preferences, Local Storage and SQLite. But, if you insist on using a singleton. You can do it like this:
object MySingleton { // This is how you declare singletons in kotlin
lateinit var token: String;
}
EDIT
So, from your comment, it looked like you need to store the token. You could start by using sharedpreferences(database would be better) and store the token there. I assume you don't know how to so here is an example:
val sp = SharedPreferences("sp", 0);
sp.edit().putString("token", theTokenVariable); // not sure of this function
sp.edit().apply(); // you could use commit if you dont mind sharedpreferences to lag your screen(if it ever will)
Now how do you get the token from retrofit? The only way i could help you right now is that you could retrieve the response body from the response variable you receive from onResponse of the retrofit call. From there it is your problem mate. I don't know how your response is formatted, how it should be retrieved etc. A recommendation would be to format it as JSON.

Categories

Resources