I have two APIs say API 1 and API 2. API 1 get authentication token and API 2 get user profile info. I need to hit API 2 from ViewModel but if API 2 gives an authentication error, then I need to get new token first and then hit API 2(user info)
Currently, I use RxJava Single and Retrofit for all APIs but for APIs one by one. There are a lot of APIs that use authentication token and if it expires in any of the APIs then I need to call authentication API and get the token again and then call the APIs with a new authentication token.
What should be the best way to design a wrapper so that it can be used with all APIs with code reusability as well.
Thanks in advance.
I have some idea. If you have basemodel for getting response for API, you can make custom getter for response code and check if response code in token_expired.
This is sample response that I got From API.
{
response_code: "200",
response_msg: "",
result: ...
}
And this is my BaseModel.
class BaseModel<T> {
#SerializedName("response_code")
var response_code: Int? = null
get() {
if(field == ErrorStatusCode.TOKEN_EXPIRE) {
ErrorCodeHandler.requestNewToken()
}
return field
},
#SerializedName("response_msg")
var response_msg: String? = null
#SerializedName("result")
var data: T? = null
}
So whenever token expire, I will request Net token by call ErrorCodeHandler.requestNewToken()
But If you want to recall API that found token timeout I have no idea what to do too.
PS. Hope this help.
Related
I have an application that is implemented with clean architecture with MVVM pattern. In this app we need a refresh token request that is needed in all the app features. When a refresh token is success then call the last request again. What is the best way to implement this?
I have two idea:
1 - Implement it in every feature and use it. So if I have three features in my app I will implement it three time.
2 - Implemented globally
I know the first idea but I can't figure out how to do the second one which I think is better.
I use retrofit for networking . The structure is : data , domain , presentation .
With Retrofit you can create a custom Authenticator which will trigger when a request was denied because of an authentication error (typically 401, the documentation for the Authenticator interface explains more). In your authenticator you can retrieve a new token and automatically create a new request with the new token.
An authenticator will be something like:
class Authenticator : okhttp3.Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// Make your network request to retrieve a new token
val newToken = ...
// Check if a new token was retrieved
val retrievedNewToken: Boolean = true
return if (retrievedNewToken) {
response.request().newBuilder()
.header("Authorization", "token-value-here")
.build()
} else {
// Couldn't get new token, abort the request
null
}
}
}
And use it in your OkHttpClient:
val client = OkHttpClient.Builder()
.authenticator(Authenticator())
.build()
This is a fairly general answer as I can't provide any specific way of doing it since I don't know the rest of your code, but that's the gist of it. Something to be aware of is that you might need to handle if a new token request is already happening, as it will potentially make multiple requests for a new token if you make several requests right after each other that all are denied.
I have just started working with callable functions on firebase.
I deployed a function that gives the response hello world.
It worked fine when I tested it locally.
Now I am trying to call it from android app.
According to this document, these callable functions automatically pass the authentication data and deserialise the data to get the user id in the function. When I run as suggested in the document above , I get an Unauthenticated error.
I have used google sign in using firebase auth in the app. I dont want to make it public and keep it private so only the users can send requests. Is there any way I could manually include authentication in the header of the request.
Here is the code from the call
sendmessage(string)
.addOnCompleteListener {
if (!it.isSuccessful){
val e= it.exception
if(e is FirebaseFunctionsException){
val code =e.code
val details = e.details
Log.d("Firebase error",code.toString()+details.toString())
}
}
else{
Log.d("result obtained",it.result.toString())
}
}
private fun sendmessage(question:String): Task<String> {
val data = hashMapOf(
"question" to question,
"push" to true
)
return functions
.getHttpsCallable("detectIntent")
.call(data)
.continueWith {
val result=it.result?.data as String
result
}
}
Callable functions don't provide any amount of control over what happens in the HTTP headers. Authentication is handled automatically, based on the state of the user signed in through Firebase Auth. If you're not getting an authenticated user in your function in context.auth, that means you made the request before sign-in was complete.
I've a cloud function that uses "context.auth.uid" and for JSON parsing reasons I want to trigger it by using Retrofit instead of Firebase functions API. But I'm getting this error when I try so: Request has incorrect Content-Type. app/google-services.json. And I don't know how to pass the Authentication information that function API passes.
So here are my codes:
Cloud Functions HTTP Function:
exports.getMatches = functions.https.onCall((req, context) => {
var uid = context.auth.uid;
console.log("uid: ", uid);
});
Android SearchAPI Interface for Retrofit
public interface ElasticSearchAPI
{
#POST("getMatches/")
Call<ElasticHitsObject> getMatches(#Header("Content-Type") String
content_type,#Header("Authorization") String secretKey);
}
Android calling the function
Call<ElasticHitsObject> call = searchAPI.getMatches("app/google-services.json","api_key");
Retrofit doesn't know how to invoke callable functions, as you have declared in your code. They have a special protocol that's designed to be used with Firebase client SDK.
If you want to use an HTTP client library to invoke a Cloud Functions, you should make an HTTP function instead, not a callable. Note that they are declared differently. One starts with functions.https.onCall() and the other with functions.https.onRequest().
I'm struggling to maintain consistent Types in my Kotlin application across Retrofit, Room, and RxJava2 due to the JSON key naming convention that an API uses. I've attempted to research the best way to tackle the problem below, but I can't think of a better way to solve it, and I'm interested in what the community has to offer.
Consider the following example;
I'm using the Kotlin language on Android, and performing network requests with Retrofit and GSON. The API I'm using provides JSON with a different root key name depending on whether the response is an Array or a single Object.
The payload of each Object is the same, but if the response is an Array, the root key is plural.
For example, let's say I have a Data Class defined for a user:
data class User(var id: Int?, name: String?)
If I make an API request for a single user, the API returns the following:
{
"user": {
"id": 1,
"name: "Sam"
}
}
If I make an API request for multiple users, the API returns the following:
{
"users": [{
"id": 1,
"name: "Sam"
}]
}
The payload is the same, but the root key changes between user and users. I've created a Data Class to handle the separate root keys:
data class Users(
#SerializedName("users") var users: List<User>?,
#SerializedName("user") var user: User?
)
And when making the separate API requests, I use the Users Data Class for both:
/** GET single user by ID */
#GET("users/{id}")
fun getUser(
#Path("id") id: Int?): Single<Users>
/** GET list of users */
#GET("users")
fun getUsers(): Single<Users>
And when I save the response to the Room database, I access it using response.users or response.user respectively. Room is set up to return Queries as either User or List<User>, and this creates a problem when using the Retrofit requests in an RxJava2 stream. For example, this is a very simple stream if the Room database returns Maybe<List<User>>:
usersRepository.getAll()
.switchIfEmpty(api.getUsers())
The result of usersRepository.getAll() is List<User>, but if the result from the database is empty and I attempt to fallback on an API request instead, RxJava2 shows an Incompatible Type error because the api.getUsers() request returns the Type Single<Users> rather than Single<List<User>>. If the API request was able to use the same type, I wouldn't have a problem, but I'm not able to specify Single<List<User>> on the Retrofit request because GSON can't deserialise the response correctly.
Because of this, from my current understanding of the issue (which could be layman and I'm open to being moulded), I'm separating the database and API requests, and doing a lot of empty/null checking, which prevents me from keeping everything in one nice and tidy RxJava2 stream.
I hope this question is clear enough, and I value anyone's input. Thank you for your time!
This is a very common problem and what people like to do is to create a wrapper class that deals with conversions, validations, and all other painful jobs, and use that class to do network requests instead of calling the retrofit api directly.
For example, consider creating a class like this:
class UserClient {
private val userApi: UserApi
fun getUser(id: Int): Single<User> {
return userApi.getUser(id) // This has a type of Single<Users>
.map { it.user } // now it's map to Single<User>
}
fun getUsers(): Single<List<User>> {
return userApi.getUsers() // This has a type of Single<Users>
.doOnSuccess { Log.d(TAG, "yay!")}
.doOnError { e -> Log.d(TAG, "Something went wrong: $e")}
.map { it.users } // Now it's map to Single<List<User>>
}
}
Then you can do something like this:
usersRepository.getAll()
.switchIfEmpty(UserClient.getInstance().getUsers())
I have a node.js application with an Angular Webfrontend and an Android App connecting to the backend via rest. Now I'm not sure how to return objects from node to the clients:
Way 1:
res.send(req.user);
If I'll do it like this I can load the user object from angular like:
return $http.post('/api/login', user)
.success(function (data) {
$rootScope.currentUser = data;
and in my Android app (simplyfied call, I use Spring and Gson):
user = SpringRestTemplate.getRestTemplate().postForObject(Routes.getLogin(), user, User.class);
This works fine, but what I actually would like to return from node would be my
Way 2:
res.send({user: req.user });
for angular everything pretty much stays the same:
$rootScope.currentUser = data.user;
but for android I didn't found any other way than to write the json conversion myself:
ResponseEntity<Map> map = SpringRestTemplate.getRestTemplate().postForEntity(Routes.getLogin(), dUser, Map.class);
LinkedTreeMap<String, User> map2 = (LinkedTreeMap<String, User>) map.getBody();
Map ltm = (Map) map.get("user");
String id = (String) ltm.get("_id");
String userName = (String) ltm.get("userName");
// ... and so on
I get, why this is the case (except why I had to use a second map), but I wonder if there's any simplier way to achieve my second way.
I left the Models and the SpringRestTemplate Code out, since this is more a general question on how to handle JSON with the following structure:
{ user : { _id: "1", userName: "foo", ...}}
Any guidance on how to deal with this problem would be much appreciated!
This is simplified, but this will solve your issue.
Create a POJO that matches the JSON Schema using Json Schema to POJO
If you can, use Retrofit or something more easily suited for Android to consume the API.
Create a retrofit service (look at the retrofit link above) and then connect to the API and you'll have a User object.
Option B:
Use GSON to deserialize your object into a POJO from your spring stuff. But I recommend using Retrofit or something similar as its easier and cleaner.