React Native & okhttp on Android - Set User-Agent - java

I'm trying to set the User-Agent with React Native on Android. Did some research and it looks like I should use an okhttp Interceptor. An example that I've found explains how this should be done(Link) but then I am not sure on how to register the Interceptor.
So in order to set the User-Agent I am using this class:
public class CustomInterceptor implements Interceptor {
#Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.removeHeader("User-Agent")
.header("User-Agent", "Trevor")
.build();
return chain.proceed(requestWithUserAgent);
}
}
Then what's left is to register the above interceptor so where it should be done? Maybe in MainActivity.java?
OkHttpClient okHttp = new OkHttpClient();
okHttp.interceptors().add(new CustomInterceptor());
I am not getting any errors when building the app so I think that the CustomInterceptor should be fine - just need to make the app use it.
UPDATE:
I'm currently trying to register the interceptor in MainActivity but it won't pick it up:
public class MainActivity extends ReactActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new CustomInterceptor());
};
};

None of the answers here worked for me for RN 0.63.2. I was able to get it working and in my research was able to find the (albeit very scarce) documentation for the support of this feature.
The only documentation I could find for this was this PR where someone added support for this feature (and broke the currently accepted answer). When I tried adding the interceptor as documented in the PR I got an exception related to CookieJar which I was able to find a solution to in this (unresolved 🙄) issue.
TLDR:
Add a Java class in the same folder as your MainApplication called UserAgentInterceptor.java and place this in it:
package YOUR.PACKAGE.NAME; // <-- REPLACE ME
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class UserAgentInterceptor implements Interceptor {
public UserAgentInterceptor() {}
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", "YOUR USER AGENT") // <-- REPLACE ME
.build();
return chain.proceed(requestWithUserAgent);
}
}
Then create another Java class in the same folder named UserAgentClientFactory.java and place this in it:
package YOUR.PACKAGE.NAME; // <-- REPLACE ME
import com.facebook.react.modules.network.OkHttpClientFactory;
import com.facebook.react.modules.network.ReactCookieJarContainer;
import okhttp3.OkHttpClient;
public class UserAgentClientFactory implements OkHttpClientFactory {
public OkHttpClient createNewNetworkModuleClient() {
return new OkHttpClient.Builder()
.cookieJar(new ReactCookieJarContainer())
.addInterceptor(new UserAgentInterceptor())
.build();
}
}
Then in your MainApplication onCreate method register the factory like this:
...
import com.facebook.react.modules.network.OkHttpClientProvider;
...
#Override
public void onCreate() {
super.onCreate();
OkHttpClientProvider.setOkHttpClientFactory(new UserAgentClientFactory());
// Your other code stuffs
}
And that's it!

So I've finally figured it out. Here is the solution for overriding the User-Agent of okhttp3 with React Native.
Create a file called CustomInterceptor.java:
package com.trevor;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class CustomInterceptor implements Interceptor {
public CustomInterceptor() {}
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
Request requestWithUserAgent = originalRequest.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", "Trevor")
.build();
return chain.proceed(requestWithUserAgent);
}
}
Then in MainActivity.java override the onCreate method:
...
import com.facebook.react.modules.network.OkHttpClientProvider;
...
public class MainActivity extends ReactActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
attachInterceptor();
}
private void attachInterceptor() {
OkHttpClient client = OkHttpClientProvider.getOkHttpClient();
client.networkInterceptors().add(new CustomInterceptor());
}
}
Note that I'm importing com.facebook.react.modules.network.OkHttpClientProvider; and overriding that client instead of creating a vanilla OkHttpClient since this is the one that React Native will use.

React Native is iterating so quickly that the accepted answer didn't work for me.
For RN 0.27.2 I had to import okhttp3.OkHttpClient in my CustomInterceptor and change the attachInterceptor() method in MainActivity to replace the client.
private void attachInterceptor() {
OkHttpClient currentClient = OkHttpClientProvider.getOkHttpClient();
OkHttpClient replacementClient = currentClient.newBuilder().addNetworkInterceptor(new CustomInterceptor()).build();
OkHttpClientProvider.replaceOkHttpClient(replacementClient);
}
Everything else from ekonstantinidis's answer works for me.

