I am building an activity in which I'm loading lists of objects from an api. I need to make multiple requests with retrofit which returns different objects. I can make the requests but I don't know how I can check when they're done.
The following code is what I have.
ApiRepository
public interface ApiRepository {
#GET("/api/troopmarker.json")
Call<List<TroopMarker>> getTroopMarkers();
#GET("/api/troop.json")
Call<List<Troop>> getTroops();
#GET("/api/treasure.json")
Call<List<TroopMarker>> getTreasures();
}
RepositoryService
public interface RepositoryService
{
void loadTroops(final TroopCallback callback);
void loadTroopMarkers(final TroopMarkerCallback callback);
//void loadTreasures(final TreasureCallback callback);
}
RepositoryServiceImpl
public class RepositoryServiceImpl implements RepositoryService {
private String url;
private Activity context;
public RepositoryServiceImpl(String url, Activity context) {
this.url = url;
this.context = context;
}
public void loadTroops(final TroopCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroops().enqueue(new Callback<List<Troop>>() {
public List<Troop> troops;
#Override
public void onResponse(Call<List<Troop>> call, Response<List<Troop>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<Troop>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTroopMarkers(final TroopMarkerCallback callback) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.build();
ApiRepository repository = retrofit.create(ApiRepository.class);
repository.getTroopMarkers().enqueue(new Callback<List<TroopMarker>>() {
#Override
public void onResponse(Call<List<TroopMarker>> call, Response<List<TroopMarker>> response) {
if(response.isSuccessful()) {
Log.d("RETROFIT", "RESPONSE " + response.body().size());
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<List<TroopMarker>> call, Throwable t) {
CharSequence text = "Error loading troops.";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
callback.onSuccess(null);
}
});
}
public void loadTreasures() {
}
}
LoadActivity
public class LoadActivity extends AppCompatActivity
{
//TODO LOAD TROOPS AND TROOPMARKERS
//Load troops, troopmarkers, treasures and put on map
public List<Troop> troops;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loading);
//Start RepositoryService
final RepositoryService repositoryService = new RepositoryServiceImpl("http://internco.eu", this);
//Load troops
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
}
});
//Load troopMarkers
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
}
}
Can someone point me out on this? I think that I have to use the RxJava library but I can't figure this out.
Your help is much appreciated.
1 hacky way of doing it would be to keep 2 flag variables loadTroopsflag & loadTroopMarkersflag.Then in the onSuccess callbacks of each check whether both are true and if they are then both your requests are complete. There might be edge cases in implementing a workaround like this but it should generally work. In case your requests depend on each other then as you will need to use nested called ie,
repositoryService.loadTroops(new TroopCallback() {
#Override
public void onSuccess(List<Troop> troops) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPS SIZE: " + troops.size());
repositoryService.loadTroopMarkers(new TroopMarkerCallback() {
public List<TroopMarker> troopMarkers;
#Override
public void onSuccess(List<TroopMarker> troopMarkers) {
Log.d("RETROFIT", "SUCCESFULLY LOADED TROOPMARKERS SIZE: " + troopMarkers.size());
}
});
}
});
Something like that,so in case you have more dependencies then your nested callbacks increase which is where Rxjava would come in and solve it in a few lines of code.I don't think you need to jump into Rx just yet as this is a relatively small problem and you Rx java brings in extra space that would increase the size of the app as well as development time.
Also note the part where you mention
//Should now here when I'm done with my requests.
Log.d("RETROFIT", "DONE");
does not imply that the requests are done,it simply means that they are queued up and in progress.These are asynchronous request and will complete when the callback completes.
Related
I created an app where data is loaded from server with the help of an API using Retrofit 2 . How can I fetch data from different API with the help of single API interface method?
If I request data from first URL then i should get data from first API, And if i request data from second URL then i should get data from second URL only. how can i do this with the help of a single API interface class.
This is my Retrofit
public class RetrofitInstance {
static {
System.loadLibrary("keys");
}
public static native String getUrl();
private static Retrofit retrofit;
public static Retrofit getRetrofit() {
if (retrofit == null) {
retrofit = new Retrofit.Builder().baseUrl(getUrl()).addConverterFactory(GsonConverterFactory.create()).build();
}
return retrofit;
}
}
and This is my API interface class where my api url is stored.
public interface APIInterfaces {
#GET("first.php")
Call<List<VideoModels>> getFirstVid();
#GET("second.php")
Call<List<VideoModels>> getSecondVid();
#GET("third.php")
Call<List<VideoModels>> getThirdVid();
#GET("fourth.php")
Call<List<VideoModels>> getFourthVid();
#GET("fifth.php")
Call<List<VideoModels>> getFifthVid();
}
This is my Activity and API interface method. Where I have to show details from API.
public class TestFragment extends Fragment {
TestFragmentBinding binding;
ArrayList<VideoModels> list = new ArrayList<>();
APIInterfaces interfaces;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = TestFragmentBinding.inflate(inflater, container, false);
ThumbnailViewer adapter = new ThumbnailViewer(list,getContext());
binding.mRecyclerView.setAdapter(adapter);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(),2);
binding.mRecyclerView.setLayoutManager(gridLayoutManager);
//Setup Data from API to ArrayList.
interfaces = RetrofitInstance.getRetrofit().create(APIInterfaces.class);
// I want to Change url as request and show data from "getSecondVid()","getThirdVid()" with singple interface method. Is it possible?
interfaces.getFirstVid().enqueue(new Callback<List<VideoModels>>() {
#SuppressLint("NotifyDataSetChanged")
#Override
public void onResponse(Call<List<VideoModels>> call, Response<List<VideoModels>> response) {
list.clear();
if (response.isSuccessful() && response !=null) {
list.addAll(response.body());
adapter.notifyDataSetChanged();
}
else {
Toast.makeText(getContext(), "No Data Found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<List<VideoModels>> call, Throwable t) {
Toast.makeText(getContext(), "Unable to Connect", Toast.LENGTH_SHORT).show();
}
});
return binding.getRoot();
}
}
Your answer is very helpful for me.
You can do something like this
public interface APIInterfaces {
#GET
Call<List<VideoModels>> getVid(#Url String URL);
}
For every call, you just have to pass the URL to the same method.
you implementation will change to this
// you can pass the URL for the first.php, second.php video etc.
interfaces.getVid("your_url").enqueue(new Callback<List<VideoModels>>() {
#SuppressLint("NotifyDataSetChanged")
#Override
public void onResponse(Call<List<VideoModels>> call, Response<List<VideoModels>> response) {
list.clear();
if (response.isSuccessful() && response !=null) {
list.addAll(response.body());
adapter.notifyDataSetChanged();
}
else {
Toast.makeText(getContext(), "No Data Found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(Call<List<VideoModels>> call, Throwable t) {
Toast.makeText(getContext(), "Unable to Connect", Toast.LENGTH_SHORT).show();
}
});
I have this method that I am trying to pull data from an API, and then update the text view. Everything works except getRecipeName doesn't finish after the "end Method" log. .getRecipeName() uses RetroFit to pull from an API.
I am currently learning MVP, Dagger, RxJava, and Butterknife all at once using
Mindork's Github page on MVP Architecture
I commented out the .subscribeOn and .observeOn to see the result difference and nothing changed.
#Override
public void onRandomButtonClicked() {
getMvpView().showLoading();
Log.e(TAG, "Random Method Open");
getCompositeDisposable().add(getDataManager()
.getRecipeName()
//.subscribeOn(getSchedulerProvider().io())
//.observeOn(getSchedulerProvider().ui())
.subscribe(new Consumer<String>() {
#Override
public void accept(String s) throws Exception {
Log.e(TAG, "accept");
getMvpView().updateTextView(title);
}
}));
Log.e(TAG, "end method");
}
Here is my getRecipeName() method
#Override
public Observable<String> getRecipeName() {
/*Create handle for the RetrofitInstance interface*/
GetDataService service = RetrofitClientInstance.getRetrofitInstance().create(GetDataService.class);
Call<RecipeList> call = service.getRecipe();
call.enqueue(new Callback<RecipeList>() {
#Override
public void onResponse(#NonNull Call<RecipeList> call, #NonNull retrofit2.Response<RecipeList> response) {
Log.e("onResponse","Recipe is Successful = " + response.isSuccessful());
//if response is false then skip to avoid null object reference
if (response.isSuccessful()) {
RecipeList drinkRecipe = response.body();
List<Recipe> recipes = drinkRecipe.getDrinks();
jokeText = String.valueOf(recipes.size());
Recipe myRecipe = recipes.get(0);
jokeText = myRecipe.getStrDrink();
Log.e("On Response", "Result2: " + jokeText);
}
//jokeText = "null";
}
#Override
public void onFailure(Call<RecipeList> call, Throwable t) {
Log.e("On Response","Failure");
}
});
//return jokeText;
return Observable.fromCallable(new Callable<String>() {
#Override
public String call() throws Exception {
return jokeText;
}
});
}
Solution
So as the comments stated RxJava Adapter was the correct way to go. I will just post my working code on myself using the adapter. I found it very difficult to find a working example.
//single api call using retrofit and rxjava
#SuppressLint("CheckResult")
private void getRandomButtonClick(){
retrofit = RetrofitClientInstance.getRetrofitInstance();
retrofit.create(GetDataService.class).getRecipe()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::handleResults, this::handleError );
}
private void handleResults(RecipeList recipeList) {
int i = recipeList.getDrinks().size();
Log.e(TAG, "size is: "+ i);
Recipe recipe = recipeList.getDrinks().get(0);
getMvpView().updateTextView(recipe.getStrDrink());
}
private void handleError(Throwable t){
Log.e("Observer", "");
}
My Retrofit Client Instance
public static Retrofit getRetrofitInstance() {
if (retrofit == null) {
retrofit = new retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
}
return retrofit;
}
My Interface
public interface GetDataService {
//#Headers({})
#GET("random.php")
Observable<RecipeList> getRecipe();
I found a great resource to reference for me to correctly implement this. Retrofit Android
The reason is because your observable is returning jokeText every time it is subscribed upon. It returns immediately after invocation and will not wait for your network operation.
One possible solution is to use the RxJavaCallAdapter. Link here: https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2
It will automatically convert your API returns to observables. No need to manually invoke retrofit requests. Just process the response and convert it to your desired object from there.
Another approach would be to wrap your entire sequence in an Observable.create or Observable.fromAsync.
I am new to RxJava. I want to fetch data from the JSON API. Assume there are two APIs, API 1 and API 2. We fetch a JSON object "mediaId" from API 1. Now, I want to fetch JSON from API 2 with "mediaId". How can I achieve this using RxJava, along with retrofit in Android?
public void gettdata(final Listerner listerner){
postitemses= new ArrayList<>();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://www.mytrendin.com")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
APiService networkAPI = retrofit.create(APiService.class);
Observable<List<Postitems>> observable = networkAPI.getFriendObservable()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread());
observable.subscribe(new Observer<List<Postitems>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
listerner.onFailure("oops... Something went wrong");
}
#Override
public void onNext(List<Postitems> postitemsList1) {
Postitems postitems;
for (int i=0;i<postitemsList1.size();i++){
postitems = new Postitems();
int id = postitemsList1.get(i).getId();
String title = postitemsList1.get(i).getTitle().getRendered();
String shortdesc= postitemsList1.get(i).getExcerpt().getRendered();
String mediaid= postitemsList1.get(i).getFeatured_media();
String authorid= postitemsList1.get(i).getAuthor();
String date = postitemsList1.get(i).getDate();
String slug = postitemsList1.get(i).getSlug();
Log.i("Hello-slug",""+slug);
String[] mediaurl= mydata(mediaid);
Log.i("Hello-mediaurl",""+mediaurl);
postitems.setId(id);
postitems.setDate(date);
postitems.setSlug(""+slug);
postitems.setPostExcerpt(shortdesc);
postitems.setPostTitle(title);
postitemses.add(postitems);
}
listerner.showpostitems(postitemses);
}
});
}
public String[] mydata(String mediaid){
final String[] mediaurl = new String[1];
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://www.mytrendin.com")
.build();
APiService aPiService = retrofit.create(APiService.class);
Call<Postitems> call = aPiService.getmediaurl(mediaid);
call.enqueue(new Callback<Postitems>() {
#Override
public void onResponse(Call<Postitems> call, Response<Postitems> response) {
Postitems postitemsList1 = response.body();
mediaurl[0] =postitemsList1.getGuid().getRendered();
// mediaurl[0][0] =postitemsList1.get(0).getGuid().getRendered();
}
#Override
public void onFailure(Call<Postitems> call, Throwable t) {
}
});
return mediaurl;
}
error occured
https://www.mytrendin.com
05-09 03:42:09.227 15315-15315/? D/AndroidRuntime: Shutting down VM
--------- beginning of crash
05-09 03:42:09.228 15315-15315/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mytrendin.mytrendin, PID: 15315
java.lang.NullPointerException: Attempt to invoke virtual method .mytrendin.dashboard.utils.Po stitems$Guid (ZygoteInit.java:755)
Sure you can use the merge operator along with the IO scheduler.By definition,merge can combine multiple Observables into one by merging their emissions.here is an example,
Observable<Integer> odds = Observable.just(1, 3, 5).subscribeOn(someScheduler);
Observable<Integer> evens = Observable.just(2, 4, 6);
Observable.merge(odds, evens)
.subscribe(new Subscriber<Integer>() {
#Override
public void onNext(Integer item) {
System.out.println("Next: " + item);
}
#Override
public void onError(Throwable error) {
System.err.println("Error: " + error.getMessage());
}
#Override
public void onCompleted() {
System.out.println("Sequence complete.");
}
});
Output :
Next: 1
Next: 3
Next: 5
Next: 2
Next: 4
Next: 6
Sequence complete.
Something like this in your case,
public Observable<Data> getMergedData() {
return Observable.merge(
networkRepository.getData().subscribeOn(Schedulers.io()),
networkRepository.getData().subscribeOn(Schedulers.io())
);
}
Alright there is another way to solve this, first create a observable for both API, then subscribe and observe changes from your first API subscription.Next create a PublishSubject instance. Which is useful because,once an Observer has subscribed, emits all subsequently observed items to the subscriber.For example publish string values from the api response.
private PublishSubject<String> subject = PublishSubject.create();
subject.observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).filter((s) -> s.size() > 0).subscribe(new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String str) {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
});
Then to trigger the observable call onNext from the subject.
subject.onNext("some data from api");
Advantages, very flexible to changes to anywhere in your class scope.
Hope this helps.
for the below snippet
call.enqueue(new Callback<Postitems>() {
#Override
public void onResponse(Call<Postitems> call, Response<Postitems> response) {
Postitems postitemsList1 = response.body();
mediaurl[0] =postitemsList1.getGuid().getRendered();
// mediaurl[0][0] =postitemsList1.get(0).getGuid().getRendered();
//use the concept of publish subject here, which i detailed in answer, example
subject.onNext(postitemsList1.getGuid().getRendered());
//the string data will be passed to the above observable for the subject instance.
}
#Override
public void onFailure(Call<Postitems> call, Throwable t) {
}
});
I'm using rxjava, retrofit2, okhttp3.
I just put Log.d to see how it works and Log is like below.
1. D/-- NetPresenter: checkConnectivity
2. D/-- NetPresenter: Observable
3. D/-- NetPresenter: onNext
4. D/-- MessageSetter: setMessage
5. D/-- MainActivity setText: setText
6. D/-- NetPresenter: onCompleted
7. D/-- NetChecker: onError
8. D/-- error msg: failed to connect to /192.168.0.27 (port 8081) after 5000ms
It should set TextView after getting value "code" which is set from onError.
That means #7 should be processed before #5.
I think it is because of timeout option when request connection but I have no idea where to fix.
Can anybody help me to figure it out?
Thanks.
class 1.
public void checkConnectivity(Context context) {
Log.d("-- NetPresenter", "checkConnectivity");
this.context = context;
myObservable.subscribe(mySubscriber);
}
private Observable<Integer> myObservable = Observable.create(
new Observable.OnSubscribe<Integer>() {
#Override
public void call(Subscriber<? super Integer> sub) {
Log.d("-- NetPresenter", "Observable");
int connType = cc.getConnectionStatus(context);
sub.onNext(connType);
sub.onCompleted();
}
}
);
private Subscriber<Integer> mySubscriber = new Subscriber<Integer>() {
#Override
public void onNext(Integer connType) {
Log.d("-- NetPresenter", "onNext");
int code = nc.netChecker(connType);
view.updateReceivedMessageTextView(ms.setMessage(code));
}
#Override
public void onCompleted() {
Log.d("-- NetPresenter", "onCompleted");
}
#Override
public void onError(Throwable e) {
Log.d("-- NetPresenter", "onError");
}
};
class 2.
public int netChecker(int connType) {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(5, TimeUnit.SECONDS)
.connectTimeout(5, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(ApiService.API_URL)
.client(okHttpClient)
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
apiService = retrofit.create(ApiService.class);
Observable<Response<ResponseBody>> result = apiService.getData("database");
result.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Response<ResponseBody>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
if(connType/10 == 0) code = 0;
else code = connType+1;
Log.d("-- NetChecker", "onError");
Log.d("-- error msg", e.getMessage());
}
#Override
public void onNext(Response<ResponseBody> response) {
Log.d("-- NetChecker", "onNext");
Log.d("message", response.message());
Log.d("code", "code :"+ response.code());
code = connType;
}
});
return code;
}
you can use .doOnError() method to do some action on error.
There is another method .onErrorReturn(), which intercept error and return some value instead so sequence do not terminate. You can use both of those methods.
Advantage of RxJava is asynchronous. You can't predict order of evaluation. I believe you should reach MainActivity from "onError" method. I see, that you use MVP pattern somehow. It should help. If you provide more info about classes relations I could say more.
Each request to the server may return error_code. I want to handle these error in one place
when I was using AsyncTask I had a BaseAsyncTask like that
public abstract class BaseAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected Context context;
private ProgressDialog progressDialog;
private Result result;
protected BaseAsyncTask(Context context, ProgressDialog progressDialog) {
this.context = context;
this.progressDialog = progressDialog;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
HttpResponse<ErrorResponse> response = (HttpResponse<ErrorResponse>) result;
if(response.getData().getErrorCode() != -1) {
handleErrors(response.getData());
}else
onResult(result);
}
private void handleErrors(ErrorResponse errorResponse) {
}
public abstract void onResult(Result result);
}
But, using retrofit each request has its error handling callback:
git.getFeed(user,new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
});
}
});
How can I handle all errors in one place?
If you need to get some 'logic' error, then you need some Java logic since it's not a Retrofit feature so basically:
Create a Your implementation Callback that implements the Retrofit Callback
Create a base object that define the method 'isError'
Modify Retrofit RestAdapter in order to get your Callback instead of the Retrofit One
MyCallback.java
import android.util.Log;
import retrofit.Callback;
import retrofit.client.Response;
public abstract class MyCallback<T extends MyObject> implements Callback<T> {
#Override
public final void success(T o, Response response) {
if (o.isError()) {
// [..do something with error]
handleLogicError(o);
}
else {
handleSuccess(o, response);
}
}
abstract void handleSuccess(T o, Response response);
void handleLogicError(T o) {
Log.v("TAG", "Error because userId is " + o.id);
}
}
MyObject.java (the base class for all your objects you get from Retrofit)
public class MyObject {
public long id;
public boolean isError() {
return id == 1;
}
}
MyRealObject.java - a class that extends the base object
public class MyRealObject extends MyObject {
public long userId;
public String title;
public String body;
}
RetroInterface.java - the interface used by retrofit you should be familiar with
import retrofit.http.GET;
import retrofit.http.Path;
public interface RetroInterface {
#GET("/posts/{id}")
void sendGet(#Path("id") int id, MyCallback<MyRealObject> callback);
}
And finally the piece of code where you use all the logic
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint("http://jsonplaceholder.typicode.com")
.build();
RetroInterface itf = adapter.create(RetroInterface.class);
itf.sendGet(2, new MyCallback<MyRealObject>() {
#Override
void handleSuccess(MyRealObject o, Response response) {
Log.v("TAG", "success");
}
#Override
public void failure(RetrofitError error) {
Log.v("TAG", "failure");
}
});
If you copy and paste this code, you'll get an error when you'll execute the itf.sendGet(1, new MyCallback..) and a success for itf.sendGet(2, new MyCallback...)
Not sure I understood it correctly, but you could create one Callback and pass it as a parameter to all of your requests.
Instead of:
git.getFeed(user,new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
}
});
First define your Callback:
Callback<gitmodel> mCallback = new Callback<gitmodel>() {
#Override
public void success(gitmodel gitmodel, Response response) {
}
#Override
public void failure(RetrofitError error) {
// logic to handle error for all requests
}
};
Then:
git.getFeed(user, mCallback);
In Retrofit you can specify ErrorHandler to all requests.
public class ApiErrorHandler implements ErrorHandler {
#Override
public Throwable handleError(RetrofitError cause) {
//here place your logic for all errors
return cause;
}
}
Apply it to RestAdapter
RestAdapter.Builder()
.setClient(client)
.setEndpoint(endpoint)
.setErrorHandler(errorHandler)
.build();
I think that it is what you asked for.
In Retrofit2 you can't set an ErrorHandler with the method .setErrorHandler(), but you can create an interceptor to fork all possible errors centralised in one place of your application.
With this example you have one centralised place for your error handling with Retrofit2 and OkHttpClient. Just reuse the Retrofit object (retrofit).
You can try this standalone example with a custom interceptor for network and server errors. These both will be handled differently in Retrofit2, so you have to check the returned error code from the server over the response code (response.code()) and if the response was not successful (!response.isSuccessful()).
For the case that the user has no connection to the network or the server you have to catch an IOException of the method Response response = chain.proceed(chain.request()); and handle the network error in the catch block.
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
try {
Response response = chain.proceed(chain.request());
if (!response.isSuccessful()) {
Log.e("tag", "Failure central - response code: " + response.code());
Log.e("tag", "central server error handling");
// Central error handling for error responses here:
// e.g. 4XX and 5XX errors
switch (response.code()) {
case 401:
// do something when 401 Unauthorized happened
// e.g. delete credentials and forward to login screen
// ...
break;
case 403:
// do something when 403 Forbidden happened
// e.g. delete credentials and forward to login screen
// ...
break;
default:
Log.e("tag", "Log error or do something else with error code:" + response.code());
break;
}
}
return response;
} catch (IOException e) {
// Central error handling for network errors here:
// e.g. no connection to internet / to server
Log.e("tag", e.getMessage(), e);
Log.e("tag", "central network error handling");
throw e;
}
}
})
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://10.0.2.2:8000/api/v1/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
UserRepository backendRepository = retrofit.create(UserRepository.class);
backendRepository.getUser("userId123").enqueue(new Callback<UserModel>() {
#Override
public void onResponse(Call<UserModel> call, retrofit2.Response<UserModel> response) {
Log.d("tag", "onResponse");
if (!response.isSuccessful()) {
Log.e("tag", "onFailure local server error handling code:" + response.code());
} else {
// its all fine with the request
}
}
#Override
public void onFailure(Call<UserModel> call, Throwable t) {
Log.e("tag", "onFailure local network error handling");
Log.e("tag", t.getMessage(), t);
}
});
UserRepository example:
public interface UserRepository {
#GET("users/{userId}/")
Call<UserModel> getUser(#Path("userId") String userId);
}
UserModel example:
public class UserModel implements Parcelable {
#SerializedName("id")
#Expose
public String id = "";
#SerializedName("email")
#Expose
public String mail = "";
public UserModel() {
}
protected UserModel(Parcel in) {
id = in.readString();
mail = in.readString();
}
public static final Creator<UserModel> CREATOR = new Creator<UserModel>() {
#Override
public UserModel createFromParcel(Parcel in) {
return new UserModel(in);
}
#Override
public UserModel[] newArray(int size) {
return new UserModel[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(mail);
}
}
Fairly simply Retrofit custom error handling example. Is set up so that you don't need to do much work in the 'failure' handler of a retrofit call to get the user-visible error message to show. Works on all endpoints. There's lots of exception handling as our server folks like to keep us on our toes by sending all kinds of random stuff..!
// on error the server sends JSON
/*
{ "error": { "data": { "message":"A thing went wrong" } } }
*/
// create model classes..
public class ErrorResponse {
Error error;
public static class Error {
Data data;
public static class Data {
String message;
}
}
}
//
/**
* Converts the complex error structure into a single string you can get with error.getLocalizedMessage() in Retrofit error handlers.
* Also deals with there being no network available
*
* Uses a few string IDs for user-visible error messages
*/
private static class CustomErrorHandler implements ErrorHandler {
private final Context ctx;
public CustomErrorHandler(Context ctx) {
this.ctx = ctx;
}
#Override
public Throwable handleError(RetrofitError cause) {
String errorDescription;
if (cause.isNetworkError()) {
errorDescription = ctx.getString(R.string.error_network);
} else {
if (cause.getResponse() == null) {
errorDescription = ctx.getString(R.string.error_no_response);
} else {
// Error message handling - return a simple error to Retrofit handlers..
try {
ErrorResponse errorResponse = (ErrorResponse) cause.getBodyAs(ErrorResponse.class);
errorDescription = errorResponse.error.data.message;
} catch (Exception ex) {
try {
errorDescription = ctx.getString(R.string.error_network_http_error, cause.getResponse().getStatus());
} catch (Exception ex2) {
Log.e(TAG, "handleError: " + ex2.getLocalizedMessage());
errorDescription = ctx.getString(R.string.error_unknown);
}
}
}
}
return new Exception(errorDescription);
}
}
// When creating the Server...
retrofit.RestAdapter restAdapter = new retrofit.RestAdapter.Builder()
.setEndpoint(apiUrl)
.setLogLevel(retrofit.RestAdapter.LogLevel.FULL)
.setErrorHandler(new CustomErrorHandler(ctx)) // use error handler..
.build();
server = restAdapter.create(Server.class);
// Now when calling server methods, get simple error out like this:
server.postSignIn(login,new Callback<HomePageResponse>(){
#Override
public void success(HomePageResponse homePageResponse,Response response){
// Do success things!
}
#Override
public void failure(RetrofitError error){
error.getLocalizedMessage(); // <-- this is the message to show to user.
}
});