I am trying to query my Realm DB such that the output will give an unmanaged object and for that, I changed my RealmList type of object to List.
Now the thing is in addchangeListener I am getting my output object(stories) value as managed. But the type of stories is List. So why my stories object is becoming managed where it should act as an unmanaged object.
List<story> stories = realm.where(story.class).findAllAsync();
stories.addChangeListener(new RealmChangeListener<RealmResults<story>>() {
#Override
public void onChange(RealmResults<story> storydata) {
if (storydata.size() != 0) {
madapter = new StoriesAdapter(stories, getBaseContext(), MR);
mrecyclerview.setNestedScrollingEnabled(false);
mrecyclerview.setLayoutManager(new LinearLayoutManager(getBaseContext()));
mrecyclerview.setAdapter(madapter);
}
}
});
StoriesAdapter
class StoriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<story> storyList;
StoriesAdapter(List<story> storyList) {
this.storyList = storyList;
}
}
I am saying my List is managed because when i am trying to write below code I am getting Cannot modify managed objects outside of a write transaction.
madapter.storyList.get(3).setTitle("Wonderland"); // where storyList is List which i am pointing to `stories`.
List<story> stories = realm.where(story.class).findAllAsync();
Because specifying the type List<story> just means you'll see the returned list as a List<story>, but technically it's still a RealmResults<story>.
stories.addChangeListener(new RealmChangeListener<RealmResults<story>>() {
This line underneath shouldn't even compile.
Stories should be stored in a field.
private RealmResults<story> stories;
public void ...() {
stories = ...
stories.addChangeListener(...
Anyways, so you are working with RealmResults, which means that in
class StoriesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<story> storyList;
This storyList you provided is a RealmResults<story>, so calling storyList.get(...) will return managed RealmObjects.
Managed RealmObjects are "temporarily immutable", meaning they can only be modified in a transaction. It is also generally not recommended to run write transactions on the UI thread.
The simplest way would be to use realm-android-adapters.
class StoriesAdapter extends RealmRecyclerViewAdapter<story, RecyclerView.ViewHolder> {
StoriesAdapter(OrderedRealmCollection<story> stories) {
super(stories, true, true);
}
}
And when you want to modify an object, you do
story item = getData().get(3);
final String id = item.getId();
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
story changedItem = realm.where(story.class).equalTo("id", id).findFirst();
changedItem.setTitle("Wonderland");
}
});
And then Realm will handle automatically updating the RealmResults, the story object, and the RecyclerView.
EDIT: If you intend to use unmanaged objects, then you could use realm.copyFromRealm(results), except that does the read on the UI thread.
You could create a background looper thread and obtain the results from there, but managing that could be tricky. Luckily for you, there's a library I made called Monarchy which lets you do exactly that.
See the relevant sample code for how you'd use it.
The stories is implicitly Managed, the reason is that RealmResults extends the list interface abstractly. Thats why the casting is possible, underneath the same mechanisms for a RealmResults still takes precedence. Also, you should only pass RealmResults instance to an Adapter directly, if you register a RealmChangeListener on it, which will call adapter.notifyDataSetChanged(). Otherwise, writes will update the RealmResults content, and your adapter will be desynchronized.
Realm is not like SQLite or Core Data. If you’re using Realm, take advantage of live objects. Don’t implement any refreshing logic or requerying. Always allow the current class to own its own instance of a realm query.
This fact is true,Realm objects and any child objects are NOT thread-safe. They’re confined to a single thread to ensure that atomic rights are maintained. There is an internal list where every single thread has its own unique Realm instance. If you want to pass objects between a thread–for example, if you create a dog object on the main thread, pass it to the background thread, and then try and access a property–it will trigger an exception straight away.
Also you are using asynchronous query, which puts it on a worker thread.
Related
I am applying filters on realm using RealmResults<>.
I begin to do like this -
RealmResults<data> filteredRealmResults;
List<data> tranfilteredlist;
private OrderedRealmCollectionChangeListener<RealmResults<data>> filteredTransChangeListener =
new OrderedRealmCollectionChangeListener<RealmResults<data>>() {
#Override
public void onChange(RealmResults<data> results, OrderedCollectionChangeSet changeSet) {
Log.d("realm", "filteredRealmResults.size():" + filteredRealmResults.size());
tranfilteredlist = results;
initFilterAdapter();
}
};
Now I want to delete the filteredRealmResults. I did like this -
void deleteFilteredRealmResults() {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
// Delete all matches
filteredRealmResults.deleteAllFromRealm();
}
});
}
After doing this my data in the realm got deleted. So I just try to delete the tranfilteredlist but it throws an exception that it does not support .clear();
I want to clear if from the memory whatever is holder the query data. Correct me if I am wrong or doesn't understand or just worrying too much.
I read This class holds all the matches of a RealmQuery for a given Realm. The objects are not copied from the Realm to the RealmResults list, but are just referenced from the RealmResult instead. This saves memory and increases speed.
I want to clear if from the memory whatever is holder the query data.
Correct me if I am wrong or doesn't understand or just worrying too
much.
Once you invoke filteredRealmResults.deleteAllFromRealm, it will clear the internal resultant elements object(which holds the elements) and as you know, resultant objects are reference so data will be deleted from realm database too. Hence, there is no need to call clear on the RealmResults object.
You can verify this by calling filteredRealmResults.size() after deletion, it will return 0.
I just try to delete the tranfilteredlist but it throws an exception
that it does not support .clear();
It is the expected behaviour as clear has been deprecated so don't use it.
Why deprecated?
deleteAllFromRealm automatically clears the list so no need to call it again explicitly.
Calling clear on RealmResults object will result in deletion of data from database, can cause unexpected behaviour if the user is not aware so API is being modified to avoid unexpected behaviours.
So, I have an Android app that uses realm.io. I have to run queries asynchronously like this :
public static void getProductsByCategoryId(Realm realm,
String categoryId,
OrderedRealmCollectionChangeListener<RealmResults<Product>> callback) {
RealmResults<Product> result = realm.where(Product.class)
.equalTo(CATEGORY, categoryId)
.findAllAsync();
result.addChangeListener(callback);
}
The callback will process this response, but then I need to run another query in sequence. So, you'll have queryA => process response => queryB => process response. So, the callback may have code like this
.....
getProductsByCategoryId(app.getRealmInstance(), "ABC123", firstCallback);
.....
private OrderedRealmCollectionChangeListener<RealmResults<Product>> firstCallback = new OrderedRealmCollectionChangeListener<RealmResults<Product>>() {
#Override
public void onChange(RealmResults<Product> realmProducts, OrderedCollectionChangeSet changeSet) {
mProdList.addAll(mRealm.copyFromRealm(realmProducts));
// get more product info (2nd call)
MainApplication.getMoreProductInfo(mRealm, mCatId, false, secondCallback);
}
};
Currently, my understanding is that you would run queryB in the callback of queryA ? Looking at the requirements for the app, I will end up with chains of 3 or 4 queries. Is this an appropriate approach, or is there a specific pattern I should be using ? I haven't found any guidance yet in the Realm documentation.
It's generally an indication of bad schema design if you need to do multiple queries in order to retrieve your result set, because the way Realm works is that if you can define your query results with one query (and you don't use realm.copyFromRealm() which you generally don't need to use anyways), then its elements and the results itself are all lazy-loaded.
If you cannot accomplish that, then even then, generally you probably shouldn't chain find*Async calls, because any RealmResults that you don't store as a field variable has a chance of being consumed by GC, and its change listener won't be called when isLoaded() is true (because said RealmResults no longer exists).
So what you really seem to want to do is just execute multiple queries on a background thread then return copied results to the main thread, in which case it'd just look like this
Executor executor = Executors.newSingleThreadedPool(); // or some other pool
Handler handler = new Handler(Looper.getMainLooper());
public void getQueryResults(DataLoadedCallback callback) {
executor.execute(() -> {
try(Realm realm = Realm.getDefaultInstance()) {
realm.refresh(); // <-- might not be necessary
RealmResults<XYZ> results1 = realm.where(XYZ.class)./*...*/.findAll();
RealmResults<ZXY> results2 = realm.where(ZXY.class)./*...*/.findAll();
RealmResults<YZX> results3 = realm.where(YZX.class)./*...*/.findAll();
List<Something> someList = new LinkedList<>();
for/*do magic transform things*/
someList.add(blah /* blah is not a managed RealmObject */);
}
handler.post(() -> {
callback.onDataLoaded(Collections.unmodifiableList(new ArrayList<>(someList)));
});
}
});
}
Chaining queries in the callbacks are fine and "should just work", but it would be far more efficient if you can express what you want is as few queries as possible.
Ideally, we should have a query language that is powerful enough to express everything you want in one query. We are not fully there yet, but we would be very interested to hear more about what specific requirements you have.
Also, it isn't clear why you are using copyFromRealm in the method you posted, but in an ideal situation that shouldn't be necessary.
I was wondering does it matter where you instantiate adapters dataset with Realm? I like to fetch all the data that any adapter needs in the adapters constructor and thus instantiate the dataset there, but almost all examples I've seen fetch the data beforehand in the activity creating the adapter and then pass it to the adapter as a parameter.
With SQLite this seems even more arbitrary, but since I'm using Realm I need to open a realm connection every time I want to access the database and to keep the data available I need to keep the connection open. Keeping this connection open in the activity seems unnecessary since you might need to make queries in the adapter thus having to open a connection to realm within the adapter anyways.
Is there some higher reason to fetch the dataset beforehand or is this just a matter of preference?
since I'm using Realm I need to open a realm connection every time I want to access the database
Wrong, you just need 1 open instance for that given thread in order to access the database.
Keeping this connection open in the activity "seems unnecessary" since you might need to make queries in the adapter
In which case you can have the activity-level Realm instance as a "scoped dependency", that you can share through the Context via getSystemService() if that's what you like to do.
public class MyActivity extends Activity {
Realm realm;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
//...
}
#Override
public void onDestroy() {
if(realm != null) {
realm.close();
}
super.onDestroy();
}
...
#Override
public Object getSystemService(String key) {
if("REALM".equals(key)) {
return realm;
}
return super.getSystemService(key);
}
}
public class MyAdapter extends RecyclerView.Adapter<MyModelViewHolder> {
private final Context context;
Realm realm;
RealmResults<MyModel> results;
private final RealmChangeListener listener = new RealmChangeListener() {
#Override
public void onChange(Object element) {
notifyDataSetChanged();
}
}
public MyAdapter(Context context) {
this.context = context;
//noinspection ResourceType
realm = (Realm)context.getSystemService("REALM");
results = realm.where(MyModel.class).findAll();
results.addChangeListener(listener);
}
...
}
thus having to open a connection to realm within the adapter anyways.
wrong
Is there some higher reason to fetch the dataset beforehand or is this just a matter of preference?
It's because your Adapter, which is just supposed to describe how to show the elements of a dataset, become a God that also determines the data that it must show.
Although to be fair, it's actually harder to externally manage the data-set; something must hold a strong reference to the result set anyways. So when I don't really bother with unit-testability, I do obtain the results inside the Adapter itself. It does work.
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();
}
}
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)