At some point in my Parse-powered application I'm building a ParseQuery to retrieve a certain amount of data from a table ("class" as Parse calls them).
This class includes Pointers to other classes. So data is not available by default (isDataAvailable() == false). I need to fetch() the data on all those Pointers.
Here is my functionning code:
// Simplified code
new ParseQuery<MyClass>("MyClass").where(...).findInBackground(new FindCallback<MyClass>() {
#Override
public void done(final List<MyClass> objects, final ParseException pe) {
for (final Object object : objects) {
object.fetchIfNeeded();
}
myCustomCallback.objects(objects);
}
});
And here comes my problem: The callbacks in Parse seems to run on UIThread (I tested Looper.getMainLooper().getThread() == Thread.currentThread()).
So I end up with:
I/Choreographer: Skipped X frames! The application may be doing too much work on its main thread.
I know I could use fetchIfNeededInBackground() to avoid that, but then my callback would not run when all the fetches are done.
How to run all fetch actions in background and yet trigger my callback only once they all are done?
I had sort of same problems. I needed to do a lot of stuff before triggering callback.
It is really difficult to handle all those requests and know which one came last.
So my solution was to create async task myself, and work with synchronous parse methods (delete, fetch, save... those without "inBackground" part =) )
new AsyncTask<Void, Void, Object>() {
#Override
protected Object doInBackground(Void... params) {
List<MyClass> objects = new ParseQuery<MyClass>("MyClass").find();
for (final Object object : objects) {
object.fetchIfNeeded();
}
return objects;
}
#Override
protected void onPostExecute(Object objects) {
myCustomCallback.objects(objects);
}
}.execute();
That is general solution for many "nested" calls. Just add them one by one.
But for you, I think "include" method on ParseQuery would be enough.
Something like
new ParseQuery<MyClass>("MyClass").where("")
.include(NAME_OF_THE_OBJECT_IN_PARSE_DB)
.findInBackground(new FindCallback<MyClass>() {
#Override
public void done(final List<MyClass> objects, final ParseException pe) {
myCustomCallback.objects(objects);
}
});
You can check in Parse documentation
I hope this helps.
Related
This question is a follow-up question from: Organize Android Realm data in lists
Due to the data returned by the API we use, it's slightly impossible to do an actual query on the realm database. Instead I'm wrapping my ordered data in a RealmList and adding a #PrimaryKey public String id; to it.
So our realm data looks like:
public class ListPhoto extends RealmObject {
#PrimaryKey public String id;
public RealmList<Photo> list; // Photo contains String/int/boolean
}
which makes easy to write to and read from the Realm DB by simply using the API endpoint as the id.
So a typical query on it looks like:
realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
This creates a slightly overhead of listening/subscribing to data because now I need to check listUser.isLoaded() use ListUser to addChangeListener/removeChangeListener and ListUser.list as an actual data on my adapter.
So my question is:
Is there a way I can query this realm to receive a RealmResults<Photo>. That way I could easily use this data in RealmRecyclerViewAdapter and use listeners directly on it.
Edit: to further clarify, I would like something like the following (I know this doesn't compile, it's just a pseudo-code on what I would like to achieve).
realm
.where(ListPhoto.class)
.equalTo("id", id)
.findFirstAsync() // get a results of that photo list
.where(Photo.class)
.getField("list")
.findAllAsync(); // get the field "list" into a `RealmResults<Photo>`
edit final code: considering it's not possible ATM to do it directly on queries, my final solution was to simply have an adapter that checks data and subscribe if needed. Code below:
public abstract class RealmAdapter
<T extends RealmModel,
VH extends RecyclerView.ViewHolder>
extends RealmRecyclerViewAdapter<T, VH>
implements RealmChangeListener<RealmModel> {
public RealmAdapter(Context context, OrderedRealmCollection data, RealmObject realmObject) {
super(context, data, true);
if (data == null) {
realmObject.addChangeListener(this);
}
}
#Override public void onChange(RealmModel element) {
RealmList list = null;
try {
// accessing the `getter` from the generated class
// because it can be list of Photo, User, Album, Comment, etc
// but the field name will always be `list` so the generated will always be realmGet$list
list = (RealmList) element.getClass().getMethod("realmGet$list").invoke(element);
} catch (Exception e) {
e.printStackTrace();
}
if (list != null) {
((RealmObject) element).removeChangeListener(this);
updateData(list);
}
}
}
First you query the ListPhoto, because it's async you have to register a listener for the results. Then in that listener you can query the result to get a RealmResult.
Something like this
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
RealmResults<Photo> photos = listPhoto.getList().where().findAll();
// do stuff with your photo results here.
// unregister the listener.
listPhoto.removeChangeListeners();
}
});
Note that you can actually query a RealmList. That's why we can call listPhoto.getList().where(). The where() just means "return all".
I cannot test it because I don't have your code. You may need to cast the element with ((ListPhoto) element).
I know you said you're not considering the option of using the synchronous API, but I still think it's worth noting that your problem would be solved like so:
RealmResults<Photo> results = realm.where(ListPhoto.class).equalTo("id", id).findFirst()
.getList().where().findAll();
EDIT: To be completely informative though, I cite the docs:
findFirstAsync
public E findFirstAsync()
Similar to findFirst() but runs asynchronously on a worker thread This method is only available from a Looper thread.
Returns: immediately an empty RealmObject.
Trying to access any field on the returned object before it is loaded
will throw an IllegalStateException.
Use RealmObject.isLoaded() to check if the object is fully loaded
or register a listener RealmObject.addChangeListener(io.realm.RealmChangeListener<E>) to be
notified when the query completes.
If no RealmObject was found after
the query completed, the returned RealmObject will have
RealmObject.isLoaded() set to true and RealmObject.isValid() set to
false.
So technically yes, you need to do the following:
private OrderedRealmCollection<Photo> photos = null;
//...
final ListPhoto listPhoto = realm.where(ListPhoto.class).equalTo("id", id).findFirstAsync();
listPhoto.addChangeListener(new RealmChangeListener<ListPhoto>() {
#Override
public void onChange(ListPhoto element) {
if(element.isValid()) {
realmRecyclerViewAdapter.updateData(element.list);
}
listPhoto.removeChangeListeners();
}
}
On my Android application I have a data access layer where I have the following property on each data store class
Realm realm = Realm.getDefaultInstance();
My problema is when I try to call any data store method from a different thread. Then I get
Realm objects can only be accessed on the thread they were created.
I've read that I should be creating a new Realm instance on the new thread. Problem is that data access layer is no thread aware, it doesn't know if it's called from main or separated and it seems to me it would smell adding logic to check that.
I was checking different questions here and issues filled on github but it's not clear for me what's the oficial way to handle a situation like this. I don't think I'm dealing with a strange scenario...
EDIT
My arquitecture is Fragment containing a Presenter whic contains a DataStore.
I detected a very long running process so I moved it to a separated thread like this on presenter
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
// Events is a collection of DTOs, they are not Realm objects (converted from Realm object to DTO inside dataStore)
events = dataStore.getEvents(filterTerm);
view.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if (events.size() > 0) {
view.showEvents(events);
}
}
});
}
});
thread.start();
EDIT
Adding 3 queries executed in a row. They are called from 3 different methods
Query 1
final RealmResults<Event> results = query.findAllAsync();
results.addChangeListener(new RealmChangeListener() {
#Override
public void onChange() {
....
}
});
Query 2
final RealmResults<T> result = realm.where(type).findAllAsync();
result.addChangeListener(new RealmChangeListener() {
#Override
public void onChange() {
...
}
});
Query 3
final RealmResults<PresentationSpeaker> results = query.findAllAsync();
results.addChangeListener(new RealmChangeListener() {
#Override
public void onChange() {
...
}
});
It is normally custom to let your DataStore be asynchronous to avoid exactly the problems you run into now: That the operation are taking to long, and you have to create custom threads and use postRunnable. This should be something the DataStore is concerned about, not your Presenter and UI which should run on the UI thread. One solution is to let your Datastore be asynchronous instead, either by implementing some observer pattern yourself or use RxJava. Then you could use Realms async API's (https://realm.io/docs/java/latest/#asynchronous-queries) to do the following:
// DataStore
public class DataStore {
private final Realm realm;
public DataStore() {
realm = Realm.getDefaultInstance(); // Called on the UI thread
}
public void getEvents(EventsLoadedListener listener) {
RealmResults<Event> results = realm.where(Events.class).findAllAsync();
results.addChangeListener(new RealmChangeListener() {
public void onChange() {
if (listener != null) {
listener.eventsLoaded(results);
}
}
});
}
public interface EventsLoadedListener {
public void eventsLoaded(List<Event> events);
}
}
// UI code
datastore.getEvents(new DataStore.EventsLoadedListener() {
public void eventsLoaded(List<Event> events) {
view.showEvents(events);
}
})
The above code can be massively simplified using RxJava, so I would really encourage you to look into that. Realm also supports RxJava natively: https://realm.io/docs/java/latest/#rxjava
I'm meeting a problem in my current app, the app containt following/follower system, when I look to user profil I want to retrieve an information if I'm already following this user or not. this is my code
private class CheckinIfFollowing extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
//preexcution
}
#Override
protected Void doInBackground(Void... params) {
ParseQuery<ParseObject> query = ParseQuery.getQuery("Follow");
query.whereEqualTo("from", ParseUser.getCurrentUser());
query.whereEqualTo("to", userId);
query.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(ParseObject object, ParseException e) {
if(e==null){
Toast.makeText(UserProfil.this, "blue", Toast.LENGTH_SHORT).show();
//btn_follow.setButtonColor(getResources().getColor(R.color.bleu));
}else{
if(e.getCode() == ParseException.OBJECT_NOT_FOUND)
{
Toast.makeText(UserProfil.this, "pink", Toast.LENGTH_SHORT).show();
//btn_follow.setButtonColor(getResources().getColor(R.color.pink));
}
else
{
//unknown error, debug
}
}
}
});
return null;
}
#Override
protected void onPostExecute(Void result) {
// followingButton(AlreadyFollowing);
// btn_follow.setClickable(true);
}
}
I'm executing the AsyncTask but nothing shown in the screen.
Btw the "from" and "to" column of the Follow class in parse.com are pointers to _User.
First of all (that's not directly related to your issue, just an observatino): if you're using <something>InBackground method of a query, you don't have to put it inside an AsyncTask. It's already being executed in the background. In your case, you are basically (simplifying a bit here) executing an async task from an async task.
Although you didn't say what's stored in the userId object, I'm assuming (based on it's name) it's an objectId of a ParseUser object and not a ParseUser object itself. And I believe that's a reason for your issue. This line to be exact:
query.whereEqualTo("to", userId);
You said:
Btw the "from" and "to" column of the Follow class in parse.com are
pointers to _User.
If you're storing Pointer to another ParseObject you should probably be passing this type of an object if you're using this field in a query (and not an objectId - String). So in your case you should be passing an instance of ParseUser instead of its objectId.
You can use createWithoutData method of ParseObject to create an object (with objectId) that you can use for your query.
I don't understand how is the Parse working?
I download data in parse to my arraylist , but when I show the Pets.size inside (//here) method "done" it will show 4, but when I show pets.size outside the done's method it will show 0?
public class Test extends AppCompatActivity {
ArrayList<Pet> pets;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
pets = new ArrayList<>();
ParseQuery<Pet> query = new ParseQuery<>("Pets");
query.findInBackground(new FindCallback<Pet>() {
#Override
public void done(List<Pet> list, ParseException e) {
if (e!=null){
Toast.makeText(Test.this,"Error",Toast.LENGTH_LONG).show();
}
for (Pet pet : list){
Pet newPet = new Pet();
newPet.setName(pet.getName());
newPet.setType(pet.getType());
pets.add(newPet);
}
// here
}
});
Toast.makeText(Test.this,"You have "+pets.size()+" pets",Toast.LENGTH_LONG).show();
}
Here's my Pet class:
#ParseClassName("Pets")
public class Pet extends ParseObject {
public String getName(){
return getString("name");
}
public void setName(String name) {
put("name", name);
}
public String getType(){
return getString("type");
}
public void setType(String type) {
put("type", type);
}
}
And an orther question , what should I do if I wanna save the data in local data?
Explanation:
findInbackground performs an operation to find all ParseObjects in a background thread (outside the main thread, or UI thread). So when it completes in the place where you have the comment
//here
That is when the background thread finishes it's call to find the objects. When you try to look at the size of the array outside that call where it shows size of 0, it is because it reached that point before the background thread finishes it's work (of adding to your array from objects it found).
What is happening is the operation for find() is happening in parallel with your main threads code.
And for your second question, make sure you enableLocalDatastore and then you can pin results from queries to your local cache. This data is stored on the device until the user deletes your app or clears cached data in settings.
Follow this guide to setup local cache Local Datastore with Parse
Note: A solution to your problem for when the background task of finding the pets is complete is to call a method from within the Callback for the findInBackground call that will handle the newly found Pet ParseObjects. Also remember to handle if the query fails either by finding no objects or some failure in connection / timeout.
just calling pet.pin() or pet.pinInBackground(); you can save a parseObject in local storage , to query objects in local storage you need set query.fromPin(true)
https://parse.com/docs/android/guide#objects-the-local-datastore
"done" method fires when the background task ends.
I have a ListView which I need to populate using a background thread. The list needs to update as each item is retrieved. Below is a very simplified example of how I implement this.
public class DownloadTask extends AsyncTask <MyUserObject, Integer, String>
{
#Override
protected MyUserObject doInBackground(MyUserObject... myUserObj)
{
MyUserObject muo = null;
int nCount = myUserObj.length;
if( nCount > 0 )
muo = myUserObj[0];
muo.DownloadStuff();
return muo.getUserName();
}
protected void onPostExecute(String userName)
{
adapter.names.add(userName);
adapter.notifyDataSetChanged();
}
}
public class MyAdapterClass extends BaseAdapter
{
private ArrayList<String>names;
public MyAdapterClass(Context context)
{
names = new ArrayList<String>();
}
public fillList()
{
for( int i=0; i<users.length; i++ )
{
DownloadTask task = new DownloadTask();
task.execute(users[i]);
}
}
In the above example, 'adapter' is an object of type MyAdapterClass, and its fillList() method is what launches the threads. Calling notifyDataSetChanged() in onPostExecute() is what updates my ListView as data arrives.
The problem is, that I am accessing my sqlite database in "DownloadStuff()' which is called in 'doInBackground', and having multiple threads accessing the DB causes it to crash. (If I comment out all DB activities in here, then it runs fine). Below is how I try to workaround this problem, however it still crashes. Any advice on how I can have my ListView update as data is retrieved from a background thread?
Semaphore semaphore = new Semaphore(1, true);
public synchronized void DownloadStuff()
{
semaphore.acquire(1);
// ... DB operations ... //
semaphore.release(1);
}
I think your approach is wrong from it's beginning. Why do you want to start separate AsyncTask for each item you have to add to your adapter. Use onProgressUpdate to notify the gui for newly added items in the adapter. In this case you want have concurrent access to your db.
I'm not sure (because I'm really tired) but I think your ot using you synchronysed correctly.
you create a different instance of MyUserObject each time you do a async task, this means you never actually call Downloadstuff on the same instance hence no conflict, but on the other hand your database is unique being called by multiple MyUserObject hence conflict.
what you want to do is have the same instance of muo in all your async task, this way they all call downloadstuff on the same instance and then synchronized will work preventing multiple access.
you also don't need the semaphoe here.
edit:
Mojo Risin answer is also very good, if you can save yourself the trouble by centralizing all you async tasks into one you should(less concurrent threads running around you have the better)