I am working with retrofit to get some statistics. They arrive at the app. When I try to set some TextView's text to the values they throw a NullPointerException. Is there something I should know?
public void init() {
getStatistics();
txtNrCompleted.setText(String.format("%s", statistics.getTask()));
}
private void getStatistics(){
endpoints = RetrofitJsonCaller.call(APIEndpoints.class);
callStatistics = endpoints.getStatistics(URLEndpoints.getStatistics());
callStatistics.enqueue(new Callback<STATISTIC>() {
#Override
public void onResponse(Call<STATISTIC> call, Response<STATISTIC> response) {
if(response.isSuccessful()) {
setStatistics(response.body());
}else{
Log.d("STATISTICS", "Error: " + response.code());
}
}
#Override
public void onFailure(Call<STATISTIC> call, Throwable t) {
Timber.d(t.getMessage());
}
});
}
public void setStatistics(STATISTIC statistics){
this.statistics = statistics;
}
LOGS:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Integer com.example.taskmanagement.model.STATISTIC.getTaskComplet()' on a null object reference
at com.example.taskmanagement.MainActivity.onCreate(MainActivity.java:111)
at android.app.Activity.performCreate(Activity.java:6948)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1126)
Retrofit is making the call to get the statistics asynchronously, but you are setting the text in the TextView synchronously. You call getStatistics() which triggers the call to get the new statistics but doesn't wait for it to finish. You then set the text immediately after, at which point the statistics object is still null. You need to update the TextView after you get a successful response. For example:
public void init() {
getStatistics();
}
private void getStatistics() {
...
#Override
public void onResponse(Call<STATISTIC> call, Response<STATISTIC> response) {
if (response.isSuccessful()) {
setStatistics(response.body());
// Call the code to update your UI here, as we have now received the stats
updateUI();
} else {
...
}
}
...
}
...
private void updateUI() {
textNrCompleted.setText(String.format("%s", statistics.getTask()));
}
Related
I'm very new to android development. Trying to connect some site and get data from it. I have this function called only from onCreate in the main activity. Every time I turn virtual Android phone left or right I see new "run()" strings in EditText and requests in Wireshark. How to stop that properly?
Tried call.cancel() and mClient.dispatcher().cancelAll() inside OnResponse
protected void Load(String url) {
Request request = new Request.Builder()
.url(url)
.build();
mClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
});
}
retrofit supports enqueue canceling, and it works great.
And i think if you will try to run this code - your client enqueues would be stoped
protected void Load(String url) {
Request request = new Request.Builder()
.url(url)
.build();
Call<Response> mCall = mClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
if (call.isCanceled()) {
Log.e(TAG, "request was cancelled");
}
else {
Log.e(TAG, "other larger issue, i.e. no network connection?");
}
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
});
mCall.cancel();
}
I don't know you project structure and what kind of patterns you using(MVP, MVVM or else), but in simple, this code can be improved by returning Call
protected void Load(String url): Call<Response>
And then you can hadle you request status, and if it longer than 5 seconds for example, you call call.cancel() and request is stopping.
onCreate is called every time configuration changes (for example you rotate your phone/emulator). For more info: https://developer.android.com/guide/components/activities/activity-lifecycle
You can save your response to prevent new request on every onCreate. Something like this:
MainActivity {
private Response savedResponse;
onCreate() {
if (savedResponse == null) {
Load(url)
}
}
}
and in your onResponse save the response:
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
savedResponse = response; // <----
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
However, correct way would be to separete network calls/requests from activity lifecycle and load data somewhere else (Service, WorkManager, ...)
I want to get the retrofit response list and use it outside the OnResponse function, but went I try to do it I'm always getting a null object.
Here is my source code
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<ActivitiesResponse> call = apiService.getUserActivities(id);
call.enqueue(new Callback<ActivitiesResponse>() {
// If success
#Override
public void onResponse(Call<ActivitiesResponse>call, Response<ActivitiesResponse> response) {
list = response.body().getActivities();// I'm getting a not null response
}
// If failed
#Override
public void onFailure(Call<ActivitiesResponse>call, Throwable t) {
// Log error here since request failed
Log.e(TAG, t.toString());
}
});
//When I try to use the list here I'm getting a null object
Requests are async, so when you try to return a value, the request is likely not done yet so you cannot use it outside of the request. If you want to return values from the request use callback interfaces.
Change your code to a method and pass a callback parameter
Example
public void doRequest(final ApiCallback callback) {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
Call<ActivitiesResponse> call = apiService.getUserActivities(id);
call.enqueue(new Callback<ActivitiesResponse>() {
// If success
#Override
public void onResponse(Call<ActivitiesResponse>call, Response<ActivitiesResponse> response) {
list = response.body().getActivities();
callback.onSuccess(list); // pass the list
}
// If failed
#Override
public void onFailure(Call<ActivitiesResponse>call, Throwable t) {
// Log error here since request failed
Log.e(TAG, t.toString());
}
});
}
public interface ApiCallback{
void onSuccess(ArrayList<YOURTYPE> result);
}
Example usage in onResume(), basically you can do this anywhere you want:
public void onResume(){
super.onResume();
doRequest(new ApiCallback(){
#Override
public void onSuccess(ArrayList<YOURTYPE> result){
//do stuff here with the list from the request
}
});
}
Let me know if this fits your needs
Because onResponse() runs asynchronously. You can not get its value like that.
You should call the method which uses list value inside onResponse().
For example.
call.enqueue(new Callback<ActivitiesResponse>() {
// If success
#Override
public void onResponse(Call<ActivitiesResponse>call, Response<ActivitiesResponse> response) {
list = response.body().getActivities();// I'm getting a not null response
yourMethod();
}
// If failed
#Override
public void onFailure(Call<ActivitiesResponse>call, Throwable t) {
// Log error here since request failed
Log.e(TAG, t.toString());
}
});
private void yourMethod() {
// list is used somewhere here
}
I am using retrofit:2.1.0 and I am trying to save the response returned to my own POJO (UserProfile) but I can't seem to access the POJO, I assign the response to, outside of the callbacks.
So, in this call below, I want to have access to UserProfile outside of this call.
//adding `UserProfile userProfile;` outside of Call didn't help either
call.enqueue(new Callback<UserProfile>() {
#Override
public void onResponse(Call<UserProfile> call, Response<UserProfile> response) {
if (response.isSuccessful()) {
UserProfile userProfile = response.body();
}
}
#Override
public void onFailure(Call<UserProfile> call, Throwable t) {
//do something
}
});
//here userProfile is null and hence can't get status
Log.d(TAG, "Status outside of Call is: " + userProfile.getStatus());
New approach, same result
private List<UserProfile> userProfileList = new ArrayList<>();
call.enqueue(new Callback<UserProfile>() {
#Override
public void onResponse(Call<UserProfile> call, Response<UserProfile> response) {
if (response.isSuccessful()) {
UserProfile userProfile = response.body();
userProfileList.add(userProfile);
}
}
#Override
public void onFailure(Call<UserProfile> call, Throwable t) {
//do something
}
});
And this one gets fired even before callback and hence is null
if(userProfileList.size() > 0) {
for(UserProfile userProfile : userProfileList) {
Log.d(TAG, "Status is: " + userProfile.getStatus());
}
} else {
Log.d(TAG, "YakkerProfileList is NULL");
}
I added a setter in onResponse() which allowed me to use the response object elsewhere in the code like so:
#Override
public void onResponse(Call<UserProfile> call, Response<UserProfile> response) {
if (response.isSuccessful()) {
UserProfile userProfile = response.body();
//elsewhere in the code, I can read the value from this method
setUserProfile(userProfile.getStatus());
}
}
I have a DialogFragment. When i click the OK button, i execute a retrofit call to my api like this
#Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
int code = response.code();
if (code == 200) {
LoginResponse lr = response.body();
if (lr.getError()) {
Utils.showToast(getActivity(), "Error! Los datos son incorrectos.");
} else {
startActivity(new Intent(getActivity(), ConfigGeneralActivity.class));
}
} else {
Toast.makeText(getActivity(), "Error: " + String.valueOf(code), Toast.LENGTH_LONG).show();
}
}
at this part of the code, getActivity() is null.
Another option is to pass the context from the oncreatedialog to this callback, but when i do this, i got the error that says activity is not attached to fragment.
I know i can implement Otto but it's too complex for what i want to do. I just want to start a new activity from a retrofit callback inside a DialogFragment.
Any ideas? Thanks!
You need to runOnUiThread to get the Context inside OnResponse Callback
Like:
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
//your code
}
});
}
}
hi i am using retrofit my callback is as follow
#Override
public void onResponse(final Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
passing this to my view
} else {
// as this failed other then 200 retroCallback.onFailure(call, new Throwable(""));
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
retroCallback.onFailure(call, t);
}
so in this how i can pass my ErrorBean instead of Throwable anyway we can pass custom model in onFailure ? as my server giving me response in some formate i want to pass that format .. i am using retrofit 2.1.0
You can subclass Throwable and pass additional object using composition.
public class ErrorBean extends Throwable {
public ErrorPayload payload = null;
public ErrorBean(ErrorPayload payload) {
this.payload = payload;
}
}
Then, in onError:
#Override
public void onFailure(Call<T> call, Throwable t) {
retroCallback.onFailure(call, t);
if (t instanceof ErrorBean) {
// do your stuff here
((ErrorBean)t).payload.text;
}
}
AFAIK,, Retrofit's onFailure is used for handling errors like no internet connection.
To handle the error response from your Server, Error response, I mean response from Server with 4xx status code but with some JSON response for client to handle it.
Say, you are getting this error structure from Server:
{
statusCode: 409,
message: "Email address already registered"
}
This error will be captured in onResponse(...). To handle this, create your
public class ErrorBean {
private int statusCode;
private String message;
public ErrorBean() {
}
public int status() {
return statusCode;
}
public String message() {
return message;
}
}
Create a simple ErrorHandler util:
public class ErrorUtils {
public static ErrorBean parseError(Response<?> response) {
Converter<ResponseBody, ErrorBean> converter =
ServiceGenerator.retrofit()
.responseBodyConverter(ErrorBean.class, new Annotation[0]);
ErrorBean error;
try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
return new ErrorBean();
}
return error;
}
}
And finally,
...
call.enqueue(new Callback<SuccessResponse>() {
#Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {
if (response.isSuccessful()) {
// use response data and do some fancy stuff :)
} else {
// parse the response body …
ErrorBean error = ErrorUtils.parseError(response);
// … and use it to show error information
// … or just log the issue like we’re doing :)
Log.d("error message", error.message());
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
// there is more than just a failing request (like: no internet connection)
}
});
Hope you got the point..!!!