I am authenticating against an API EndPoint using the Password Grant Flow for OAuth2. The library I am using is Retrofit.
I get the following error:
D/OkHttp: {"error":"invalid_clientId","error_description":"ClientId should be sent."}
I would like to see exactly what API Call I am sending so I can check it for errors.. I have searched the web and found that you need to add a LoggingInterceptor, which I am doing. Still not getting the full API Call..
In my log I get a track of the headers I am sending, as well as the URL, but not the body parameters.. Is there a way to also get the body parameters in the logging?
I would like to see each parameter with its sent value..
The code with provisional credentials can be found at:
https://github.com/NVwingh84/ATTAPITest
Code that I am using is:
public class MainActivity extends AppCompatActivity {
private String grant_type = "password";
private String username = "xxxxx";
private String password = "xxxxx";
private String clientId = "xxxxx";
private String client_secret = "xxxxx";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AccessTokenRequest mytokenrequest = new AccessTokenRequest(grant_type,username,password,clientId,client_secret);
sendNetworkRequest(mytokenrequest);
}
public void sendNetworkRequest(AccessTokenRequest accessTokenRequest){
//create okhttpclientbuilder and set up logging for full logging level "Body"
OkHttpClient.Builder okhttpclientbuilder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//App will only log if you are in development mode
if (BuildConfig.DEBUG){
okhttpclientbuilder.addInterceptor(loggingInterceptor);
}
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("https://api.allthingstalk.io")
.addConverterFactory(GsonConverterFactory.create())
.client(okhttpclientbuilder.build());
Retrofit retrofit = builder.build();
AccessTokenClient client = retrofit.create(AccessTokenClient.class);
Call<AccessTokenRequest> call = client.getAccessToken();
call.enqueue(new Callback() {
#Override
public void onResponse(Call call, Response response) {
Toast.makeText(MainActivity.this, "EndPoint Response", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call call, Throwable t) {
Toast.makeText(MainActivity.this, "Something went wrong", Toast.LENGTH_SHORT).show();
}
});
};
}
Try this code.
add below dependency into app level gradle file..
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'
and make separate class for retrofit object define and it is easy to access like below code..
public class ApiClient {
private final static String BASE_URL = "https://dog.ceo/api/breed/";
public static ApiClient apiClient;
private Retrofit retrofit = null;
public static ApiClient getInstance() {
if (apiClient == null) {
apiClient = new ApiClient();
}
return apiClient;
}
//private static Retrofit storeRetrofit = null;
public Retrofit getClient() {
return getClient(null);
}
private Retrofit getClient(final Context context) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(60, TimeUnit.SECONDS);
client.writeTimeout(60, TimeUnit.SECONDS);
client.connectTimeout(60, TimeUnit.SECONDS);
client.addInterceptor(interceptor);
client.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
return chain.proceed(request);
}
});
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
}
}
Related
I'm new to android development and trying to learn it. I recently ran into an issue, when I use get method I get a response as below:
As there is a status given as '0' from the backend I'm unable to catch the "response_data" array.
Could anyone please guide me how can I catch the response.
Thanks.
API CLIENT:
public class ApiClient {
private final static String BASE_URL = "http://api.xxxxxx.com/app/";
public static ApiClient apiClient;
private Retrofit retrofit = null;
public static ApiClient getInstance() {
if (apiClient == null) {
apiClient = new ApiClient();
}
return apiClient;
}
public Retrofit getClient() {
return getClient(null);
}
private Retrofit getClient(final Context context) {
HttpLoggingInterceptor interceptor = new
HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(60, TimeUnit.SECONDS);
client.writeTimeout(60, TimeUnit.SECONDS);
client.connectTimeout(60, TimeUnit.SECONDS);
client.addInterceptor(interceptor);
client.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws
IOException {
Request request = chain.request();
return chain.proceed(request);
}
});
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
}
}
MAIN ACTIVITY:
public class MainActivity extends AppCompatActivity {
TextView tvResponse;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvResponse=findViewById(R.id.tvResponse);
ApiInterface apiInterface = ApiClient.getInstance().getClient().create(ApiInterface.class);
Call<ResponseData> responseDataCall=apiInterface.getData();
responseDataCall.enqueue(new Callback<ResponseData>() {
#Override
public void onResponse(Call<ResponseData> call, Response<ResponseData> response) {
if (response.isSuccessful() && response.body()!=null && response!=null){
List<ResponseDataItem> data=response.body().getResponseData();
}
}
#Override
public void onFailure(Call<ResponseData> call, Throwable t) {
t.printStackTrace();
}
});
}
}
RESPONSE DATA:
public class ResponseData {
#SerializedName("response_data")
private List<ResponseDataItem> responseData;
#SerializedName("status")
private int status;
public void setResponseData(List<ResponseDataItem> responseData){
this.responseData = responseData;
}
public List<ResponseDataItem> getResponseData(){
return responseData;
}
public void setStatus(int status){
this.status = status;
}
public int getStatus(){
return status;
}
}
In this case you need to let Gson know how you want to parse your json.
You can add many specific TypeAdapter's for each specific class case or you can create one TypeAdapterFactory that will be used to parse all your jsons. Remember to add it to your Retrofit builder.
This code example is a TypeAdapterFactory that will ignore status and parse only response_data to your object.
class ResponseDataTypeAdapterFactory implements TypeAdapterFactory {
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
TypeAdapter<JsonElement> elementTypeAdapter = gson.getAdapter(JsonElement.class);
return new TypeAdapter<T>() {
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
}
public T read(JsonReader reader) throws IOException {
JsonElement jsonElement = elementTypeAdapter.read(reader);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
if (jsonObject.has("response_data")) {
jsonElement = jsonObject.get("response_data");
}
}
return delegate.fromJsonTree(jsonElement);
}
};
}
}
And on your Retrofit builder
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(ResponseDataTypeAdapterFactory())
.create();
new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client.build())
.addConverterFactory(new GsonConverterFactory.create(gson))
.build();
And at the Retrofit interface, you only need to call the class that corresponds with response_datamapping.
public interface ApiInterface {
#GET("/product-data")
Call<List<ResponseDataItem>> fetchData();
}
With this implementation, you can remove your ResponseData class and care only about the important model.
Make an interface
public interface ApiInterface {
#GET
Call<JsonElement> getTimeDifference(#Url String url);
}
Crate an Retrofit client calss
public class RetrofitClient {
private static final String TAG = "RetrofitClient";
public static Retrofit geBaseUrl() {
Retrofit retrofit = null;
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
if (BuildConfig.DEBUG) {
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
}else{
interceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
} OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl("...your base url...")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
String endpoint = "...your end point...";
ApiInterface ret = RetrofitClient.geBaseUrl(url).create(ApiInterface.class);
Call<JsonElement> call = ret.getTimeDifference(endpoint);
call.enqueue(new Callback<JsonElement>() {
#Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
try {
Log.d("String", "onResponse: response" + response.body().toString());
} catch (Exception e) {
}
}
#Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Log.d("response", "onFailure: " + t + " " + call);
}
});
For Catching response you use interceptor like HttpLoggingInterceptor ,stetho,chuck
Creating the Retrofit instance
// Add the interceptor to OkHttpClient
OkHttpClient client=new OkHttpClient().newBuilder()
.addNetworkInterceptor(new StethoInterceptor()) \\ StethoInterceptor
.addInterceptor(new ChuckInterceptor(context)) \\ ChuckInterceptor
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.MINUTES)
.writeTimeout(5, TimeUnit.MINUTES)
.build();
public static final String BASE_URL = "http://api.myservice.com/";
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
for stetho interceptor
implementation 'com.facebook.stetho:stetho:1.5.0'
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
for chuck
debugImplementation 'com.readystatesoftware.chuck:library:1.1.0'
releaseImplementation 'com.readystatesoftware.chuck:library-no-op:1.1.0'
I am plugging Retrofit into my android app.
Here is how I build retrofit, notice the interceptor for the logging and headers.
public void buildRetrofit(String token){
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addNetworkInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json")
.header("api-version", "1")
.method(chain.request().method(), chain.request().body())
.build();
return chain.proceed(newRequest);
}
});
httpClient.addInterceptor(logging);
Retrofit.Builder buidler = new Retrofit.Builder()
.baseUrl("XXX_HIDDEN_FORSTACKOVERFLOW")
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build());
retroFit = buidler.build();
}
I make the call like so
OrderApi orderApi = mainActivity.retroFit.create(OrderApi.class);
Call<Order> call = orderApi.getOpenOrder();
call.enqueue(new Callback<Order>() {
#Override
public void onResponse(Call<Order> call, Response<Order> response) {
Order a = response.body();
int b = 1;
}
#Override
public void onFailure(Call<Order> call, Throwable t) {
}
});
And here is how the actual request tag
public interface OrderApi {
#POST("/HIDDEN")
Call<Order> getOpenOrder();
}
Lastly, here is the order class
public class Order {
private String orderId;
private OrderStatus orderStatus;
public String getOrderId(){
return orderId;
}
public OrderStatus getOrderStatus() {
return orderStatus;
}
}
I get a response of 400. I have no idea why, and It works in postman etc. Something to note is that the response contains a lot more properties than just the ones in the class. I just want a proof on concept, but that shouldn't break things right?
.................
Managed to fix it. Had to send an empty body request as it was a post but I wasn't posting anything. API is dumb.
See here to send empty request Send empty body in POST request in Retrofit
I want to use two server url using retrofit, but only one is working when I am using two base url. Please tell me how to use two base url in android.
public class APIUtils {
public static String Url1 = "http://10.0.13.46:19460";
public static String Url12 = "http://freshcamera.herokuapp.com";
public static SOService getSOService(String url) {
return RetrofitClient.getClient(url1).create(SOService.class);
}
}
SOService class
public interface SOService {
//URL 2
#FormUrlEncoded
#POST("/api/user/LoginUser")
Call<Login> Login(#Field("username") String username, #Field("password")String password, #Field("grant_type")String passwords);
}
SOService_AI class
public interface SOService_AI {
//URL 1
#FormUrlEncoded
#POST("/finalresult1")
Call<List<AIImageProcessing>> AiImageCheck(#Field("img_data") String imgdata, #Field("name")String imgName);
}
I guess what you need is changing URL at runtime to a completely different one.
For example, the following code will override the URL passed as baseUrl to retrofit object.
#GET
public Call<ResponseBody> profilePicture(#Url String url);
Note: You can't add url param to #GET and #POST. The URL must be passed to #Url.
// ERROR ( #Url cannot be used with #GET URL)
#GET("users") // or POST
public Call<Foo> getUsers(#Url String url);
// CORRECT
#GET
public Call<Foo> getUsers(#Url String fullUrl);
Checkout this tutorial for further information.
if you are working two url then you create two retrofit object. because single retrofit object work on single url.
if you want to access two your make two retofit object like below code..
public class ApiClient {
private final static String BASE_URL = "https://simplifiedcoding.net/demos/";
private final static String BASE_URL2 = "http://freshcamera.herokuapp.com";
public static ApiClient apiClient;
private Retrofit retrofit = null;
private Retrofit retrofit2=null;
public static ApiClient getInstance() {
if (apiClient == null) {
apiClient = new ApiClient();
}
return apiClient;
}
//private static Retrofit storeRetrofit = null;
public Retrofit getClient() {
return getClient(null);
}
public Retrofit getClient2() {
return getClient2(null);
}
private Retrofit getClient(final Context context) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(60, TimeUnit.SECONDS);
client.writeTimeout(60, TimeUnit.SECONDS);
client.connectTimeout(60, TimeUnit.SECONDS);
client.addInterceptor(interceptor);
client.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
return chain.proceed(request);
}
});
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
}
private Retrofit getClient2(final Context context) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(60, TimeUnit.SECONDS);
client.writeTimeout(60, TimeUnit.SECONDS);
client.connectTimeout(60, TimeUnit.SECONDS);
client.addInterceptor(interceptor);
client.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
return chain.proceed(request);
}
});
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL2)
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
}
}
then after access like below code ..
ApiClient.getInstance().getClient();
ApiClient.getInstance().getClient2();
with Kotlin its even easier
companion object {
// init Retrofit base server instance
val redditClient by lazy { ApiService.invoke(REDDIT_BASE_URL) }
val stackClient by lazy { ApiService.invoke(STACK_BASE_URL) }
private val loggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
operator fun invoke(baseUrl: String): ApiService {
val client = OkHttpClient.Builder().apply {
/**addNetworkInterceptor(StethoInterceptor()) */
addNetworkInterceptor(loggingInterceptor)
connectTimeout(10, TimeUnit.MINUTES)
readTimeout(10, TimeUnit.MINUTES)
writeTimeout(10, TimeUnit.MINUTES)
}.build()
return Retrofit.Builder()
.client(client)
.baseUrl(baseUrl)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
just pass the baseUrl in the invoke method
This is really easy now
Simply use Post or Get without a constant url instead accept it in a parameter and annotate that parameter with #Url
#GET
suspend fun handshakeUser(#Url url : String): Response<JsonObject>
#POST
suspend fun makePostRequest(
#Header("Authorization") token: String = getToken(),
#Url url: String,
#Body inputModel: JsonObject
): Response<JsonObject>
I am trying to use an Interceptor to add a header when using Retrofit. I think I have created my Interceptor in the right way but I don't know what should I do to call it and connect it with my GET Retrofit method.
This is my Interceptor:
public class HeaderInterceptor
implements Interceptor {
#Override
public Response intercept(Chain chain)
throws IOException {
Request request = chain.request();
request = request.newBuilder()
.addHeader(Constants.VersionHeader.NAME, Constants.VersionHeader.VALUE)
.addHeader("Authorization", "Bearer " + token)
.addHeader("Origin","MY URL")
.build();
Response response = chain.proceed(request);
return response;
}
}
And this is my interface:
public interface CategoryService {
#GET("/v3/projects/{projectId}/categories/")
Call<ArrayList<Category2>> getProjectCategories(#Path("projectId") String projectId);
}
I also have this client which I don't know if I should use it anymore considering that I am using an Interceptor:
public class CategoryClient {
public static final String BASE_URL = "MY URL";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
So I have this GET method getProjectCategories, where I pass the projectID and it returns the contents. What I want to know is how can I call the method using the Interceptor and be able to get the results from the request.
I was able to fix my problem by creating a method called SendNetworkRequest sending the projectId as a parameter, and inside this class I created my OkHttpClient, my Interceptor and my retrofit builder to handle everything that i needed.
private void SendNetworkRequest(String projectID) {
OkHttpClient.Builder okhttpBuilder = new OkHttpClient.Builder();
okhttpBuilder.addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Request.Builder newRequest = request.newBuilder().header("Authorization", "Bearer " + token);
return chain.proceed(newRequest.build());
}
});
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("MY URL")
.client(okhttpBuilder.build())
.addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
CategoryService category = retrofit.create(CategoryService.class);
Call<ArrayList<Category2>> call = category.getProjectCategories(projectID, token);
call.enqueue(new Callback<ArrayList<Category2>>() {
#Override
public void onResponse(Call<ArrayList<Category2>> call, Response<ArrayList<Category2>> response) {
listCategories = response.body();
listCategories.remove(response.body().size() - 1);
if (response.body().size() > 0){
add_category_layout.setVisibility(View.VISIBLE);
layout_bar.setVisibility(View.VISIBLE);
message_body.setVisibility(View.INVISIBLE);
message_title.setVisibility(View.INVISIBLE);
edit_image.setVisibility(View.INVISIBLE);
adapter2 = new CategoryAdapter2(getApplicationContext(), listCategories);
recyclerView.setAdapter(adapter2);
recyclerView.setVisibility(View.VISIBLE);
}
}
#Override
public void onFailure(Call<ArrayList<Category2>> call, Throwable t) {
// Log error here since request failed
Log.e(TAG, t.toString());
}
});
}
I'm making service that makes user able to login in LoginActivity, and if login is successful, user can post something in PostActivity. I'm using Restful api.
I found good sharedpreference example on github : https://gist.github.com/nikhiljha/52d45ca69a8415c6990d2a63f61184ff
and
https://gist.github.com/tsuharesu/cbfd8f02d46498b01f1b
AddCookiesInterceptor.java
public class AddCookiesInterceptor implements Interceptor {
public static final String PREF_COOKIES = "PREF_COOKIES";
private Context context;
public AddCookiesInterceptor(Context context) {
this.context = context;
}
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request.Builder builder = chain.request().newBuilder();
HashSet<String> preferences = (HashSet<String>) PreferenceManager.getDefaultSharedPreferences(context).getStringSet(PREF_COOKIES, new HashSet<String>());
for (String cookie : preferences) {
builder.addHeader("Cookie", cookie);
}
return chain.proceed(builder.build());
}
}
RecievedCookiesInterceptor.java
public class ReceivedCookiesInterceptor implements Interceptor {
private Context context;
public ReceivedCookiesInterceptor(Context context) {
this.context = context;
}
#Override
public Response intercept(Chain chain) throws IOException {
Response originalResponse = chain.proceed(chain.request());
if (!originalResponse.headers("Set-Cookie").isEmpty()) {
HashSet<String> cookies = (HashSet<String>) PreferenceManager.getDefaultSharedPreferences(context).getStringSet("PREF_COOKIES", new HashSet<String>());
for (String header : originalResponse.headers("Set-Cookie")) {
cookies.add(header);
}
SharedPreferences.Editor memes = PreferenceManager.getDefaultSharedPreferences(context).edit();
memes.putStringSet("PREF_COOKIES", cookies).apply();
memes.commit();
}
return originalResponse;
}
}
I used this code very well on my LoginActivity like this,
OkHttpClient client = new OkHttpClient();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(new AddCookiesInterceptor(getApplicationContext()));
builder.interceptors().add(new RecievedCookiesInterceptor(getApplicationContext()));
client = builder.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(LoginApiService.Login_API_URL)
.build();
loginApiService = retrofit.create(LoginApiService.class);
Call<ResponseBody> getkey = loginApiService.getkey(loginData);
getkey.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
if(response.code() == 200) {
startActivity(new Intent(LoginActivity.this, PostActivity.class));
}
}
But in PostActivity, (it's from if (response.code() == 200) startActivity)
I used this like
OkHttpClient client = new OkHttpClient();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(new AddCookiesInterceptor(getApplicationContext()));
#just used AddCookiesInterceptor, Not RecievedCookiesInterceptor
client = builder.build();
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(PostApiService.Post_API_URL)
.build();
PostApiService = retrofit.create(PostApiService.class);
It give me HTTP/1.1 401 error, I found it means there is problem in Headers.
I write PostApiService like this
public interface PostApiService {
#POST("posts/")
Call<ResponseBody> gettest(#Body TextData textData);}
My question :
I want Header like
key : Authorization //
value : Token e0af91707f0434a1a2a7581dd3f4f483bdd717
Where do i modify?
I know too much codes bothers you maybe, so I have to say i'm very sorry.