I'm new to rx java, so can you help with it. I have simple retrofit implementation and i'm using it to get data about radio. I need to get this data every 10 seconds. The only way i know to do it is using Service with AlarmManager, but i don't like it. How can i do it using rx java? Can i get data every 10 seconds.
Here is the code of retrofit implementation
public class ApiProvider {
public static final String PRODUCTION_API_URL = "http://radio.somesite.org";
static final int DISK_CACHE_SIZE = (int) MEGABYTES.toBytes(50);
private static ApiProvider instance;
private Application application;
private ApiProvider( ) {
this.application = CApplication.getApplication();
}
public static ApiProvider getInstance() {
if (instance != null)
return instance;
else {
instance = new ApiProvider();
return instance;
}
}
public static OkHttpClient createOkHttpClient(Application app) {
OkHttpClient client = new OkHttpClient();
client.setConnectTimeout(10, SECONDS);
client.setReadTimeout(10, SECONDS);
client.setWriteTimeout(10, SECONDS);
// Install an HTTP cache in the application cache directory.
File cacheDir = new File(app.getCacheDir(), "http");
Cache cache = new Cache(cacheDir, DISK_CACHE_SIZE);
client.setCache(cache);
return client;
}
private RestAdapter getRestAdapter() {
Gson gson = new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeConverter())
.create();
OkHttpClient client = createOkHttpClient(application);
Endpoint endpoint = Endpoints.newFixedEndpoint(PRODUCTION_API_URL);
return new RestAdapter.Builder() //
.setClient(new OkClient(client)) //
.setEndpoint(endpoint) //
.setConverter(new GsonConverter(gson)) //
.build();
}
private RadioLiveInfoService getRadioInfo() {
return getRestAdapter().create(RadioLiveInfoService.class);
}
private RadioWeekInfoService getRadioWeek() {
return getRestAdapter().create(RadioWeekInfoService.class);
}
public void getRadioInfo(Type type, final CallbackInfoListener listener) {
Callback callback = new Callback() {
#Override
public void success(Object o, Response response) {
try {
LiveInfo liveInfo = (LiveInfo) o;
listener.dataLoaded(liveInfo, true);
Log.d("Success", response.toString());
} catch (ClassCastException ex) {
ex.printStackTrace();
}
}
#Override
public void failure(RetrofitError retrofitError) {
listener.dataLoaded(new LiveInfo(), false);
Log.e("Error", retrofitError.toString());
}
};
getRadioInfo().commits(type, callback);
}
public void getRadioWeekInfo(final CallbackWeekListener listener) {
Callback callback = new Callback() {
#Override
public void success(Object o, Response response) {
try {
WeekInfo weekInfo = (WeekInfo) o;
listener.dataLoaded(weekInfo, true);
Log.d("Success", response.toString());
} catch (ClassCastException ex) {
ex.printStackTrace();
}
}
#Override
public void failure(RetrofitError retrofitError) {
listener.dataLoaded(new WeekInfo(), false);
Log.e("Error", retrofitError.toString());
}
};
getRadioWeek().commits(callback);
}
}
Thanks in advance
I made it to work this way. Still i need to understand how to do it with composit subscription.
RadioLiveInfoObservableService radioLiveInfoObservableService=ApiProvider.getInstance().getRadioObserverInfo();
radioLiveInfoObservableService.commits(Type.INTERVAL)
.observeOn(AndroidSchedulers.mainThread())
.doOnError(trendingError)
.onErrorResumeNext(Observable.<LiveInfo>empty()).subscribe(new Action1<LiveInfo>() {
#Override
public void call(LiveInfo liveInfo) {
List<Current> currents=new ArrayList<Current>();
currents.add(liveInfo.getCurrent());
adapter.currentShows=currents;
adapter.notifyDataSetChanged();
rv.setAdapter(adapter);
}
});
I am using it this way
fun getEventDetails(eventId: Long) : MutableLiveData<Response> {
Log.d("Retrofit", "Get event detail")
compositeDisposable.add(useCase.getEvents()
.repeatWhen {
return#repeatWhen it.delay(10, TimeUnit.SECONDS) //Repeat every 10 seconds
}
.repeatUntil {
return#repeatUntil repeat //Boolean
}
.map(this::parseResponse)
.subscribeOn(Schedulers.io())
.unsubscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
dashboardResponse.postValue(Response().success(it))
}, {
Timber.d(it.message)
dashboardResponse.postValue(Response().error(it.getRetrofitErrorMessage()))
})
)
return dashboardResponse
}
And API Method is :
#GET("events")
fun getEvents(): Observable<String>
Related
I am currently recording a video on a device, compressing this video and then uploading the compressed video to the server using Retrofit 2. I am also using the Worker class to perform all this in the background. A Progress is also being displayed in the notification bar while the upload is happening. My problem is when the app is Killed the entire upload process stops. I have tried to return WorkerResult.RETRY, which does work but it just repeats and thus one file is uploaded multiple times.The code is mentioned below :
Worker Class
public class UploadWorker extends Worker implements ProgressRequestBody.UploadCallBacks {
private static final String LOG_TAG = UploadWorker.class.getSimpleName();
public static final int UPDATE_PROGRESS = 8344;
Context context;
WorkerParameters parameters;
private static final String SERVER_PATH = "";
String filePath;
/*For notification update*/
private NotificationCompat.Builder notificationBuilder;
private NotificationManager notificationManager;
public UploadWorker(Context context,
WorkerParameters parameters) {
super(context, parameters);
this.context = context;
this.parameters = parameters;
}
#NonNull
#Override
public Result doWork() {
showNotificationUpdate();
compressVideo(getInputData().getString("currentPhotoPath");,
getInputData().getString("fileDestination"););
constructFile(filePath);
// Indicate success or failure with your return value:
return Result.SUCCESS;
}
private void uploadVideoToServer(File fileToUpload) {
ProgressRequestBody fileBody = new ProgressRequestBody(fileToUpload, this);
Gson gson = new GsonBuilder()
.setLenient()
.create();
MultipartBody.Part vFile = MultipartBody.Part.createFormData("fileToUpload", fileToUpload.getName(), fileBody);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(SERVER_PATH)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(provideClient())
.build();
VideoInterface vInterface = retrofit.create(VideoInterface.class);
Call<ResponseBody> serverCom = vInterface.uploadVideo(vFile);
serverCom.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
try {
if (response.body() != null) {
Log.d(LOG_TAG, "Resposne == " + response.body().string());
}
} catch (IOException e) {
e.printStackTrace();
}
Log.d(LOG_TAG, "Response In String == " + response);
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.d(LOG_TAG, "Error == " + t.getLocalizedMessage());
}
});
}
#Override
public void onProgressUpdate(int percentage) {
updateNotification(percentage);
}
#Override
public void onError() {
sendProgressUpdate(false);
}
#Override
public void onFinish() {
sendProgressUpdate(true);
notificationManager.cancel(0);
notificationBuilder.setProgress(0, 0, false);
notificationBuilder.setContentTitle("Upload Done");
notificationBuilder.setSmallIcon(android.R.drawable.stat_sys_upload_done);
notificationManager.notify(0, notificationBuilder.build());
}
}
This is how I am setting up the worker in my activity
Data inputData = new Data.Builder()
.putString("currentPhotoPath", mCurrentPhotoPath)
.putString("fileDestination", f.getPath())
.build();
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresStorageNotLow(true)
.build();
OneTimeWorkRequest compressionWork =
new OneTimeWorkRequest.Builder(UploadWorker.class)
.setConstraints(constraints)
.addTag("CompressVideo")
.setInputData(inputData)
.build();
WorkManager.getInstance()
.enqueue(compressionWork);
WorkManager.getInstance().getStatusById(compressionWork.getId())
.observe(this, new Observer<WorkStatus>() {
#Override
public void onChanged(#Nullable WorkStatus workStatus) {
Log.d(LOG_TAG, "WORKER STATE == " + workStatus.getState().name());
if (workStatus != null && workStatus.getState().isFinished()) {
Log.d(LOG_TAG, "Is WOrk Finished == " + workStatus.getState().isFinished());
}
}
});
Can someone please help me out in finding an appropriate solution so that when the app is closed, the file upload still continues and stops when it is done?
i triying to do a simple MVP in Android using AndroidAnnotations and Retrofit2,
But I never get the information.
Code Presenter:
public class ListHomePresenter {
private List<Post> mPost;
Client client;
int error_code = 0;
String error_msg = "";
/**
* TODO: get all post
* */
public List<Post> getPost() throws SampleExceptions{
RestService restService;
client = new Client();
restService = client.getApi();
Call<List<Post>> task = restService.downloadPost();
task.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
mPost = response.body();
}
#Override
public void onFailure(Call<List<Post>> call, Throwable t) {
mPost = null;
error_code = 1; // error
error_msg = t.getMessage(); // message
}
});
if(error_code == 1 )
throw new SampleExceptions(error_code, "Error");
return mPost;
}
}
Code Activity
#EActivity(R.layout.activity_home)
public class HomeActivity extends AppCompatActivity {
#ViewById(R.id.rvPosts)
RecyclerView mRvPost;
PostAdapter mPostAdapter;
ListHomePresenter mListHomePresenter;
private StaggeredGridLayoutManager gaggeredGridLayoutManager;
#AfterViews
void setupHome(){
downloadPost();
}
#UiThread(propagation = Propagation.REUSE)
void downloadPost(){
try{
mListHomePresenter = new ListHomePresenter();
gaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, 1);
mRvPost.setLayoutManager(gaggeredGridLayoutManager);
mPostAdapter = new PostAdapter(HomeActivity.this, mListHomePresenter.getPost());
mRvPost.setAdapter( mPostAdapter );
}catch (SampleExceptions e){
}
}
}
but, ever i getting the null data.
Help Me!
Here an example:
Presenter:
public class ListHomePresenter {
private List<Post> mPost;
Client client;
int error_code = 0;
String error_msg = "";
HomeView view;
public ListHomePresenter(HomeView view) {
this.view = view;
}
/**
* TODO: get all post
* */
public void loadPost() throws SampleExceptions{
RestService restService;
client = new Client();
restService = client.getApi();
Call<List<Post>> task = restService.downloadPost();
task.enqueue(new Callback<List<Post>>() {
#Override
public void onResponse(Call<List<Post>> call, Response<List<Post>> response) {
view.retrieveData(response.body());
}
#Override
public void onFailure(Call<List<Post>> call, Throwable t) {
mPost = null;
error_code = 1; // error
error_msg = t.getMessage(); // message
}
});
if(error_code == 1 )
throw new SampleExceptions(error_code, "Error");
}
}
Activity:
#ViewById(R.id.rvPosts)
RecyclerView mRvPost;
PostAdapter mPostAdapter;
ListHomePresenter mListHomePresenter;
private StaggeredGridLayoutManager gaggeredGridLayoutManager;
#AfterViews
void setupHome() {
downloadPost();
}
#UiThread(propagation = Propagation.REUSE)
void downloadPost() {
try {
mListHomePresenter = new ListHomePresenter(this);
mListHomePresenter.loadPost()
} catch (SampleExceptions e) {
}
}
// Method implemented from view interface
#Override
void retrieveData(List<Post> postList) {
gaggeredGridLayoutManager = new StaggeredGridLayoutManager(2, 1);
mRvPost.setLayoutManager(gaggeredGridLayoutManager);
mPostAdapter = new PostAdapter(HomeActivity.this, postList);
mRvPost.setAdapter(mPostAdapter);
}
Only you needs implement an interface view to interacts between two classes
You have a race condition on your getPost method, when return mPost, the callback has not yet returned the response.
I recommend that the getPost method does not return anything, and once you pass onResponse, you call a view method that fills the adapter.
This ensures that you will always have data.
I do a API call to a webserver and I get the ID back in the method onResponse.
Now I want to save this ID and return this id in the return of the method doLogin. How can I get that variable ID in the return statement?
This is my code:
public class LoginController {
public static String doLogin(String loginMail, String loginPassword) {
//Logging Retrofit
final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("###URLTOAPICALL###")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
APIService service = retrofit.create(APIService.class);
Call<JsonElement> call = service.doLogin(loginMail, loginPassword);
call.enqueue(new Callback<JsonElement>() {
#Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
if (response != null) {
JSONObject obj = null;
try {
obj = new JSONObject(response.body().toString());
} catch (JSONException e) {
e.printStackTrace();
}
JSONObject setup = null;
try {
setup = obj.getJSONObject("setup");
} catch (JSONException e) {
e.printStackTrace();
}
if(setup != null) {
try {
Setup stp = new Setup();
stp.setUserId(setup.getInt("id"));
//I WANT HERE TO SAVE MY ID
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
#Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Log.v("ERROR", t+"");
}
});
return "I WANT RETURN THAT ID HERE";
}
}
As retrofit is asynchronous don't return from method instead use interface callbacks.
public class LoginController {
public interface LoginCallbacks{
void onLogin(String id);
void onLoginFailed(Throwable error);
}
public static void doLogin(String loginMail, String loginPassword, final LoginCallbacks loginCallbacks) {
//Logging Retrofit
final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("###URLTOAPICALL###")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
APIService service = retrofit.create(APIService.class);
Call<JsonElement> call = service.doLogin(loginMail, loginPassword);
call.enqueue(new Callback<JsonElement>() {
#Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
if (response != null) {
JSONObject obj = null;
try {
obj = new JSONObject(response.body().toString());
} catch (JSONException e) {
e.printStackTrace();
}
JSONObject setup = null;
try {
setup = obj.getJSONObject("setup");
} catch (JSONException e) {
e.printStackTrace();
}
if(setup != null) {
try {
Setup stp = new Setup();
stp.setUserId(setup.getInt("id"));
//I WANT HERE TO SAVE MY ID
if (loginCallbacks != null)
loginCallbacks.onLogin(setup.getInt("id"));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
#Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Log.v("ERROR", t+"");
if (loginCallbacks != null)
loginCallbacks.onLoginFailed(t);
}
});
}
}
Call method:
doLogin("email", "password", new LoginCallbacks() {
#Override
public void onLogin(String id) {
}
#Override
public void onLoginFailed(Throwable error) {
}
});
You can use a setter method within the onResponse method of your Retrofit call.
Take an instance where I have a global variable to hold distance between two points that I get from the Google maps distance matrix API:
String final_distance;
Here's my retrofit call:
call.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
// TODO Auto-generated method stub
JsonObject object = response.body();
String distance = object.get("rows").getAsJsonArray().get(0).getAsJsonObject().get("elements").getAsJsonArray().
get(0).getAsJsonObject().get("distance").getAsJsonObject().get("value").getAsString();
//The setter method to change the global variable
setDistance(distance);
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
// TODO Auto-generated method stub
}
});
This is what the setter method does:
private static void setDistance(String distance) {
final_distance = distance;
}
Since the Retrofit onResponse method is asynchronous, you will need to always first check whether the final_distance is not null before using it
While call.execute() function is synchronous it triggers app crashes on Android 4.0 or newer and you'll get NetworkOnMainThreadException. You have to do an async request initializing your global variable into a runnable thread. At your class name add Runnable implementation.Your getDataFunction() will look something like this:
public void getData(){
Call<JsonElement> call = service.doLogin(loginMail, loginPassword);
call.enqueue(new Callback<JsonElement>() {
#Override
public void onResponse(Call<JsonElement> call, Response<JsonElement> response) {
if (response.isSuccessful() && response != null) {
jsonObject = response.body().toString();//initialize your global variable
}
}
#Override
public void onFailure(Call<JsonElement> call, Throwable t) {
Log.v("ERROR", t+"");
}
});
}
#Override
pulic void run(){
getDataFunction();
//here you can use your initialized variable
}
Now on your onCreate function create the run thread and start it.
Thread thread = new Thread(this);
thread.start();
This is the way it solved a similar problem of mine.
You can't since the Call you are requesting is async. If you want to run it in the same thread you must avoid using enqueue and use execute(). Keep in mind that you need to create a thread since you cant use Network Operations on the same thread.
You can solve it using Observables or use execute like in this case (not tested)
public static String doLogin(String loginMail, String loginPassword) {
//Logging Retrofit
final HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("###URLTOAPICALL###")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
APIService service = retrofit.create(APIService.class);
Call<JsonElement> call = service.doLogin(loginMail, loginPassword);
try {
Response response = call.execute();
if (response.isSuccessful()) {
// do your stuff and
return yourString;
}
}catch (IOException ex) {
ex.printStackTrace();
}
}
You can call it in your activity using
new Thread(new Runnable() {
#Override
public void run() {
String var = doLogin("email", "paswsord");
}
});
Take care that if you want to update your UI you need to use
runOnUiThread();
I am really trying to get a hang of using Retrofit with RxJava / RxAndroid. I've done this using normal Retrofit2 Callback method in a previous app without the use of Reactive Programming and it worked fine. So, here is it. I need to Tail Recall a function meant to fetch all Local Government from the server. The API uses pagination (I have to construct the URL with ?page=1, perPage=2). I've to do this till I've the whole data. So, below is my Rx code
public static Observable<LgaListResponse> getPages(Context acontext) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pageControl = BehaviorSubject.<Integer>create(1);
Observable<LgaListResponse> ret2 = pageControl.asObservable().concatMap(integer -> {
if (integer > 0) {
Log.e(TAG, "Integer: " + integer);
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(integer), String.valueOf(21))
.doOnNext(lgaListResponse -> {
if (lgaListResponse.getMeta().getPage() != lgaListResponse.getMeta().getPageCount()) {
pageControl.onNext(initialPage + 1);
} else {
pageControl.onNext(-1);
}
});
} else {
return Observable.<LgaListResponse>empty().doOnCompleted(pageControl::onCompleted);
}
});
return Observable.defer(() -> ret2);
}
And my ServiceGenerator Class
public class ServiceGenerator {
private static final String TAG = "ServiceGen";
private static OkHttpClient.Builder builder = new OkHttpClient.Builder();
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BuildConfig.HOST)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create(CustomGsonParser.returnCustomParser()));
public static <S> S createService(Class<S> serviceClass, String token) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
/*builder.addNetworkInterceptor(new StethoInterceptor());*/
builder.connectTimeout(30000, TimeUnit.SECONDS);
builder.readTimeout(30000, TimeUnit.SECONDS);
if (token != null) {
Interceptor interceptor = chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("x-mobile", "true")
.addHeader("Authorization", "Bearer " + token).build();
return chain.proceed(newRequest);
};
builder.addInterceptor(interceptor);
}
OkHttpClient client = builder.build();
Retrofit retrofit = retrofitBuilder.client(client).build();
Log.e(TAG, retrofit.baseUrl().toString());
return retrofit.create(serviceClass);
}
public static Retrofit retrofit() {
OkHttpClient client = builder.build();
return retrofitBuilder.client(client).build();
}
public static class CustomGsonParser {
public static Gson returnCustomParser(){
return new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
}
}
}
So, I noticed on the first call, I get a response, but on the second one, the 440Error is thrown. The URL is formed, but the request throws a 400Error. I don't know why it's throwing a 400 everything is working fine if I use POSTMAN to test. And, I tested with my old code too. The Log is too long, so I put it in pastebin LOGS any help thanks. I've written most of this app with RxAndroid / RxJava. Thanks
I suggest you simplify things (and remove recursion). First build up your pages using something like
public static Observable<LgaListResponse> getPages(Context acontext, int initialPage, int perPage) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pagecontrol = BehaviorSubject.<Integer>create(initialPage);
Observable<LgaListResponse> ret2 = pagecontrol.asObservable().concatMap(
new Func1<Integer,Observable<LgaListResponse>>() {
Observable<LgaListResponse> call(Integer pageNumber) {
if (pageNumber > 0) {
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(aKey), String.valueOf(perPage))
.doOnNext(
new Action1<LgaListResponse>() {
void call(LgaListResponse page) {
if (page.getMeta().getPage() != page.getMeta().getPageCount()) {
pagecontrol.onNext(page.getMeta().getNextPage());
} else {
pagecontrol.onNext(-1);
}
}
}
);
}
else {
return Observable.<LgaListResponse>empty().doOnCompleted(()->pagecontrol.onCompleted());
}
}
}
);
return Observable.defer(
new Func0<Observable<LgaListResponse>() {
Observable<LgaListResponse> call() {
return ret2;
}
}
);
}
then subscribe to the resulting observable. It looks horrible because I've avoided using lambdas but it should work.
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.
}
});