I am using Parse.com
as a backend for my app. They also offer a local database to store information, as an alternative to SQLite
.
I want to add numbers from phone to my database with parse. Before adding a number I need to check if the number already exists in the database, so I use findInBackground()
to get a list of numbers that match the number I want to add. If the list is empty the number I want to add doesn't exists in the database.
The method to do this is:
public void putPerson(final String name, final String phoneNumber, final boolean isFav) {
// Verify if there is any person with the same phone number
ParseQuery<ParseObject> query = ParseQuery.getQuery(ParseClass.PERSON_CLASS);
query.whereEqualTo(ParseKey.PERSON_PHONE_NUMBER_KEY, phoneNumber);
query.fromLocalDatastore();
query.findInBackground(new FindCallback<ParseObject>() {
public void done(List<ParseObject> personList,
ParseException e) {
if (e == null) {
if (personList.isEmpty()) {
// If there is not any person with the same phone number add person
ParseObject person = new ParseObject(ParseClass.PERSON_CLASS);
person.put(ParseKey.PERSON_NAME_KEY, name);
person.put(ParseKey.PERSON_PHONE_NUMBER_KEY, phoneNumber);
person.put(ParseKey.PERSON_FAVORITE_KEY, isFav);
person.pinInBackground();
Log.d(TAG,"Person:"+phoneNumber+" was added.");
} else {
Log.d(TAG, "Warning: " + "Person with the number " + phoneNumber + " already exists.");
}
} else {
Log.d(TAG, "Error: " + e.getMessage());
}
}
}
);
}
Then I call this method 3 times to add 3 numbers:
ParseLocalDataStore.getInstance().putPerson("Jack", "0741234567", false);
ParseLocalDataStore.getInstance().putPerson("John", "0747654321", false);
ParseLocalDataStore.getInstance().putPerson("Jack", "0741234567", false);
ParseLocalDataStore.getInstance().getPerson(); // Get all persons from database
Notice that the third number is the same as the first, and it shouldn't be added to database. But the logcat
shows:
12-26 15:37:55.424 16408-16408/D/MGParseLocalDataStore: Person:0741234567 was added.
12-26 15:37:55.424 16408-16408/D/MGParseLocalDataStore: Person:0747654321 was added.
12-26 15:37:55.484 16408-16408/D/MGParseLocalDataStore: Person:0741234567 was added.
The third number was added even if it wasn't supposed to do this, because fintInBackground()
is running in 3 background threads almost simultaneously, so it will find that there is no number in the database like the one I want to add.
In this question a guy told me that I should use Bolts
library from Parse
. I read about it from here and some Parse
blog posts, but I don't fully understand how to use this with the method I already have, and how to syncronize the queries to be executed one after another.
If someone worked with this library please guide me on how to do this or provide some basic examples so I can understand the workflow.
Thanks!
It sounds like you have a race condition. There are lots of different ways you could go about solving this problem. Here is a non bolts alternative.
The primary problem is that the search queries are happening at roughly the same time so they don't find duplicates in the database. In this case the way we solve it is to only search and add one person at a time, obviously not on the main thread as we don't want to tie up the ui doing searches. So lets make a List that does two things 1) contains items that need to be added, 2)verifies that they can be added. We can use async task to keep our search off of the mainthread.
Here is a rough idea of that needs to be done:
import com.parse.ParseException;
import com.parse.ParseObject;
import com.parse.ParseQuery;
import java.util.ArrayList;
import java.util.List;
public class AddPersonAsyncQueue {
ArrayList<ParseObject> mPeople = new ArrayList();
Boolean mLock;
AddTask mRunningTask;
public synchronized void addPerson(final String name, final String phoneNumber, final boolean isFav) {
// we aren't adding a person just yet simply creating the object
// and keeping track that we should do the search then add for this person if they aren't found
ParseObject person = new ParseObject(ParseClass.PERSON_CLASS);
person.put(ParseKey.PERSON_NAME_KEY, name);
person.put(ParseKey.PERSON_PHONE_NUMBER_KEY, phoneNumber);
person.put(ParseKey.PERSON_FAVORITE_KEY, isFav);
synchronized (mLock) {
mPeople.add(person);
}
processQueue();
}
public boolean processQueue() {
boolean running = false;
synchronized (mLock) {
if (mRunningTask == null) {
if (mPeople.size() > 0) {
mRunningTask = new AddTask(null);
mRunningTask.execute();
running = true;
} else {
// queue is empty no need waste starting an async task
running = false;
}
} else {
// async task is already running since it isn't null
running = false;
}
}
return running;
}
protected void onProcessQueueCompleted() {
mRunningTask = null;
}
private class AddTask extends AsyncTask<Void, ParseObject, Boolean> {
AddPersonAsyncQueue mAddPersonAsyncQueue;
public AddTask(AddPersonAsyncQueue queue) {
mAddPersonAsyncQueue = queue;
}
@Override
protected Boolean doInBackground(Void... voids) {
boolean errors = false;
ParseObject nextObject = null;
while (!isCancelled()) {
synchronized (mLock) {
if (mPeople.size() == 0) {
break;
} else {
// always take the oldest item fifo
nextObject = mPeople.remove(0);
}
}
if (alreadyHasPhoneNumber(nextObject.getInt(ParseKey.PERSON_PHONE_NUMBER_KEY))) {
// do nothing as we don't want to add a duplicate
errors = true;
} else if (addPerson(nextObject)) {
// nice we were able to add successfully
} else {
// we weren't able to add the person object we had an error
errors = true;
}
}
return errors;
}
private boolean alreadyHasPhoneNumber(int phoneNumber) {
try {
ParseQuery<ParseObject> query = ParseQuery.getQuery(ParseClass.PERSON_CLASS);
query.whereEqualTo(ParseKey.PERSON_PHONE_NUMBER_KEY, phoneNumber);
query.fromLocalDatastore();
List<ParseObject> objects = query.find();
return objects.size() > 0;
} catch (Exception error) {
// may need different logic here to do in the even of an error
return true;
}
}
private boolean addPerson(ParseObject person) {
try {
// now we finally add the person
person.pin();
return true;
} catch (ParseException e) {
e.printStackTrace();
return false;
}
}
@Override
protected void onPostExecute(Boolean aBoolean) {
super.onPostExecute(aBoolean);
onProcessQueueCompleted();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With