Now, I am trying to understand how to use Realm with RxJava2. There aren't a lot of information. For example, I have a simple query:
CommentObject firstComments = realmForThisThread.where(CommentObject.class)
.equalTo(CommentObject.COMMENT,comment).findFirst();
How to get firstComment using RxJava2? As I understand asObservable method doesn't work now.
The same questions appear when I want to get RealmResults or set listener only for one model, not all Realm database.
Are there any documents about Realm + RxJava2. I found only RxJava, but it needn't. Before that I worked with Realm without Rx, but now it's important to use this concept
You can read my article on using Realm with RxJava2 in the Realm Academy.
Synchronous single-value query makes no sense to be exposed as Observable.
If you want to listen to whether there is 0 or 1 element existing of a RealmObject, then you should use RealmResults<T>.asFlowable().
Using RealmObject.asFlowable() is designed for listening to single-element object notifications that provides field change as well.
Anyways, using RxJava2 is to allow you to turn this
public class MyActivity extends AppCompatActivity {
Realm realm;
RealmResults<Task> results; // kept as strong reference!
RealmChangeListener<RealmResults<Task>> listener = new RealmChangeListener<RealmResults<Task>>() {
#Override
public void onChange(RealmResults<Task> results) {
if(results.isLoaded()) {
// results is always up to date here
// after a write to Realm from ANY thread!
updateUi(results);
}
}
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
ButterKnife.bind(this);
realm = Realm.getDefaultInstance();
results = realm.where(Task.class)
.findAllSortedAsync(TaskFields.ID);
// TaskFields is generated
// using https://github.com/cmelchior/realmfieldnameshelper
results.addChangeListener(listener);
}
protected void onDestroy() {
super.onDestroy();
results.removeChangeListener(listener);
results = null;
realm.close();
}
private void updateUi(List<MyObject> objects) {
// do something
}
}
into this
#Singleton
public class TaskRepository {
#Inject
public TaskRepository() {
}
// this implementation works on any thread.
public Flowable<List<Task>> getTasks(Realm realm) {
if(realm.isAutoRefresh()) { // for looper threads
return realm.where(Task.class)
.findAllSortedAsync(TaskFields.ID)
.asFlowable()
.filter(RealmResults::isLoaded);
} else { // for background threads
return Flowable.just(realm.where(Task.class).findAllSorted(TaskFields.ID));
}
}
}
and
public class MyActivity extends AppCompatActivity {
TaskRepository taskRepository;
Realm realm;
Disposable subscription;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
ButterKnife.bind(this);
realm = Realm.getDefaultInstance();
taskRepository = Injector.get().taskRepository();
subscription = taskRepository.getTasks(realm).subscribeWith(tasks -> {
updateUi(tasks);
});
}
protected void onDestroy() {
super.onDestroy();
subscription.dispose();
subscription = null;
realm.close();
}
private void updateUi(List<MyObject> objects) {
// do something
}
}
Related
This is my first time using MVVM architecture.I am also using LiveData. I simply retrieve data from server using Retrofit.So upon clicking a button in the View(MainActivity.class) I invoke the ViewModel class's method(handleRetrofitcall()) to take up the duty of Api calling from the Model class(Retrofit Handler.class).The Model class upon retrieving the data informs the ViewModel of the data(which is actually the size of items).I set the size to LiveData and try to listen for it.Unfortunately I couldn't.For detailed analysis please go through the code.
Model...
RetrofitHandler.class:
public class RetrofitHandler {
private ApiInterface apiInterface;
private SimpleViewModel viewModel;
public void getData(){
apiInterface= ApiClient.getClient().create(ApiInterface.class);
Call<Unknownapi> call=apiInterface.doGetListResources();
call.enqueue(new Callback<Unknownapi>() {
#Override
public void onResponse(Call<Unknownapi> call, Response<Unknownapi> response) {
List<Unknownapi.Data> list;
Unknownapi unknownapi=response.body();
list=unknownapi.getData();
viewModel=new SimpleViewModel();
viewModel.postValue(list.size());
Log.e("Size",Integer.toString(list.size()));
}
#Override
public void onFailure(Call<Unknownapi> call, Throwable t) {
}
});
}
}
ViewModel....
SimpleViewModel.class:
public class SimpleViewModel extends ViewModel {
private RetrofitHandler retrofitHandler;
private int size;
private MutableLiveData<Integer> mutablesize=new MutableLiveData<>();
public SimpleViewModel() {
super();
}
#Override
protected void onCleared() {
super.onCleared();
}
public void handleRetrofitcall(){
retrofitHandler=new RetrofitHandler();
retrofitHandler.getData();
}
public void postValue(int size){
this.size=size;
mutablesize.postValue(this.size);
Log.e("lk","f");
}
public MutableLiveData<Integer> getObject() {
return mutablesize;
}
}
View.....
MainActivity.class:
public class MainActivity extends AppCompatActivity {
private TextView status;
private SimpleViewModel viewModel;
private Observer<Integer> observer;
private MutableLiveData<Integer> mutableLiveData;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
status=findViewById(R.id.status);
viewModel=ViewModelProviders.of(MainActivity.this).get(SimpleViewModel.class);
observer=new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer integer) {
Log.e("lk","f");
status.setText(Integer.toString(integer));
}
};
viewModel.getObject().observe(MainActivity.this,observer);
findViewById(R.id.retrofit).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewModel.handleRetrofitcall();
}
});
}
#Override
protected void onDestroy() {
if (observer!=null){
viewModel.getObject().removeObserver(observer);
}
super.onDestroy();
}
}
You're creating a new ViewModel in the RetrofitHandler, so nothing is observing that viewmodel. Instead of having the RetrofitHandler rely on a ViewModel internally, it's probably safer to handle the Retrofit callback inself, and post data there.
public void handleRetrofitcall(){
retrofitHandler=new RetrofitHandler();
retrofitHandler.getData(new Callback<List<Unknownapi.Data>> {
// add actual callback implementation here
); // add a callback here, so that the data is available in the view model. Then post the results from here.
}
Edit: More clarification.
In the Activity, you're correctly creating a ViewModel and observing it (we'll call that ViewModel A). ViewModel A is then creating a RetrofitHandler and calling getData on that Retrofithandler. The issue is that RetrofitHandler is creating a new ViewModel in getData (which I'm going to call ViewModel B).
The issue is that the results are being posted to ViewModel B, which nothing is observing, so it seems like nothing is working.
Easy way to avoid this issue is to make sure that only an Activity/Fragment is relying on (and creating) ViewModels. Nothing else should know about the ViewModel.
Edit 2: Here's a simple implementation. I haven't tested it, but it should be more or less correct.
// shouldn't know anything about the view model or the view
public class RetrofitHandler {
private ApiInterface apiInterface;
// this should probably pass in a different type of callback that doesn't require retrofit
public void getData(Callback<Unknownapi> callback) {
// only create the apiInterface once
if (apiInterface == null) {
apiInterface = ApiClient.getClient().create(ApiInterface.class);
}
// allow the calling function to handle the result
apiInterface.doGetListResources().enqueue(callback);
}
}
// shouldn't know how retrofit handler parses the data
public class SimpleViewModel extends ViewModel {
private RetrofitHandler retrofitHandler = new RetrofitHandler();
// store data in mutableSize, not with a backing field.
private MutableLiveData<Integer> mutableSize = new MutableLiveData<>();
public void handleRetrofitCall() {
// handle the data parsing here
retrofitHandler.getData(new Callback<Unknownapi>() {
#Override
public void onResponse(Call<Unknownapi> call, Response<Unknownapi> response) {
Unknownapi unknownapi = response.body();
int listSize = unknownapi.getData().size;
// set the value of the LiveData. Observers will be notified
mutableSize.setValue(listSize); // Note that we're using setValue because retrofit callbacks come back on the main thread.
Log.e("Size", Integer.toString(listSize));
}
#Override
public void onFailure(Call<Unknownapi> call, Throwable t) {
// error handling should be added here
}
});
}
// this should probably return an immutable copy of the object
public MutableLiveData<Integer> getObject() {
return mutableSize;
}
}
public class MainActivity extends AppCompatActivity {
private TextView status;
// initialize the view model only once
private SimpleViewModel viewModel = ViewModelProviders.of(MainActivity.this).get(SimpleViewModel.class);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
status = findViewById(R.id.status);
// observe the view model's changes
viewModel.getObject().observe(this, new Observer<Integer>() {
#Override
public void onChanged(#Nullable Integer integer) {
// you should handle possibility of interger being null
Log.e("lk","f");
status.setText(Integer.toString(integer));
}
});
findViewById(R.id.retrofit).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// call the view model's function
viewModel.handleRetrofitCall();
}
});
}
}
I have a messaging application which uses RealmDB to store messages and threads for users. The message threads are RealmObjects. I'm trying to figure out how to add a RealmChangeListener which will trigger when any of the threads are updated (i.e a new message is received). The threads are initially retrieved and subsequently updated from my MessageThreadList activity via a syncMessages function in the API. It seems like no matter where I try to add the listener I get the
Cannot add listener from unmanaged object
error.
My code:
MessageThread.java
public class MessageThread extends RealmObject {
//Some member variables here - Id, title, etc
//This is the field that would indicate a new message if changed
private Date mLatestSentTimeStamp;
//Empty constructor
public MessageThread() { }
public MessageThread(JSONObject jsonOb) {
//Filling fields from JSON here
}
}
ThreadListActivity.java
public class ThreadListActivity {
private Realm realm;
#Override
public void onCreate() {
Realm.init();
realm = Realm.getDefaultInstance();
MyAPI.syncMessages();
}
MyAPI.java
static protected void syncMessages() {
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
JSONArray jsonMsgThreads = getJSONFromServer();
for (int i = 0; i < jsonMsgThreads.length(); i++) {
MessageThread m = new MessageThread(jsonMsgThreads.getJSONObject(i));
realm.copyToRealmOrUpdate(m);
}
realm.commitTransaction();
}
If it's not possible to add it anywhere here, is there anything that I could do to have a listener on each MessageThread?
public class ThreadListActivity {
private Realm realm;
private RealmResults<MessageThread> messageThreads;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
Realm.init(this);
realm = Realm.getDefaultInstance();
messageThreads = realm.where(MessageThread.class).findAll();
messageThreads.addChangeListener((results) -> {
// any message thread was modified
}
MyAPI.syncMessages();
}
So according to android developers: "Architecture Components provides ViewModel helper class for the UI controller that is responsible for preparing data for the UI. ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance."
In the code below there is an asynchronous class that gets called in deleteItem function. My question is this: Does ViewModel also handles the asynchronous calls made inside it or will cause memory leaks?
Thank you
public class BorrowedListViewModel extends AndroidViewModel {
private final LiveData<List<BorrowModel>> itemAndPersonList;
private AppDatabase appDatabase;
public BorrowedListViewModel(Application application) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
itemAndPersonList = appDatabase.itemAndPersonModel().getAllBorrowedItems();
}
public LiveData<List<BorrowModel>> getItemAndPersonList() {
return itemAndPersonList;
}
public void deleteItem(BorrowModel borrowModel) {
new deleteAsyncTask(appDatabase).execute(borrowModel);
}
private static class deleteAsyncTask extends AsyncTask<BorrowModel, Void, Void> {
private AppDatabase db;
deleteAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
#Override
protected Void doInBackground(final BorrowModel... params) {
db.itemAndPersonModel().deleteBorrow(params[0]);
return null;
}
}
}
I would provide an example, probably you need to modify the code.
First you need a live data change and subscribe to that in your view. Then in the controller you post the value telling the subscriber that something appends. This way asynchronously the view would get alerted.
private MutableLiveData<String> databaseLiveData = new MutableLiveData<>();
...
And in the deleteAsyncTask class you can add:
protected void onPostExecute(Void result) {
databaseLiveData.postValue("some data deleted");
}
And in the BorrowedListViewModel class this method to access from the view add this method:
public LiveData<String> getChanger() {
return databaseLiveData;
}
In the view e.g.Activity add this:
private BorrowedListViewModel mBorrowedListViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
BorrowedListViewModel = ViewModelProviders.of(this).get(BorrowedListViewModel.class);
subscribe();
}
private void subscribe() {
final Observer<String> liveDataChange = new Observer<String>() {
#Override
public void onChanged(#Nullable final String message) {
Log.d("Activity", message);
}
};
liveDataChange.getChanger().observe(this, liveDataChange);
}
Hope this help.
using this code:
public class App extends Application {
private static App instance;
#Override
public void onCreate() {
super.onCreate();
initRealmDB();
}
private void initRealmDB() {
instance = this;
Realm.init(this);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().modules(new SimpleRealmModule()).name("RealmSample.realm").build();
Realm realm = null;
try {
realm = Realm.getInstance(realmConfiguration);
realm.setDefaultConfiguration(realmConfiguration);
} finally {
if (realm != null) {
realm.close();
}
}
}
}
**In use:**
Realm realm = Realm.getDefaultInstance();
RealmResults<OrganizationModelClass> results = realm.where(OrganizationModelClass.class).findAll();
if(realm.isInTransaction())
{
realm.cancelTransaction();
}
realm.beginTransaction();
if (results != null) {
if(membershipList != null)
{
membershipList.clear();
}
for (int i = 0; i < results.size(); i++) {
Log.d(OrganizationActivity.class.getName(), " i :" + results.get(i).getCurrent_membership_uuid());
}
}
Is this best way to use?
Should i use singleton approach?
If there is another good approach to fulfill this task, please share with me.
i followed this https://dzone.com/articles/realm-practical-use-in-android
but this code is not working with this dependency: classpath "io.realm:realm-gradle-plugin:3.3.1"
Realm realm = Realm.getInstance(SimpleRealmApp.getInstance());
Is this best way to use?
No
Realm realm = Realm.getDefaultInstance(); // <-- opens Realm
RealmResults<OrganizationModelClass> results = realm.where(OrganizationModelClass.class).findAll();
if(realm.isInTransaction())
{
realm.cancelTransaction(); // <-- what if that transaction was important?
}
realm.beginTransaction();
if (results != null) {
if(membershipList != null)
{
membershipList.clear(); // <-- ??
}
for (int i = 0; i < results.size(); i++) {
Log.d(OrganizationActivity.class.getName(), " i :" + results.get(i).getCurrent_membership_uuid()); // <-- if the result set was modified here because of the transaction, then the RealmResults will update, and you'll skip elements
}
// <-- where is the commit?
} // <-- where is realm.close()?
Instead
try(Realm r = Realm.getDefaultInstance()) {
r.executeTransaction((realm) -> { // AS 3.0+ desugar
RealmResults<OrganizationModelClass> results = realm.where(OrganizationModelClass.class).findAll(); // <-- get in transaction
for (OrganizationModelClass model : results) { // uses snapshot() internally
Log.i(model.getClass().getName(), getCurrentMembershipUuid());
}
}
} // <-- auto-close because of try-with-resources
Should i use singleton approach?
Realm instances you open with getInstance()/getDefaultInstance() are thread-local and reference counted, so it is NOT suitable for being used as a singleton across the application. You need to open thread-local instances.
So on UI Thread, based on documentation:
// Setup Realm in your Application
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Realm.init(this);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder()
//.deleteIfMigrationNeeded()
.migration(new MyMigration())
.build();
Realm.setDefaultConfiguration(realmConfiguration);
}
}
// onCreate()/onDestroy() overlap when switching between activities.
// Activity2.onCreate() will be called before Activity1.onDestroy()
// so the call to getDefaultInstance in Activity2 will be fast.
public class MyActivity extends Activity {
private Realm realm;
private RecyclerView recyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setAdapter(
new MyRecyclerViewAdapter(this, realm.where(MyModel.class).findAllSortedAsync(MyModelFields.ID)));
// ...
}
#Override
protected void onDestroy() {
super.onDestroy();
realm.close();
}
}
// Use onCreateView()/onDestroyView() for Fragments.
// Note that if the db is large, getting the Realm instance may, briefly, block rendering.
// In that case it may be preferable to manage the Realm instance and RecyclerView from
// onStart/onStop instead. Returning a view, immediately, from onCreateView allows the
// fragment frame to be rendered while the instance is initialized and the view loaded.
public class MyFragment extends Fragment {
private Realm realm;
private RecyclerView recyclerView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
View root = inflater.inflate(R.layout.fragment_view, container, false);
recyclerView = (RecyclerView) root.findViewById(R.id.recycler_view);
recyclerView.setAdapter(
new MyRecyclerViewAdapter(getActivity(), realm.where(MyModel.class).findAllSortedAsync(MyModelFields.ID)));
// ...
return root;
}
#Override
public void onDestroyView() {
super.onDestroyView();
realm.close();
}
}
For background thread, see the docs:
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try (Realm realm = Realm.getDefaultInstance()) {
// No need to close the Realm instance manually
}
}
});
thread.start();
If you want to use Realm as a singleton, you have to use a class that can increment, decrement, and get instance without incrementing ref count for thread local Realms, kinda like this experiment here.
public RealmController(Context context) {
realm = Realm.getDefaultInstance();
}
public static RealmController with(Activity activity) {
if (instance == null) {
instance = new RealmController(activity.getApplication());
}
return instance;
}
public static RealmController with(Application application) {
if (instance == null) {
instance = new RealmController(application);
}
return instance;
}
public static RealmController getInstance() {
if (instance == null) {
instance = new RealmController(SysApplication.getAppContext());
}
return instance;
}
I already gone through these similar questions for the issue, but could not find the answer
SO question1 , SO question2 and SO question3
My application flow is on button click, network is requested as follows using Volley. Included only relevant code . Error getting in ActivityCustomer in the following line
Object obj = realmObj.where(ExplorerFolderData.class)
.equalTo(Constants.DBPARENTID,"0")
.or()
.equalTo(Constants.DBPARENTID,"-1")
.findAllAsync();
Error:
Caused by: java.lang.IllegalStateException: Realm access from
incorrect thread. Realm objects can only be accessed on the thread
they were created.
1) MyApplication
public class MyApplication extends Application
{
private static Context appContext;
#Override
public void onCreate() {
super.onCreate();
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this)
.name(Realm.DEFAULT_REALM_NAME)
.schemaVersion(0)
.deleteRealmIfMigrationNeeded()
.build();
Realm.setDefaultConfiguration(realmConfiguration);
appContext = getApplicationContext();
}
public static Context getAppContext(){
return appContext;
}
}
2) Interface OnAsyncTaskComplition
//This interface will get back the status to Activity/Fragment
public interface OnAsyncTaskComplition {
public void networkResponse(ResultObject responseObject);
}
3) NetworkProcessor
public class NetworkProcessor{
private OnAsyncTaskComplition mCaller;
public NetworkProcessor(Activity activity){
//setting caller Activity/Fragment to get back data
mCaller=(OnAsyncTaskComplition)activity;
processNetworkData();
}
//Method for Volley Network Procesing
public void processNetworkData(){
JsonObjectRequest jsonObjReq = new JsonObjectRequest(methodReq,urlBuffer.toString(),null,
new Response.Listener<JSONObject>(){
#Override
public void onResponse(JSONObject response) {
JsonProcessor jsonProcessor=new JsonProcessor();
mCaller.networkResponse(jsonProcessor.getThingList(response));
}
}, new Response.ErrorListener(){
#Override
public void onErrorResponse(VolleyError error) {
//Handle error also back to caller
}
});
}
}
4) JsonProcessor
public class JsonProcessor {
public status void getThingList(JSONObject response){
boolean status=false;
try{
RealmProcessor realmProcessor=RealmProcessor.with(MyApplication.getAppContext());
Realm realmObj = realmProcessor.getRealm();
//Code for setting values to RealmObject class ExplorerFolderData
realmObj.beginTransaction();
realmObj.copyToRealm(ExplorerFolderData RealmObject which has values populated);
realmObj.commitTransaction();
status=true;
}catch(Exception e){
}
}
}
5) RealmProcessor
public class RealmProcessor {
private static RealmProcessor instance;
private Realm realm;
private RealmProcessor(Context context) {
realm = Realm.getDefaultInstance();
}
public static RealmProcessor with(Context context) {
if (instance == null) {
instance = new RealmProcessor(context);
}
return instance;
}
public Realm getRealm() {
return realm;
}
}
6) Activity class ActivityCustomer
public class ActivityCustomer extends AppBaseActivity implements OnAsyncTaskComplition
{
//Method called on Button click
private void callNetwork(){
new NetworkProcessor(this);
}
#Override
public void networkResponse(ResultObject responseObject) {
new ExplorerDBOperation().execute();
}
class ExplorerDBOperation extends AsyncTask<Void,Boolean,Boolean> {
ProgressDialog dialog;
#Override
protected Boolean doInBackground(Void... params) {
RealmProcessor realmProcessor=RealmProcessor.with(MyApplication.getAppContext());
Realm realmObj = realmProcessor.getRealm();
//ERROR OVER HERE
Object obj = realmObj.where(ExplorerFolderData.class)
.equalTo(Constants.DBPARENTID,"0")
.or()
.equalTo(Constants.DBPARENTID,"-1")
.findAllAsync();
return true;
}
}
I am getting realm object using the same line in Activity as well as JsonProcessor class. What is the mistake I am making over here.
The way you are set up with the singleton you have only 1 Realm instance.
If you call realmProcessor.getRealm(); in thread A, and then call it again in thread B, they get back the same instance. Realm does not allow sharing instances between threads. Since the AsyncTask's doInBackground runs on a separate thread, this is not working.
Changing to this will rid you of the error. However you have some redesigning to do.
#Override
protected Boolean doInBackground(Void... params) {
Realm realmObj = Realm.getDefaultInstance();
try {
Object obj = realmObj.where(ExplorerFolderData.class)
.equalTo(Constants.DBPARENTID,"0")
.or()
.equalTo(Constants.DBPARENTID,"-1")
.findAll();
} catch (Exception ex) {
// handle error
} finally {
realmObj.close();
}
return true;
}
Note that you are responsible for each and every realm instance. This means that you must manually ensure that you close every instance once you're done with it. A common practice for AsyncTasks is that you wrap your doInBackground operations in a try/catch/finally and close the realm instance in the finally block to ensure it gets closed.
See more in the docs
The problem is that your Realm object is created only once on first call to RealmProcessor.with (because it is singleton).Lets say that JsonProcessor::getThingList happen on Thread#1 and ExplorerDBOperation::doInBackground() happens on another Thread#2.
So if JsonProcessor::getThingList call will be prior to ExplorerDBOperation::doInBackground() then Realm object will be bound to Thread#1, and when you try to access it from Thread#2 you will get that error.