2
votes

The sunflower example app by Google uses a private Class with a companion object to implement a Singleton pattern of their repository instead of simply implementing the repository as an (inherently Singleton) Object.

This is the first time I've seen a Singleton implemented this way in Kotlin instead of implementing it as an Object. In what context(s) should this private constructor implementation be used instead of the more common Object implementation?

class GardenPlantingRepository private constructor(
  private val gardenPlantingDao: GardenPlantingDao
) {
  suspend fun createGardenPlanting(plantId: String) {
    withContext(IO) {
      val gardenPlanting = GardenPlanting(plantId)
      gardenPlantingDao.insertGardenPlanting(gardenPlanting)
    }
  }

  suspend fun removeGardenPlanting(gardenPlanting: GardenPlanting) {
    withContext(IO) {
      gardenPlantingDao.deleteGardenPlanting(gardenPlanting)
    }
  }

  fun getGardenPlantingForPlant(plantId: String) =

    gardenPlantingDao.getGardenPlantingForPlant(plantId)

  fun getGardenPlantings() = gardenPlantingDao.getGardenPlantings()

  fun getPlantAndGardenPlantings() = gardenPlantingDao.getPlantAndGardenPlantings()

  companion object {
    // For Singleton instantiation
    @Volatile private var instance: GardenPlantingRepository? = null

    fun getInstance(gardenPlantingDao: GardenPlantingDao) =
      instance ?: synchronized(this) {
        instance ?: GardenPlantingRepository(gardenPlantingDao).also { instance = it }
      }
  }
}
1

1 Answers

3
votes

Using an object is an issue if your singleton instance needs parameters, like in this case here, with GardenPlantingDao, as they cannot take constructor arguments. This comes up frequently on Android, as there's many cases where singletons requires a Context to operate.

You could still use an object in these cases, but it would either be unsafe or inconvenient:

  • The first option would be to provide it with its dependencies using a setter method before using any of its other methods. This would mean that every other method would have to check if the dependencies have been initialized, and probably throw exceptions in case they haven't, which leads to runtime problems.
  • Alternatively, you could require any dependencies as arguments to each method of the singleton, which is tedious at the call site.

Hence the "traditional" way of implementing a singleton with a private constructor and a factory method instead.