I have an issue with android studio and the retrofit library and the way in which it processes the data.
I have a simple flow of operation I would like:
Request single item from database on server(fetch request)
Wait for callback to confirm it has been received by the app
Add another request(Loop)
Stop adding requests when all data is sent
The issue I have is my onResponse callback for my fetch result does not run until all my requests are sent. Then all the responses are errors. ( If I call a single item(1 from the database)) the call back runs fine.
How do I force it to send one request and wait until that response before sending another?
Loop code
private void Pull_data_loop(int total_entries){
//int current_data_point = 0;
boolean datum_processing = false;
for (int i = 1; i <= total_entries; i++) {
Add_single_datam(i);//Call until all entries are fetched from the server
}
}
Fetch code- Not running callback need to wait for this callback before sending next request
private void Add_single_datam(int id)
{
HashMap<String, String> map = new HashMap<>();
map.put("Id_request", Integer.toString(id));//The ID value
Call<Fetch_result> call = retrofitInterface.executeGet_data(map);//Run the post
call.enqueue(new Callback<Fetch_result>() {
#Override
public void onResponse(Call<Fetch_result> call, Response<Fetch_result> response) {
if (response.code() == 200)//Successful login
{
D1= response.body().getD1_String();
D2= response.body().getD2_String();
boolean result = BLE_DB.addData_Downloaded(D1, D2);//Add data
if (result == true) {
Log.d(TAG, "data_changes: Added data correctly");
}
if (result == false) {
Log.d(TAG, "data_changes: did not add data correctly");
}//false
} else if (response.code() == 404) {
Utils.toast(getApplicationContext(), "Get data fail");//Pass information to the display
}
}
#Override
public void onFailure(Call<Fetch_result> call, Throwable t) {
Utils.toast(getApplicationContext(), "Get data error");
}
});
}
Note:
I am using a node js server for my requests. I send the Id and it returns that Id in the database.
You could send a callBack instance to your Add_single_datam then in your retrofit response, send to that callback success.
Then in that callBack you would have iteravel i and you could see if you reached the end of total_entries added +1 in i and make request again, or just stop.
use some threading solutions like RxJava or Coroutines or AsyncTask. The reason it's not following the rule is because of there are two threads on which work is getting distributed so in order to get it make it work in sync, we have to use some threading solutions mentioned above and execute this for loop on the background thread and make it like a synchronous call and get all the results and finally switch back to main thread with the results.
If you are familiar with the AsynTask.
private class FetchDataTask extends AsyncTask<Int, Integer, List<Fetch_result>> {
protected Long doInBackground(Int... total_entries) {
List<Fetch_result> allResults = new ArrayList<Fetch_result>();
for (int i = 1; i <= total_entries[0]; i++) {
HashMap<String, String> map = new HashMap<>();
map.put("Id_request", Integer.toString(total_entries[0]));
Fetch_result response = retrofitInterface.executeGet_data(map).execute().body();
allResults.add(response);
}
return allResults;
}
protected void onProgressUpdate(Integer... progress) {
//show progress
}
protected void onPostExecute(List<Fetch_result> result) {
//do something on main thread, in loop on result
D1= result[0].getD1_String();
D2= result[0].getD2_String();
boolean result = BLE_DB.addData_Downloaded(D1, D2);//Add data
if (result == true) {
Log.d(TAG, "data_changes: Added data correctly");
}
if (result == false) {
Log.d(TAG, "data_changes: did not add data correctly");
}//false
}
}
now call like this.
new FetchDataTask().execute(total_entries);
Related
So I'm completely lost on this one, it might be obvious solution or I'm just trying somethin that's not possible but here it is.
I have two classes one is being used as e listener class and second one is the one that handles queue(i will only include relevant code).
Handler class:
public void check() {
for (Queueable queueable : queue) {
if (!doesReceiverHavePlayers(queueable)) continue;
}
}
private boolean doesReceiverHavePlayers(Queueable queueable) {
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("PlayerCount");
out.writeUTF(queueable.getReceiver());
Player player = Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
player.sendPluginMessage(plugin, "BungeeCord", out.toByteArray());
return /*response*/ > 0;
}
Listener class:
#Override
public void onPluginMessageReceived(String channel, #NotNull Player player, byte[] message) {
if (!channel.equals("BungeeCord")) return;
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subChannel = in.readUTF();
switch (subChannel) {
case "PlayerCount":
int response = in.readInt();
break;
}
}
The check method is called every 5 seconds and doesReceiverHavePlayers requests player count from a certain server to see if there are any players on it, but the 'response' arrives in the listener class onPluginMessageReceived method. But as you can see I'm trying to use response in the doesReceiverHavePlayers method and return boolean value. Is there any way I can achieve this and how should I do it?
In onPluginMessageReceived store the result in a ConcurrentHashMap and then lookup the value in doesReceiverHavePlayers instead of making a blocking call.
Something like this:
ConcurrentHashMap<String, Integer> playerCounts = new ConcurrentHashMap<>();
void onPluginMessageReceived() {
playerCounts.put(subChannel, response);
}
boolean doesReceiverHavePlayers() {
return playerCounts.get(queueable.getReceiver()) > 0;
}
swipeRefreshLayout.setOnRefreshListener(() -> {
swipeRefreshLayout.setRefreshing(true);
retrieveData(mCardAdapter, db);
});
For some reason the following method is blocking my main UI thread, but it should be running in the background. For example, the refresh indicator hangs when I run retrieveData(). If I initialize a progress dialog before running, it also hangs and I can't scroll through my RecyclerView. Am I fundamentally misunderstanding something here?
public void retrieveData(final CardAdapter mCardAdapter, SQLiteHelper db) {
CausticRetrofitService service = ServiceFactory.createRetrofitService(CausticRetrofitService.class, CausticRetrofitService.SERVICE_ENDPOINT);
service.getMedia()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber < MediaResponse > () {
#Override
public final void onCompleted() {
Log.e("CausticRetrofitService", "Caustic Request Completed!");
/* Cancel all progress indicators after data retrieval complete */
setRefreshingFalse();
// TODO: Add media to local data store and then display them one-by-one in real-time
mCardAdapter.addData(db.getAllMediaImages()); // Add all media images to card views
Log.d(getClass().toString(), "Added to local database: " + db.getAllMediaImages());
mCardAdapter.notifyDataSetChanged();
}
#Override
public final void onError(Throwable e) {
/* Cancel all progress indicators on data retrieval error */
setRefreshingFalse();
Toast.makeText(getApplicationContext(), "Cannot retrieve data. Please try again later.", Toast.LENGTH_SHORT).show();
Log.e("CausticRetrofitService", e.getMessage());
}
#Override
public final void onNext(MediaResponse mediaResponse) {
if (mediaResponse != null) {
Log.e("CausticRetrofitService", "Returned objects: " + mediaResponse.getResults());
for (String mediaId: mediaResponse.getResults()) {
Log.e("CausticRetrofitService", mediaId);
}
List < String > mediaIds = mediaResponse.getResults();
Log.d(getClass().toString(), "All Media IDs: " + mediaIds);
if (mediaIds.isEmpty()) {
Toast.makeText(getApplicationContext(), "Cannot retrieve data. Please try again later.", Toast.LENGTH_SHORT).show();
}
mCardAdapter.clear();
mCardAdapter.notifyDataSetChanged();
/* Store objects from remote web service to local database */
for (String mediaId: mediaIds) {
// TODO: Why are these null?
Log.d(getClass().toString(), "Media Id: " + mediaId);
MediaImage newMediaImage = new MediaImage();
newMediaImage.setTitle(mediaId);
db.addMediaImage(newMediaImage); // Add media image to local database
}
} else {
Log.e("CausticRetrofitService", "Object returned is null.");
}
}
});
}
I'm thinking that adding the remote data to the local data store in the onNext() method might be the thing that's blocking, although I'm not certain.
Your network call is done in a new thread as you specified, but the Subscriber methods onNext() and onComplete() runs on the observing Scheduler, which is the main thread.
You seem to be doing some database operations on those, try to offload the caching also to the background thread using a doOnNext() operator.
What doOnNext() will do, is that it is called for each emission in your stream.
It can go something like that
service.getMedia()
.doOnNext(data -> cacheData(data))
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
Where cacheData() is a method that does all your DB calls. And the only things left in your onNext() and onComplete() would be updating the UI only.
I have a problem with the waiting requests functionality in the volley library. The debugging led me to the AbstractQueue class in java.util where an element is being added (according to some values in the method that indicate a successful addition to the queue) and simultaneously - not being added(according to the 0 elements in the queue - that don't change their value). The adding method is synchronized. Bellow you can find a detailed description of the situation and my research so far. I will be really thankful if you have a look at them and share if you have any idea what is happening.
I try to automatically retry requests upon any kind of error ( for example - when there is no connection, or the server name is not correct ).
The error handler of a request adds the request back to the static singleton RequestQueue of my app.
RetriableRequestWraper.java
m_request = new StringRequest(
method,
url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
handleResponse(response);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError volleyError) {
handleError(volleyError);
}
});
public void handleError(VolleyError volleyError)
{
Log.d("Request: ", m_request.toString());
Log.d("handleError: ", volleyError.toString());
if(retriesCount<3)
{
executeRequest();
++retriesCount;
}
else
{
retriesCount = 0;
}
}
public void executeRequest()
{
RequestsManager.getInstance().executeRequest(m_request);
}
public void executeRequest(Request request)
{
Log.d("executeRequest ","m_requestQueue.add(request)");
m_requestQueue.add(request);
}
RequestManager.java
public void executeRequest(Request request)
{
Log.d("executeRequest ","m_requestQueue.add(request)");
m_requestQueue.add(request);
}
This approach doesn't work and when debugging inside the volley library I come to the point where the request could not be added to the mCacheQueue of the RequestQueue class, because the cacheKey of the reuqest is present in the mWaitingRequests Map. So the request is added in the queue in mWaitingRequests map, corresponding to its key. When the previous request is finished - the new one is not added to the queue although these lines are being executed in the RequestQueue class:
synchronized(this.mWaitingRequests) {
String cacheKey1 = request.getCacheKey();
Queue waitingRequests1 = (Queue)this.mWaitingRequests.remove(cacheKey1);
if(waitingRequests1 != null) {
if(VolleyLog.DEBUG) {
VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", new Object[]{Integer.valueOf(waitingRequests1.size()), cacheKey1});
}
this.mCacheQueue.addAll(waitingRequests1);
}
}
When debugging further this line
this.mCacheQueue.addAll(waitingRequests1);
In the AbstractQueue.java (class in java.util ) the element is being added to the queue, the "modified" value is true, but throughout the hole time the "this" parameter continues to contain 0 elements.
public boolean addAll(Collection<? extends E> c) {
if (c == null)
throw new NullPointerException("c == null");
if (c == this)
throw new IllegalArgumentException("c == this");
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
Inside the offer(E e) method of PriorityBlockingQueue.java the execution of the program stops at line 453.
l452 siftUpUsingComparator(n, e, array, cmp);
l453 size = n+1;
Obviously the returned value is true, but the element is not added. My debugger could not get into the method that adds the element - siftUpUsingComparator(n, e, array, cmp);
I am going to add a timer before retrying my request, and will construct a new one. So I am not really interested in a workaround, I want to understand what and how is happening in this situation. Do you have any idea as to what could be the reason behind this?
The issue is that you try to add the same Request instance once again to the queue it has been added to. This messes up with the queue and the Request itself as it has states. For example if you simply enable markers you'll have a crash. The solution is to either just use the default retry policy or clone the requests.
I have a single incident where a complete duplicate of a entry was made into the database (the same user comment appeared twice). They had different object IDs but were otherwise the exact same. It was slower than usual to finish the posting and only happened once out of dozens of comments, so I want to say it was a Parse issue during the saveInBackground call. Even so, I expect a service like Parse to be a little more robust. As my first time working with Android though, I also can't be sure nothing is wrong on my end. Any help? Also just any criticisms? This is the method called when the user hits a comment submission button:
private void submitComment() {
String text = commentText.getText().toString().trim();
Intent intent = getIntent();
String ID = intent.getStringExtra("imageID");
String parentID = intent.getStringExtra("parent");
// Set up a progress dialog
final ProgressDialog loadingDialog = new ProgressDialog(CommentSubmitActivity.this);
loadingDialog.setMessage(getString(R.string.publishing_comment));
loadingDialog.show();
Comment comment = new Comment();
comment.setText(text);
comment.setUser((ParseUser.getCurrentUser()));
if (ID.equals("#child")) {
comment.setParent(parentID);
comment.setImage("#child");
ParseQuery<ParseObject> query = ParseQuery.getQuery("Comment");
query.getInBackground(parentID, new GetCallback<ParseObject>() {
public void done(ParseObject parentComment, ParseException e) {
if (e == null) {
int numChild = parentComment.getInt("numChild");
parentComment.put("numChild", ++numChild);
parentComment.saveInBackground();
} else {
Log.d("numChild: ", "error");
}
}
});
} else {
comment.setImage(ID);
comment.put("numChild", 0);
ParseQuery<ParseObject> query = ParseQuery.getQuery("ImageUpload");
query.getInBackground(ID, new GetCallback<ParseObject>() {
public void done(ParseObject image, ParseException e) {
if (e == null) {
int numComments = image.getInt("numComments");
image.put("numComments", ++numComments);
image.saveInBackground();
} else {
Log.d("numComments: ", "error");
}
}
});
}
comment.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
loadingDialog.dismiss();
finish();
}
}
});
}
I encountered similar problem like yours.
I created an app where user can create account and add photo to it and list of objects (friends in my case).
Once when I was testing it user was created twice.
I went through my code and my my suspicions are connected with async calls.
I see that you use asynchronous parse api in you application so no fragment of code is waiting for response and blocking the rest of operations.
You cannot control when parse server will response.
What I did I just put all synchronous requests in my custom async code (AsyncTask in Android).
Hope that my answer somehow meeets your expectations.
Not solely a Firebase question, but I am using Firebase to make posts to a backend from Android and run it 10 times, once every second.
Firebase ref = new Firebase(URL);
ref.child("Time").setValue(Currentime());
However this is an Asynchronous Call and when I put a while loop:
while (time_now < time_start + 10 seconds) {
Firebase ref = new Firebase(URL);
ref.child("Time").setValue(Currentime());
}
It seems to run the while loop first and then ~10 Firebase calls at the end. Is there a way to add a timeout so that it forces the Asynchronous (Firebase) calls to run for a second before calling the next Async call?
If you look at the Java example on the Firebase web site, you'll see that it has a doTransactions method and an onComplete method:
Firebase countRef = new Firebase(URL);
Transaction.Handler handler = new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData currentData) {
// set the new time
...
}
#Override
public void onComplete(FirebaseError error, boolean committed, DataSnapshot currentData) {
if (error != null) {
...
} else {
if (!committed) {
...
} else {
// transaction committed, do next iteration
...
}
...
}
}
});
countRef.runTransaction(handler);
So you'd set the new time in the doTransaction method:
// set the new time
currentData.setValue(time_now);
return Transaction.success(currentData);
And then start a next iteration in the onComplete method.
// transaction committed, do next iteration
if (time_now < time_start + 10 seconds) {
countRef.runTransaction(handler);
}