Old issue, but we still ran into the same problem with React Native 0.59. This is what we did to fix (in Kotlin), as recent versions of okhttp prevent (and throw an exception) when trying to add an interceptor to an already initialized client:
import android.os.Build
import com.facebook.react.modules.network.OkHttpClientFactory
import com.jaredrummler.android.device.DeviceName
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
class UserAgentInterceptor(val userAgent: String): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val correctRequest = originalRequest.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", userAgent)
.build()
return chain.proceed(correctRequest)
}
}
class UserAgentClientFactory(val appName: String, val appVersion: String, val buildNumber: String): OkHttpClientFactory {
private fun userAgentValue(): String {
val deviceName = DeviceName.getDeviceName()
val osVersion = Build.VERSION.RELEASE
return "$appName/$appVersion (build: $buildNumber; device: $deviceName; OS: Android $osVersion)"
}
override fun createNewNetworkModuleClient(): OkHttpClient {
val builder = com.facebook.react.modules.network.OkHttpClientProvider.createClientBuilder()
return builder.addInterceptor(UserAgentInterceptor(userAgent = userAgentValue())).build()
}
}
This was done in a shared library between 2 apps, thus why we passed in the app name, version, and build number.
Usage from the app itself looked like:
private fun configureUserAgent() {
val versionName = BuildConfig.VERSION_NAME
val versionCode = BuildConfig.VERSION_CODE
OkHttpClientProvider.setOkHttpClientFactory(UserAgentClientFactory(appName = "My App", appVersion = versionName, buildNumber = "$versionCode"))
}
This was called from the onCreate method in the main activity of the app.
Hope this helps!

I've implemented this functionality using OkHttp and my code is pretty the same as yours - and everything works fine.
Consider using addHeader("User-Agent", "Trevor") instead of header("User-Agent", "Trevor"), because the latter will replace all of already set headers.
I'm using okHttp.networkInterceptors().add(new CustomInterceptor()); instead of okHttp.interceptors().add(new CustomInterceptor());, but I don't think it's a matter of concern here.
Update I do it in onCreate() method too. Everything works as it should.

Related

Can not get response from 000webhost using Retrofit in Android Studio

I am new to Android, i have a file named "https://codyderunner.000webhostapp.com/Server/songbannner.php" which response JSON like: Json response from my file
I want to get those JSON in Android studio using Retrofit.
I have a DataService interface:
public interface DataService {
#GET("songbannner.php")
Call<List<Ads>> GetDataBanner();
}
Ads is a model class of the Data I want to Receive.
My APIService.java file code:
public class APIService {
private static String base_url = "https://codyderunner.000webhostapp.com/Server/";
public static DataService getService(){
return APIRetrofitClient.getClient(base_url).create(DataService.class);
};
}
My API.RetrofitClient code:
public class APIRetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String base_url){
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(10000, TimeUnit.MILLISECONDS)
.writeTimeout(10000, TimeUnit.MILLISECONDS)
.connectTimeout(10000, TimeUnit.MILLISECONDS)
.retryOnConnectionFailure(true)
.protocols(Arrays.asList(Protocol.HTTP_1_1))
.build();
Gson gson = new GsonBuilder().setLenient().create();
retrofit = new Retrofit.Builder()
.baseUrl(base_url)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
return retrofit;
};
In my main class, i have a function to get data from that file:
private void GetData(){
DataService dataService = APIService.getService();
Call<List<Ads>> callback = dataService.GetDataBanner();
callback.enqueue(new Callback<List<Ads>>() {
#Override
public void onResponse(Call<List<Ads>> call, Response<List<Ads>> response) {
ArrayList<Ads> banner = (ArrayList<Ads>) response.body();
Log.d("AAAA", banner.get(0).getNameSong());
}
#Override
public void onFailure(Call<List<Ads>> call, Throwable t) {
}
});
}
Now, the thing is, i can not receive any response from the file, I think it may because of the authentication when I try to get the data which the file response. Its fine when I open the link with the browser, cause it remember my password, but when I try to read the JSON of the File from Android Studio, Its seem hopeless. I also try to read the link on Postman, its released "Unauthorized", so I think the point is how can I put my user-name and password in Retrofit so the file will response as it did in the browser.
Is there anyone meet the same problem? Help me, I tried Everything I know. Many thanks.
OK, finally, I find out that the website which I store my .php file require user name and password, just add it into the Okhttpclient.
First, I created a java class called BasicAuthInterceptor:
import java.io.IOException;
import okhttp3.Credentials;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
public class BasicAuthInterceptor implements Interceptor {
private String credentials;
public BasicAuthInterceptor(String user, String password) {
this.credentials = Credentials.basic(user, password);
}
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request authenticatedRequest = request.newBuilder()
.header("Authorization", credentials).build();
return chain.proceed(authenticatedRequest);
}
}
And add it with password and username into your OkhttpClient:
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(new BasicAuthInterceptor(username, password))
.build();
with your username and password which you use to login into the website. It took me 2 days finding out, hell ya.

