3 votes

Android Room - Éviter de passer le contexte dans le Singleton

J'essaie de migrer un projet vers Android Room. Après avoir lu la documentation d'Android Room, j'ai remarqué qu'un Singleton était approprié pour accéder à ma base de données.

Citation du développeur Android :

Note : Si votre application fonctionne dans un seul processus, vous devriez suivre le modèle de conception singleton lors de l'instanciation d'un objet AppDatabase. Chaque instance de RoomDatabase est assez coûteuse, et vous avez rarement besoin d'accéder à plusieurs instances au sein d'un même processus.

J'ai écrit le code suivant :

@Database(entities = {Category.class, News.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

    private static final String DB_NAME = "database.db";
    private static AppDatabase instance;

    public abstract CategoryDao categoryDao();
    public abstract NewsDao newsDao();

    private AppDatabase () {}

    public static AppDatabase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDatabase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(context.getApplicationContext(),
                            AppDatabase.class, DB_NAME).build();
                }
            }
        }
        return instance;
    }
}

Il s'agit simplement d'une double vérification du verrouillage de Singleton.

J'ai lu quelques guides/tutoriels et presque tout le monde a une approche similaire, mais je vois quelques problèmes avec cette approche :

  • Nécessité de passer un Context à chaque fois, même si vous n'en avez besoin qu'une seule fois pour initialiser le Singleton.
  • Que se passe-t-il si j'ai besoin d'accéder à la base de données sans un Context disponible ?
  • Est-il même admissible d'envoyer des paramètres à un Singleton ?

Une idée pour mettre en place un Singleton de base de données des salles qui résoudrait ces problèmes ?

J'aimerais éviter les bibliothèques DI comme Dagger2 si possible.

1voto

ArbenMaloku Points 498

Vous pouvez initialiser votre base de données et enregistrer une instance de celle-ci dans votre classe d'application de la manière suivante.

public class MyApplication extends Application {

    public AppDatabase database;

    @Override
    public void onCreate() {
        super.onCreate();

        database = AppDatabase.getInstance(this)
    }
}

En fin de compte, vous pouvez accéder à votre référence de la manière suivante

((MyApplication)activity).dabase

J'espère que cela vous aidera.

0voto

Jatin Sahgal Points 89
@Database(entities = {Category.class, News.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

   private static final String DB_NAME = "database.db";
   private static AppDatabase instance;
   public abstract CategoryDao categoryDao();
   public abstract NewsDao newsDao();

   private AppDatabase () {}

   // Use this to call on any place
   public static AppDatabase getInstance() {
      return instance;
   }
   // Use once to Create and setup the object
   public static AppDatabase setInstance(Context context) {
      if (instance == null) {
         synchronized (AppDatabase.class) {
            if (instance == null) {
                instance = Room.databaseBuilder(context.getApplicationContext(),
                        AppDatabase.class, DB_NAME).build();
            }
        }
     }
     return instance;
   }
}

// Second you need to set instance on Application Class which create and make your DB 
  //Ready for Use Before anything perform
public class MyApplication extends Application {
   @Override
   public void onCreate() {
      super.onCreate();
      AppDatabase.setInstance(this)
   }
}

A utiliser

//Need to call
AppDatabase.getInstance().categoryDao();

0voto

Dickson Points 11

Vous pouvez instancier la base de données puis verrouiller l'instance.

De même, l'appel context.applicationContext renvoie le contexte de l'élément unique , Application mondiale du processus en cours. Le cycle de vie de ce contexte est distinct de celui du processus en cours. context qui est lié à la durée de vie du processus plutôt qu'au composant actuel.

/**
 * The Room Database that contains the item table.
 */
@Database(entities = arrayOf(Item::class), version = 1, exportSchema = false)
abstract class ItemDb : RoomDatabase() {
    abstract fun itemDao(): ItemDao

    companion object {
        private var INSTANCE: ItemDb? = null

        private val lock = Any()

        @JvmStatic
        fun getInstance(context: Context): ItemDb {

            // When calling this instance concurrently from multiple threads we're in serious trouble:
            // So we use this method, 'synchronized' to lock the instance
            synchronized(lock) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.applicationContext, ItemDb::class.java, "items.db").build()
                }
                return INSTANCE!!
            }
        }
    }
}

0voto

Marian Paździoch Points 164

Il y a beaucoup d'éléments à prendre en compte. Cela dépend totalement de votre cas d'utilisation - car il se peut que votre application ne touche la base de données que sur un seul thread, vous n'avez donc pas besoin de synchronisation à ce niveau.

Toutefois, si vous souhaitez une solution complémentaire (que l'on pourrait également qualifier de "superflue" dans les situations mentionnées ci-dessus), veuillez consulter le site suivant https://github.com/Android/architecture-components-samples que je colle ici à titre de référence uniquement (la discussion sur cette approche est ici :

/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.observability.persistence

import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context

/**
 * The Room database that contains the Users table
 */
@Database(entities = [User::class], version = 1)
abstract class UsersDatabase : RoomDatabase() {

    abstract fun userDao(): UserDao

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

D'une manière générale, vous pouvez jeter un coup d'œil sur les éléments suivants Kotlin : double vérification des singletons et/ou Verrouillage doublement vérifié en Java avec Singleton .

0voto

Rahul Sain Points 26

Vous pouvez utiliser cette fonction

public class DatabaseClient {

private Context mCtx;
private static DatabaseClient mInstance;

//our app database object
private final AppDatabase appDatabase;
private DatabaseClient(Context mCtx) {
    this.mCtx = mCtx;

    //creating the app database with Room database builder
    //alldata is the name of the database
    appDatabase = Room.databaseBuilder(mCtx, AppDatabase.class, "alldata").build();
}

public static synchronized DatabaseClient getInstance(Context mCtx) {
    if (mInstance == null) {
        mInstance = new DatabaseClient(mCtx);
    }
    return mInstance;
}

public AppDatabase getAppDatabase() { return appDatabase; }

}

Et utilisez ceci pour appeler getInstance()

DatabaseClient.getInstance(MainActivity.this).getAppDatabase().memberDao();

La classe AppDatabase ressemble à ceci :

@Database(entities = {Member.class} ,  version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract MemberDao memberDao();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X