Single Object in Realm - android

I want to persist object of User in Realm, and I want to persist only single object and get it everywhere like singleton UserProfile.getInstance().
So how to implement it?
I do it using dirty approach (as I mind)
public static User getInstance() {
Realm realm = Realm.getDefaultInstance();
User user = realm.where(User.class).findFirst();
if (user != null) {
// If object exists in db
return user;
}
// If object does not exist, we should to create it
realm.executeTransaction(realm -> {
realm.insertOrUpdate(new User());
});
// After create we should to return already 'managed' object.
return realm.where(User.class).findFirst();
}
This code smells bad, but I not found any better solution. Also I not found any useful information in official docs.
How do you implement singleton objects in Realm?

Since this is a Singleton you can use copyToRealm instead of copyOrUpdate which infers that you want to update the user (defies the goal you're trying to achieve).
class Foo {
private volatile User user = null;
public User getInstance() {
if (user == null) {
synchronized(this) {
if (user == null)
// If object does not exist, we create it
realm.executeTransaction(realm -> {
user = realm.copyToRealm(new User());
});
}
}
return user;
}
}
Note the use of copyToRealm instead of insertToRealm since copy* methods will return the managed object (no need to query for it again).

Related

How to display data in Fragment and Activity (both independent) from same Snapshot Listener (Firebase)

Would like to have your help on my weird problem that currently I am facing. I tried for couple of days but no luck and finally decided to post here to take help.
I created a Snapshot Listener attached to a Collection in Firebase defined as follows :-
public class FirebaseTypingStatusLiveData extends LiveData<List<documentSnapshot>> {
// Logging constant
private static final String TAG = "FirebaseQueryLiveData";
// Document Reference
private final DocumentReference documentReference;
// Listener
private final MyDocumentListener listener = new MyDocumentListener();
// Handler
private final Handler handler = new Handler();
private ListenerRegistration listenerRegistration;
// Flag to remove listener
private boolean listenerRemovePending = false;
private MutableLiveData <List<documentSnapshot> mutableLiveData = new MutableLiveData<>();
// Constructor
public FirebaseTypingStatusLiveData(DocumentReference documentReference) {
this.documentReference = documentReference;
}
public LiveData<List<documentSnapshot>> checknow(){
// Add listener
if (!Listeners.LIVESAMPLE.containsKey(documentReference)) {
listenerRegistration = documentReference.addSnapshotListener(listener);
Listeners.LIVESAMPLE.put(documentReference, listenerRegistration);
} else {
listenerRegistration = Listeners.LIVETYPINGSTATUSSAMPLE.get(documentReference);
}
return mutableLiveData;
}
// Listener definition
private class MyDocumentListener implements EventListener<DocumentSnapshot> {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable
FirebaseFirestoreException e) {
Log.d(TAG, "onEvent");
// Check for error
if (e != null) {
// Log
Log.d(TAG, "Can't listen to query snapshots: " + documentSnapshot
+ ":::" + e.getMessage());
return;
}
setValue(documentSnapshot);
mutableLiveData.setValue(documentSnapshot);
}
}
}
}
The snapshot reads the data perfectly and advised as and when data is available.
The snapshot data is getting displayed 1. in Fragment (not part of Activity that i am talking about) 2. Activity through two view models that have the same code as follows :
#NonNull
public LiveData<List<documentSnapshot>> getDataSnapshotLiveData() {
Firestore_dB db = new Firestore_dB();
DocumentReference docref = db.get_document_firestore("Sample/"+docID);
FirebaseTypingStatusLiveData firebaseTypingStatusLiveData = new
FirebaseTypingStatusLiveData(docref);
return firebaseTypingStatusLiveData.checknow();
}
The Fragment & Activity code is also same except changing owner which are as follows :-
LiveData<List<documentSnapshot>> liveData = viewmodel.getDataSnapshotLiveData();
liveData.observe(this, new Observer<List<documentSnapshot>>() {
#Override
public void onChanged(DocumentReference docreef) {
String name = docreef.get("name");
stringname.setText(name); // The text is displaying either in Fragment or in Activity but not in both.
});
My problem is i need data in both i.e. Fragment & Activity whereas I am getting data either in Fragment or in Activity depending upon the code which I commented.
Kindly advise where I am making mistake. Thanks in Advance
Honestly, I am not sure that my answer wouldn't lead you away to the false way, but you can try.
My guess is that your problem could be somehow connected with ViewModel sharing.
There is a well-known task How to share Viewmodel between fragments.
But in your case, that can't help, because you have to share ViewModel between activities (now you have two separate ViewModels and that could be problem with Firestore EventListeners).
Technically you can share ViewModel between activities (I haven't try since usually I use Single activity pattern). For that as a owner parameter in ViewModelProvider constructor you can set instance of your custom Application class (but you have implement interface ViewModelStoreOwner for it). After that both in your activity and in your fragment you can get the same ViewModel with the Application class-instance:
val sharedViewModel = ViewModelProvider(mainApplication, viewModelFactory).get(SharedViewModel::class.java)
I made LiveData static that listens to changes in source data and provide updated content were ever required in different Activity.

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

Is it possible to reuse the same RealmObject instance with different IDs to save/update multiple objects in a write transaction?

Currently, we are creating a new instance for every RealmObject that we want to save in our Mapper class.
#Override
public Person toRealmObject(Realm realm, PersonXML businessObject) {
Person person = new Person();
person.setId(businessObject.getId());
person.setName(businessObject.getName());
return person;
}
When we create a new one, we collect it into a list.
#Override
public void populateRealmListWithMappedModel(Realm realm, RealmList<Person> realmList, PersonsXML personXML) {
for(PersonXML personXML : personXML.getPersons()) {
realmList.add(personMapper.toRealmObject(realm, personXML));
}
}
/*next the following happens:*/
//realm.beginTransaction();
//personRepository.saveOrUpdate(realm, list);
//realm.commitTransaction();
Then we save the list.
#Override
public RealmList<T> saveOrUpdate(Realm realm, RealmList<T> list) {
RealmList<T> realmList = new RealmList<T>();
for(T t : realm.copyToRealmOrUpdate(list)) {
realmList.add(t);
}
return realmList;
}
The question is, is the following possible, can I re-use the same Person object instead and change its values to specify to Realm the objects that I want to have saved, but not have a whole object associated with it?
As in, something like this:
#Override
public Person toRealmObject(Realm realm, PersonXML businessObject, Person person) {
person.setId(businessObject.getId());
person.setName(businessObject.getName());
return person;
}
Then
#Override
public void writeObjectsToRealm(Realm realm, PersonsXML personXML) {
realm.beginTransaction();
Person person = new Person();
for(PersonXML personXML : personXML.getPersons()) {
person = personMapper.toRealmObject(realm, personXML, person));
personRepository.saveOrUpdate(person);
}
realm.closeTransaction();
}
Where this method is
#Override
public T saveOrUpdate(Realm realm, T t) {
return realm.copyToRealmOrUpdate(t);
}
I'm asking this because rewriting the architecture to use the following would require rewriting every populateRealmListWithMappedModel() methods I have, and that would be a bit concerning if it doesn't work. So I'm curious if it theoretically works.
Basically the short question is, if I call copyToRealmOrUpdate(t) on a realmObject, and alter its id and data, and save the same object again and again, will the write transaction succeed?
Yes you can re-use the object used as input to copyToRealm. We are creating a copy of your input object but not altering the original in any way, so reusing the object like you are doing should work and will also reduce the amount work the GC has to do.
Person javaPerson = new Person();
Person realmPerson = realm.copyToRealm(javaPerson);
// The following is true
assertTrue(javaPerson != realmPerson)
assertFalse(javaPerson.equals(realmPerson))
assertFalse(realmPerson.equals(javaPerson))
Yep, it worked.
#Override
public void persist(Realm realm, SchedulesXML schedulesXML) {
Schedule defaultSchedule = new Schedule();
if(schedulesXML.getSchedules() != null) {
realm.beginTransaction();
for(SchedulesByChannelXML schedulesByChannelXML : schedulesXML.getSchedules()) {
Channel channel = channelRepository.findOne(realm, schedulesByChannelXML.getChannelId());
if(channel == null) {
Log.w(TAG,
"The channel [" + schedulesByChannelXML.getChannelId() + "] could not be found in Realm!");
}
for(ScheduleForChannelXML scheduleForChannelXML : schedulesByChannelXML.getSchedules()) {
defaultSchedule = scheduleMapper.toRealmObject(realm, scheduleForChannelXML, defaultSchedule);
defaultSchedule.setChannel(channel);
defaultSchedule.setChannelId(schedulesByChannelXML.getChannelId());
boolean isInRealm = scheduleRepository.findOne(realm,
scheduleForChannelXML.getScheduleId()) != null;
Schedule savedSchedule = scheduleRepository.saveOrUpdate(realm, defaultSchedule);
if(!isInRealm) {
if(channel != null) {
channel.getSchedules().add(savedSchedule);
}
}
}
}
realm.commitTransaction();
}
}
Instead of creating 150 Schedule each time I save the batch of schedules, now I only create one per batch, and it works flawlessly.

How to properly handle Realm instance deletion

I have a situation where I want to handle a realm migration in a lazy fashion. Instead of using the Realm Migration api, which the realm developer's state is cumbersome, I want to delete the realm instance and then re-instantiate it only if I need to do a migration. How would I properly handle a this situation such that I only delete and re-instantiate the database ONLY IF it needs to be upgraded?
Say I have a model
#RealmClass
public class testmodel extends RealmObject {
private String foo;
private int bar;
public int getBar() {
return bar;
}
public void setBar(int bar) {
this.bar = bar;
}
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
}
and I want to add another string bazz into the model with the proper getters and setters.
Currently, I can do this as long as the first time I instantiate realm, I delete it. Like so:
Realm.deleteRealmFile(getApplicationContext());
realm = Realm.getInstance(getApplicationContext());
Now, let's suppose I didn't want to go blowing away my data every time the app starts, but still wanted to do this in the instance when I needed to. I thought a good way would be like this.
try{
Log.d(TAG, "started realm creation");
realm = Realm.getInstance(getApplicationContext());
Log.d(TAG, "successfully created realm");
}
catch (RealmException e){ // I tried RealmMigrationNeededException and IllegalState Exception but neither are caught
Log.d(TAG, "try deleting realm");
Realm.deleteRealmFile(getApplicationContext());
realm = Realm.getInstance(getApplicationContext());
Log.d(TAG, "deleted realm and remade successfully");
}
However, when I try this instead of the exception being caught, my app crashes.
How would I properly handle a this situation such that I only delete and re-instantiate the database ONLY IF it needs to be upgraded?
In Realm 0.81.0 you can use the new RealmConfiguration object:
RealmConfiguration realmConfig = new RealmConfiguration.Builder(context)
.schemaVersion(42)
.deleteRealmIfMigrationNeeded()
.build();
This will do exactly what you are looking for.
Edit:
JavaDoc is here: https://realm.io/docs/java/latest/api/io/realm/RealmConfiguration.Builder.html
And you can read more about setup here:
https://realm.io/docs/java/latest/#configuring-a-realm

return an object Android

I want to return an object with some things in them.
Here is the declaration;
Object user_det = get_user_det();
Here is the function code:
private Object get_user_det() {
Firebase f_user = new Firebase("https://myapp.firebaseio.com/User/");
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot snap_user) {
// TODO Auto-generated method stub
Iterable<DataSnapshot> rs = snap_user.getChildren();
Iterator<DataSnapshot> irs = rs.iterator();
long allNum2 = snap_user.getChildrenCount();
int maxNum2 = (int)allNum2;
int count_user = 1;
while(irs.hasNext())
{
if(count_user <= maxNum2)
{
Firebase user_data = new Firebase("https://myapp.firebaseio.com/");
AuthData authData = user_data.getAuth();
Map<String, Object> nPost = (Map<String, Object>) irs.next().getValue();
String db_email = nPost.get("email_addr").toString();
if (authData != null) {
String usr_email = authData.getProviderData().get("email").toString();
if(usr_email.equals(db_email))
{
//NB: I WANT TO ADD THE FOLLOWING INTO THE OBJECT
String disp_name = nPost.get("disp_name").toString();
String real_name = nPost.get("real_name").toString();
}
} else {
System.out.println("Failed");
}
}
count_user++;
}
}
#Override
public void onCancelled(FirebaseError arg0) {
// TODO Auto-generated method stub
}
});
return null; //NB: I NEED TO RETURN THE OBJECT HERE.
}
I want to return the string disp_name and real_name but they are inside the addListenerForSingleValueEvent, so how do I get them out and return it to the function.
I have wrote "NB" in the code where I need help with.
Thanks for your time.
If you want to return an object from your method in java, do it like this:
The Object class:
This contains the structure of your Object, and defines what data will be in it. Also includes methods to easily get the data.
private class myObject {
private String name;
private String realName;
//The constructor, so you can set the data when creating the Object.
public myObject (String disp_name, String real_name) {
name = disp_name;
realName = real_name;
}
//Getter methods, to get the data.
public String getRealName() {return realName;}
public String getDisplayName() {return name;}
}
Your code:
private Object get_user_det() {
myObject o; //Declare it, so it can be returned.
...
String disp_name = nPost.get("disp_name").toString();
String real_name = nPost.get("real_name").toString();
o = new myObject(disp_name, real_name); //create it and set the data.
...
return myobject; //return the new Object with the data.
}
To get the data from the Object:
myObject o = get_user_det(); //Call the metod which return our Object.
String realName = o.getRealName(); //Get the data from the Object.
String displayName = o.getDisplayName;
In your case, it would be much easier to use a String array.
Hope this helps.
It's probably easiest to see what's going on, if you add some printlns to your code:
private Object get_user_det() {
Firebase f_user = new Firebase("https://myapp.firebaseio.com/User/");
System.out.println("Adding listener");
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
#Override
public void onDataChange(DataSnapshot snap_user) {
System.out.println("Data received");
}
#Override
public void onCancelled(FirebaseError arg0) {
// TODO Auto-generated method stub
}
});
System.out.println("Returning");
return null; //NB: I NEED TO RETURN THE OBJECT HERE.
}
If you execute this code, you will see that it logs:
Adding listener
Returning
Data received
Most likely, this is not what you expected. But hopefully, it makes sense if you read my explanation below.
Asynchronous loading
When you register your listener:
f_user.addListenerForSingleValueEvent(new ValueEventListener(){
You tell Firebase to start listening for events. It goes off and starts retrieving the data from the server.
Since retrieving the data may take some time, it does this retrieval asynchronously so that your thread isn't blocked. Once the data is completely retrieved, Firebase calls the onDataChange method in your listener.
Between the time you start listening and the time onDataChange is called, your code continues executing. So there is no way to return data that is loaded asynchronously, because by the time your function returns, the data isn't loaded yet.
Solutions
Disclaimer: I am not an expert at solving this problem in Java, so there may be problems with my solutions. If^H^HWhen you find any, please report them in the comments.
I know of three possible solutions to the problem:
force the code to wait for the data to be returned
return a Future that at some point will contain the data
pass a callback into get_user_det and call that function once the data is available
You will probably be tempted to selected option 1, since it matches most closely with your mental modal of loading data. While this is not necessarily wrong, keep in mind that there is a good reason that the loading is done asynchronously. It might be worth taking the "learning how to deal with asynchronicity" penalty now.
Instead of writing up examples for all solutions, I'll instead refer to some relevant questions:
Retrieving data from firebase returning NULL (an answer that uses approach 3)
Is waiting for return, ok?
Java wait() & notify() vs Android wait() & notify() (a question from a user taking approach 1)
How it works:
Firebase uses reflection to build a JSON tree object to save to the database. When you retrieve this JSON tree, you can cast it back to your original object. Just like serializing and deserializing. This means you do not need to handle the keys and values when trying to "rebuild" your object like you are. It can all be done like so:
YourObject object = (YourObject) dataSnapshot.getValue(YourObject.class);
Notice the YourObject.class in the getValue(). This tells firebase to reflect through this class and find the appropriate accessors with the dataSnapshot.
How to do it
Be sure that your object has:
Accessors Appropriate getters and setters for ALL fields - (or annotated with #JsonIgnore if you wish to not save a particular field)
Empty constructor. Your object must provide a constructor that does not modify itself at all.
What your object should look like:
public class YourObject {
private String displayName;
private String realName;
public YourObject() { /*Empty constructor needed for Firebase */ }
// Accessors
public void setRealName(String realName){
this.realName = realName;
}
public String getRealName(){
return this.realName;
}
public String getDisplayName(){
return this.displayName;
}
public void setDisplayName(String displayName){
this.displayName = displayName;
}
}
Then, in any of the firebase callbacks, you can just cast your DataSnapshot in to your object:
public void onDataChange(DataSnapshot snap_user) {
YourObject object = new Object;
if(snap_user.getValue() != null) {
try {
object = (YourObject) snap_user.getValue(YourObject.class); <-- Improtant!!!
} catch(ClassCastException ex) {
ex.printStackTrace();
}
}
return object;
}
Also
It seems you are retrieving many objects. When doing this, I find it best to use the onChildEventListener then for each of the YourObjects in that node, onChildAdded(DataSnapshot ds, String previousChild); will be called.

Categories

Resources