Android adapter dataset instantiation practices with Realm - android

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.

Related

Realm DB how to get query output object as unmanaged?

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.

Best practice for opening/closing Realm instances

I have an Android app that uses a pretty common design pattern:
The main activity is essentially presenting a list of objects - on small devices it does so by hosting a single fragment that displays a recyclerview of this list. On larger devices it hosts two fragments, one which has the same recyclerview of objects, and another which will host the detail for individual objects when one is selected in the list.
On smaller devices, when a selection from the list is made, an activity is launched that hosts a fragment that utilizes a ViewPager to allow "swiping" through the list of objects, and edit each one in place.
In both cases, the user is allowed to edit only from the detail fragment.
I currently have my realm instance initialized in the application class, then the default instance retrieved in an activity base class I use to hold some housekeeping methods:
public abstract class SingleFragmentActivity extends AppCompatActivity {
private Realm realm;
protected abstract Fragment createFragment();
#LayoutRes
protected int getLayoutResId() {
return R.layout.activity_fragment;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
// Initialize ProfileLab
ProfileLab.get(realm);
setContentView(getLayoutResId());
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragment_container);
if (fragment == null) {
fragment = createFragment();
fm.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
if ( realm != null) {
realm.close();
}
}
}
Note that I am storing this instance of realm in a static class "ProfileLab":
// Initialize ProfileLab
ProfileLab.get(realm);
Then in the various fragments that update data, I am doing stuff like:
mProfile = ProfileLab.get().getProfile(profileId);
*
* do CRUD activities here for example:
*
private void deleteProfile() {
ProfileLab.get().deleteProfile(mProfile);
mCallbacks.onProfileUpdated(mProfile);
}
Then in ProfileLab, it looks like:
public boolean deleteProfile(Profile c) {
boolean retVal = true;
try {
mRealm.beginTransaction();
c.deleteFromRealm();
} catch (Exception e) {
retVal = false;
} finally {
if ( mRealm != null ) {
if (retVal) {
mRealm.commitTransaction();
} else {
mRealm.cancelTransaction();
}
}
}
return (retVal);
}
My question - is this a problem to essentially hold that Realm instance open like that throughout the use of the app? I noticed this paragraph in the docs:
If you get a Realm instance from a thread that does not have a Looper
attached, then objects from such instance will not be updated unless
the waitForChange() method is called. It is important to note that
having to hold on to an old version of your data is expensive in terms
of memory and disk space and the cost increases with the number of
versions between the one being retained and the latest. This is why it
is important to close the Realm instance as soon as you are done with
it in the thread.
The thing is, I am not 'done with it' because this is on the UI thread, which is obviously running throughout the lifetime of my app.
I can't really open/close the realm instance just for the atomic updates, because I need to use the result of the initial query to show the list of objects from which to choose to edit - when I tried that initially (I had realm object open/close within each method in ProfileLab itself) I got an error in my recycler adapters that the realm had been closed...
The example code showing use of the recycler view shows realm being retrieved/used/closed at the individual activity level, if I do that between say the two simple activities (hosting the RecyclerView and hosting the ViewPager), will the data updates be reflected in each other?
Opening and closing the realm within try/catch block is recommended. for an example:
try {
Realm realm = Realm.getDefaultInstance();
//Use the realm instance
}catch(Exception e){
//handle exceptions
}finally {
realm.close();
}
It's a basic example when going to use. If you can close within AsyncTask, it'll be better.
The official documentation refers that if you use minSdkVersion >= 19 and Java >= 7, you won't close it manually.
try (Realm realm = Realm.getDefaultInstance()) {
// No need to close the Realm instance manually
}
Realm will automatically keep Realms on Looper threads up to date. That particular line in the documentation mostly refers to background threads. So your code is fine, even if onDestroy might not be called.
You can also read these relevant sections in the docs:
https://realm.io/docs/java/latest/#closing-realms
https://realm.io/docs/java/latest/#realm-instance-lifecycle

