Search code examples
androidkotlinandroid-roomviewmodel

How to Instantiate Two ViewModels for Two Databases in One Fragment


I have working app that uses Room database to maintain a sports league roster, with a recycler adapter and a single RosterViewModel class and Factory. Several fragments each instantiate a viewmodel to access the database. The roster db has one table, a record for each player.

In each fragment:
    private val viewModel : RosterViewModel by activityViewModels {
        RosterViewModelFactory(
                (activity?.application as RosterDbApplication).myDatabase.itemDao()
        )
    }

In the viewModel:

class RosterViewModelFactory(private val itemDao : DbDao) : ViewModelProvider.Factory {
    
    override fun <T : ViewModel> create(modelClass : Class<T>) : T {
        if (modelClass.isAssignableFrom(RosterViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return RosterViewModel(itemDao) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }

Now I'm adding another fragment to use an inMemoryDatabase (to track a match between 2 teams while the app is active). To populate player records in the match db, I need to access the roster db. The match db has two tables. The player record is not the same as in the roster db.

Defining both ViewModels in the (new) match Fragment:

val leagueViewModel : RosterViewModel by activityViewModels { RosterViewModelFactory(
            (activity?.application as RosterDbApplication).myDatabase.itemDao())
        }
val matchViewModel : MatchViewModel by activityViewModels() {MatchViewModelFactory(
        (activity?.application as MatchDbApplication).matchDatabase.matchDao())
        }

The (new) matchViewModel Factory:

class MatchViewModelFactory(private val matchDao : MatchDao) : ViewModelProvider.Factory {
   
   override fun <T : ViewModel> create(modelClass : Class<T>) : T {
      if (modelClass.isAssignableFrom(MatchViewModel::class.java)) {
         @Suppress("UNCHECKED_CAST")
         return MatchViewModel(matchDao) as T
      }
      throw IllegalArgumentException("Unknown ViewModel class")
   }

The application classes:

class RosterDbApplication : Application() {
    val myDatabase : PlayerDatabase by lazy { PlayerDatabase.getDatabase(this) }
}

class MatchDbApplication : Application() {
    val matchDatabase : TeamsDatabase by lazy { TeamsDatabase.getMatchDb(this) }
}

Whichever of the two viewmodels I instantiate first works. But the second one always throws a fatal exception. When matchViewModel is first, I get this:

*RosterDbApplication cannot be cast to MatchDbApplication*

When rosterViewModel is first, I get the reverse:

   *MatchDbApplication cannot be cast to RosterDbApplication*  

Clearly the two instantiations are not completely independent, but I can't see why. The viewmodel code snippets come from the Android Developers Codelabs, if RAM serves (it's been over a year).

I've tried various permutations of the ViewModel definitions without success. The other SO question that I found concerning two view models in one fragment does not apply. Numerous ViewModel tutorials didn't help.

Is there another way to build the viewmodels? This is a single-user unpublished application, with a small (<300 records) db, so I'd be willing to access the db in the main thread if necessary.


Solution

  • You seem to be trying to use two application classes but an Android app has exactly one.

    Easiest thing to do would probably be to just put both databases in one application class.

    class SingleApplication : Application() {
        val playerDatabase : PlayerDatabase by lazy { PlayerDatabase.getDatabase(this) }
        val matchDatabase : TeamsDatabase by lazy { TeamsDatabase.getMatchDb(this) }
    }