I have thousands of Strings and depending on the contents I want to add them to different RealmObjects Models, and I have about 10 Realm
Models,
right now what I doing something like this:
if (string.contains("abc")) {
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(new ABCModel(string));
mRealm.commitTransaction();
}
if (string.contains("xyz")) {
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(new XYZModel(string));
mRealm.commitTransaction();
}
// and so on
I was wondering how can pass this to a single method to do the transaction where i would just pass the string and the model class name,
How can I achieve something like this:
private void copyToRealm(RealmModel model, String string){
mRealm.beginTransaction();
mRealm.copyToRealmOrUpdate(new model(string));
mRealm.commitTransaction();
}
and call it like
copyToRealm(ABCModel, string);
and I want to do the same when querying like
public RealmResults<?> queryChemistry(RealmModel model, String year) {
return realm.where(model.class)
.contains("Title", "string")
.findAll();
}
and by models I mean the class which extends the RealmObject.
if you need any more info lemme know I will update the question, Thanks
This isn't strictly a Realm question to be honest, I personally would just create an enum that knows how to create its corresponding thing and how to evaluate if the model belongs to it.
public enum Models {
ABC {
#Override
public boolean evaluateIfModel(String input) {
return input.contains("abc");
}
#Override
public RealmModel createModel(String input) {
return new ABCModel(input);
}
};
public abstract boolean evaluateIfModel(String input);
public abstract RealmModel createModel(String input);
}
for(Models model : Models.values()) {
if(model.evaluateIfModel(input)) {
final _model = model;
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(_model.createModel(input));
}
});
break;
}
}
public <T extends RealmModel> RealmResults<T> queryChemistry(T model, String year) {
return realm.where(model.getClass())
.contains("Title", "string")
.findAll();
}
Related
What the proper way to create DAO with Room and Retrofit?
I have database module like this:
#Module
public class ApplicationDatabaseModule {
private final String mDatabaseName;
ApplicationDatabase mApplicationDatabase;
public ApplicationDatabaseModule(#ApplicationContext Context context, Class<? extends ApplicationDatabase> roomDataBaseClass, String databaseName) {
mDatabaseName = databaseName;
mApplicationDatabase = Room.databaseBuilder(context, roomDataBaseClass, mDatabaseName).build();
}
#Singleton
#Provides
ApplicationDatabase provideApplicationDatabase() {
return mApplicationDatabase;
}
#Singleton
#Provides
CitiesDao provideCitiesDao() {
return mApplicationDatabase.getCitiesDao();
}
}
POJO class like this:
#Entity
public class City {
#PrimaryKey
#ColumnInfo(name = "id")
private int cityId;
#ColumnInfo(name = "name")
private String cityName;
public int getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
#Override
public String toString() {
return "City [cityId = " + cityId + ", cityName = " + cityName + "]";
}
}
DAO interface like this:
#Dao
public interface CitiesDao {
#Insert
void insertCities(City... cities);
#Query("SELECT * FROM City")
City[] queryCities();
}
And API for Retrofit:
public interface CitiesApi {
#GET("/api/cities")
Call<City[]> requestCities();
}
As I know DAO is responsible for accessing data, including data passed through REST-client. But these two parts are represented by interfaces and built into separate classes. What is the proper way to implement DAO?
DAO is responsible for accessing data
yes
, including data passed through REST-client.
God no
What is the proper way to implement DAO?
Room already generates a proper way of implementation for your DAO based on your interface + annotations, I think it's called CitiesDao_Impl.
What the proper way to create DAO with Room and Retrofit?
Room doesn't know about Retrofit and shouldn't need to know about Retrofit. It only cares about local data persistence.
Meaning your DAO needs to look like this:
#Dao
public interface CitiesDao {
#Insert
#Transaction
void insertCities(City... cities);
#Query("SELECT * FROM City")
LiveData<List<City>> queryCities();
}
So what you actually need is a Worker that will fetch new data in background when either cache is invalid (force fetch new data) or when your sync task should run (for example when device is charging and you are on WIFI and you're at 2 AM to 7 AM -- for this you'd need WorkManager).
Immediately fetching new data though is fairly easy, all you need is either an AsyncTask in a singleton context that returns null from doInBackground, or your own Executor that you post your background task to.
public class FetchCityTask extends AsyncTask<Void, Void, Void> {
...
#Override
protected Void doInBackground(Void... params) {
List<City> cities = citiesApi.requestCities().execute().body(); // TODO error handling
citiesDao.insertCities(cities);
return null;
}
}
And then
new FetchCityTask(...).execute();
Now when this task runs, your UI will be updated with latest data by observing the LiveData that you store in a ViewModel.
public class CitiesViewModel
extends ViewModel {
private final CitiesDao citiesDao;
private LiveData<List<City>> liveResults;
public CitiesViewModel(...) {
...
liveResults = citiesDao.queryCities();
}
public LiveData<List<City>> getCities() {
return liveResults;
}
}
And
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = view.findViewById(R.id.recycler_view);
CitiesViewModel viewModel = ViewModelProviders.of(this).get(CitiesViewModel.class, ...);
...
viewModel.getTasks().observe(getViewLifecycle(), list -> {
//noinspection Convert2MethodRef
listAdapter.submitList(list);
});
}
You want to create a repository class to handle your data. Then you simply interact with your repository. Some pseudocode for you:
class Repository {
private CitiesDao localSource;
private CitiesApi remoteSource;
public Repository() {
//initialize objects here
}
City[] getCities() {
if (networkIsAvailable) {
City[] cities = remoteSource.requestCities();
saveCitiesToDatabase(cities);
return cities;
} else {
return localSource.queryCities();
}
}
private void saveCitiesToDatabase(City[] cities) {
//TODO save cities to databse
}
}
I'm running in to an issue where I delete a RealmObject (and confirm its gone using StethoRealm), but when I create and save a new RealmObject with the same variable value, the deleted RealmObject reappears along with my new RealmObject.
With the below object - I delete the myObject1 by matching the 'id', but when I create a new MyObject myObject2 with the same 'name' both objects appear in my realm
Any tips on why this might be happening?
Example:
public class MyObject extends RealmObject {
#PrimaryKey
private String id;
private String name;
// getters and setters
}
Edit
Delete call
#Override
public void deleteMyObjectById(final String id, final OnDeleteMyObjectById callback) {
Realm realm = new MyRealmConfiguration().getDefaultRealm();
final RealmResults<MyObject> myObjects = realm.where(MyObject.class).equalTo(RealmTable.MyObject.ID, id).findAll();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
myObjects.deleteAllFromRealm();
if (callback != null) {
callback.onSuccess();
}
}
});
realm.close();
}
Add call
#Override
public void addMyObject(MyObject myObject, OnSaveMyObjectCallback callback) {
Realm realm = new MyRealmConfiguration().getDefaultRealm();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(myObject);
if (callback != null) {
callback.onSuccess();
}
}
});
realm.close();
}
I'm not sure as to how you are deleting your objects. The way I'm deleting them (and it works) is...
final RealmResults<UserCredentials> credentialResults = realm.where(UserCredentials.class).findAll();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
credentialResults.deleteAllFromRealm();
}
});
realm.close();
Of course, you are using MyObject instead of UserCredentials, and you aren't deleting everything of that type, but it should be similar enough. Try using executeTransaction and realm.close() if you aren't already.
Try something like this. get the instance inside transcation method and delete. i have been using this for one of my app. and it works fine with deletion process
Realm realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<MyObject> myObjects = realm.where(MyObject.class).equalTo(RealmTable.MyObject.ID, id).findAll();
myObjects.deleteAllFromRealm();
}
});
I have this classes
class Student extends RealmObject {
public String code;
public String name;
public String email;
public Course course;
}
class Course extends RealmObject {
public String code;
public String name;
}
class Sync {
// ...
// To sync data I am using retrofit, look the method to update course
public void onResponse(Call<...> call, Response<...> response) {
if (response.isSuccessful()) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.delete(Course.class);
realm.copyToRealm(response.body());
}
});
}
}
}
After call Sync to update Courses, all Student object has its course setting to null, this is expected behavior after called realm delete?
Even after table is populated again, the course on Student is still null.
Today I made this change on the code:
class Course extends RealmObject {
#PrimaryKey
public String code;
public String name;
}
class Sync {
// ...
// To sync data I am using retrofit, look the method to update course
public void onResponse(Call<...> call, Response<...> response) {
if (response.isSuccessful()) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(response.body());
}
});
}
}
}
I made this too late to avoid delete the courses.
There is something that can I do to recovery the references courses and set it again to student?
Thank you.
This is expected behavior, because you invalidate the object links by deleting the objects you are pointing to.
To restore them, you would have to set the links again.
Another solution would be to not delete courses that you still need. This would be done if you annotate code with #PrimaryKey, that way you would "update" courses that are already in. Then the problem would be removing courses/students no longer in the response, but there are solutions ready-made for that.
public class Robject extends RealmObject {
#PrimaryKey
private String code;
#Index
private String name;
//...
#Index
private boolean isBeingSaved;
//getters, setters
}
And
// background thread
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Robject robject = new Robject();
for(Some some : somethings) {
robject.set(some....);
realm.insertOrUpdate(robject);
}
realm.where(Robject.class)
.equalTo(Robject.IS_BEING_SAVED, false) // compile 'dk.ilios:realmfieldnameshelper:1.1.0'
.findAll()
.deleteAllFromRealm(); // delete all non-saved data
for(Robject robject : realm.where(Robject.class).findAll()) { // realm 0.89.0+
robject.setIsBeingSaved(false); // reset all save state
}
}
});
} finally {
if(realm != null) {
realm.close();
}
}
I have data in my firebase DB, everything works fine until I try to De-serialize the data.
Error: argument 1 has type io.realm.RealmList, got java.util.ArrayList
Here's my code:
DatabaseReference root = FirebaseDatabase.getInstance().
getReferenceFromUrl("https://swing-8792d.firebaseio.com/playlist");
Query playlistQuery = root.orderByKey().equalTo(key);
playlistQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
Log.d("Child", child + "");
Playlist receivedPlaylist = child.getValue(Playlist.class);
Playlist playlist = new Playlist();
playlist.setCreatedBy(receivedPlaylist.getCreatedBy());
playlist.setName(receivedPlaylist.getName());
playlist.setMyMap(receivedPlaylist.getMyMap());
playlist.setQrKey(receivedPlaylist.getQrKey());
playlist.setCount(receivedPlaylist.getCount());
playlist.setId(receivedPlaylist.getId());
playlist.setTracks(receivedPlaylist.getTracks());
mPlaylist.add(playlist);
}
This is my POJO class:
#RealmClass
public class Playlist extends RealmObject {
String name;
Long id;
RealmList<Track> tracks;
Integer count;
String createdBy;
RealmList<UserMap> myMap;
String qrKey;
public RealmList<UserMap> getMyMap() {
return myMap;
}
public void setMyMap(RealmList<UserMap> myMap) {
this.myMap = myMap;
}
public Playlist(){}
public String getQrKey() {
return qrKey;
}
public void setQrKey(String qrKey) {
this.qrKey = qrKey;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public RealmList<Track> getTracks() {
return tracks;
}
public void setTracks(RealmList<Track> tracks) {
this.tracks = tracks;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
If I try to de-serialize with Normal POJO class (i.e Removing Realm) it works fine.
Firebase won't work with classes that do not have default constructor or private variables i.e no public getter/setter.
A easier solution in your case would be to make a middleware class that is the same pojo just not extending RealmObject. Next initialise your RealmObject subclass using the values of the pojo.
Pseudo code
class SimplePojoPlaylist {
public String variable;
}
class Playlist extends RealmObject {
public String variable;
}
Then first cast into SimplePojoPlaylist
for (DataSnapshot child : dataSnapshot.getChildren()) {
SimplePojoPlaylist receivedPlaylist = child.getValue(SimplePojoPlaylist.class);
Playlist playList = new Playlist();
playList.variable = receivedPlaylist.variable;
}
RealmList is not a supported type for deserialization. Your database checks its structure and deduces that tracks should be an ArrayList. Then, when it tries to convert it, it finds that the types do not match.
Check this link from the docs:
Also, it is a good practice to make your objects immutable to avoid unwanted access and/or modifications.
Creating an empty object from scratch and then calling setter methods to define its state is not a very good pattern, because it can create a situation where an object is accessed before when its state is "broken".
If you need to create an object that is flexible, has a few mandatory fields and some optional, consider using the Builder pattern, although to do it you'd have to redesign your model.
wikipedia - Builder
If you don't need/want to use a builder, my advice is:
1) Make the empty constructor private and create another public one that requires all the fields.
2) Change your tracks field to be of type "List". Then, if you need the object to return a RealmList create another getter method such as tracksAsRealmList() that makes a RealmList out of the member list and returns it.
3) Make sure that the "Track" model has an empty private constructor, a public one with all of its parameters and that all of its fields are supported by firebase deserialization.
4) Unless strictly necessary, make your object fields private and set its value through a setter method.
I hope this helps you.
I have data in my firebase DB, everything works fine until I try to De-serialize the data.
Error: argument 1 has type io.realm.RealmList, got java.util.ArrayList
Here's my code:
DatabaseReference root = FirebaseDatabase.getInstance().
getReferenceFromUrl("https://swing-8792d.firebaseio.com/playlist");
Query playlistQuery = root.orderByKey().equalTo(key);
playlistQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
Log.d("Child", child + "");
Playlist receivedPlaylist = child.getValue(Playlist.class);
Playlist playlist = new Playlist();
playlist.setCreatedBy(receivedPlaylist.getCreatedBy());
playlist.setName(receivedPlaylist.getName());
playlist.setMyMap(receivedPlaylist.getMyMap());
playlist.setQrKey(receivedPlaylist.getQrKey());
playlist.setCount(receivedPlaylist.getCount());
playlist.setId(receivedPlaylist.getId());
playlist.setTracks(receivedPlaylist.getTracks());
mPlaylist.add(playlist);
}
This is my POJO class:
#RealmClass
public class Playlist extends RealmObject {
String name;
Long id;
RealmList<Track> tracks;
Integer count;
String createdBy;
RealmList<UserMap> myMap;
String qrKey;
public RealmList<UserMap> getMyMap() {
return myMap;
}
public void setMyMap(RealmList<UserMap> myMap) {
this.myMap = myMap;
}
public Playlist(){}
public String getQrKey() {
return qrKey;
}
public void setQrKey(String qrKey) {
this.qrKey = qrKey;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public RealmList<Track> getTracks() {
return tracks;
}
public void setTracks(RealmList<Track> tracks) {
this.tracks = tracks;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}
If I try to de-serialize with Normal POJO class (i.e Removing Realm) it works fine.
Firebase won't work with classes that do not have default constructor or private variables i.e no public getter/setter.
A easier solution in your case would be to make a middleware class that is the same pojo just not extending RealmObject. Next initialise your RealmObject subclass using the values of the pojo.
Pseudo code
class SimplePojoPlaylist {
public String variable;
}
class Playlist extends RealmObject {
public String variable;
}
Then first cast into SimplePojoPlaylist
for (DataSnapshot child : dataSnapshot.getChildren()) {
SimplePojoPlaylist receivedPlaylist = child.getValue(SimplePojoPlaylist.class);
Playlist playList = new Playlist();
playList.variable = receivedPlaylist.variable;
}
RealmList is not a supported type for deserialization. Your database checks its structure and deduces that tracks should be an ArrayList. Then, when it tries to convert it, it finds that the types do not match.
Check this link from the docs:
Also, it is a good practice to make your objects immutable to avoid unwanted access and/or modifications.
Creating an empty object from scratch and then calling setter methods to define its state is not a very good pattern, because it can create a situation where an object is accessed before when its state is "broken".
If you need to create an object that is flexible, has a few mandatory fields and some optional, consider using the Builder pattern, although to do it you'd have to redesign your model.
wikipedia - Builder
If you don't need/want to use a builder, my advice is:
1) Make the empty constructor private and create another public one that requires all the fields.
2) Change your tracks field to be of type "List". Then, if you need the object to return a RealmList create another getter method such as tracksAsRealmList() that makes a RealmList out of the member list and returns it.
3) Make sure that the "Track" model has an empty private constructor, a public one with all of its parameters and that all of its fields are supported by firebase deserialization.
4) Unless strictly necessary, make your object fields private and set its value through a setter method.
I hope this helps you.