Search code examples
androidandroid-roomandroid-viewmodel

How do i properly initialize Room (AndroidViewModel)?


I am using a Room Database with AndroidViewModel (without Factory)

class RoomModel(app: Application) : AndroidViewModel(app) {
        // ....
}

and I am not sure about how to correctly initialize that, and also if I could use one-time initialization for whole app.

I have two ways to deal with initializations which seem to work OK:

Initializing in onViewCreated():

...
private lateinit var database: RoomModel
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
        database = ViewModelProvider(this)[RoomModel::class.java]
        ...
}

Initializing by activityViewModels():

...
private val mData: RoomModel by activityViewModels()
...

The both seem to work, but not sure which one would never lead to an app crash at some point.

Also, I would like to know, if it's a good practice using one shared reference variable of my RoomModel declared and initialized in a base Fragment that is used by other fragments like this:

class BaseFragment : Fragment() {
        ...
        lateinit var database: RoomModel
        // Or
        val database: RoomModel by activityViewModels()
        ...
        
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
                ...
                database = ViewModelProvider(this)[RoomModel::class.java]
                ...
        }
}

Some other fragments extended by BaseFragment() like this:

class FragmentA : BaseFragment() {
        ...
            
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
            ...
            // use the reference variable database to observe or add or edit data
        }
} 

and

class FragmentB : BaseFragment() {
        ...
            
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
            ...
            // use the reference database to observe or add or edit data

        }
} 

and so on...

In this way I have only to init the Room once and use it without reinitializing in each fragment I need to access to. Is that a good idea or rather do I need to use individual declaration and initialization of RoomModel in each Fragment I need access to?


Solution

  • Your Room Database instance should be a singleton. The instance should live as long as the application itself. It is costly to initialize it every time you need to use it. You should initialize Room Database in the application and each ViewModel that needs Room Database instance will get it from the application as a dependency. Or you can provide the Room Database as a dependency to your ViewModel, but then you need to create a factory for your ViewModel. You can also use a Dependency Injection library to manage it for you, but it can be overwhelming at first.

    Without using any dependency injection library, you can see the example below.

    Override the Application, and don't forget to declare it in the AndroidManifest.xml file.

    AndroidApp.kt

    class AndroidApp : Application() {
    
        lateinit var myRoomDatabase: MyRoomDatabase
    
        override fun onCreate() {
            super.onCreate()
            myRoomDatabase = Room
                .databaseBuilder(applicationContext, MyRoomDatabase::class.java, "my-room-database")
                .build()
        }
    }
    

    AndroidManifest.xml

    ...
    <application
            android:name=".AndroidApp"
    ...
    >
    ...
    

    When you extends the AndroidViewModel you have the Application instance as a dependency. Cast it to AndroidApp and now you can get myRoomDatabase.

    MainViewModel.kt

    class MainViewModel(app:Application) : AndroidViewModel(app) {
        private val myRoomDatabase = (app as AndroidApp).myRoomDatabase
        ...
    }
    

    FragmentAViewModel.kt

    class FragmentAViewModel(app:Application) : AndroidViewModel(app) {
        private val myRoomDatabase = (app as AndroidApp).myRoomDatabase
        ...
    }
    

    FragmentBViewModel.kt

    class FragmentBViewModel(app:Application) : AndroidViewModel(app) {
        private val myRoomDatabase = (app as AndroidApp).myRoomDatabase
        ...
    }