Getting Bad Request from API Post Call using Retrofit

I have created the next savePartner() method inside PartnerController class like this:
public void savePartner(View partnerForm) {
context = partnerForm.getContext();
PartnerDto partner = createPartner(partnerForm);
String jsonPartner = convert(partner);
Call<String> call = appAPI.savePartner("application/json", jsonPartner);
Log.i(TAG, "getPartners submitted to API.");
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if(response.isSuccessful()) {
String responseCall = response.body();
} else {
System.out.println(response.errorBody());
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
TableRow rowHeader = new TableRow(context);
TextView name = new TextView(context);
name.setText(t.getMessage());
rowHeader.addView(name);
//partnerForm.addView(rowHeader);
t.printStackTrace();
}
});
}
And I have added the method savePartner to retrofit interface:
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
public interface IApplicationApi {
#GET("Partner/")
//Call<List<PartnerDto>> loadPartners(#Header("Authorization") String authorization);
Call<List<PartnerDto>> loadPartners();
#POST("Partner/")
Call<String> savePartner(#Header("Content-Type") String content_type, #Body String partner);
}
When I execute the post call in postman works (code 200), but I debugged the previous in android-studio and I obtain the next error:
Response{protocol=http/1.1, code=400, message=Bad Request, url=https://localhost/Partner/}
And I can't obtain more info about the error. The request is the next:
Request{method=POST, url=https://localhost/Partner/, tags={class retrofit2.Invocation=administracion.MyProject.APIService.IApplicationApi.savePartner() [application/json, {"email":null,"id":4,"lastname":null,"name":"me","phonenumber":0,"productamount":0.0,"productquantity":0.0,"registereddate":"2021-02-10T00:00:00"}]}}
I put these values on postman, and it works like a charm. I don't know why this request is bad. Could someone give me some clue?
Thanks in advance for the help! ^^
Updated 01/03/2021
I can get the cause of the error using httplogginginterceptor, I share this in case someone more need it :)
https://howtodoinjava.com/retrofit2/logging-with-retrofit2/
you can use HttpLoggingInterceptor and log your request. I hope you are missing a field in your json body or request body
try replacing this #POST("Partner/")
with #POST("Partner")

How can I configure Lagom framework to work with CORS?

How can I configure Lagom framework to work with CORS request (method request 'options').
I have enabled CORS in lagom for one of my projects in this way.
Define a method in service class to handle OPTIONS calls.
ServiceCall<NotUsed, Done> options();
Implement the method in the service-impl class.
#Override
public ServiceCall<NotUsed, Done> options() {
return request -> CompletableFuture.completedFuture(Done.getInstance());
}
Define the options call in the descriptor. As an example, assume that the actual call is,
GET /api/v0.1/user
The service descriptor should look like this:
#Override
default Descriptor descriptor() {
// #formatter:off
return named("notification").withCalls(
restCall(Method.GET, "/api/v0.1/user", this::getUser),
restCall(Method.OPTIONS, "/api/v0.1/user", this::options)
).withAutoAcl(true).withHeaderFilter(new CORSHeaderFilter());
// #formatter:on
}
Note that it has a header filter attached using,
.withHeaderFilter(new CORSHeaderFilter())
CORSHeaderFilter Class should look like this.
import com.lightbend.lagom.javadsl.api.transport.HeaderFilter;
import com.lightbend.lagom.javadsl.api.transport.Method;
import com.lightbend.lagom.javadsl.api.transport.RequestHeader;
import com.lightbend.lagom.javadsl.api.transport.ResponseHeader;
public class CORSHeaderFilter implements HeaderFilter {
#Override
public RequestHeader transformClientRequest(RequestHeader request) {
return request;
}
#Override
public RequestHeader transformServerRequest(RequestHeader request) {
return request;
}
#Override
public ResponseHeader transformServerResponse(ResponseHeader response, RequestHeader request) {
ResponseHeader modifiedResponse = response.withHeader("Access-Control-Allow-Origin", "*");
if (Method.OPTIONS.equals(request.method())) {
modifiedResponse = modifiedResponse.withStatus(204).withHeader("Access-Control-Allow-Headers",
"Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With" +
",If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range").
withHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PATCH").
withHeader("Access-Control-Max-Age", "1728000");
}
return modifiedResponse;
}
#Override
public ResponseHeader transformClientResponse(ResponseHeader response, RequestHeader request) {
ResponseHeader modifiedResponse = response.withHeader("Access-Control-Allow-Origin", "*");
if (Method.OPTIONS.equals(request.method())) {
modifiedResponse = modifiedResponse.withStatus(204).withHeader("Access-Control-Allow-Headers",
"Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With" +
",If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range").
withHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PATCH").
withHeader("Access-Control-Max-Age", "1728000");
}
return modifiedResponse;
}
}
Whenever you add a new endpoint, make sure to add the OPTIONS version of it as well.
To allow a Lagom service written in Java to work with CORS, you'll need to implement a CORS filter per Play:
package example.service.impl
import play.filters.cors.CORSFilter;
import play.http.DefaultHttpFilters;
import javax.inject.Inject;
// See https://playframework.com/documentation/2.5.x/CorsFilter
public class MyCORSFilter extends DefaultHttpFilters {
#Inject
public MyCORSFilter(CORSFilter corsFilter) {
super(corsFilter);
}
}
and then in your application.conf, you'll need to add the filter:
play.http.filters = "example.service.impl.MyCORSFilter"
// To properly setup the CORSFilter, please refer to https://playframework.com/documentation/2.5.x/CorsFilter
// This example is only meant to show what's required for Lagom to use CORS.
play.filters.cors {
// review the values of all these settings to fulfill your needs. These values are not meant for production.
pathPrefixes = ["/api"]
allowedOrigins = null
allowedHttpMethods = null
allowedHttpHeaders = null
exposedHeaders = []
supportsCredentials = false
preflightMaxAge = 6 hour
}
For more info, see the example CORS service and the Play docs.

method in the class returns null instead of a string retrieved via async call

Utils.java
/**
* Created by faiz on 15/08/17.
*/
package com.example.android.whereabouts;
import android.util.Log;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class Utils {
private String apiresponse;
private OkHttpClient okHttpClient;
public Utils(String url){
okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
okHttpClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
Log.e("exception",e.toString());
}
#Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
apiresponse = response.body().string();
Log.e("onResponse",apiresponse);
}
});
}
public String getData(){
if(apiresponse != null)
return apiresponse ;
else
return "error";
}
}
When i instantiate this class in my main activity and try to Log return value of getData method, it logs "error" instead of the value of variable apiresponse .
Its obvious that that it takes some time to get response from the server and getData call gets executed before the server returns a response.
So how to make sure getData gets called after onResponse has been executed.
Instead of getting a callback in Utility, You can implement that in your Activity so that call back will come to ur activity and you can update in UI without any issue.
public Utils(String url, Callback callback){
okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
okHttpClient.newCall(request).enqueue(callback);
}
in your actvity, you can call like,
public void getdata() {
new Utils("[url]", new Callback() {
#Override
public void onFailure(Call call, IOException e) {
Log.e("exception",e.toString());
}
#Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
apiresponse = response.body().string();
Log.e("onResponse",apiresponse);
// here you can show in UI.
}
})
}
or you can create a separate custom interface and you can do same after getting the response from the server.
You can implement a callback mechanism and call the callback method from inside the onResponse(Call call, okhttp3.Response response) method.
Here is an example. You should define this in the Utils class -
public interface Callback {
void getData(String data);
}
private Callback callback;
In the constructor pass in the activity reference when you instantiate the Utils class.
// In the activity
utilsInstance = new Utils(url, this);
// In your Utils class
public Utils(String url, Callback activityCallback) {
callback = activityCallback
}
In your activity, you need to implement this interface -
public class RetrofitActivity implements Utils.Callback {
#Override
public void getData(String data) {
// do stuff
}
}
And in your OnResponse() method, you'll call this method like so -
#Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
apiresponse = response.body().string();
callback.getData(apiresponse);
Log.e("onResponse",apiresponse);
}
I found two workarounds which I'll share here and I request the people who answered this question to share their thoughts on this.(I appreciate the answers shared on this question..thanks to you all )
Note: Its more of a "clean code question" than "how to get this thing to work question"
Solution 1:
We can wait till the onResponse method gets called in getData() like this
while(true)
if(apiresponse!=null)
return(apiresponse);
to make sure this is not an indefinite while loop we set the variable apiresponse to something in both onResponse and OnFailure methods(either one will surely get executed) to break the loop.
this one seems to work but its pretty naive to me to use infinite loop and break it.
Solution 2:
Writing an AsyncTask and the Executing Utils class in the doInBackground method of that async task.
(I think the second approach makes sense as it falls under standard practices of android so it makes the thing work and also satisfies the best practices).
Another thing: an obvious question to some would be that when you're ready to write the whole async task inside the activity, then why is it an issue to write the Utils.java code directly inside the activity. My response to that is , despite of writing the asynctask in the activity, its better because it saves me from manually handling Handlers to create another thread inside onResponse to update the UI and it also falls under standard practices . So I think this approach makes more sense overall.
Step 1 : In Utils method pass calling activity as below
public Utils(CallingActivity activity , String url)
{
Request request = new Request.Builder().url(url).build();
okHttpClient.newCall(request).enqueue(activity);
}
Step 2 : Implement Callback of OkHttpClient in your calling activity.
Step 3 : Override onFailure() and onResponse() of OkHttpClient and call Utils(this,url) in calling activity.

