I can't get the data outside onResponse in Restrofit
this is my code
List<Categorie> categorylist=new ArrayList<>();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
final CategoryApi api = retrofit.create(CategoryApi.class);
Call<List<Categorie>> categoryCall = api.categories();
categoryCall.enqueue(new Callback<List<Categorie>>() {
#Override
public void onResponse(Call<List<Categorie>> call, Response<List<Categorie>> response) {
categorylist = (List<Categorie>)response.body();
Log.i("success","Inside "+categorylist.toString());
// here i get the data
}
#Override
public void onFailure(Call<List<Categorie>> call, Throwable t) {
System.out.println("Erreur");
}
});
Log.i("success","Outside "+categorylist.toString());
// here i get null
i've tried making categorylist volatile and static and it didn't work
It appears you're trying to access categorylist immediately after calling categoryCall.enqueue. enqueue is an asynchronous operation which will not complete before the next line ( containing categorylist.toString()) is executed.
Try calling another method where you've left the comment "here i get the data"
public void onResponse(/*...*/ response) {
categorylist = (List<Categorie>)response.body();
Log.i("success","Inside "+categorylist.toString());
// here i get the data
businessLogic(categorieslist);
}
private void businessLogc(List<Categorie> categories) {
myView.showCategories(categories); // or whatever you're doing with data
}
You know, http request is a process that require time, so it should be done asynchronously. Fortunately retrofit handle everything for us.
What you need to do, is when we get response from OnResponse, you need to update your current view, for example if you're using recyclerview, there is recyclerview.notifyDataSetChanged() method.
If you want to get the result from beginning. I'm suggest you call the enqueue method in other activity or somewhere else, and then when you get the result, save it into sqlite database.
And then when you open CategoryActivity load it from database.
You can add refresh button to call the enqueue again to update the database.
Related
I am using AWS AppSync for Android, and I want to display a list of Persons into a RecyclerView. I am fetching paginated data with the AppSyncResponseFetchers.CACHE_AND_NETWORK flag. If data is present in the cache, onResponse is called twice, causing data duplication in the RecyclerView.
What is the right way to combine data returned by these 2 calls?
private void loadNextPersons() {
AWSAppSyncClient client = AppSyncClientFactory.getAppSyncClient(AppSyncAuthMode.KEY);
client.query(AllPersonsByCityQuery.builder()
.city(cityName)
.limit(10)
.nextToken(nextToken)
.build())
.responseFetcher(AppSyncResponseFetchers.CACHE_AND_NETWORK).enqueue(loadCallback);
}
private GraphQLCall.Callback<AllPersonsByCityQuery.Data> loadCallback= new GraphQLCall.Callback<AllPersonsByCityQuery.Data>() {
#Override
public void onResponse(#Nonnull Response<AllPersonsByCityQuery.Data> response) {
// Called twice if we have data in the cache and network connection
runOnUiThread(() -> {
adapter.addData(response.data().allPersonsByCity().persons());
}
}
}
I have something like this
Retrofit retrofit =new retrofit2.Retrofit.Builder()
.baseUrl("URL")
.addConverterFactory(GsonConverterFactory.create())
.build();
requestService = retrofit.create(RequestInterface.class);
call = requestService.getData(page);
call.enqueue(new Callback<List<Cats>>() {
#Override
public void onResponse(Call<List<Cats>> call, Response<List<Cats>> response) {
....
}
#Override
public void onFailure(Call<List<Cats>> call, Throwable t) {
...
}
});
However when i want to get the second page, when i make a request for the second page within the same class, retrofit callback methods is not getting called.
call = requestService.getData(page); // page incremnted
call and requestService is globally defined
in Retrofit, each "call" instance is linked to one API call (single network request) and cannot be reused. You can reuse your RetrofitSerive instance, but for every new API call you will have to create a new Call object and enqueue it separately
You can use a generic response and use an url each time.
#GET
Call<Generic<T>> getUsers(#Url String url);
I use Retrofit2 to get information for an specified location selected on map and display it on my application UI by calling method getLabelFromServer as below.
Since user may change selected location while I'm waiting to receive information from server, I should cancel previous call to received information and wait to receive information for new location and update UI upon response for new call.
Please note that previous call should not update UI since it will display information for previous location which is not selected by user now.
How can I change below code to achieve this (I should note I call this method on UI thread every time new location is selected on map).
private void getLabelFromServer(Context ctx, GeoPoint geoPoint) {
MapServiceGenerator serviceGenerator = MapServiceGenerator.getInstance(ctx, null);
Call<JsonObject> call = serviceGenerator.getGeoCodeApi().getAddress(geoPoint.getLatitude() + "," + geoPoint.getLongitude());
call.enqueue(new Callback<JsonObject>() {
#Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
JsonObject result = response.body();
String positionLabel = MapUtils.generateLabelFromJSON(result);
tvLabel.setText(positionLabel);
}
#Override
public void onFailure(Call<JsonObject> call, Throwable t) {
if (t != null) {
t.printStackTrace();
}
}
}
);
}
check call.isExecuted(); method before giving the next request.
if it returns true your request has been completed.
else, it has not been completed so call call.cancel() method to cancel the request and re initiate your request again
I want to post each RealmResults data to a REST endpoint, and want to delete its data if sending is success.
Running following code, success sending but fail to delete.
I tried to use target.deleteFromRealm() in Response() but IllegalStateException occurred.
java.lang.IllegalStateException: Realm access from incorrect thread.
Realm objects can only be accessed on the thread they were created.
How can I delete target?
(using Realm Java 3.1.2 and Retrofit 2.2.0)
RealmResults<Model> results = realm.where(Model.class).findAll();
for ( final Model target: results ){
Call<Void> task = restInterface.post(gson.toJson(target));
task.enqueue( new CallBack<Void>(){
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
// ?? how to delete target from realm ??
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
// do nothing
}
});
}
It is the same as removing items for normal ArrayLists. That is not allowed either and will throw ConcurrentModificationException.
Also I would suggest not to send items one by one to server, but collect them into array and pass all the data in one request.
To collect all data into ArrayList you can use
RealmResults<Model> results = realm.where(Model.class).findAll();
ArrayList<Model> list = new ArrayList(results);
And then try to send data like this:
Call<Void> task = restInterface.post(gson.toJson(list));
task.enqueue( new CallBack<Void>(){
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
// As a result all data will be uploaded in the same one batch and you can safely clear the db
results.deleteAllFromRealm();
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
// do nothing
}
});
I realize similar questions have been asked but I am new to android and find the answers a bit confusing since they are in a slightly different context.
I have looked at CountDownLatch aswell as using Threads and am not sure which method to use. Any help would be much appreciated. I have also tried using apply() instead of commit() for SharedPreferences.
I am making 2 retrofit2 calls from LoginActivity. I need the token from the first call to use in the second call. I am saving the token to a string in sharedpreferences in the onResponse method of the first retrofit call.
In my second call the value of serverToken is coming back as the token set in previous run of the app
1st call(getToken) onResponse
call.enqueue(new retrofit2.Callback<TokenResponse>() {
#Override
public void onResponse(Call<TokenResponse> call, retrofit2.Response<TokenResponse> response) {
if (response.isSuccessful()) {
TokenResponse tokenResponse = response.body();
LoginActivity.editor.putString("serverToken", tokenResponse.getAccessToken());
LoginActivity.editor.commit();
} else {
Log.i("Server Token", "failed");
}
}
}
LoginActivity
public class LoginActivity extends AppCompatActivity {
public static SharedPreferences preferences;
public static SharedPreferences.Editor editor;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
authenticationController = new AuthenticationController();
preferences = PreferenceManager.getDefaultSharedPreferences(this);
editor = preferences.edit();
}
public void onLoginClicked(View view) {
getToken(); //FIRST RETROFIT CALL
connectToPush(); //SECOND CALL WHERE I NEED TOKEN FROM FIRST CALL
}
public void getToken() {
authenticationController.login(grantType, username, password);
}
public void connectToPush() {
authenticationController.connectToPush();
}
My Second Retrofit call
public void connectToPush(){
Log.i("sharedpreferencesToken", LoginActivity.preferences.getString("serverToken", "null serverToken"));
}
The onResponse() method is a callback interface, which ,simply putting it, means that is where you get the info back from your request\event (you called, it came back, hence callback) and implement what you want to do with it (it's an interface, you implement it, hence the #Override annotation).
this means:
You don't need CountDownLatch, at least not in this case, and Retrofit2 takes care of the threading for you.
no real need for SharedPreferences, you can just call the method you want straight from that callback, since the info is in that instance (unless you want to save it for reasons other than the next request, see next...).
if you want to locally store the value because you need it later (or to use as an auto-login thing later, you can use SharedPreferences, but you don't need to get your value from there in that instance - since it exists in the callback instance (your saving the value right there, it's redundant to load it from the Prefs again while the response holds the exact value that can be simply passed.
so:
call.enqueue(new retrofit2.Callback<TokenResponse>() {
#Override
public void onResponse(Call<TokenResponse> call, retrofit2.Response<TokenResponse> response) {
if (response.isSuccessful()) {
TokenResponse tokenResponse = response.body();
//right here you can call the other request and just give it the token
connectToPush(tokenResponse);
//if you really need to, save your value
LoginActivity.editor.putString("serverToken", tokenResponse.getAccessToken());
LoginActivity.editor.commit();
} else {
Log.i("Server Token", "failed");
}
}
}
And in your second call:
public void connectToPush(TokenResponse tokenFromFirstRequest){
//fire the next request using your token as a param!
}
Well i ended up finding the solution. Found the answer on the retrofit github
"Use the callback from method 1 to trigger method 2"
I moved connectToPush() to the onResponse of the first call.
call.enqueue(new retrofit2.Callback<TokenResponse>() {
#Override
public void onResponse(Call<TokenResponse> call, retrofit2.Response<TokenResponse> response) {
if (response.isSuccessful()) {
TokenResponse tokenResponse = response.body();
LoginActivity.editor.putString("serverToken", tokenResponse.getAccessToken());
LoginActivity.editor.commit();
connectToPush(); //MOVED TO HERE
} else {
Log.i("Server Token", "failed");
}
}
}
Feel free to delete my question. I will leave it as it may help someone else
You can call connectToPush(); from the onResponse part of your Retrofit call.