I have created an application in Android that uses realm.
I have a splash screen that loads all the data needed to operate, and a number of activities that use that data.
That data is stored in an Application class extension as static variables.
My problem is that when I try to manipulate the data in other activities I get an error about the realm database being closed.
I have solved this by creating a static reference to my database on Application. Opening it in OnCreate() and closing it in OnTerminate() but I get the feeling that this is wrong.
I have also solved it by creating unmanaged objects (i.e. after retrieving them from Realm using copyFromRealm) and then when I need to do alterations copying them back into a realm instance I just created.
What is the correct way to solve this problem?
I have a splash screen that loads all the data needed to operate, and a number of activities that use that data.
That data is stored in an Application class extension as static variables.
That's very interesting - have you tried to put the application in background on a screen, then go to Android Studio's Logcat tab, go to the additional menu items in the bottom left, and click TERMINATE?
Fun fact is that if your application is in background when you do this, and then you re-open the app from the launcher, you'll have the exact same effect as if your app had been killed via low memory condition.
In your case, you'll just crash with an NPE on pretty much any screen that is not the splash screen! Very, very interesting.
(Also, Application.onTerminate() is never called. So that's not helpful either.)
Anyways, to get back to your original question,
My problem is that when I try to manipulate the data in other activities I get an error about the realm database being closed.
What is the correct way to solve this problem?
The section Best Practices: Controlling the lifecycle of Realm instances section of the official documentation is fairly helpful in this regard.
public class MyActivity extends Activity {
private Realm realm;
private RealmResults<Dog> dogs;
private RealmChangeListener<RealmResults<Dog>> realmChangeListener = (results) -> {
// do something on first load + future changes of Dogs
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
setContentView(R.layout.activity_main);
dogs = realm.where(Dog.class).sort("age").findAllAsync();
dogs.addChangeListener(realmChangeListener);
}
#Override
protected void onDestroy() {
super.onDestroy();
dogs.removeAllChangeListeners();
dogs = null;
realm.close();
realm = null;
}
}
or
public class MyFragment extends Fragment {
private Realm realm;
private RealmResults<Dog> dogs;
private RealmChangeListener<RealmResults<Dog>> realmChangeListener = (results) -> {
// do something on first load + future changes of Dogs
};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
View root = inflater.inflate(R.layout.fragment_view, container, false);
return root;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
dogs = realm.where(Dog.class).sort("age").findAllAsync();
dogs.addChangeListener(realmChangeListener);
}
#Override
public void onDestroyView() {
super.onDestroyView();
dogs.removeAllChangeListeners();
dogs = null;
realm.close();
realm = null;
}
}
Realm instances are ref-counted on a given thraead, so I guess the answer is, "keep one open while you need it". And of course, close it when you no longer do.
Related
As we can find in Google site, it says:
If the activity is re-created, it receives the same MyViewModel instance that was created by the first activity. When the owner activity is finished, the framework calls the ViewModel objects's onCleared() method so that it can clean up resources.
So, right now, I do not want that feature. I want to recreate the ViewModel each time the Fragment gets created. Because I have a reference of the object using the id.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SaleViewModel viewModel = ViewModelProviders.of(this).get(SaleViewModel.class);
viewModel.getSaleLiveData().observe(this, new Observer<List<SaleEntity>>() {
#Override
public void onChanged(#Nullable List<SaleEntity> saleEntities) {
initAll();
}
});
}
Inside SaleViewModel, I have the reference of the Store (which is set activities before):
private static FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
private static DatabaseReference HOT_STOCK_REF = FirebaseDatabase.getInstance().getReference("users").child(user.getUid()).child(FirebaseStoreRef.getInstance().getStoreEntity().getId()).child("income");
private FirebaseQueryLiveData liveData = new FirebaseQueryLiveData(HOT_STOCK_REF);
With that being said, I need to recreate the ViewModel every single time because of the reference I need to refresh (otherwise, I will be getting the first reference set).
Thank you in advance.
I use Realm in a Project. I built this RealmActivity to easily use Realm in code with getActivity.getRealm().
public abstract class RealmActivity extends AppCompatActivity {
private Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
realm = Realm.getDefaultInstance();
super.onCreate(savedInstanceState);
}
#Override
protected void onDestroy() {
super.onDestroy();
if (!realm.isClosed()) {
realm.close();
}
}
public Realm getRealm() {
return realm;
}
Now I want to delete the Realm-file to clear the data for this session/user. To do this, all RealmInstances have to be closed. I tried to start a Logout-Activity with an intent to clear the activityStack. It works fine for my Nexus 5 (Android 6) but not for my Nexus 4 (Android 5).
My idea was to create a Singleton for the RealmInstance, which used in the UI-Thread
public abstract class RealmActivity extends AppCompatActivity {
private Realm realm;
#Override
protected void onCreate(Bundle savedInstanceState) {
realm = ManagerLayer.getInstance().getRealm();
super.onCreate(savedInstanceState);
}
#Override
protected void onDestroy() {
super.onDestroy();
}
public Realm getRealm() {
return realm;
}
}
Now, I got only one Instance and can close it before deleting the file. But I never close the RealmInstance when I´m not logging out. Is that an Problem?
But when I clear the ActivityStack and the onDestroy() method isn´t called, the RealmInstance for the Activity gets never closed, too. Right?
Can I use a Singleton for the UI-Thread realmInstance? I never pass it to another Thread and never close it.
Or is there another solution to close all RealmInstances to delete the RealmFile?
If you want to keep the instance of some object that is needed for the whole application, you should keep this instance in:
The Application class, extending it and setting the android:name in the AndroidManifest.xml.
Another class that is not an Android component, a simple java class.
Talking about lifecycles, if you have some action that closes the Realm instance and this is enough, simply create a clear method in your singleton and call it properly.
If this is not the case, you have to keep in mind that you can never warranty that the onDestroy method is called, but if you don't release the resources because the process has been killed, you don't have to worry at all, since when a process is killed all the objects not persisted will be completely released, even if they are using the filesystem or the network.
If I have a MainActivity like this:
public class MainActivity extends AppCompatActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Set up database
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).build();
Realm.deleteRealm(realmConfiguration); // Clean slate
Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default
realm = Realm.getDefaultInstance();
}
#Override
public void onDestroy() {
realm.close();
super.onDestroy();
}
}
And I use realm.getDefaultInstance() in another class (same thread) like this:
public class ViewBookActivity extends Activity {
private Realm realm;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_scan_result);
realm = Realm.getDefaultInstance();
}
}
Should I then call realm.close() in onDestroy() in ViewBookActivity? Or is it sufficient to close it in MainActivity?
Realm documentation says:
Realm instances are reference counted, which means that if you call
getInstance() twice in a thread, you will also have to call close()
twice as well.
But I'm not sure if this applies to getDefaultInstance().
Also, is it OK to stick to Realm.getDefaultInstance(), even in other threads, if I close it when I'm done writing to it? I don't really understand the potential usage of Realm.getInstance(Context context).
Thanks
Best practise is that if you open the Realm in onCreate you should close it again in onDestroy in all your activities as it means you reference count will reach 0 when all your activities have closed. So in your case: Yes, you should do it in both MainActivity and ViewBookActivity
With regard to Realm.getDefaultInstance(). That is just a shorthand for Realm.getInstance(myConfig), so you have to call close() on those as well.
Realm.getInstance(Context) is just a shorthand for Realm.getInstance(new RealmConfiguration.Builder(context).build()) and is intended to make it really easy to get started with Realm in small examples. If you plan to create a larger app you should create your configuration manually. But I agree it can be confusing and we should probably consider removing it all together.
I'm a beginner to android development, and I'm trying to write my code in an MVC pattern, but I'm having trouble understanding how a model would work. As far as I can tell every time you start a new activity with an intent you are not able to pass a model along with it. As far as i can tell you'd have to reinitialize it each time you start a new activity. Am I missing something? I looked into Parcelable, but it seems that you loose your methods if you make your model Parcelable. right now I'm building a log in system, which checks my local sqllite db on start up if the user has already logged in, and if so it passes to another activity, otherwise it passes to the log in activity, but I wan't to keep that user model alive through all the activities. Is thee a way to do that?
You might want to also consider keeping a static reference around to the model data that you want to share across activities so that you don't have to keep serializing/deserializing the model when switching between activities. You can get away with using Parcelable if your models are small, but at some point, performance may become an issue.
I'm working on a project where we keep the models in a Singleton that we can access throughout the app, and although I generally hate Singleton's for how they can make unit testing more difficult, I have found this approach to perform better with larger models than trying to rely on Android's serialization mechanism.
Here's is a very rough example of what I mean (disclaimer: I have not actually run tested this code, but I hope this illustrates the concept):
You might have a singleton class that I terribly called Models
public class Models {
private static Models instance;
private boolean isInitialized = false;
private User user;
private OtherInterestingModel otherInterestingModel;
private Models() {
}
public static synchronized Models getInstance() {
if (instance == null) {
instance = new Models();
}
return instance;
}
public void loadModels() {
if (!isInitialized) {
/*
* One-time model initialization here.
*/
isInitialized = true;
}
}
public User getUser() {
return user;
}
public OtherInterestingModel getOtherInterestingModel() {
return otherInterestingModel;
}
}
In your LoginActivity, you can initialize the Models class, say, in your onCreate():
public class LoginActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Models.getInstance().loadModels();
User user = Models.getInstance().getUser();
OtherInterestingModelData otherData = Models.getInstance().getOtherInterestingModel();
// Do something with the model data...
}
/*
* This might be called after the user enters data and clicks a login button...
*/
private void login() {
startActivity(new Intent(this, AwesomeLoggedInActivity.class));
}
}
Once the user successfully logs into your app, you could have basically the same code in your main activity:
public class AwesomeLoggedInActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Models.getInstance().loadModels();
User user = Models.getInstance().getUser();
OtherInterestingModelData otherData = Models.getInstance().getOtherInterestingModel();
// Do something with the model data...
}
}
Notice that by having a Singleton, you avoided having to serialize the model data by passing it through the intent that started the main activity.
Yes, you can do that with the Parcelable interface.
You do not lose your class's methods when you implement the Parcelable interface. The interface simply defines a method for writing your member variables to a Parcel object when you need to pass the object around.
Once you retrieve the data from your Intent via getParcelableExtra(), the object is recreated from the Parcel and you can once again treat it as an instance of whatever class it is.
For example, if you have a User class that extends Parcelable, you can bundle it with an Intent by calling putExtra("user", myUser). myUser is then (behind the scenes) packed into a Parcel and attached to the Intent. In your next Activity, you can retrieve that User object with User myUser = (User) getParcelableExtra("user");, and the Parcel will be unpacked and returned to you. You wil once again have a fully functioning User object.
This article shows the different database approaches nicely:
database approaches
I use approach 2: An Application object which holds the single LocalDatabaseAdapter I made. This holds a DatabaseOpenHelper etc.
public class MyApplication extends Application {
private static LocalDbAdapter lDb;
public void onCreate() {
super.onCreate();
MyApplication.context = getApplicationContext();
lbm = LocalBroadcastManager.getInstance(context);
[..]
}
public static LocalDbAdapter getLDb(){
if(lDb==null){
lDb = new LocalDbAdapter(context);
}
if(lDb.isOpen()){
return lDb;
}else{
return lDb.open();
}
}
#Override
public void onTerminate() {
super.onTerminate();
lDb.close();
}
Now when I start the camera app and then return to my main activity, I get the database never closed error. As it points out, this db was created in the Application context, so why is it a problem that I dont close it in my Activity, I thought that was the idea. An important adavantage of having only one database object is that all methods of LocalDbAdapter that do any writing use the protected(this) statement around the insert or update.
In OnActivityResult, after the camera has taken a picture, I acquire the database like
LocalDbAdapter ldb = MyApplication.getLDb();
This has me seriously bummed out. If I close it in onPause of my MainActivity, I'm afraid that a background service using the same object gets in trouble, and Im even keeping the object local to the onActivityResult...