J'essaie de convertir une application Android de Java à Kotlin. Il y a quelques singletons dans l'application. J'ai utilisé un objet compagnon pour les singletons sans paramètres de constructeur. Il existe un autre singleton qui prend un paramètre constructeur.
Code Java:
public class TasksLocalDataSource implements TasksDataSource {
private static TasksLocalDataSource INSTANCE;
private TasksDbHelper mDbHelper;
// Prevent direct instantiation.
private TasksLocalDataSource(@NonNull Context context) {
checkNotNull(context);
mDbHelper = new TasksDbHelper(context);
}
public static TasksLocalDataSource getInstance(@NonNull Context context) {
if (INSTANCE == null) {
INSTANCE = new TasksLocalDataSource(context);
}
return INSTANCE;
}
}
Ma solution en kotlin:
class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
private val mDbHelper: TasksDbHelper
init {
checkNotNull(context)
mDbHelper = TasksDbHelper(context)
}
companion object {
lateinit var INSTANCE: TasksLocalDataSource
private val initialized = AtomicBoolean()
fun getInstance(context: Context) : TasksLocalDataSource {
if(initialized.getAndSet(true)) {
INSTANCE = TasksLocalDataSource(context)
}
return INSTANCE
}
}
}
Est-ce que je manque quelque chose? Sécurité du fil? Paresse ?
Il y avait quelques questions similaires mais je n'aime pas les réponses :)
Voici une alternative intéressante aux composants d'architecture de Google exemple de code , qui utilise la fonction also
:
class UsersDatabase : RoomDatabase() {
companion object {
@Volatile private var INSTANCE: UsersDatabase? = null
fun getInstance(context: Context): UsersDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(context.applicationContext,
UsersDatabase::class.Java, "Sample.db")
.build()
}
}
Je ne suis pas tout à fait sûr de savoir pourquoi vous auriez besoin d'un tel code, mais voici mon meilleur atout:
class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
private val mDbHelper = TasksDbHelper(context)
companion object {
private var instance : TasksLocalDataSource? = null
fun getInstance(context: Context): TasksLocalDataSource {
if (instance == null) // NOT thread safe!
instance = TasksLocalDataSource(context)
return instance!!
}
}
}
Ceci est similaire à ce que vous avez écrit et a la même API.
Quelques notes:
N'utilisez pas lateinit
ici. Son objectif est différent et une variable nullable est idéale ici.
Que fait checkNotNull(context)
? context
n'est jamais nul ici, ceci est garanti par Kotlin. Tous les contrôles et assertions sont déjà implémentés par le compilateur.
Si tout ce dont vous avez besoin est une instance paresseuse de la classe TasksLocalDataSource
, utilisez simplement un tas de propriétés paresseuses (dans un objet ou au niveau du package):
val context = ....
val dataSource by lazy {
TasksLocalDataSource(context)
}
si vous voulez passer un paramètre au singleton d'une manière plus facile, je pense que c'est mieux et plus court
object SingletonConfig {
private var retrofit: Retrofit? = null
private const val URL_BASE = "https://jsonplaceholder.typicode.com/"
fun Service(context: Context): Retrofit? {
if (retrofit == null) {
retrofit = Retrofit.Builder().baseUrl(URL_BASE)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
et vous l'appelez de cette manière simple
val api = SingletonConfig.Service(this)?.create(Api::class.Java)
Vous pouvez déclarer un objet Kotlin, en surchargeant l'opérateur "invoke" .
object TasksLocalDataSource: TasksDataSource {
private lateinit var mDbHelper: TasksDbHelper
operator fun invoke(context: Context): TasksLocalDataSource {
this.mDbHelper = TasksDbHelper(context)
return this
}
}
Quoi qu'il en soit, je pense que vous devriez injecter TasksDbHelper à TasksLocalDataSource au lieu d'injecter un contexte.
Thread-Safe Solution
- Maximum Simplicity in usage
Vous pouvez créer une classe qui implémente la logique de singleton et contient l'instance de singleton. Il instancie l'instance à l'aide de double vérification du verrouillage dans un bloc synchronized afin d'éliminer la possibilité d'une situation de concurrence critique dans les environnements multi-tâches.
Singleton.kt
open class Singleton<out T, in A>(private val constructor: (A) -> T) {
@Volatile
private var instance: T? = null
fun getInstance(arg: A): T {
return when {
instance != null -> instance!!
else -> synchronized(this) {
if (instance == null) instance = constructor(arg)
instance!!
}
}
}
}
.
Usage
Dans la classe cible qui devrait être un singleton, écrivez un companion object
qui s'étend au-dessus de la classe. La classe Singleton
est générique et accepte les types de classe cible et ses paramètres nécessaires en tant que paramètres génériques. Il a également besoin d'une référence au constructeur de la classe cible utilisé pour l'instanciation:
class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {
...
companion object : Singleton<TasksLocalDataSource, Context>(::TasksLocalDataSource)
}
solution avec paresseux
class LateInitLazy<T>(private var initializer: (() -> T)? = null) {
val lazy = lazy { checkNotNull(initializer) { "lazy not initialized" }() }
fun initOnce(factory: () -> T) {
initializer = factory
lazy.value
initializer = null
}
}
val myProxy = LateInitLazy<String>()
val myValue by myProxy.lazy
println(myValue) // error: Java.lang.IllegalStateException: lazy not inited
myProxy.initOnce { "Hello World" }
println(myValue) // OK: output Hello World
myProxy.initOnce { "Never changed" } // no effect
println(myValue) // OK: output Hello World
Si le seul paramètre dont vous avez besoin est l'application Context
, vous pouvez l'initialiser à une val
de niveau supérieur, au début d'une ContentProvider
, comme le fait le SDK Firebase.
Étant donné que déclarer ContentProvider
est un peu fastidieux, j'ai créé une bibliothèque fournissant une propriété de niveau supérieur nommée appCtx
pour tous les endroits où vous n'avez pas besoin d'une activité ou d'un autre contexte lié au cycle de vie.
class CarsRepository(private val iDummyCarsDataSource: IDummyCarsDataSource) {
companion object {
private var INSTANCE: CarsRepository? = null
fun getInstance(iDummyCarsDataSource: IDummyCarsDataSource): CarsRepository {
if (INSTANCE == null) {
INSTANCE = CarsRepository(
iDummyCarsDataSource = iDummyCarsDataSource)
}
return INSTANCE as CarsRepository
}
}
}