I am trying to use Retrofit and RxJava to make an API call within a custom view in an app that I am working on, but I encounter an incompatible type error when trying to subscribe to the Observable from my Retrofit API call.
My retrofit interface:
public interface ApiQueryInterface{
// Request method and URL specified in the annotation
// Callback for the parsed response is the last parameter
#GET("users/")
Observable<Users> getUsers (
#Query("key") String key,
#Query("address") String address
);
#GET("posts/")
Observable<Posts> getPosts (
#Query("key") String key,
#Query("address") String address
);
}
and the Retrofit call located within the onFinishInflate() of the custom view:
// Create RxJava adapter for synchronous call
RxJava2CallAdapterFactory rxAdapter = RxJava2CallAdapterFactory.create();
// Create Retrofit2 instance for API call
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(rxAdapter)
.build();
// Make API call using retrofit
final ApiQueryInterface apiQueryInterface = retrofit.create(ApiQueryInterface.class);
// API return type defined by interface
Observable<Users> query = apiQueryInterface
.getUsers(KEY, ADDRESS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Users>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Users users) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
}
When I build the project I hit an incompatible types error in the custom view on the line beginning with Observable<Users> query = ...:
Error:(60, 27) error: incompatible types: void cannot be converted to Observable<Users>
"Users" is a generic model class which matches the JSON object returned from the API
RxJava 1 returns a Subscription object not an Observable. RxJava 2 subscription returns void. That's why you are getting Error:(60, 27) error: incompatible types. You are getting the Disposable in the callback onSubscribe. If you need a reference to it, you can assign it to a class level member when the callback is invoked
Change returned object to Subscription
private Subscription subscription;
....
subscription = ApiClient.getInstance()
.getUsers(KEY, ADDRESS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Users>>() {
#Override public void onCompleted() {
}
#Override public void onError(Throwable e) {
}
#Override public void onNext(List<Users> users) {
}
});
apiclient
public class ApiClient {
private static ApiClient instance;
private ApiQueryInterface apiqueryinterface;
private ApiClient() {
final Gson gson =
new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
final Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.build();
apiqueryinterface = retrofit.create(ApiQueryInterface.class);
}
public static ApiClient getInstance() {
if (instance == null) {
instance = new ApiClient();
}
return instance;
}
public Observable<List<Users>> getUsers(#NonNull String key, #NonNull String address) {
return apiqueryinterface.getUsers(key, address);
}
}
interface
public interface ApiQueryInterface{
// Request method and URL specified in the annotation
// Callback for the parsed response is the last parameter
#GET("users")
Observable<<List<Users>> getUsers (
#Query("key") String key,
#Query("address") String address
);
Related
i try this for just 1 time create retrofit but i have error
i want call my retrofit class and give endPoint of url and body class , and get body from server clearly
ApiClient
public class ApiClient {
private static Retrofit retrofit = null;
public static Retrofit getClient() {
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(App.SERVER)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
ApiService
public interface ApiService {
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
}
Retrofit Object
public class Request<C,T> {
private C c = null;
public C rest(T body, String urlEndPoint) {
ApiService apiService = ApiClient.getClient().create(ApiService.class);
Call<C> call = apiService.request(body, urlEndPoint);
call.enqueue(new Callback<C>() {
#Override
public void onResponse(Call<C> call, Response<C> response) {
if (response.isSuccessful())
c = response.body();
else
Toaster.shorter(App.context.getString(R.string.serverError));
#Override
public void onFailure(Call<C> call, Throwable t) {
Toaster.shorter(App.context.getString(R.string.connectionError));
}
});
return c;
}
}
calling method:
private void requestForCode() {
Request request = new Request();
int i = (int) request.rest(App.car, "/Rest/ReturnActivationCode");
if (i == 0)
Toaster.longer(App.context.getString(R.string.validateYourNumber));
else
Toaster.shorter(App.context.getString(R.string.serverError));
}
error:
12-05 12:18:04.119 773-907/? E/ConnectivityService: RemoteException caught trying to send a callback msg for NetworkRequest [ id=535, legacyType=-1, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED] ]
12-05 12:18:09.575 10359-10359/com.rayanandisheh.peysepar E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rayanandisheh.peysepar, PID: 10359
java.lang.IllegalArgumentException: Method return type must not include a type variable or wildcard: retrofit2.Call<C>
for method ApiService.request
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:755)
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:746)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:229)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:165)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:393)
at $Proxy0.request(Unknown Source)
retrofit don't support generic objects???
It seems that you're trying to minimize your boilerplate by having a generic function to be called, but there's a better way to do this.
First, you're encapsulating the retrofit setup with your:
#POST("{urlEndPoint}")
<C, T> Call<C> request(#Body T body, #Path("urlEndPoint") String urlEndPoint);
And then you're calling it with the function you created:
request.rest(App.object1, "endpoint");
But actually, this will just make things complicated and the code is very tightly coupled. You will still need to call the same method on every different APIs (request.rest(App.object2, "endpoint2"), request.rest(App.object3, "endpoint3")). This also limits the capability of retrofit (such as multiple params, customize headers, etc). What you can do is just follow the setup of retrofit:
#POST("yourendpoint")
Call<YourObjectResp> saveObject(#Body YourObjectParam param)
And to minimize your boilerplate, I suggest to make it functional:
Call<YourObjectResp> call = apiService.saveObject(new YourObjectParam());
call.enqueue(new ApiServiceOperator<>(new
ApiServiceOperator.OnResponseListener<YourObjectResp>() {
#Override
public void onSuccess(YourObjectResp body) {
// do something with your response object
}
#Override
public void onFailure(Throwable t) {
// here, you can create another java class to handle the exceptions
}
}));
And for your ApiServiceOperator.java:
/**
* Handles retrofit framework response.
* Extract the body if success, otherwise throw an exception.
*/
public class ApiServiceOperator<T> implements Callback<T> {
interface OnResponseListener<T> {
void onSuccess(T body);
void onFailure(Throwable t);
}
private OnResponseListener<T> onResponseListener;
public ApiServiceOperator(OnResponseListener<T> onResponseListener) {
this.onResponseListener = onResponseListener;
}
#Override
public void onResponse(#NonNull Call<T> call, #NonNull Response<T> response) {
if (response.isSuccessful()) { // here, do the extraction of body
onResponseListener.onSuccess(response.body());
} else {
onResponseListener.onFailure(new ServerErrorException());
}
}
#Override
public void onFailure(#NonNull Call<T> call, #NonNull Throwable t) {
onResponseListener.onFailure(new ConnectionErrorException());
}
// these exception can be on a separate classes.
public static class ServerErrorException extends Exception {
}
public static class ConnectionErrorException extends Exception {
}
}
With these setup, you still minimize your boilerplate and also, it makes thing reusable, scalable, and testable. ApiServiceOperator also is loosely couple with Android Context and instead, throws a plain java exception, in which, you can create a function that knows Android Context to get the appropriate message base on the exception thrown.
I am using from rx for connect to service with retrofit, bellow is RetrofitApi.java :
public class RetrofitApi {
private static PublicApi retrofit = null;
public static PublicApi getClient(String url) {
retrofit = new Retrofit.Builder()
.baseUrl(url)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build().create(PublicApi.class);
return retrofit;
}
}
And here is PublicApi.java :
public interface PublicApi {
#GET("/web_service/mobile/rest")
Observable<LastNews> lastNews(#Query("function") String function);
}
Bellow I am connecting to my service :
#Override
public void fetchLastNewsStartPage(RemoteDataSource.ResultListener<List<LastNews>> resultListener) {
PublicApi publicApi = RetrofitApi.getClient("https://xxx.xxx.xxx/web_service/");
CompositeDisposable mCompositeDisposable = new CompositeDisposable();
mCompositeDisposable.add(publicApi.lastNews("getLastNews")
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse, this::handleError));
}
My problem is here, how can I send parameter to handleResponse and handleError. I need to send this RemoteDataSource.ResultListener<List<LastNews>> resultListener to handleResponse and handleError:
private void handleResponse(LastNews lastNewses) {
}
private void handleError(Throwable error) {
}
Just don't use method reference as it can only accept one parameter. You can achieve the result with a lambda expression. Instead of
this::handleResponse
write
lastNews -> handleResponse(lastNews, resultListener)
I'm trying to return a value that i get from onResponse method in retrofit call request, is there a way that i can get that value out of the overrided method? here is my code:
public JSONArray RequestGR(LatLng start, LatLng end)
{
final JSONArray jsonArray_GR;
EndpointInterface loginService = ServiceAuthGenerator.createService(EndpointInterface.class);
Call<GR> call = loginService.getroutedriver();
call.enqueue(new Callback<GR>() {
#Override
public void onResponse(Response<GR> response , Retrofit retrofit)
{
jsonArray_GR = response.body().getRoutes();
//i need to return this jsonArray_GR in my RequestGR method
}
#Override
public void onFailure(Throwable t) {
}
});
return jsonArray_GR;
}
i can't get the value of jsonArray_GR because to be able to use it in onResponse method i need to declare it final and i can't give it a value.
The problem is you are trying to synchronously return the value of enqueue, but it is an asynchronous method using a callback so you can't do that. You have 2 options:
You can change your RequestGR method to accept a callback and then chain the enqueue callback to it. This is similar to mapping in frameworks like rxJava.
This would look roughly like:
public void RequestGR(LatLng start, LatLng end, final Callback<JSONArray> arrayCallback)
{
EndpointInterface loginService = ServiceAuthGenerator.createService(EndpointInterface.class);
Call<GR> call = loginService.getroutedriver();
call.enqueue(new Callback<GR>() {
#Override
public void onResponse(Response<GR> response , Retrofit retrofit)
{
JSONArray jsonArray_GR = response.body().getRoutes();
arrayCallback.onResponse(jsonArray_GR);
}
#Override
public void onFailure(Throwable t) {
// error handling? arrayCallback.onFailure(t)?
}
});
}
The caveat with this approach is it just pushes the async stuff up another level, which might be a problem for you.
You can use an object similar to a BlockingQueue, Promise or an Observable or even your own container object (be careful to be thread safe) that allows you to check and set the value.
This would look like:
public BlockingQueue<JSONArray> RequestGR(LatLng start, LatLng end)
{
// You can create a final container object outside of your callback and then pass in your value to it from inside the callback.
final BlockingQueue<JSONArray> blockingQueue = new ArrayBlockingQueue<>(1);
EndpointInterface loginService = ServiceAuthGenerator.createService(EndpointInterface.class);
Call<GR> call = loginService.getroutedriver();
call.enqueue(new Callback<GR>() {
#Override
public void onResponse(Response<GR> response , Retrofit retrofit)
{
JSONArray jsonArray_GR = response.body().getRoutes();
blockingQueue.add(jsonArray_GR);
}
#Override
public void onFailure(Throwable t) {
}
});
return blockingQueue;
}
You can then synchronously wait for your result in your calling method like this:
BlockingQueue<JSONArray> result = RequestGR(42,42);
JSONArray value = result.take(); // this will block your thread
I would highly suggest reading up on a framework like rxJava though.
I'm new to retrofit and i am trying te get a json response to an object called RootObject. The error that i am stuck with is :
"Error:(21, 44) error: incompatible types: NewsController cannot be
converted to Callback>"
Does someone now my mistake here? thanks in regards!
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
try {
service.GetNewsItems().enqueue(this); //asynchronous
Response<List<RootObject>> response = service.GetNewsItems().execute(); //synchronous
}
catch (IOException e){
}
}
}
class to put the data:
public class RootObject implements Serializable {
public ArrayList<Result> results ;
public int nextId;
public ArrayList<Result> getResults() { return results; }
public int getNextId() { return nextId; }
public String toString() {
return String.format("JEEJ" + nextId);
}
}
Interface:
public interface GetNewsService {
#GET("/Articles")
Call<List<RootObject>> GetNewsItems();
}
First of all,
change your interface to this:
public interface GetNewsService {
#GET("/Articles")
void GetNewsItems(Callback<List<RootObject>> cb);
}
Also change your newsController class.
public class NewsController {
private RestAdapter restAdapter;
static final String API_URL = "[Enter your API base url here]";
public void getNews(){
OkHttpClient mOkHttpClient = new OkHttpClient();
mOkHttpClient.setConnectTimeout(15000,TimeUnit.MILLISECONDS);
mOkHttpClient.setReadTimeout(15000,TimeUnit.MILLISECONDS);
restAdapter = new RestAdapter.Builder().setEndpoint(API_URL).setClient(new OkClient(mOkHttpClient)).setLogLevel(RestAdapter.LogLevel.FULL) .build();
GetNewsService service = restAdapter.create(GetNewsService.class);
Callback<List<RootObject> cb = new Callback<List<RootObject>>() {
#Override
public void success(List<RootObject> rootObjectList, Response response) {
//whatever you want to do with the fetched news items
}
#Override
public void failure(RetrofitError error) {
//whatever you want to do with the error
}
};
service.GetNewsItems(cb);
}
}
You'll need to add the following dependencies in your build.gradle:
compile 'com.squareup.retrofit:retrofit:1.9.0'
compile 'com.google.code.gson:gson:2.3.1'
compile 'com.squareup.okhttp:okhttp:2.4.0'
#megh vidani's answer works, but he had you switch your code from Retrofit 2 to Retrofit 1. Here is how to do it in Retrofit 2. You would need to go back to your original gradle settings, etc. --
public class NewsController {
public void getNews(){
Retrofit retrofit = new Retrofit.Builder().baseUrl("apilink").addConverterFactory(GsonConverterFactory.create()).build();
GetNewsService service = retrofit.create(GetNewsService.class);
service.GetNewsItems().enqueue(new Callback<List<RootObject>>() {
#Override
public void onResponse(Response<List<RootObject>> response) {
// Handle your response
// Note HTTP errors are delivered here, you can check
// response.isSuccess() or response.code() to determine
// HTTP failures
}
#Override
public void onFailure(Throwable t) {
// Network errors
}
});
}
}
I am writing an android app that will use Retrofit to make API requests.
I have a helper class like this:
public class ApiService {
public static final String TAG = ApiService.class.getSimpleName();
public static final String BASE_URL = "https://myapiurl.com";
public static void testApi(){
ApiEndpointInterface apiService = prepareService();
apiService.ping(new Callback<Response>() {
#Override
public void success(Response apiResponse, retrofit.client.Response response) {
Log.e(TAG, apiResponse.toString());
}
#Override
public void failure(RetrofitError error) {
Log.e("Retrofit:", error.toString());
}
});
}
private static ApiEndpointInterface prepareService() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(BASE_URL)
.build();
ApiEndpointInterface apiService =
restAdapter.create(ApiEndpointInterface.class);
restAdapter.setLogLevel(RestAdapter.LogLevel.FULL);
return apiService;
}
}
And my actual Retrofit implementation is simple:
public class ApiEndpointInterface {
#GET("/v1/myendpoint")
void ping(Callback<Response> cb);
}
The problem is, I cannot build the project, I get the error:
Error:(12, 10) error: missing method body, or declare abstract
Referring to my ApiEndpointInterface class.
Any idea what's going on?
Try public interface for your API declaration.
public interface ApiEndpointInterface {
#GET("/v1/myendpoint")
void ping(Callback<Response> cb);
}
Also, looks like you're creating your ApiEndpointInterface before telling the builder to set log level to full.
private static ApiEndpointInterface prepareService() {
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(BASE_URL)
.setLogLevel(RestAdapter.LogLevel.FULL);
.build();
ApiEndpointInterface apiService =
restAdapter.create(ApiEndpointInterface.class);
return apiService;
}
In case you update to okHttp Version 2.4.0 , you will get an exception for empty Body as latest version no more allows zero length request , in which case you would have to use the following syntax
public interface ApiEndpointInterface {
#GET("/v1/myendpoint")
void ping(Callback<Response> cb, #Body String dummy);
}
call
ApiEndpointInterface apiService =
restAdapter.create(ApiEndpointInterface.class);
apiService.ping(callback,"");
Ref
https://github.com/square/okhttp/issues/751