I am using retrofit request:
#Headers("Authorization: Basic: UsernamePassword")
#POST("services/2/something/something")
fun createPlan(#Body planData: PlanInfo): Call<Plan>
In my header I requred to send usrname and password and string in Base64 encode. But it not so hard to decode if decomiling the app.
So I refered to manual: https://medium.com/novumlogic/hiding-sensitive-data-in-android-app-dbd64e88224f for hiding sensitive data in android app.
But now when I use data from constant like so:
#Headers("Authorization: "+ConstantsEncrypted.usernamePassword())
#POST("services/2/something/something")
fun createPlan(#Body planData: PlanInfo): Call<Plan>
I get an error: An annotation argument must be a compile-time constant.
So how can I hide this sensitive data properly?
You cannot do that using the annotation method. Here's how I have done it in my projects.
class Authenticator : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
return response.request().newBuilder()
.header("Authorization", ConstantsEncrypted.usernamePassword()).build()
}
}
And don't forget to add it to your OkHttpClient.Builder() instance using authenticator(Authenticator()) something like below:
OkHttpClient.Builder().apply {
writeTimeout(120, TimeUnit.SECONDS)
readTimeout(120, TimeUnit.SECONDS)
connectTimeout(120, TimeUnit.SECONDS)
callTimeout(120, TimeUnit.SECONDS)
// your code
// this is where you add your authenticator
authenticator(Authenticator())
// more code
}.build()
The Authenticator is from package okhttp3. You can find more info on class here.
Found a solution, sending header to the function:
fun createPlan(#HeaderMap headers: Map<String, String>,#Body planData: PlanInfo): Call<Plan>
Related
I have my Retrofit and OkHttp instances injected. I am changing the headers of my OkHttp object using an Interceptor. In this Interceptor, I set information such as app name, version etc.
I am now adding a login and want to set an authToken to the headers. However, this only exists AFTER login, obviously. So what I want to do is be able to add a new header after login for authToken. However, I can't get access to my OkHttp object as its buried in my module, and I just get my RestClient object injected in my class, with everything else built.
I was thinking of copying all my provides... code and making duplicates for before an after Login. For example, wrap my RestClient in a class called "PreLoginApi" that doesn't have authToken, and then creating another wrapper class called "PostLoginApi" which has the authToken, but this won't work if Dagger creates the objects on app launch.
#Provides
#Singleton
#Named("HeaderInterceptor")
fun provideHeaderInterceptor(context: Context): Interceptor {
val headerMap = context.getHeaderMap()
return Interceptor { chain ->
val original = chain.request()
val builder = original.newBuilder()
headerMap.keys.forEach {
builder.addHeader(it, headerMap[it] ?: "")
}
val request = builder.build()
chain.proceed(request)
}
}
#Provides
fun providesOkHttpClient(
#Named("HeaderInterceptor") headerInterceptor: Interceptor,
): OkHttpClient {
val builder = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.addInterceptor(headerInterceptor)
return builder.build()
}
You can see here where I call my extension function Context.getHeaderMap(). Inside this function, I'm retrieving the authToken from EncryptedSharedPreferences.
So ideally, I'd like to be able to tell Dagger to redo this initialisation once I login, because at this stage the authToken will be set. Is it possible to just re-initialise this one module or will I have to wrap my RestClient in another class, and maybe use Lazy loading to initialise the "PostLoginApi"?
Thanks.
EDIT:
I do similar for Firebase FCM token, where I pass in the headerMap during initialisation, like this:
private fun updateFirebaseToken(headerMap: HashMap<String, String>) {
FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
if (!task.isSuccessful) {
return#OnCompleteListener
}
// Get new FCM registration token
task.result?.let { token ->
headerMap["FirebaseId"] = token
}
})
}
But I don't think I can do something like this for a SharedPrefs value.
I am writing an android application using Kotlin ,Retrofit ,OKHttp and Rxjava when make a call to my API in order to add a new user to database i get the following message in logcat:
I/system_server: oneway function results will be dropped but finished with status OK and parcel size 4
The problem is that nothing happens no user is added in fact the API does not receive any requests from my app even thought i have the appropriate permissions and a android:usesCleartextTraffic="true" annotation in my AndroidManifest.xml.
By putting println() in various places in the code I was able to confirm that the function responsible for making the call is in fact executed but code responsible for handling response is never called and no error is reported.
Here is the code of above mentioned function:
private fun addUser(user: UserDataObject) {
val client = OkHttpClient.Builder()
.addInterceptor(BasicAuthInterceptor("admin", "admin")) // temporarily hardcoded
.build()
val requestInterface = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.client(client)
.build().create(GetData::class.java)
myCompositeDisposable?.add(
requestInterface.addUser(user)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse, this::onError)
)
}
and the code of the interface:
interface GetData {
#POST("/api/some/API/URL")
fun addUser(#Body user: UserDataClass) : Completable}
also ( just in case ) the code of interceptor:
class BasicAuthInterceptor(username: String, password: String): Interceptor {
private var credentials: String = Credentials.basic(username, password)
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
var request = chain.request()
request = request.newBuilder().header("Authorization", credentials).build()
return chain.proceed(request)
}
}
Can you please tell me why is my app not able to communicate with API ?
While searching for answer on the internet i found this question so I gather that it has something to do with selinux permissions but this doesn't help me at all since I'm just a beginner.
I can not understand the retrofit interceptors ,
private val OkHttpClient by lazy {
okhttp3.OkHttpClient.Builder()
.addInterceptor {
onOnIntercept(it)
}
.addInterceptor(LoggingInterceptor())
.addInterceptor(getInterceptor404())
.callTimeout(10, TimeUnit.MILLISECONDS)
// .addInterceptor(TimeoutInterceptor())
.build()
}
and what do these lines do, and If I have, multiple does the speed down?
val response: Response = chain.proceed(chain.request())
return chain.proceed(chain.request())
In Android sometimes you need to add a couple of parameters, like headers, to make a successful request, this is normal behavior from all the Android Apps when you are using Retrofit, you can do it in multiple ways
For example, you can add parameters directly to your request interface using the annotation Headers and putting a plain String, like this:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Path("someParam") someParam: String?): Observable<LoginResponseViewModel>
Another solution is to send the Headers as a parameter to your interface function, using an annotation Header and sending a parameter, this gives you the possibility to have a custom parameter that you can manage from every request:
#Headers("Content-Type:application/json; charset=UTF-8")
#GET("yourwebsite/{someParam}/login")
fun logout(#Header(UUID.randomUUID().toString()) authToken: String?, #Path("someParam") someParam: String?): Observable<LoginResponseViewModel>**
Interceptor
A couple of people using Dagger probably will go for an Interceptor, you can have two types of interceptor:
The first one is using an interceptor directly in your Singleton, this will not give you versatility, but it will solve your problem faster, in this example, you can go for the chain object, get the request of the Retrofit call, get a new Builder and then add the Headers.
#Provides
#Singleton
fun getUnsafeOkHttpClient(): OkHttpClient {
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
val builder = OkHttpClient.Builder()
builder.addInterceptor(interceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(true)
.followSslRedirects(true)
.addInterceptor { chain ->
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", UUID.randomUUID().toString())
.build()
chain.proceed(newRequest)
}
}
Yes, you can use multiple interceptors. When you do a request calling interface method using retrofit, your request go to the interceptor and then continue. In the interceptor you can rewrite or retry request. For example, you could add the access token in all request and refresh the token if is necessary, add the headers, another bodies, etc. When you received a response from api, the interceptor intercept the response too. But please, read the documentation to understand how it works. Have a nice coding!
I am using the new Retrofit2 with suspending coroutines, and with GET requests everything works fine.
But I now have to implement a POST request, and just can't get it to work
I have a CURL example that looks like this:
curl -X POST -H "Content-Type: application/json;charsets: utf-8" -d '{"tx_guapptokenlist_tokenitem":{"tokenchar":"my-token-string","platform":"android"}}' https://www.example-url.com/tokens?type=56427890283537921
This works fine, and returns this response: {"errors":false,"success":true}%
So here's what my request looks like in my Api class right now:
#Headers( "Content-Type: application/json" )
#POST("/tokens?type=56427890283537921")
suspend fun sendFirebaseToken(#Body tokenRequest: RequestBody) : Call<TokenResponse>
This is my TokenResponse class:
#JsonClass(generateAdapter = true)
data class TokenResponse(
#Json(name="errors")
val errors: Boolean,
#Json(name="success")
val success: Boolean)
and the ApiClient class I'm using:
object ApiClient {
private const val BASE_URL = "https://myExampleUrl.com"
private var retrofit: Retrofit? = null
var moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val client: Retrofit?
get() {
if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(
BASE_URL
).client(getOkHttpClient())
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
return retrofit
}
fun getOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor(getLoggingInterceptor())
.connectTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS).writeTimeout(90, TimeUnit.SECONDS).build()
}
private fun getLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(
if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.HEADERS
else HttpLoggingInterceptor.Level.NONE
)
}
}
The first odd thing I noticed: Even with the #POST annotation, if my suspend fun has no return type, I get no error, but okhttp will always send a GET request (at least the endpoint always receives a GET). Not sure if that is supposed to be like that?
Anyway: I need the return values, so I'm returning Call<TokenResponse>.
This leads me to my main problem, that I can't solve: If now I execute my code, it crashes with this log:
java.lang.IllegalArgumentException: Unable to create converter for retrofit2.Call<myapp.communication.TokenResponse>
for method TokenApi.sendToken
at retrofit2.Utils.methodError(Utils.java:52)
To try and deal with this I have used moshi-kotlin-codegen to generate the proper adapter (hence the annotations in the data class), but to no avail. The class is generated, but not used. I have tried to pass a Moshi with JsonAdapterFactory like this var moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()to my ConverterFactory but that doesn't work either.
Tried to add the generated adapter maually to moshi but that also did not work.
I've also tried returning different types in my request. The Retrofit docs state that without a converter one could only return a ResponseBody, but same result: Retrofit complains it has no converter. The same for returning Call<Void>
I feel like I'm missing something here? Who can help? Happy to provide more details, please request what's needed.
Your request function should look like this.
#Headers( "Content-Type: application/json" )
#POST("/tokens?type=56427890283537921")
suspend fun sendFirebaseToken(#Body tokenRequest: RequestBody): TokenResponse
You don't use Call<...> since you have marked it as suspend.
Think the annotation should be:
#JsonClass(generateAdapter = true)
data class TokenResponse(
#field:Json(name = "errors") val errors: Integer,
#field:Json(name = "success") val success: Boolean
)
And try to remove the suspend keyword once, which might clash with generateAdapter = true.
I've got it working now, this is what I learned:
First of all: #Dominic Fischer here is right, Call is wrong, and with everything set up correctly, there is no need to wrap the result object at all (I noticed by the way the #Headers annotation looks to be not necessary, Retrofit seems to just take care of it).
The second and biggest problem is that the client object in my ApiClient class was not used correctly. See the new version:
fun getRetrofitService(): ApiService {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(MoshiConverterFactory.create())
.build().create(ApiService::class.java)
}
See that now the 'create()' step is added, which before I handled outside of this class. There I used my Retrofit object to create the service just like here, but I accidentally passed ApiClient::class.java. Interestingly that compiles and runs just fine, but of course this must mess up somewhere - it's unable to properly build the JSON adapters.
As a result I pulled this step into my ApiClientin order to prevent such accidents in the future.
If anybody has suggestions as to meking this question + answer more useful for future readers, please let me know!
I am trying to post data to REST API server with retrofit + RxJava . When I am trying to send data to server , it said " HTTP 500 Internal Server Error Occurred". But when the data is send with POSTMAN, it succeeded.
This is the function for sending data in Model.
// Encountering with 500 server error
fun postSchedule(data : ScheduleResponse , errorLD: MutableLiveData<String>){
Log.d("POST DATA", "${data.title} ${data.remindMeAt}" )
userClient.postScheduleItem(data)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.unsubscribeOn(Schedulers.io())
.subscribe(object : io.reactivex.Observer<ServerResponse>{
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: ServerResponse) {
errorLD.value = t.status
}
override fun onError(e: Throwable) {
errorLD.value = e.message
}
})
}
This is my API interface
#Headers("Accept: application/json")
#POST("schedules")
fun postScheduleItem(#Body data: ScheduleResponse): Observable<ServerResponse>
This is the retrofit client.
val httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val httpClient = OkHttpClient.Builder()
var dbInstance: TodoDB = TodoDB.getInstance(context)
var rxJavaAdapter = RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())
val retrofitBuilder =
Retrofit.Builder()
.baseUrl(AppConstants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(rxJavaAdapter)
fun <T> createService(serviceClass: Class<T>, authToken: String?): T {
if (!TextUtils.isEmpty(authToken)) {
val interceptor = AuthenticationInterceptor(authToken!!)
if (!httpClient.interceptors().contains(interceptor)) {
httpClient.addInterceptor(interceptor)
retrofitBuilder.client(httpClient.build())
}
}
return retrofitBuilder.build().create(serviceClass)
}
Please help me with this.Thank you.
Client side code is not enough to determine what causes the server to respond with 500. The best you can do is start debugging the issue.
There are several directions you can go:
If you have access to the server or know someone who does, you could debug the server and determine what causes the Internal server error. Maybe the server logs can help as well and you don't have to actually step through the server code.
If you don't have access to the server, you could look at the body of the server response. Maybe there's a detailed error description there in html, json or some other format that will help you find out the root cause.
If the above steps don't help then it's very useful that you know the request works with POSTMAN. You can compare the exact POSTMAN request with the exact Retrofit request, header by header,
line by line. To do that, you should first add your httpLoggingInterceptor to your okhttp client builder with
val httpClient = OkHttpClient.Builder().addNetworkInterceptor(httpLoggingInterceptor)
and look for the request log in logcat.
If you spot the differences between the working and the not working requests, then you should work your way through all the differences, and adjust the retrofit request by adding or modifying headers using okhttp interceptors so that, at the end, the retrofit request looks exactly the same as the POSTMAN request. I suggest you remove the AuthenticationInterceptor at first and simulate it "manually" with a custom interceptor and a hard coded auth token.
Retry the request every time you eliminate a difference to isolate the cause of the internal server error.
Hope this helps!