web-dev-qa-db-fra.com

Singleton avec paramètre en Kotlin

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 :)

26
LordRaydenMK

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()
    }
}
56
fal

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.

METTRE À JOUR:

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)
}
17
voddan

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)
2

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.

1
Yisus Threepwood

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)
}
1
aminography

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
0
William Leung

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.

0
Louis CAD
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
        }
    }

}
0
IT Developers