I know this question might seem duplicated, I've read like ten other threads about this same thing, but I cannot find the problem
I have this method in my activity:
public void saveResponse(final Response studentResponse, final Content content)
fb.getReference("...").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
new Thread(new Runnable() {
#Override
public void run() {
Map<String, Object> responseMap = new HashMap<>();
responseMap.put("end_date", studentResponse.end_date);
responseMap.put("start_date", studentResponse.start_date);
responseMap.put("time", studentResponse.time);
responseMap.put("points", studentResponse.points);
responseMap.put("max_points", studentResponse.max_points);
responseMap.put("selected_options", studentResponse.selected_options);
if (!TextUtils.isEmpty(studentResponse.free_text))
responseMap.put("free_text", studentResponse.free_text);
DataSnapshot contentRef = dataSnapshot.child("/sections/" + currentSection + "/sections/" + currentSubsection + "/contents/" + content.id);
final int oldPoints = contentRef.hasChild("points") ? contentRef.child("points").getValue(int.class) : 0;
contentRef.getRef().setValue(responseMap);
contentRef.getRef().setPriority(ServerValue.TIMESTAMP);
DataSnapshot subSectionRef = dataSnapshot.child("/sections/" + currentSection + "/sections/" + currentSubsection);
long subSectionPoints = (subSectionRef.hasChild("points") ? subSectionRef.child("points").getValue(long.class) : 0) + studentResponse.points - oldPoints;
subSectionRef.child("points").getRef().setValue(subSectionPoints);
int indexOf = currentContents.indexOf(content) + 1;
if(indexOf > 0 && indexOf < currentContents.size()) {
CourseContent content = currentContents.get(indexOf);
subSectionRef.child("currentPosition").getRef().setValue(content.order);
}
DataSnapshot sectionRef = dataSnapshot.child("/sections/" + currentSection);
long sectionPoints = (sectionRef.hasChild("points") ? sectionRef.child("points").getValue(long.class) : 0) + studentResponse.points - oldPoints;
sectionRef.child("points").getRef().setValue(sectionPoints);
long coursePoints = (dataSnapshot.hasChild("points") ? dataSnapshot.child("points").getValue(long.class) : 0) + studentResponse.points - oldPoints;
dataSnapshot.child("points").getRef().setValue(coursePoints);
dataSnapshot.getRef().setPriority(MAX_SAFE_INTEGER - coursePoints);
int completed = 0;
for (DataSnapshot sect : dataSnapshot.child("sections").getChildren()) {
for (DataSnapshot subSect : sect.child("sections").getChildren()) {
int currPos = subSect.hasChild("currentPosition") ? subSect.child("currentPosition").getValue(int.class) : 0;
completed += currPos;
}
}
double progress = totalContents > 0 ? (double) completed / (double) totalContents : 0;
dataSnapshot.child("progress").getRef().setValue(progress);
}
}.start();
}
...
});
}
in a click handler I call this method, and then I change the fragment (with custom animations).
The thing is, the fragment transition is not smooth, it freezes a little, if I comment everything inside the runnable then it runs smooth. I've tried also with an AsyncTask and the same happens.
Inside the runnable, I'm just querying the dataSnapshot and its children, and setting some values (dataSnapshot.child("item").getRef().setValue(x))
Another strange thing is that if I put a breakpoint inside run(), it also works smooth.
every time you call onDataChange method will create a new thread,and the strange thing: " if I put a breakpoint inside run(), it also works smooth."
may be you should check out whether there are too many thread created.
I think the problem is with the logic of your method.
The listener onDataChange() is active and it will respond to any change of the data. I mean if you change data inside the onDataChange() method, it will be called each time you set values with (dataSnapshot.child("item").getRef().setValue(x))), so, it is similar to do a "recursive" call without exit.
In order to fix this problem, you should obtain the key of what you want to change in the on click event and just use
mDatabase = FirebaseDatabase.getInstance().getReference();
mDatabase.child("key").child("item").setValue(x);
Check https://firebase.google.com/docs/database/android/read-and-write for more info
A spawned thread inherits the priority of the thread that created it. Try lowering the priority of your worker thread to prevent it from competing with the UI thread:
#Override
public void onDataChange(final DataSnapshot dataSnapshot) {
new Thread(new Runnable() {
#Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
...
}).start();
Related
I have a few data fetching methods that call each other in order to fetch data according to the previous data fetched.
My issue is that I am holding a long variable called 'allContestsCounterIndex' which is incremented only at 2 certain places and for some reason after fetching the data for the first time, when clicking again to try to fetch for the second time the value of it starts with 1, meaning I can't continue because it makes the logic stuck.
here are the functions I am using -
private void updateContestCallCounter(long childrenCount) {
allContestsCounterIndex++;
if (allContestsCounterIndex == (childrenCount - 1)) { // at this point at the second time this variable starts with a value of 1 instead of 0
fetchWinnersFromOurValidContests();
}
}
private void fetchAllEndedStatusContests(DataSnapshot dataSnapshot) {
if (!dataSnapshot.exists()) {
// For some reason, we've reached this point with no dataSnapShot
Timber.d("dataSnapShot doesnt exists");
return;
}
long timeInSeconds = (System.currentTimeMillis() / 1000);
mEndedContests = new ArrayList<>();
long childrenCount = dataSnapshot.getChildrenCount();
for (DataSnapshot status : dataSnapshot.getChildren()) {
//fetching the status key for deeper querys inside the db
final String key = status.getKey();
checkIfContestEnded(timeInSeconds, key, new OnContestStateReceived() {
#Override
public void contestHasEnded() {
mEndedContests.add(key);
updateContestCallCounter(childrenCount);
}
#Override
public void contestStillScheduled() {
updateContestCallCounter(childrenCount);
}
});
}
}
You should set all of your counters to 0 once you have finished re-fetching the data.
I'm currently trying to delete specific realm object in my model using for loop,
but every time that i execute the deleteFromRealm(i) it stops the loop and I can no longer delete the other object.
I haven't tried any other options though.
final Realm realms = Realm.getDefaultInstance();
realms.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<CashCountScoreModel> cashCountScoreModels =
CashCountScoreModel.getAll(realm);
for (int i = 0; i < cashCountScoreModels.size(); i++) {
if (cashCountScoreModels.get(i) != null && cashCountScoreModels.get(i).isCashOnHand) {
Log.d("CheckName : pos -- ", i +"~~" + cashCountScoreModels.get(i).isCashOnHand);
Log.d("CheckName : pos --", i + "~~" + cashCountScoreModels.get(i).employeeName);
cashCountScoreModels.deleteFromRealm(i);
// continue;
}
}
}
});
Whenever i try to run the app, and execute this specific code cashCountScoreModels.deleteFromRealm(i);, it stops the loop.
You should not call deleteFromRealm(i) inside a loop because it always causes crash. Use this code instead:
realms.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<CashCountScoreModel> results = CashCountScoreModel.getAll(realm);
results.where().equalTo("isCashOnHand", true).findAll().deleteAllFromRealm();
}
});
Not sure what version of Realm you are using. But since 3.0.0, Realm collections are live and hence updated immediately. So, cashCountScoreModels.size() will return a count less upon every deletion. In your case, I suspect you have just 2 entries in the collection. You might want to use OrderedRealmCollectionSnapshot instead. Try out following code.
final Realm realms = Realm.getDefaultInstance();
realms.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<CashCountScoreModel> cashCountScoreModels = CashCountScoreModel.getAll(realm);
OrderedRealmCollectionSnapshot snapshot = cashCountScoreModels.createSnapshot();
for (CashCountScoreModel cashCountScoreModel : snapshot) {
if (cashCountScoreModel != null && cashCountScoreModel.isCashOnHand) {
Log.d("CheckName : pos -- ", i +"~~" + cashCountScoreModel.isCashOnHand);
Log.d("CheckName : pos --", i + "~~" + cashCountScoreModel.employeeName);
cashCountScoreModel.deleteFromRealm();
}
}
}
});
Look for Iterations & snapshots on https://realm.io/docs/java/latest/ or the documentation available at https://realm.io/docs/java/3.0.0/api/io/realm/OrderedRealmCollection.html#loops to know more realtime update in collections and OrderedRealmCollectionSnapshot
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<CashCountScoreModel> cashCountScoreModels=realm.where(CashCountScoreModel.class).equalTo(CashCountScoreModel.isCashOnHand,true).findAll();
cashCountScoreModels.deleteAllFromRealm();
}
});
#Md. Nowshad Hasan is Correct. Just run in Realm Thread.
It happens because I think you want to delete multiple realm objects from a single execution block.
Try below code in execute block.
RealmResults<CashCountScoreModel> cashCountScoreModels=realm.where(CashCountScoreModel.class).equalTo(CashCountScoreModel.isCashOnHand,true).findAll();
cashCountScoreModels.deleteAllFromRealm();
I know this kind of a weird question but I am trying to use my Retrofit call inside a for loop. What I am doing is sending my String[] elements one by one in the call with func like insertdata(seperated2[0], seperated2[1], email, tag);
But the loop are behaving weirdly when they are skipping the anonymous call for call.enqueue(......onResponse(...) onfailure(.....))
Instead of calling it with the loop control first finishes the loop and then comes to call.enqueue and always last element in loop. This is how loop looks like ....
separated = currentString.split("\n");
for (int i=1; i<separated.length; i++) {
seperated2 = separated[i].split(":");
for (String aSeperated2 : seperated2) {
Call<ServerResponse2> call = requestInterface.insertQrdata(seperated2[0], seperated2[1], email, tag);
call.enqueue(new Callback<ServerResponse2>() {
#Override
public void onResponse(Call<ServerResponse2> call, Response<ServerResponse2> response) {
ServerResponse2 serverResponse2 = response.body();
Toast.makeText(getActivity(), serverResponse2 != null ? serverResponse2.getMessage() : null, Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(Call<ServerResponse2> call, Throwable t) {
Toast.makeText(getActivity(), t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
}
here is an ex for seperated[] and seperated2[]
0 1
2 3
4 5
6 7
7 8
9 10
seperated[] is spliting them by line and seperated2 is spliting them by column.
The Problem
When I check my seperated2[0] and seperated2[1] value for each iteration in on Response method it should be
sep2[0]= 0 sep2[1] = 1
2 3
and so on... for each iteration
but in each iteration the value in onResponse is always the last, i.e.
sep2[0] = 9 sep2[1] = 10
untill the length (say 6) same value at each iteration.
I don't know if am doing anything wrong but values are showing correctly when i use them outside of onResponse().
I know using retrofit like is not good practice but I was curious to how it will react in this situation. Can anyone help or give any suggestions ?
THANKS IN ADVANCE !!
Here is an example of looping with retrofit library..if we use for loop the call.enqueue will not be called instantly for all iterations.so use this model.
private void UploadImagesTask(final String image, final int position) {
ApiInterface apiService = ApiClient.getClient().create(ApiInterface.class);
File file = new File(image);
RequestBody reqFile = RequestBody.create(MediaType.parse("image/*"), file);
MultipartBody.Part photoArray = MultipartBody.Part.createFormData("photo", file.getName(), reqFile);
HashMap<String, RequestBody> params = new HashMap<>();
params.put("token", Utils.createPartFromString(pref.getToken()));
params.put("app_id", Utils.createPartFromString(pref.getAppId()));
Call<ImageUploadResponse> imageUploadResponseCall = apiService.uploadImage(photoArray, params);
imageUploadResponseCall.enqueue(new Callback<ImageUploadResponse>() {
#Override
public void onResponse(#NonNull Call<ImageUploadResponse> call, #NonNull Response<ImageUploadResponse> response) {
if (response.isSuccessful()) {
urlList.add(Objects.requireNonNull(response.body()).getUrl());
completeData.remove(position);
completeData.add(position, getString(R.string.uploaded));
uploadAdapter.notifyDataSetChanged();
pref.setTempData(Constants.IMAGE_UPLOADED, gson.toJson(urlList));
if (position != uriData.size() - 1) {
int posi = position + 1;
CompressImages(posi);
} else {
uploadDone.setActivated(true);
uploadDone.setEnabled(true);
}
} else {
Utils.showSnackView(getString(R.string.error_occurred), snackView);
uploadDone.setActivated(true);
uploadDone.setEnabled(true);
}
}
#Override
public void onFailure(#NonNull Call<ImageUploadResponse> call, #NonNull Throwable t) {
Utils.showSnackView(getString(R.string.error_occurred), snackView);
uploadDone.setActivated(true);
uploadDone.setEnabled(true);
}
});
}
Instead of using for loop, you can use recursion too.
API will be called again and again but only after getting the response of the previous index.
In the onResponse() method you can call the method by incrementing the index value.
Because in for loop, the iteration will not wait for the completion of the execution of your API response, it will jump to the next iteration.
If you still want to use the loop then go for while loop
example for recursion:
int i=1;
separated = currentString.split("\n");
void callMethod(){
seperated2 = separated[i].split(":");
Call<ServerResponse2> call = requestInterface.insertQrdata(seperated2[0], seperated2[1], email, tag);
call.enqueue(new Callback<ServerResponse2>() {
#Override
public void onResponse(Call<ServerResponse2> call, Response<ServerResponse2> response) {
ServerResponse2 serverResponse2 = response.body();
Toast.makeText(getActivity(), serverResponse2 != null ? serverResponse2.getMessage() : null, Toast.LENGTH_SHORT).show();
i++;
callMethod();
}
#Override
public void onFailure(Call<ServerResponse2> call, Throwable t) {
Toast.makeText(getActivity(), t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
As per My suggestion do not use loops for this types of operation. Its bad idea If you can see performance wise.
You can do this things by following way:
Retrofit is just call server URL and passing your data to the server.
So whatever process doing server side that you can change to do once only rather than doing everytime.
You can pass whole loop data in json format to the server once and do all looping process in server side. It will be best for you.
Hope you clear my point of view.
Do let me know if you have any problem.
Hey if you are yet never found the answer I have a suggestion for you. Divide the whole response into parts after that apply loop on that data and send data using Asynctask to server and create one interface so you can do anything on a response back.
public interface GetResponse{
public void onSuccess(ResponseModel model);
publich void onFail();
}
In your async task
public class AsyncTaskForApiSync extends AsyncTask<Void, Void, Void> {
GetResponse mcallback;
public AsyncTaskForApiSync(GetResponse mcallBack){
this.mcallback=mcallBack;
}
#Override
protected Void doInBackground(Void... voids) {
//retrofit api call in on success
mcallback.onSuccess(response);
}
}
Sorry for my English. Also, let me know if you find any issue. Thanks.
I am using /.info/serverTimeOffset to get the approximate server time from firebase.
Long clockSkew = null;
...
void registerForClockSkew() {
DatabaseReference offsetRef =
FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset");
offsetRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
double offset = snapshot.getValue(Double.class);
clockSkew = (long) offset;
}
#Override
public void onCancelled(DatabaseError error) {
clockSkew = null;
}
});
}
However as mentioned here - https://github.com/firebase/firebase-ios-sdk/issues/363 - the offset does not refresh when there is a device-time-change. I have tried to close and reconnect firebase instance to refresh the skew value but it does not seem to be working. In my code I simply put the following when there is device time change -
FirebaseDatabase.getInstance().goOffline();
FirebaseDatabase.getInstance().goOnline();
but this does not trigger the value-event-listener callback. What am I missing here? Is it because goOffline and goOnline are consecutive statements, it is not having any effect?
The only way to handle the situation that I have found is to keep the skew w.r.t elapsedRealtime which keeps constant in case of device time change.
long clockSkewWRTELapsed
....
#Override
public void onDataChange(DataSnapshot snapshot) {
double offset = snapshot.getValue(Double.class);
clockSkewWRTELapsed = ((long) offset) + System.currentTimeMillis() - SystemClock.elapsedRealTime();
}
Whenever needed, one can get the server-time by adding elapsedRealTime to the clockSkewWRTElapsed time.
I need to solve putting data to a realm database like this:
I have an object called obtained_code;
I have a realmList of Obtained codes in an object called Offer;
I download obtained codes separately, and by their offer id assign them to the lists of each object. The problem is that I can't add them because when I check the size, it's always 0.
Here is the code:
ObtainedCodes codes = response.body();
for (ObtainedCode c : codes.getObtainedCodes()) {
Offer offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
if (offer != null) {
Log.d("Size", "Offer not null");
realm1.beginTransaction();
RealmList<ObtainedCode> list = offer.getObtained_codes();
if (!list) { // if the 'list' is managed, all items in it is also managed
RealmList<ObtainedCode> managedImageList = new RealmList<>();
for (ObtainedCode item : list) {
if (item) {
managedImageList.add(item);
} else {
managedImageList.add(realm1.copyToRealm(item));
}
}
list = managedImageList;
}
offer.setObtained_codes(obtainedCodes);
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
realm1.copyToRealmOrUpdate(offer);
realm1.commitTransaction();
}
offer = RealmController.with(SplashActivity.this).getOffer(c.getOffer_id());
Log.d("Size", String.valueOf(offer.getObtained_codes().size()));
}
1.) the Ravi Tamada tutorial on InfoHive is a terrible mess, please refer to my remake of that example instead.
If you managed to start using 0.82.1 because Ravi Tamada claimed that a 4 years old version is "stable", well I know that it's not. Use 1.2.0 instead (or the latest version which is 3.4.1)
And if you see a RealmController.with(), run, because it ignores thread-confinement. The moment you try to access it from a background thread, it'll crash.
On background threads, you'd need to do
#Override
public void run() {
try(Realm realm = Realm.getDefaultInstance()) {
repository.whatever(realm); // pass Realm instance to database methods
} // auto-close
// end of thread
}
2.) you are executing writes on the UI thread, that is bad, from UI thread you should use realm.executeTransactionAsync(), but in your case you should actually execute the Retrofit call on a background thread using Ęxecutors.newSingleThreadedPool() and call it with call.execute() instead of call.enqueue().
3.) You should write to Realm on the background thread, and on the UI thread you should use RealmChangeListener to listen to writes.
4.) your code doesn't work because you're setting an unmanaged list to a managed RealmObject.
You should modify the existing RealmList inside the RealmObject, and add only managed objects to it.
Executor executor = Executors.newSingleThreadExecutor(); // field variable
// ...
void someMethod() {
executor.execute(new Runnable() {
#Override
public void run() {
Response<ObtainedCodes> response = retrofitService.getObtainedCodes().execute(); // run on current thread
ObtainedCodes codes = response.body();
if(codes == null) return;
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for(ObtainedCode obtainedCode : codes.getObtainedCodes()) {
Offer offer = realmRepository.getOffer(realm, obtainedCode.getOfferId());
if(offer == null) {
offer = realm.createObject(Offer.class, obtainedCode.getOfferId());
// map properties to offer if possible
}
RealmList<ObtainedCode> offerCodes = offer.getObtainedCodes();
ObtainedCode managedObtainedCode = realm.where(ObtainedCode.class).equalTo("obtainedCodeId", obtainedCode.getId()).findFirst();
if(managedObtainedCode == null) {
managedObtainedCode = realm.createObject(ObtainedCode.class, obtainedCode.getId());
// map properties from obtained code to managed obtained code
}
if(!offerCodes.contains(managedObtainedCode)) {
offerCodes.add(managedObtainedCode);
}
}
}
});
}
}
});
}