Query realm data contained on other object

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();
}
}

ArrayList Length gets 0 in Singleton

I am using a singleton for fetching data from a web service and storing the resulting data object in an ArrayList. It looks like this:
public class DataHelper {
private static DataHelper instance = null;
private List<CustomClass> data = null;
protected DataHelper() {
data = new ArrayList<>();
}
public synchronized static DataHelper getInstance() {
if(instance == null) {
instance = new DataHelper();
}
return instance;
}
public void fetchData(){
BackendlessDataQuery query = new BackendlessDataQuery();
QueryOptions options = new QueryOptions();
options.setSortBy(Arrays.asList("street"));
query.setQueryOptions(options);
CustomClass.findAsync(query, new AsyncCallback<BackendlessCollection<CustomClass>>() {
#Override
public void handleResponse(BackendlessCollection<CustomClass> response) {
int size = response.getCurrentPage().size();
if (size > 0) {
addData(response.getData());
response.nextPage(this);
} else {
EventBus.getDefault().post(new FetchedDataEvent(data));
}
}
#Override
public void handleFault(BackendlessFault fault) {
EventBus.getDefault().post(new BackendlessFaultEvent(fault));
}
});
}
public List<CustomClass> getData(){
return this.data;
}
public void setData(List<CustomClass> data){
this.data = data;
}
public void addData(List<Poster> data){
this.data.addAll(data);
}
public List<CustomClass> getData(FilterEnum filter){
if(filter == FilterEnum.NOFILTER){
return getData();
}else{
// Filtering and returning filtered data
}
return getData();
}
}
The data is fetched correctly and the list actually contains data after it. Also, only one instance is created, as intended. However, whenever I call getData later, the length of this.data is 0. Because of this I also tried it with a subclass of Application holding the DataHelper object, resulting in the same problem.
Is there a good way of debugging this? Is there something like global watches in Android Studio?
Is there something wrong with my approach? Is there a better approach? I am mainly an iOS developer, so Android is pretty new to me. I am showing the data from the ArrayList in different views, thus I want to have it present in an the ArrayList as long as the application runs.
Thanks!
EDIT: Example use in a list view fragment (only relevant parts):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
filter = FilterEnum.NOFILTER;
data = DataHelper.getInstance().getData(filter);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
customClassListAdapter = new customClassListAdapter(getActivity(), data);}
EDIT2: Added code where I fetch the data from Backendless, changed reference of DataHelper to reference of data in first EDIT
EDIT3: I usa a local EventBus for notifying the list view about the new data. This looks like this and works (initially the data gets populated, but after e.g. applying a filter, the ArrayList I get with getData is empty):
#Subscribe
public void onMessageEvent(FetchedDataEvent event) {
customClassListAdapter.notifyDataSetChanged();
}
Try instead of keeping reference to your DataHelper instance, keeping reference to your list of retrieved items. F.e. when you first fetch the list (and it's ok as you say), assign it to a class member. Or itarate through it and create your own array list of objects for future use.
Okay I finally found the problem. It was not about the object or memory management at all. Since I give the reference on getData to my ArrayAdapter, whenever I call clear (which I do when changing the filter) on the ArrayAdapter, it empties the reference. I basically had to create a copy of the result for the ArrayAdapter:
data = new ArrayList<>(DataHelper.getInstance().getData(filter));
I was not aware of the fact that this is a reference at all. So with this the data always stays in the helper entirely. I only did this because this:
customClassListAdapter.notifyDataSetChanged();
does hot help here, it does not call getData with the new filter again.
Thanks everyone for your contributions, you definitely helped me to debug this.
It is likely that getData does get called before the data is filled.
A simple way to debug this is to add (import android.util.Log) Log.i("MyApp.MyClass.MyMethod", "I am here now"); entries to strategic places in fetchData, addData and getData and then, from the logs displayed by adb logcat ensure the data is filled before getData gets called.

AsyncTask that accesses Sqlite database causes crash

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)

Categories

Resources