I'm looking the answer of my problem. It's my first contact with Realm database and I have a problem with null object reference in fragment during saving data. Does anyone know what I'm doing wrong?
Helper method:
public void saveTask(final String text, final String date, final String time ) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm bgRealm) {
Task task = bgRealm.createObject(Task.class);
task.setText(text);
task.setDate(date);
task.setTime(time);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
// Transaction was a success.
Log.d(TAG, "ON_SUCCESS: Data Written Successfully");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
Log.d(TAG, "ON_ERROR: Error");
}
});
}
OnClick method in fragment:
#OnClick(R.id.save_button)
public void onClickSave() {
realmHelper.saveTask(title_text.getText().toString().trim(), date_text.getText().toString().trim(), time_text.getText().toString().trim());
}
Code inside onCreateView:
ButterKnife.bind(this, view);
((MainActivity) getActivity()).hideFloatingActionButton();
Realm.init(getActivity());
RealmConfiguration configuration = new RealmConfiguration
.Builder()
.deleteRealmIfMigrationNeeded()
.build();
realm = Realm.getInstance(configuration);
I tried solution with textview fields inside onClick method, but still same.
From your OnClick method in fragment, I observe that you have a realm helper class which you created an instance of called realmHelper. Since you are using realm asynchronously I suggest you pass the instantiated realm object to the helper method or instantiate realm afresh in the helper method or class.
Passing the realm instance to the helper method
public void saveTask(Realm realm, final String text, final String date, final String time ) {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm bgRealm) {
Task task = bgRealm.createObject(Task.class);
task.setText(text);
task.setDate(date);
task.setTime(time);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
// Transaction was a success.
Log.d(TAG, "ON_SUCCESS: Data Written Successfully");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
Log.d(TAG, "ON_ERROR: Error");
}
});
}
Instantiating realm in the helper method
public void saveTask(Context context, final String text, final String date, final String time ) {
Realm.init(context);
RealmConfiguration configuration = new RealmConfiguration
.Builder()
.deleteRealmIfMigrationNeeded()
.build();
Realm realm = Realm.getInstance(configuration);
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm bgRealm) {
Task task = bgRealm.createObject(Task.class);
task.setText(text);
task.setDate(date);
task.setTime(time);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
// Transaction was a success.
Log.d(TAG, "ON_SUCCESS: Data Written Successfully");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// Transaction failed and was automatically canceled.
Log.d(TAG, "ON_ERROR: Error");
}
});
}
Related
I am using LiveData to get data from the server.
In the onResume method, Calling same function every after 5 second
I am able to get data only on the First API call.
Second time the observer is not triggered and not able to get data in the fragment.
this is my fragment:
private int delay = 5 * 1000;
private ViewModel mViewModel;
private DetailsModel details = new DetailsModel();
mViewModel = ViewModelProviders.of(this).get(ViewModel.class);
mViewModel.getDetailsResponse("token", "ids");
mViewModel.getData().observe(this, new Observer< DetailsModel >() {
#Override
public void onChanged(DetailsModel response) {
details = response;
}});
//getting data in every 5 seconds
#Override
public void onResume() {
super.onResume();
liveHandler.postDelayed(runnable = new Runnable() {
public void run() {
mViewModel. getDetailsResponse("token", "ids");
liveHandler.postDelayed(runnable, delay);
}
}, delay);
}
ViewModel.java
private MutableLiveData<DetailsModel> detailsResponse;
private ProjectRepository repository = new ProjectRepository();
public void getDetailsResponse(String token, String ids) {
detailsResponse = repository.getMapData("token", "ids");
}
public MutableLiveData<DetailsModel> getData() {
return detailsResponse;
}
ProjectRepository.java
public MutableLiveData<DetailsModel> getMapData(String token, String ids) {
final MutableLiveData<DetailsModel> responseMutableLiveData = new MutableLiveData<>();
Call<DetailsModel> call = service.getMapDetails(token, ids);
call.enqueue(new Callback<DetailsModel>() {
#Override
public void onResponse(#NonNull Call<DetailsModel> call, #NonNull Response<DetailsModel> response) {
responseMutableLiveData.postValue(response.body());
}
#Override
public void onFailure(#NonNull Call<DetailsModel> call, #NonNull Throwable t) {
t.printStackTrace();
}
});
return responseMutableLiveData;
}
Whenever you call getDetailsResponse, you create a new LiveData object, which is the problem, you should do this in your ProjectRepository
final MutableLiveData<DetailsModel> responseMutableLiveData = new MutableLiveData<>();
public MutableLiveData<DetailsModel> getMapData(String token, String ids) {
Call<DetailsModel> call = service.getMapDetails(token, ids);
call.enqueue(new Callback<DetailsModel>() {
#Override
public void onResponse(#NonNull Call<DetailsModel> call, #NonNull Response<DetailsModel> response) {
responseMutableLiveData.postValue(response.body());
}
#Override
public void onFailure(#NonNull Call<DetailsModel> call, #NonNull Throwable t) {
t.printStackTrace();
}
});
return responseMutableLiveData;
}
And in your VM:
private MutableLiveData<DetailsModel> detailsResponse = null;
private ProjectRepository repository = new ProjectRepository();
public void getDetailsResponse(String token, String ids) {
if (detailsResponse == null) {
detailsResponse = repository.getMapData("token", "ids");
} else {
// Just call it, you already assigned before
repository.getMapData("token", "ids");
}
}
public MutableLiveData<DetailsModel> getData() {
return detailsResponse;
}
So, basically move the object creation out of the function itself. However, the design of your MVVM implementation can be simplified a lot. I would urge to check some examples!
You are using postDelayed() twice, so it is not working. Change your onResume() code to below mentioned.
#Override
public void onResume() {
super.onResume();
liveHandler.postDelayed(runnable = new Runnable() {
public void run() {
mViewModel.getDetailsResponse("token", "ids");
liveHandler.post(runnable);
}
}, delay);
}
I have this method, i want return value when the transaction complete, but i cant. This's my code
public List<Group> getConversations() {
final RealmResults<Group> conversations;
try {
mRealm = Realm.getDefaultInstance();
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<Group> conversations = realm.where(Group.class).findAllSorted("time", Sort.DESCENDING);
cursorConversation(conversations);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
//return conversation
}
});
}
return null;
}
What should i do ?
I am not sure what are you doing in cursorConversation(..) but you can use the same method on returned values from Realm.
give a try
public List<Group> getConversations() {
try (Realm realm = Realm.getDefaultInstance()) {
return realm.copyFromRealm(realm.where(Group.class).findAllSorted("time", Sort.DESCENDING));
}
}
You don't need to run a transaction for getting the conversations. You can run your query on the realm db and add a change listener to the result. When the query completes, it'll call that change listener with the RealmResults<Converstaion>, Check this link for more.
Something like
public void listenToConversations(RealmChangeListener<RealmResults<Conversation>> listener) {
RealmResults<Conversations> conversations = realm.where(Group.class).sort("time", Sort.DESCENDING).findAllAsync();
conversations.addChangeListener(listener);
}
where listener is something like
listener = new RealmChangeListener<RealmResults<Conversations>>() {
\#Override
public void onChange(RealmResults<Conversations> conversations) {
// React to change
}
}
You'll also need to remove listener to avoid any memory leaks.
My question might be a little awkard:
My scenario:
I have a singleton which contains many references to my objects.
I initialize this singleton in my activity running an UI operation with Realm with realm.where(myobj.class).first();
In my activity I have a ViewPager with many fragments. Those fragments refers to the Singleton's properties.
In my many fragments, I need to update some of my singleton object properties, but I want to do it with an async transaction. Obv it result on a thread realm error.
Relevant code (cutted)
singleton
public class OpenedIntervention {
private static OpenedIntervention openedIntervention;
public static OpenedIntervention getIstance() {
if (openedIntervention == null) {
openedIntervention = new OpenedIntervention();
}
return openedIntervention;
}
public void dispose() {
openedIntervention = null;
}
private Intervention intervention = null;
//getter setter
}
My activity init:
//...
Intervention i = realm.where(Intervention.class).equalTo("ID", intId).findFirst(); //this realm istance has activity-scope
openedIntervention.setIntervention(i);
//...
My fragment, what I would like to do:
realm.executeTransactionAsync(
new Realm.Transaction() {
#Override
public void execute(Realm realm) {
//...
openedIntervention.getIntervention().setTIPOLOGIA_INTERVENTO(etInterventionType.getText().toString());
openedIntervention.getIntervention().setTIPOLOGIA_INTERVENTO_ID(etInterventionType.getTag().toString());
}
},
new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
//...
}
},
new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
//...
}
});
Any Idea about how can I solve this problem? I 'd like to do it async to avoid lag
I thought I was following the recommended Realm approach for running Async data inserts like this:
public void addCustomer(final Customer customer) {
Realm insertRealm = Realm.getDefaultInstance();
insertRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm backgroundRealm) {
long id = customerPrimaryKey.incrementAndGet();
customer.setId(id);
backgroundRealm.copyToRealm(customer);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.d(LOG_TAG, "Customer Added");
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.d(LOG_TAG, error.getMessage());
}
});
insertRealm.close();
}
However, when I run the above code I get "Your Realm is opened from a thread without a Looper and you provided a callback, we need a Handler to invoke your callback"
I am running this code in a non-Activity class, what I am doing wrong here and how can I fix it. Thanks.
Update - Fixed
It turns out that there is nothing wrong with the query, problem is that I was calling it from IntentService. I was trying to seed the database on app first run, so I fixed this like this:
protected void onHandleIntent(Intent intent) {
Realm realm = Realm.getDefaultInstance();
//Add sample Customers to database
List<Customer> customers = SampleCustomerData.getCustomers();
realm.beginTransaction();
for (Customer customer: customers){
customer.setId(customerPrimaryKey.getAndIncrement());
realm.copyToRealm(customer);
}
realm.commitTransaction();
realm.close();
}
That fixed, outside of the IntentService, the query works fine when called from a UI Thread.
In order for
insertRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm backgroundRealm) {
//...
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
// !!!
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
// !!!
}
});
asynchronous transaction callbacks to be called on a background thread, the thread needs to be associated with a Looper (and therefore have Handlers that can communicate with the thread with that Looper).
Solution, use synchronous transaction on background thread.
But you've already figured that out.
protected void onHandleIntent(Intent intent) {
Realm realm = null;
try {
realm = Realm.getDefaultInstance();
final List<Customer> customers = SampleCustomerData.getCustomers();
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
for (Customer customer: customers){
customer.setId(customerPrimaryKey.getAndIncrement());
realm.insert(customer);
}
}
});
} finally {
if(realm != null) {
realm.close();
}
}
}
Realm relies on Android' thread communication by Hanfler and Looper classes.
It looks like you query async operation from another background Thread (why?, it is already in background, use synchronous version).
To fix that you need thread with Active Looper. Use Handler thread as you background thread - it will have Looper initialized
You need to call addCustomer from UI thread.
Try this line
insertRealm.close();
to add within onSuccess and onError method. Remove it from last line as it is currently.
So your code will look like
public void addCustomer(final Customer customer) {
final Realm insertRealm = Realm.getDefaultInstance();
insertRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm backgroundRealm) {
long id = customerPrimaryKey.incrementAndGet();
customer.setId(id);
backgroundRealm.copyToRealm(customer);
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
Log.d(LOG_TAG, "Customer Added");
insertRealm.close();
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
Log.d(LOG_TAG, error.getMessage());
insertRealm.close();
}
});
}
io.realm:realm-gradle-plugin:2.0.0'
Android Studio 2.2.2
I am trying to delete objects from the realm database. The items seems to get deleted. But when I close the app and load items from the database the deleted ones still seem to have a reference to them. This is my code below for deleting.
If the delete onSuccess is called I send back the item to be removed from the recyclerview's adapter. Is this the correct way to do this?
#Override
public void deletePerson(final Person person, final DeleteListener deleteListener) {
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<Person> results = realm.where(Person.class).equalTo("mId", person.getId()).findAll();
results.deleteAllFromRealm();
}
}, new Realm.Transaction.OnSuccess() {
#Override
public void onSuccess() {
/* send the person object back to be removed from the recyclerview after success*/
deleteListener.onDeleteSuccess(person);
}
}, new Realm.Transaction.OnError() {
#Override
public void onError(Throwable error) {
deleteListener.onDeleteFailure(error.getMessage());
}
});
}
And when I load the persons the ones that are deleted seem to have a reference in realm and doesn't seem to be completely removed.
#Override
public void loadPersons(final LoadPersonListener loadPersonListener) {
if(mRealm.isClosed()) {
mRealm = Realm.getDefaultInstance();
}
RealmResults<Person> personsList = mRealm.where(Person.class).findAll();
if(personsList.size() > 0) {
loadPersonListener.onLoadPersonSuccess(personsList);
}
else {
loadPersonListener.onLoadPersonFailure("No items in the database");
}
}
You aren't removing anything from the Realm at the moment, you're just querying. Also, you're accessing the Person you sent in on a background thread, which ought to throw IllegalStateException.
So instead of
#Override
public void deletePerson(final Person person, final DeleteListener deleteListener) {
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
RealmResults<Person> results = realm.where(Person.class).equalTo("mId", person.getId()).findAll();
}
You should have
#Override
public void deletePerson(final Person person, final DeleteListener deleteListener) {
final String id = person.getId();
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.where(Person.class).equalTo("mId", id).findAll().deleteAllFromRealm();
}
Here in you code you only query data asynchronously. For deletion use .remove() method while looping on result of query.
Suppose, that mId is #Primary key, removal will look like:
RealmResults<Person> results = realm.where(Person.class).equalTo("mId", person.getId()).findFirst().removeFromRealm();