I've written the following code to fetch & cache MarvelAPI data on to my device. The cache folder contains cache data but Retrofit NEVER reads the cache when offline, instead it gives 504 Unsatisfiable request error.
I'm not really sure what am I doing wrong.
URL: http://gateway.marvel.com/v1/public/comics?apikey=publickey&hash=04bd8e724fe4a647063489f19822cbcss3&ts=1491690808&limit=6&offset=6
private static final String HTTP_CACHE_PATH = "http-cache";
private static final String CACHE_CONTROL = "Cache-Control";
private static final String PRAGMA = "Pragma";
#Provides
#NetworkComponentScope
public Retrofit providesRetrofit(OkHttpClient okHttpClient, GsonConverterFactory gsonConverterFactory, String baseUrl) {
return new Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(gsonConverterFactory)
.baseUrl(baseUrl)
.build();
}
#Provides
#NetworkComponentScope
public OkHttpClient providesOkHttpClient(Context context, OkHttpClient.Builder builder, HttpLoggingInterceptor okhttpLoggingInterceptor, Cache cache) {
builder.cache(cache);
builder.addNetworkInterceptor(provideCacheInterceptor(5000));
builder.addInterceptor(provideOfflineCacheInterceptor(context, 5000));
if(BuildConfig.DEBUG) {
builder.addInterceptor(okhttpLoggingInterceptor);
}
return builder.build();
}
#Provides
#NetworkComponentScope
#PicassoLoggingInterceptor
public OkHttpClient providesOkHttpClientForPicasso(OkHttpClient.Builder builder, HttpLoggingInterceptor okhttpLoggingInterceptor) {
if(BuildConfig.DEBUG) {
builder.addInterceptor(okhttpLoggingInterceptor);
}
return builder.build();
}
#Provides
public OkHttpClient.Builder provideOkHttpClientBuilder() {
OkHttpClient.Builder okhttpClientBuilder = new OkHttpClient().newBuilder();
okhttpClientBuilder.connectTimeout(25, TimeUnit.SECONDS);
okhttpClientBuilder.readTimeout(25, TimeUnit.SECONDS);
return okhttpClientBuilder;
}
#Provides
#NetworkComponentScope
public Cache providesCache(Context context) {
File httpCacheDirectory = new File(context.getCacheDir(), "marvel_responses");
int cacheSize = 10 * 1024 * 1024; // 10 MB
return new Cache(httpCacheDirectory, cacheSize);
}
#Provides
public HttpLoggingInterceptor providesOkhttpLoggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
return interceptor;
}
private Interceptor provideCacheInterceptor(final int maxAgeMin) {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(maxAgeMin, TimeUnit.MINUTES)
.build();
return response.newBuilder()
.removeHeader(PRAGMA)
.removeHeader(CACHE_CONTROL)
.header(CACHE_CONTROL, cacheControl.toString())
.build(); }
};
}
public Interceptor provideOfflineCacheInterceptor(final Context context, final int maxStaleDay) {
return new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!Utils.isNetworkAvailable(context)) {
CacheControl cacheControl = new CacheControl.Builder()
.maxStale(maxStaleDay, TimeUnit.DAYS)
.build();
request = request.newBuilder()
.cacheControl(cacheControl)
.build();
}
return chain.proceed(request);
}
};
}
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.squareup.retrofit2:converter-gson:2.2.0'
compile "com.squareup.retrofit2:adapter-rxjava:2.2.0"
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
Related
I am trying to test my Service class that uses Retrofit to make #GET call.
I updated my test case and now I am using the mock-retrofit from square to mock the server following their test sample and some other examples here. But I am struggling to unit test the method that calls the retrofit interface.
The sample example of retrofit-mock tests the interface class. How can I mock the retrofit call for my method that implements the interface method.
public class XmattersApi {
private XmattersInterface service;
public GroupsResponse getGroupsWithSupervisor() throws IOException {
String host = xmattersApiConfiguration.getHost();
Interceptor interceptor = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder().addHeader("Authorization", "TestToken").build();
return chain.proceed(newRequest);
}
};
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.interceptors().add(interceptor);
OkHttpClient client = builder.build();
Retrofit retrofit = new Retrofit.Builder()
.baseurl(getBaseUri(host).toUriString())
.addConverterFactory(JacksonConverterFactory.create())
.client(client)
.build();
service = retrofit.create(XmattersInterface.class);
Call<GroupsResponse> retrofitCall = service.getGroupsWithSupervisor("supervisors");
Response<GroupsResponse> response = retrofitCall.execute();
if (!response.isSuccessful()) {
throw new IOException(response.errorBody() != null
? response.errorBody().string(): "unknown error");
}
return response.body();
}
}
My interface class defined below:
public interface XmattersInterface {
#Headers({"Content-type: application/json;charset=UTF-8"})
#GET("groups")
Call<GroupsResponse> getGroupsWithSupervisor(#Query("embed") String embed);
}
I have tried mocking the retrofit call and returning the mocked response when the service interface is called.
It is not calling the mock. Here is my updated test case.
public class XmattersApiTest {
XmattersApi xmattersAPi;
XmattersInterfaceMock xmattersInterfaceMock;
private final NetworkBehavior behavior = NetworkBehavior.create();
#BeforeEach
public void setUp() throws Exception {
Retrofit retrofit = new Retrofit.Builder()
.addCallAdapterFactory(JacksonConverterFactory.create())
.baseUrl("http://example.com").build();
MockRetrofit mockRetrofit = new MockRetrofit.Builder(retrofit).networkBehavior(behavior).build();
final BehaviorDelegate<XmattersInterface> delegate = mockRetrofit.create(XmattersInterface.class);
xmattersInterfaceMock = new XmattersInterfaceMock(delegate);
}
#Test
void testGroupsWithSupervisorCase_1() throws IOException {
Call<GroupsResponse> call = mock(Call.class);
XmattersInterface xmattersInterface = mock(XmattersInterface.class)
Call<GroupsResponse> mockCall = xmattersInterfaceMock.getGroupsWithSupervisor("supervisors");
Response<GroupsResponse> mockResponse = mockCall.execute; // Mock data is received here from the XmattersInterfaceMock
when(xmattersInterface.getGroupsWithSupervisor(ArgumentMatchers.anyString())).thenReturn(mockCall);
when(call.execute()).thenReturn(mockResponse)
xmattersApi.getGroupsWithSupervisor(); //Fails with java.net.SocketTimeoutException error
}
}
The service mock class to delegate the request:
public class XmattersInterfaceMock implements XmattersInterface {
private final BehaviorDelegate<XmattersInterface> delegate;
public XmattersInterfaceMock(BehaviorDelegate<XmattersInterface> delegate) {
this.delegate = delegate;
}
#Override
public Call<GroupsResponse> getGroupsWithSupervisor(String embed) {
return delegate.returningResponse(getMockGroupsResponse()).getGroupsWithSupervisor(embed);
}
}
What am I doing wrong here? Someone please help!
I am using Springboot- JUnit, mockito
I wanted to load a json, one after one, beacause its large JsonArray. before then i used Retrofit call and Its oKey, but the output take too long. Any help on how to implement it this way is appreciated.
ApiInterface.java
public interface ApiInterface {
#POST("/token/login")
Call<User> login(#Body Login login);
#Streaming
#GET("/api/schools/")
Observable<ResponseBody> getAllSchools(#Header("Authorization") String authToken);
}
Client.java
public class Client {
public static final String BASE_URL = "site.net";
private static Retrofit retrofit;
public static Retrofit getClient(){
if (retrofit == null){
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
}
ApiInterface apiInterface = Client.getClient().create(ApiInterface.class);
String tok = "Token " + token;
apiInterface.getAllSchools(tok)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
#Override
public void onSubscribe(Disposable disposable) {
}
#Override
public void onNext(ResponseBody responseBody) {
//fetch json one after one.
Toast.makeText(getContext(), responseBody.toString(), Toast.LENGTH_SHORT).show();
}
#Override
public void onError(Throwable throwable) {
}
#Override
public void onComplete() {
Toast.makeText(getContext(), "finish--", Toast.LENGTH_SHORT).show();
}
});
In my case, I need to change URL dynamically, but I don't want to create 2 instances of the retrofit client. I'm trying to change base URL via interceptor modifications, but retrofit still uses the old value. What am I doing wrong?
App.java
public class App extends Application {
private static AppComponent appComponent;
#Override
public void onCreate() {
super.onCreate();
appComponent =
DaggerAppComponent
.builder()
.appModule(new AppModule(this))
.build();
}
#NonNull
public static App get(#NonNull Context context) {
return (App) context.getApplicationContext();
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
AppModule.java
#Module
public class AppModule {
private Context context;
public AppModule(Context context) {
this.context = context;
}
#Provides
#Singleton
Context provideContext() {
return context;
}
}
NetModule.java
#Module
public class NetModule {
#Provides
#Singleton
Gson provideGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
return gsonBuilder.create();
}
#Provides
#Singleton
MainInterceptor provideMyApiInterceptor() {
return MainInterceptor.get();
}
#Provides
#Singleton
OkHttpClient provideOkhttpClient() {
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(15, TimeUnit.SECONDS);
client.connectTimeout(20, TimeUnit.SECONDS);
return client.build();
}
#Provides
#Singleton
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("first.url.com")
.client(okHttpClient)
.build();
}
}
NetComponent.java
#Singleton
#Component(modules = NetModule.class)
public interface NetComponent {
Retrofit getRetrofit();
MainInterceptor getInterceptor();
}
MainInterceptor.class
public class MainInterceptor implements Interceptor {
private static MainInterceptor sInterceptor;
private String mScheme;
private String mHost;
public static MainInterceptor get() {
if (sInterceptor == null) {
sInterceptor = new MainInterceptor();
}
return sInterceptor;
}
private MainInterceptor() {
}
public void setInterceptor(String url) {
HttpUrl httpUrl = HttpUrl.parse(url);
mScheme = httpUrl.scheme();
mHost = httpUrl.host();
}
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
if (mScheme != null && mHost != null) {
HttpUrl newUrl = original.url().newBuilder()
.scheme(mScheme)
.host(mHost)
.build();
original = original.newBuilder()
.url(newUrl)
.build();
}
return chain.proceed(original);
}
}
And this is the code to initialize the component in some class.
NetComponent component= DaggerNetComponent.create();
DataService service = component.getRetrofit().create(DataService.class);
MainInterceptor interceptor = component.getInterceptor();
interceptor.setInterceptor("second.url.com");
service.getSomeData();
After that, the URL is still "first.url.com"
The simple way is to use #Named:
#Provides
#Singleton
#Named("retrofit_1")
Retrofit provideRetrofit1(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
...
.baseUrl("url_1.com")
...;}
#Provides
#Singleton
#Named("retrofit_2")
Retrofit provideRetrofit2(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
...
.baseUrl("url_2.com")
...;}
then use the same #Named:
#Provides
IApi1 provideApi_1(#Named("retrofit_1") RestAdapter adapter){....}
#Provides
IApi2 provideApi_2(#Named("retrofit_2") RestAdapter adapter){....}
When you provide a Retrofit, you already set baseUrl "first.url.com"
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("first.url.com")
.client(okHttpClient) //<------ It not OkHttp with your MainInterCeptor
.build();
And fun fact is, your MainInterceptor which is set-up with new url is unrelated to your Retrofit, because Retrofit was built with OkHttp
#Provides
#Singleton
OkHttpClient provideOkhttpClient() {
OkHttpClient.Builder client = new OkHttpClient.Builder();
client.readTimeout(15, TimeUnit.SECONDS);
client.connectTimeout(20, TimeUnit.SECONDS);
return client.build();
}
If you want to dynamically change your BASE_URL, there are many ways for you to do that.
public class ApiConstant {
public static String BASE_URL = "yourUrl";
private ApiConstant() {
}
}
Create a #Scope for Retrofit because it can be changed
#Scope
#Retention(RetentionPolicy.RUNTIME)
public #interface PerActivity {
}
Then use it when building Retrofit
#Provides
#PerActivity
Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
return new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl("first.url.com")
.client(ApiConstant.BASE_URL)
.build();
}
Then change the BASE_URL when init DaggerActivityComponent. (Or create a new #Scope for new url)
Read this documentation for more detail
Hope this help!
Add Interceptor in your AppModule
#Provides
#Singleton
OkHttpClient provideOkHttpClient(Cache cache, MainInterceptor interceptor) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.cache(cache)
.build();
return okHttpClient;
}
and then in your activity or presenter set url before calling retrofit service
mInterceptor.setInterceptor(urlname);
mRetrofitService.call();
If you already have a base URL set but you want to override it for just one API call and not all of them, it can be done pretty easily.
#PUT("https://my-api.com/user")
fun cancelOrder(#Path("user") user: String): Single<MyResponse>
Works the same way for POST and GET as well.
My interface
#POST("/insert.php")
void login(Callback<Response> callback);
Java code
Retrofit adapter = new Retrofit.Builder()
.baseUrl(ROOT_URL) //Setting the Root URL
.addConverterFactory(GsonConverterFactory.create())
.build(); //Finally building the adapter
Register_Retrofit api = adapter.create(Register_Retrofit.class);
api.login( new Callback<Response>() {
public void onResponse(Call<Response> call, Response<Response> response) {
}
public void onFailure(Call<Response> call, Throwable t) {
}
});
Your login method return void, so you need to define it like this:
#POST("/insert.php")
Call<Void> login();
Then, to call the login method try this:
Retrofit adapter = new Retrofit.Builder()
.baseUrl(ROOT_URL) //Setting the Root URL
.addConverterFactory(GsonConverterFactory.create())
.build(); //Finally building the adapter
Register_Retrofit api = adapter.create(Register_Retrofit.class);
Call<Void> loginCall = api.login();
loginCall.enqueue(new Callback<Void>() {
public void onResponse(Call<Void> call, Response<Void> response) {
...
}
public void onFailure(Call<Void> call, Throwable t) {
...
}
});
I just implemented the retrofit android library for rest api call but it is not working and has no error. My code is
ApiInterface.java
public interface ApiInterface {
#POST("url")
void getLoginResponse(#Field("username") String username , #Field("password") String password,
#Field("clientId") String clientId , Callback<LoginResponse> cb);
}
RestClient.java
public class RestClient {
private static ApiInterface REST_CLIENT;
private static String BASE_URL = "base_url";
static {
setupRestClient();
}
private RestClient() {}
public static ApiInterface get() {
return REST_CLIENT;
}
private static void setupRestClient() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(BASE_URL)
.build();
REST_CLIENT = restAdapter.create(ApiInterface.class);
}
}
and in activity i call
RestClient.get().getLoginResponse(usernameText, passwordText, clientId, new Callback<LoginResponse>() {
#Override
public void success(LoginResponse loginResponse, Response response) {
Toast.makeText(getApplicationContext(), loginResponse.getToken(), Toast.LENGTH_SHORT).show();
}
#Override
public void failure(RetrofitError error) {
}
});
And in AndroidManifest i set the permission for internet.
How to make RestClient as a singleton:
public class RestClient {
private static ApiInterface REST_CLIENT;
private static String BASE_URL = "base_url";
public RestClient() {}
public static ApiInterface getInstance() {
//if REST_CLIENT is null then set-up again.
if (REST_CLIENT == null) {
setupRestClient();
}
return REST_CLIENT;
}
private static void setupRestClient() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(BASE_URL)
.build();
REST_CLIENT = restAdapter.create(ApiInterface.class);
}
}
Then when you wanna call api you should always call:
ApiInterface api = RestClient.getInstance();
api.callWhatApiYouWant
I am answering late but it will be useful for others, I preferred to use retrofit 2.
// Retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
// JSON Parsing
compile 'com.google.code.gson:gson:2.7'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
Quit Simple to create instance.
public static Retrofit getClient(String baseUrl) {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
Here is Detailed explanation about retrofit 2 android best example and quit simple to understand.
http://al-burraq.com/retrofit-android-get-and-post-api-request-tutorial/