I'm trying to figure out the best way to set up a RealmObject with a RealmResult as one of its fields.
For example, let's say I have two RealmObjects, Goal and Achievement. The Goal object contains fields that define a query of Achievement's the user wants to track (e.g. date range the achievement was created, type of achievement, etc) and has custom methods to extract statistics from those Achievements.
What is the best way for Goal to contain this RealmResult of Achievements? Here are some ways I've thought of doing this:
Have a persisted RealmList field in Goal and update it anytime a field is changed that would change the resulting query. But how would this RealmList get updated if a new Achievement gets added to the realm?
Use #Ignore annotation on a RealmResult<Achievement> field within Goal. Anywhere in Goal where mResult is used, first check if null and requery if needed. This seems like I will be doing a lot of unneccessary querying if I'm using something like a RecyclerView that refetches the object in getItem().
Have a wrapper class that contains a Goal object and the RealmResult<Achievement> as fields. Add a listener to Goal so that anytime a relevant field changes the RealmResult can be requeried.
I'm leaning towards the last one as the cleanest way to keep a valid RealmResult. Am I missing an easier way to accomplish this?
Okay so I'm trying to implement a wrapper class (which I think is similar to the DAO abstraction #EpicPandaForce was mentioning, but I'm not super familiar with that)
public class GoalWrapper {
private RealmResults<Achievements> mResults;
private Goal mGoal;
private Realm mRealm;
public GoalWrapper(Realm realm, Goal goal) {
mRealm = realm;
mGoal = goal;
// TODO: does this need to be removed somewhere? What happens when GoalWrapper gets GC'd?
goal.addChangeListener(new RealmChangeListener<RealmModel>() {
#Override
public void onChange(RealmModel element) {
// rerun the query
findResultForGoal();
}
});
findResultForGoal();
}
/**
* Run a query for the given goal and calculate the result
*/
private void findResultForGoal() {
mResults = mRealm.where(Achievement.class)
.greaterThanOrEqualTo("date", mGoal.getStartDate())
.lessThanOrEqualTo("date", mGoal.getEndDate())
.equalTo("type", mGoal.getAchievementType())
.findAll();
calculateStats();
}
private void calculateStats() {
// Get relevant stats from mResult...
}
}
I haven't tested this code yet but I plan to have a RecyclerView.Adapter with an ArrayList of GoalWrapper objects.
My one concern is that I never remove the listener on mGoal. Do I even need to remove it? What happens in the case that the ArrayList gets GC'ed? I would think that the Goal field and resulting listeners attached to it all get GC'ed as well.
Related
I don't know if this is a stupid question. This may defeat the purpose of LiveData/ViewModel.
Can I make the LiveData static? My reason is I have a listener from a Service which updates the information. So I need to have a way from a service to "set/change" the LiveData.
I used to do following and it works:
1. Service changes the DB
2. ViewModel listens to the DB change
3. UI updates from the liveData change
I found this way is too slow. To increase the performance, I want something like:
1. Service changes the class object directly
2. ViewModel listens to the the class object changes
3. UI updates from the liveData change
In order to achieve what I want, either I need to make the MutableLiveData static or make the ViewModel class to share the same instance of ViewModel between Activities.
Is this good idea?
public class MyViewModel extends AndroidViewModel {
// Note: this MutableLiveData is static
private static MutableLiveData<MyModel> mutableLiveData;
public MyViewModel(#NonNull Application application) {
super(application);
}
LiveData<MyModel> getLiveDataList() {
if (mutableLiveData == null) {
mutableLiveData = new MutableLiveData<>();
loadDataFromDb();
}
return mutableLiveData;
}
private void loadDataFromDb() {
// load data from DB
// mutableLiveData.setValue(MyModelFromDb); // Omit the real implementation
}
// Note: this method is static
public static void setData(MyModel newData) {
mutableLiveData.setValue(newData);
}
#Override
protected void onCleared() {
super.onCleared();
}
}
The whole point of ViewModel from Android Jetpack (as opposed to other versions) is for the ViewModel to be lifecycle aware and perform magic like destroying itself when observer is destroyed (activity/fragment), or surviving configuration changes (for example, orientation) without initialising itself all over again thereby making it much easier to deal with issues related to configuration changes.
So if you made the ViewModel or LiveData static you would actually beat their purpose and most likely leak ViewModel's data, though the need to do this is understandable. So this requires you to engineer your way around it, and the first way you mentioned is probably the best way you can do it. I don't understand why you have an issue with the first solution. The way I see it, it provides the best user experience:
You init ViewModel in your fragment or activity in onCreate and add an Observer to the data.
If database already has some data, your observer will receive it instantly and UI will be updated with existing data instantly.
Service makes the API request and changes the DB
DB changes triggers an update to the data in ViewModel
Observer refreshes received data and you pass this to your views/adapters
UI updates with latest data with some nice animations that indicate addition/removal of items.
From what I can see it cant get better than this. Since your question is from months ago, I am curious to know what you ended up doing?
I think if MyViewModel will have lots of LiveData fields it will grow with large amount of getters and setters. And what even worst, as for me, you will break the testablity of your code, because if you will create a new instance of MyViewModel you will expect that your LiveData objects are stateless at this point of time, but as it's a static object you don't know in what exactly state it is after simple creation.
As well static methods can't be overriden. And about fields: if you will want to have common field, suppose errorMessage, in class A and class B while both of them extend class C(which contains your common field) you can have unexpected behavior. On the other hand you can duplicate this code in other classes(what is bad).
The memory issue: if a large number of static variables/methods are used. Because they will not be GC until program ends.
But it just my opinion.
When using Firebase to store and retrieve objects (POJOs) created by the user (for example: posts or comments), it becomes necessary to pass these objects around the application. But what is the suggested way to keep track of the associated DatabaseReference, location or unique key in the database for this object?
Example scenario
A simple to do list app allows the user to freely add, edit and remove items in their list. So when the user creates an item, something similar to the below would happen:
private Item storeItem(String title) {
String key = mDatabase.child("items").push().getKey(); // Where do we keep this key?
Item item = new Item(title);
mDatabase.child("items").child(key).setValue(item);
return item;
}
Where Item is this Java object:
public class Item {
private String title;
private String description;
public Item() {}
public Item(String title) {
this.title = title;
}
// ...
}
Behind the scenes, this item is added to a RecyclerView, either by inserting the returned Item to the adapter or when a ChildEventListener attached to the "items" reference is fired.
The user then wishes to rename this new item or add text to the description field, so tapping on it in the RecyclerView starts a separate Activity which receives the passed Item and uses getters/setters to make changes.
Now, we'll need to save these changes to the database, which we can do by calling setValue() again, as above. However, we didn't store the key variable from storeItem() so we don't actually know where this item is currently stored in the database.
So, where can we keep track of the created item's key for later use to save changes back to the database?
Possible solutions
There are a number of different paths we could take here, but I'm looking for some guidance on the suggested method, as the Firebase documentation doesn't mention this hurdle. I've outlined some examples that I can think of:
Store the key inside the object. We could add another field to the Item object to store the database key. So within the previous storeItem() method, the key variable is added to the Item constructor and stored in the database as a field.
Create a wrapper object. We could wrap the Item object in a container that has methods such as getItem() and getKey() or getDatabaseReference() and then pass this around the app instead of the Item itself.
Use the DataSnapshot instead. Once the item is created, wait until an attached listener receives it, then use and pass around the retrieved DataSnapshot, which has methods for getKey() and getRef().
Retrieve the object every time it is needed. Instead of passing Item around the app, we could retrieve it from the database every time it is needed, by using the key or DatabaseReference.
Wrapping up
Looking back on this huge question, it seems I might have overcomplicated it a little, but I wanted to be thorough in my explanation. I'm also hoping that it's not purely opinion-based and there currently is some standard way to achieve this.
So I guess my question is: is there a standard method to handle and make changes to Java objects stored in Firebase?
Most developers I see struggling with this end up storing the key inside the Java objects too. To prevent it being duplicated in the JSON, you can annotate it in the Java class:
public class Item {
private String title;
private String description;
#Exclude
public String key;
public Item() {}
public Item(String title) {
this.title = title;
}
// ...
}
See: Is there a way to store Key in class which I cast from Firebase object?
My personal preference in such cases is to keep the DataSnapshot around. The main disadvantage I see in that is that the information on the object-type of the snapshot is spreading out over my code since this exists in multiple places:
snapshot.getValue(Item.class);
I've been lobbying to generify the DataSnapshot class so that it'd become DataSnapshot<Item>, which would solve that problem. I think that is currently being considered in the Firestore SDK for JavaScript/TypeScript.
But lacking such a solution for the Android SDK for the Realtime Database, you're probably better off with the first approach: storing the key inside the Java objects.
I'm trying to understand notification types in Realm from the Notifications section in the official docs, and when I'm using RealmObject addChangeListener in multiple managed object all of them are called when only one object is changing.
This is my code
Person first = realm.where(Person.class).equalTo("id", 0).findFirst();
first.addChangeListener(new RealmChangeListener<Person>() {
#Override
public void onChange(Person person) {
Log.e(LOG_TAG, "First element is changing: " + person);
}
});
Person second = realm.where(Person.class).equalTo("id", 1).findFirst();
second.addChangeListener(new RealmChangeListener<Person>() {
#Override
public void onChange(Person person) {
Log.e(LOG_TAG, "Second person is changing: " + person);
}
});
When I trigger an update in any of these Person objects (for example in first) both of the listeners are being called.
This what official docs say:
Listeners can also be attached to RealmObject instances as well as RealmResults instances. This allows you to react to changes to your objects and query results.
And
Lastly, typed-based change listeners will get notified when their referenced types change.
From what I understand the seen behaviour agrees with the second definition but I need to use the first behaviour, that's, I want to be notified when the object corresponding to that listener is changed.
So, if first Person is updated, only its corresponding listener get notified, not all Person listeners.
Right now it is happening because our change detection is not granular enough. It will trigger change listeners for all objects of the same type, not just those that changed.
Getting the changelisteners to only notify if the exact object has changed is being tracked here https://github.com/realm/realm-java/issues/989.
Use findFirstAsync() which returns a realmModel being empty and invalid. This is how you are able to get updates with its addChangeListener()
Specific change listener is now supported.
official release!
Demo
Goal(s):
1: Effortless updating for dynamic items.
Example:
I have a List<T> returned from an API, I use that list in my RecyclerView.Adapter. User swipes to refresh and a new list is returned from the API containing some new items and some updated old items. Now the older list needs to remove duplicate items.
Note: assume all items have an updated attribute that might change if a user interacts with it.
2: Immediate user feedback (this might tie in with goal #1).
Example:
To insert a new item into the RecyclerView.Adapter it needs to be created in an API first. Implementation creates object in the RecyclerView.Adapter and in the API simultaneously. When the new object is returned from the API the immediate object that was previously injected right away into the RecyclerView.Adapter "syncs" with the API response. This way the user sees immediate feedback.
Code Example:
I don't really have anything in mind for Goal #1 BUT for Goal 2 I was thinking something like this maybe inside my ViewHolder? (I have heard that updating / syncing models in Viewholders is not a good practice in general because viewholders recycle):
// JAVA 7
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject(new SyncRequestCallback() {
#Override
public void onSuccessFromAPI(ModelObject model) {
mAdapter.updateObject(model);
}
});
}
// JAVA 8
private void createNewObjectToBeInsertedIntoRecyclerView(String data) {
// Pass callback to API and at the same time insert object into adapter
mAdapter.addNewObject(data);
mPresenter.createObject((sync) -> { mAdapter.updateObject(model); });
}
This is just off the top of my head and it is definitely bug prone.
How Can I Achieve This?:
Looking for a robust solution here, but something that doesn't involve content providers (if possible).
You should not do anything like that in the ViewHolder, just bind the data you got from the API to the UI.
What you should do is operate on the Adapter
when the new List<T> returns from the API, just make the old list in the adapter to point to this new one (oldList = newList) and call mAdapter.notifyDataSetChanged()
You can do like point 1) but that way updates the whole Adapter. If you know where in the Adapter you have inserted that item (and I assume you know), just call mAdapter.notifyItemInserted(position) or alternatively, if you have already created it the Adapter, call mAdapter.notifyItemChanged(position)
I need to record a change in my boolean variable that depends on certain conditions in my code. I have a timertask that runs every second to track any such change. When a change occurs, i.e., when the variable change say from 'true' to 'false', I need to save the time at which this event happened. All of these events need to be able to retrieved from another Activity at a later time. I can implement this task either using SharedPreferences: I was thinking about using the putstring method:
sp_editor.putString("alarm_activated",String.valueOf(System.currentTimeMillis()));
but I realized alarm_activated is overridden each time. But I need all the 'time' values.
I could also change the key each time (key,value) by adding the 'time' to the key itself as below.
sp_editor.putStrng("alarm_activated"+String.valueOf(System.currentTimeMillis(),placeholder);
This looks like a really inefficient way to do it.
Is there any other way I can do this?
The right way to do this is to set up and update an SQLite database your app can use. It will give you maximum flexibility in terms of saving and retrieving data locally without worrying about loosing data if the app gets killed. Here is a good tutorial
Create a singleton class and inside create ListArray where you can save data.
If it data need to be accessible from another App use Android SQLite.
You can create a class like this.
public class TimeKeeper {
private static TimeKeeper instance;
public List<String> timeList = new ArrayList<String>();
public static TimeKeeper getTimeKeeper() {
if (instance == null) {
instance = new TimeKeeper();
}
return instance;
}
}
From your TimerTask you can add time to the list as
TimeKeeper.getTimeKeeper().timeList.add("Your Time");
and from any other activity you can access this same list very easily.
Mark as up if it works for you.
//======= For Saving it permanently you can loop through it and save it into db.
for(Strng time :TimeKeeper.getTimeKeeper().timeList)
{
db.insertTime(time); //call Your method to insert time string into your db
}