How to add interceptor to all API requests except one or two?

I know it's possible to add an interceptor to all requests through an OkHttpClient, but I would like to know if it's possible to add headers to all requests in Okhttp except for one request or two using the OkHttpClient.
For example, in my API all requests require a bearer token (Authorization: Bearer token-here header) except for the oauth/token (to get a token) and api/users (to register a user) routes. Is it possible to add an interceptor for all requests except the excluded ones using the OkHttpClient in one step or should I add the headers individually for each request?
I found the answer!
Basically I needed an interceptor as usual and I needed to check the URL there to know whether I should add the authorization header or not.
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
* Created by Omar on 4/17/2017.
*/
public class NetInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.url().encodedPath().equalsIgnoreCase("/oauth/token")
|| (request.url().encodedPath().equalsIgnoreCase("/api/v1/users") && request.method().equalsIgnoreCase("post"))) {
return chain.proceed(request);
}
Request newRequest = request.newBuilder()
.addHeader("Authorization", "Bearer token-here")
.build();
Response response = chain.proceed(newRequest);
return response;
}
}
#Omar answer is Good :) but I found a more clean way to implement using custom annotation.
Add annotation
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
private annotation class DECRYPTRESPONSE
check if the annotation is true or false in an intercepter like this
val method = chain.request().tag(Invocation::class.java)!!.method()
if(method.isAnnotationPresent(DECRYPTRESPONSE::class.java)) {
//when annotion is present
} else..
add an annotation in retrofit interface
#DECRYPTRESPONSE
#GET
Call<ItemsModel> getListing(#Url String url);
below is the complete code of my interceptor also don't forget to add intercepter in the Okhttpclient builder
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
private annotation class DECRYPTRESPONSE
class DecryptInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain
.run {
proceed(request())
}
.let { response ->
return#let if (response.isSuccessful) {
val body = response.body!!
val contentType = body.contentType()
val charset = contentType?.charset() ?: Charset.defaultCharset()
val buffer = body.source().apply { request(Long.MAX_VALUE) }.buffer()
val bodyContent = buffer.clone().readString(charset)
val method = chain.request().tag(Invocation::class.java)!!.method()
if(method.isAnnotationPresent(DECRYPTRESPONSE::class.java)) {
response.newBuilder()
.body(ResponseBody.create(contentType, bodyContent.let(::decryptBody)))
.build()
}
else{
response.newBuilder()
.body(ResponseBody.create(contentType, bodyContent))
.build()
}
} else response
}
private fun decryptBody(content: String): String {
return content //your decryption code
}
}

Categories

Resources