Salle Android - requête de sélection simple - Impossible d'accéder à la base de données sur le fil principal

126

J'essaye un échantillon avec la bibliothèque de persistance de pièce . J'ai créé une entité:

@Entity
public class Agent {
    @PrimaryKey
    public String guid;
    public String name;
    public String email;
    public String password;
    public String phone;
    public String licence;
}

Création d'une classe DAO:

@Dao
public interface AgentDao {
    @Query("SELECT COUNT(*) FROM Agent where email = :email OR phone = :phone OR licence = :licence")
    int agentsCount(String email, String phone, String licence);

    @Insert
    void insertAgent(Agent agent);
}

Création de la classe Database:

@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract AgentDao agentDao();
}

Base de données exposée en utilisant la sous-classe ci-dessous dans Kotlin:

class MyApp : Application() {

    companion object DatabaseSetup {
        var database: AppDatabase? = null
    }

    override fun onCreate() {
        super.onCreate()
        MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").build()
    }
}

Fonction implémentée ci-dessous dans mon activité:

void signUpAction(View view) {
        String email = editTextEmail.getText().toString();
        String phone = editTextPhone.getText().toString();
        String license = editTextLicence.getText().toString();

        AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
        //1: Check if agent already exists
        int agentsCount = agentDao.agentsCount(email, phone, license);
        if (agentsCount > 0) {
            //2: If it already exists then prompt user
            Toast.makeText(this, "Agent already exists!", Toast.LENGTH_LONG).show();
        }
        else {
            Toast.makeText(this, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
            onBackPressed();
        }
    }

Malheureusement, lors de l'exécution de la méthode ci-dessus, il se bloque avec la trace de pile ci-dessous:

    FATAL EXCEPTION: main
 Process: com.example.me.MyApp, PID: 31592
java.lang.IllegalStateException: Could not execute method for android:onClick
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
    at android.view.View.performClick(View.java:5612)
    at android.view.View$PerformClick.run(View.java:22288)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6123)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
 Caused by: java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
    at android.view.View.performClick(View.java:5612) 
    at android.view.View$PerformClick.run(View.java:22288) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6123) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) 
 Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
    at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:137)
    at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:165)
    at com.example.me.MyApp.RoomDb.Dao.AgentDao_Impl.agentsCount(AgentDao_Impl.java:94)
    at com.example.me.MyApp.View.SignUpActivity.signUpAction(SignUpActivity.java:58)
    at java.lang.reflect.Method.invoke(Native Method) 
    at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
    at android.view.View.performClick(View.java:5612) 
    at android.view.View$PerformClick.run(View.java:22288) 
    at android.os.Handler.handleCallback(Handler.java:751) 
    at android.os.Handler.dispatchMessage(Handler.java:95) 
    at android.os.Looper.loop(Looper.java:154) 
    at android.app.ActivityThread.main(ActivityThread.java:6123) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) 

Il semble que ce problème soit lié à l'exécution de l'opération db sur le thread principal. Cependant, l'exemple de code de test fourni dans le lien ci-dessus ne s'exécute pas sur un thread distinct:

@Test
    public void writeUserAndReadInList() throws Exception {
        User user = TestUtil.createUser(3);
        user.setName("george");
        mUserDao.insert(user);
        List<User> byName = mUserDao.findUsersByName("george");
        assertThat(byName.get(0), equalTo(user));
    }

Est-ce que je manque quelque chose ici? Comment puis-je l'exécuter sans planter? Veuillez suggérer.

Devarshi
la source
1
Bien qu'écrit pour Kotlin, cet article explique très bien le problème sous-jacent!
Peter Lehnhardt
Jetez un œil à stackoverflow.com/questions/58532832/…
nnyerges
Regardez cette réponse. Cette réponse fonctionne pour moi stackoverflow.com/a/51720501/7655085
Somen Tushir

Réponses:

59

L'accès à la base de données sur le thread principal verrouillant l'interface utilisateur est l'erreur, comme l'a dit Dale.

Créez une classe imbriquée statique (pour éviter les fuites de mémoire) dans votre activité en étendant AsyncTask.

private static class AgentAsyncTask extends AsyncTask<Void, Void, Integer> {

    //Prevent leak
    private WeakReference<Activity> weakActivity;
    private String email;
    private String phone;
    private String license;

    public AgentAsyncTask(Activity activity, String email, String phone, String license) {
        weakActivity = new WeakReference<>(activity);
        this.email = email;
        this.phone = phone;
        this.license = license;
    }

    @Override
    protected Integer doInBackground(Void... params) {
        AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
        return agentDao.agentsCount(email, phone, license);
    }

    @Override
    protected void onPostExecute(Integer agentsCount) {
        Activity activity = weakActivity.get();
        if(activity == null) {
            return;
        }

        if (agentsCount > 0) {
            //2: If it already exists then prompt user
            Toast.makeText(activity, "Agent already exists!", Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(activity, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
            activity.onBackPressed();
        }
    }
}

Ou vous pouvez créer une classe finale sur son propre fichier.

Puis exécutez-le dans la méthode signUpAction (View view):

new AgentAsyncTask(this, email, phone, license).execute();

Dans certains cas, vous souhaiterez peut-être également conserver une référence à AgentAsyncTask dans votre activité afin de pouvoir l'annuler lorsque l'activité est détruite. Mais vous devrez interrompre vous-même toutes les transactions.

Aussi, votre question sur l'exemple de test de Google ... Ils indiquent dans cette page Web:

L'approche recommandée pour tester l'implémentation de votre base de données consiste à écrire un test JUnit qui s'exécute sur un appareil Android. Étant donné que ces tests ne nécessitent pas la création d'une activité, ils devraient être plus rapides à exécuter que vos tests d'interface utilisateur.

Aucune activité, aucune interface utilisateur.

--ÉDITER--

Pour les gens qui se demandent ... Vous avez d'autres options. Je recommande de jeter un œil aux nouveaux composants ViewModel et LiveData. LiveData fonctionne parfaitement avec Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Une autre option est le RxJava / RxAndroid. Plus puissant mais plus complexe que LiveData. https://github.com/ReactiveX/RxJava

--EDIT 2--

Puisque beaucoup de gens peuvent trouver cette réponse ... La meilleure option de nos jours, en général, est Kotlin Coroutines. Room le prend désormais en charge directement (actuellement en version bêta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

Mcastro
la source
31
Suis-je censé effectuer cette énorme tâche asynchrone (que vous avez proposée dans l'exemple de code) chaque fois que j'ai besoin d'accéder à la base de données? C'est une douzaine de lignes de code au lieu d'une pour obtenir des données de db. Vous avez également proposé de créer une nouvelle classe, mais cela signifie-t-il que je dois créer une nouvelle classe AsyncTask pour chaque appel d'insertion / sélection de base de données?
Piotrek
7
Vous avez d'autres options, oui. Vous voudrez peut-être jeter un œil aux nouveaux composants ViewModel et LiveData. Lorsque vous utilisez LiveData, vous n'avez pas besoin d'AsyncTask, l'objet sera notifié chaque fois que quelque chose change. developer.android.com/topic/libraries/architecture/... developer.android.com/topic/libraries/architecture/... Il y a aussi AndroidRx (bien qu'il fasse à peu près ce que LiveData fait) et Promises. Lorsque vous utilisez AsyncTask, vous pouvez créer une architecture de telle manière que vous puissiez inclure plusieurs opérations dans une AsyncTask ou les séparer.
mcastro
@Piotrek - Kotlin a maintenant cuit async (bien qu'il soit signalé comme expérimental). Voir ma réponse qui est relativement triviale. La réponse de Samuel Robert couvre Rx. Je ne vois pas de réponse LiveData ici, mais cela pourrait être le meilleur choix si vous voulez un observable.
AjahnCharles
Utiliser AsyncTask ordinaire ne semble même pas fonctionner maintenant, obtenant toujours l'exception d'état illégal
Peterstev Uremgba
@Piotrek vous me dites que vous avez l'habitude d'exécuter l'accès à la base de données sur le fil principal de toute votre expérience?
mr5
142

Ce n'est pas recommandé mais vous pouvez accéder à la base de données sur le thread principal avec allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
mpolat
la source
10
Room n'autorise pas l'accès à la base de données sur le thread principal, sauf si vous avez appelé allowMainThreadQueries()le générateur, car il pourrait potentiellement verrouiller l'interface utilisateur pendant une longue période. Les requêtes asynchrones (requêtes qui retournent LiveDataou RxJava Flowable) sont exemptées de cette règle car elles exécutent la requête de manière asynchrone sur un thread d'arrière-plan lorsque cela est nécessaire.
pRaNaY
4
Merci, c'est très utile pour la migration, car je veux tester que Room fonctionne comme prévu avant de
passer
5
@JideGuruTheProgrammer Non, ça ne devrait pas. Dans certains cas, cela pourrait ralentir considérablement votre application. Les opérations doivent être effectuées de manière asynchrone.
Alex
@Alex Il n'y a jamais lieu de faire une requête sur le thread principal?
Justin Meiners
2
@JustinMeiners c'est juste une mauvaise pratique, vous serez d'accord tant que la base de données reste petite.
lasec0203
54

Kotlin Coroutines (clair et concis)

AsyncTask est vraiment maladroit. Les coroutines sont une alternative plus propre (il suffit de saupoudrer quelques mots-clés et votre code de synchronisation devient asynchrone).

// Step 1: add `suspend` to your fun
suspend fun roomFun(...): Int
suspend fun notRoomFun(...) = withContext(Dispatchers.IO) { ... }

// Step 2: launch from coroutine scope
private fun myFun() {
    lifecycleScope.launch { // coroutine on Main
        val queryResult = roomFun(...) // coroutine on IO
        doStuff() // ...back on Main
    }
}

Dépendances (ajoute des portées coroutines pour les composants de l'arche):

// lifecycleScope:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha04'

// viewModelScope:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha04'

- Mises à jour:
8 mai 2019: la salle 2.1 prend désormais en charge suspend
13 septembre 2019: mise à jour pour utiliser la portée des composants d'architecture

AjahnCharles
la source
1
Avez-vous une erreur de compilation avec le @Query abstract suspend fun count()en utilisant le mot clé suspend? Pouvez-vous s'il vous plaît examiner cette question similaire: stackoverflow.com/questions/48694449/…
Robin
@Robin - Oui, je le fais. Mon erreur; J'utilisais suspend sur une méthode DAO publique (non annotée) qui appelait une fonction protégée non suspend @Query. Lorsque j'ajoute le mot-clé suspend à la @Queryméthode interne, la compilation échoue en effet. Cela ressemble à l'intelligent sous le capot pour suspendre et clash de salle (comme vous le mentionnez dans votre autre question, la version compilée de suspension renvoie une suite que Room ne peut pas gérer).
AjahnCharles
Cela a beaucoup de sens. Je vais plutôt l'appeler avec les fonctions coroutine.
Robin le
1
@Robin - Pour info, ils ont ajouté le support de la suspension dans la salle 2.1 :)
AjahnCharles
Apparemment, il n'y a launchplus de mot-clé, vous lancez avec une portée, telle queGlobalScope.launch
nasch
48

Pour tous les RxJava ou RxAndroid ou RxKotlin amateurs là - bas

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }
Samuel Robert
la source
3
Si je mets ce code dans une méthode, comment retourner le résultat de l'opération de base de données?
Eggakin Baconwalker
@EggakinBaconwalker J'ai override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }là où applySchedulers()je viens de fairefun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman
Cela ne fonctionnera pas pour IntentService. Parce que IntentService sera terminé une fois son thread terminé.
Umang Kothari
1
@UmangKothari Vous n'obtenez pas l'exception si vous êtes sur IntentService#onHandleIntentparce que cette méthode s'exécute sur le thread de travail, vous n'avez donc pas besoin d'un mécanisme de thread pour effectuer l'opération de base de données de la salle
Samuel Robert
@SamuelRobert, Oui d'accord mon mal. Ça m'a échappé.
Umang Kothari
27

Vous ne pouvez pas l'exécuter sur le thread principal à la place, utilisez des gestionnaires, des threads asynchrones ou de travail. Un exemple de code est disponible ici et lisez l'article sur la bibliothèque de la salle ici: Bibliothèque de la salle d'Android

/**
 *  Insert and get data using Database Async way
 */
AsyncTask.execute(new Runnable() {
    @Override
    public void run() {
        // Insert Data
        AppDatabase.getInstance(context).userDao().insert(new User(1,"James","Mathew"));

        // Get Data
        AppDatabase.getInstance(context).userDao().getAllUsers();
    }
});

Si vous souhaitez l'exécuter sur le thread principal, ce n'est pas la manière préférée.

Vous pouvez utiliser cette méthode pour réaliser sur le fil principal Room.inMemoryDatabaseBuilder()

Rizvan
la source
Si j'utilise cette méthode pour obtenir les données (uniquement getAllUsers () dans ce cas), comment renvoyer les données de cette méthode? Il apparaît une erreur, si je mets le mot «retour» à l'intérieur du «run».
Eggakin Baconwalker
1
créez une méthode d'interface quelque part et ajoutez une classe anonyme pour obtenir des données à partir d'ici.
Rizvan
1
C'est la solution la plus simple pour insérer / mettre à jour.
Beer Me
12

Avec lambda, il est facile à exécuter avec AsyncTask

 AsyncTask.execute(() -> //run your query here );
Afjalur Rahman Rana
la source
2
C'est pratique, merci. Au fait, Kotlin est encore plus facile: AsyncTask.execute {}
alexrnov
1
mais comment obtenez-vous le résultat en utilisant cette méthode?
leeCoder
11

Avec la bibliothèque Jetbrains Anko, vous pouvez utiliser la méthode doAsync {..} pour exécuter automatiquement des appels de base de données. Cela résout le problème de verbosité que vous sembliez avoir avec la réponse de mcastro.

Exemple d'utilisation:

    doAsync { 
        Application.database.myDAO().insertUser(user) 
    }

Je l'utilise fréquemment pour les insertions et les mises à jour, mais pour certaines requêtes, je recommande d'utiliser le flux de travail RX.

Arsala Bangash
la source
7

Effectuez simplement les opérations de base de données dans un thread séparé. Comme ça (Kotlin):

Thread {
   //Do your database´s operations here
}.start()
Angel Enrique Herrera Crespo
la source
6

Vous devez exécuter la demande en arrière-plan. Un moyen simple pourrait être d'utiliser un exécuteur :

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}
Phil
la source
comment renvoyez-vous le résultat?
leeCoder
5

Une solution RxJava / Kotlin élégante est à utiliser Completable.fromCallable, qui vous donnera un Observable qui ne retourne pas de valeur, mais peut être observé et abonné sur un thread différent.

public Completable insert(Event event) {
    return Completable.fromCallable(new Callable<Void>() {
        @Override
        public Void call() throws Exception {
            return database.eventDao().insert(event)
        }
    }
}

Ou à Kotlin:

fun insert(event: Event) : Completable = Completable.fromCallable {
    database.eventDao().insert(event)
}

Vous pouvez l'observer et vous abonner comme vous le feriez habituellement:

dataManager.insert(event)
    .subscribeOn(scheduler)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...)
dcr24
la source
5

Vous pouvez autoriser l'accès à la base de données sur le thread principal, mais uniquement à des fins de débogage, vous ne devriez pas le faire en production.

Voici la raison.

Remarque: Room ne prend pas en charge l'accès à la base de données sur le thread principal, sauf si vous avez appelé allowMainThreadQueries () sur le générateur, car il peut verrouiller l'interface utilisateur pendant une longue période. Les requêtes asynchrones (requêtes qui renvoient des instances de LiveData ou Flowable) sont exemptées de cette règle car elles exécutent la requête de manière asynchrone sur un thread d'arrière-plan lorsque cela est nécessaire.

Nilesh Rathore
la source
4

Vous pouvez simplement utiliser ce code pour le résoudre:

Executors.newSingleThreadExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        appDb.daoAccess().someJobes();//replace with your code
                    }
                });

Ou en lambda, vous pouvez utiliser ce code:

Executors.newSingleThreadExecutor().execute(() -> appDb.daoAccess().someJobes());

Vous pouvez remplacer appDb.daoAccess().someJobes()par votre propre code;

Mostafa Rostami
la source
4

Comme asyncTask est obsolète, nous pouvons utiliser le service exécuteur. OU vous pouvez également utiliser ViewModel avec LiveData comme expliqué dans d'autres réponses.

Pour utiliser le service exécuteur, vous pouvez utiliser quelque chose comme ci-dessous.

public class DbHelper {

    private final Executor executor = Executors.newSingleThreadExecutor();

    public void fetchData(DataFetchListener dataListener){
        executor.execute(() -> {
                Object object = retrieveAgent(agentId);
                new Handler(Looper.getMainLooper()).post(() -> {
                        dataListener.onFetchDataSuccess(object);
                });
        });
    }
}

Main Looper est utilisé, de sorte que vous pouvez accéder à l'élément d'interface utilisateur à partir du onFetchDataSuccessrappel.

Sasuke Uchiha
la source
3

Le message d'erreur,

Impossible d'accéder à la base de données sur le thread principal car il peut potentiellement verrouiller l'interface utilisateur pendant de longues périodes.

Est assez descriptif et précis. La question est de savoir comment éviter d'accéder à la base de données sur le thread principal. C'est un sujet énorme, mais pour commencer, lisez AsyncTask (cliquez ici)

-----ÉDITER----------

Je vois que vous rencontrez des problèmes lorsque vous exécutez un test unitaire. Vous avez plusieurs choix pour résoudre ce problème:

  1. Exécutez le test directement sur la machine de développement plutôt que sur un appareil Android (ou un émulateur). Cela fonctionne pour les tests centrés sur la base de données et ne se soucient pas vraiment de savoir s'ils s'exécutent sur un appareil.

  2. Utilisez l'annotation @RunWith(AndroidJUnit4.class) pour exécuter le test sur l'appareil Android, mais pas dans une activité avec une interface utilisateur. Plus de détails à ce sujet peuvent être trouvés dans ce tutoriel

Dale Wilson
la source
Je comprends votre point, je suppose que le même point est valable lorsque vous essayez de tester une opération de base de données via JUnit. Cependant, dans developer.android.com/topic/libraries/architecture/room.html, l'exemple de méthode de test writeUserAndReadInList n'appelle pas la requête d'insertion sur le thread d'arrière-plan. Est-ce que je manque quelque chose ici? Veuillez suggérer.
Devarshi
Désolé j'ai manqué le fait que c'était le test ayant des problèmes. Je modifierai ma réponse pour ajouter plus d'informations.
Dale Wilson
3

Si vous êtes plus à l'aise avec la tâche Async :

  new AsyncTask<Void, Void, Integer>() {
                @Override
                protected Integer doInBackground(Void... voids) {
                    return Room.databaseBuilder(getApplicationContext(),
                            AppDatabase.class, DATABASE_NAME)
                            .fallbackToDestructiveMigration()
                            .build()
                            .getRecordingDAO()
                            .getAll()
                            .size();
                }

                @Override
                protected void onPostExecute(Integer integer) {
                    super.onPostExecute(integer);
                    Toast.makeText(HomeActivity.this, "Found " + integer, Toast.LENGTH_LONG).show();
                }
            }.execute();
Hitesh Sahu
la source
2

Mise à jour: J'ai également reçu ce message lorsque j'essayais de créer une requête en utilisant @RawQuery et SupportSQLiteQuery dans le DAO.

@Transaction
public LiveData<List<MyEntity>> getList(MySettings mySettings) {
    //return getMyList(); -->this is ok

    return getMyList(new SimpleSQLiteQuery("select * from mytable")); --> this is an error

Solution: créez la requête à l'intérieur du ViewModel et transmettez-la au DAO.

public MyViewModel(Application application) {
...
        list = Transformations.switchMap(searchParams, params -> {

            StringBuilder sql;
            sql = new StringBuilder("select  ... ");

            return appDatabase.rawDao().getList(new SimpleSQLiteQuery(sql.toString()));

        });
    }

Ou...

Vous ne devez pas accéder à la base de données directement sur le thread principal, par exemple:

 public void add(MyEntity item) {
     appDatabase.myDao().add(item); 
 }

Vous devez utiliser AsyncTask pour les opérations de mise à jour, d'ajout et de suppression.

Exemple:

public class MyViewModel extends AndroidViewModel {

    private LiveData<List<MyEntity>> list;

    private AppDatabase appDatabase;

    public MyViewModel(Application application) {
        super(application);

        appDatabase = AppDatabase.getDatabase(this.getApplication());
        list = appDatabase.myDao().getItems();
    }

    public LiveData<List<MyEntity>> getItems() {
        return list;
    }

    public void delete(Obj item) {
        new deleteAsyncTask(appDatabase).execute(item);
    }

    private static class deleteAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        deleteAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().delete((params[0]));
            return null;
        }
    }

    public void add(final MyEntity item) {
        new addAsyncTask(appDatabase).execute(item);
    }

    private static class addAsyncTask extends AsyncTask<MyEntity, Void, Void> {

        private AppDatabase db;

        addAsyncTask(AppDatabase appDatabase) {
            db = appDatabase;
        }

        @Override
        protected Void doInBackground(final MyEntity... params) {
            db.myDao().add((params[0]));
            return null;
        }

    }
}

Si vous utilisez LiveData pour sélectionner des opérations, vous n'avez pas besoin d'AsyncTask.

l'amour en direct
la source
1

Pour les requêtes rapides, vous pouvez autoriser la salle à l'exécuter sur le fil de l'interface utilisateur.

AppDatabase db = Room.databaseBuilder(context.getApplicationContext(),
        AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build();

Dans mon cas, j'ai dû comprendre que l'utilisateur cliqué dans la liste existe ou non dans la base de données. Sinon, créez l'utilisateur et démarrez une autre activité

       @Override
        public void onClick(View view) {



            int position = getAdapterPosition();

            User user = new User();
            String name = getName(position);
            user.setName(name);

            AppDatabase appDatabase = DatabaseCreator.getInstance(mContext).getDatabase();
            UserDao userDao = appDatabase.getUserDao();
            ArrayList<User> users = new ArrayList<User>();
            users.add(user);
            List<Long> ids = userDao.insertAll(users);

            Long id = ids.get(0);
            if(id == -1)
            {
                user = userDao.getUser(name);
                user.setId(user.getId());
            }
            else
            {
                user.setId(id);
            }

            Intent intent = new Intent(mContext, ChatActivity.class);
            intent.putExtra(ChatActivity.EXTRAS_USER, Parcels.wrap(user));
            mContext.startActivity(intent);
        }
    }
Vihaan Verma
la source
1

Vous pouvez utiliser Future et Callable. Ainsi, vous ne seriez pas obligé d'écrire une longue asynctask et pouvez effectuer vos requêtes sans ajouter allowMainThreadQueries ().

Ma requête dao: -

@Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();

Ma méthode de dépôt: -

public UserData getDefaultData() throws ExecutionException, InterruptedException {

    Callable<UserData> callable = new Callable<UserData>() {
        @Override
        public UserData call() throws Exception {
            return userDao.getDefaultData();
        }
    };

    Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);

    return future.get();
}
débutant
la source
C'est pourquoi nous utilisons Callable / Future car Android ne permet pas aux requêtes de s'exécuter sur le thread principal. Comme demandé dans les questions ci
débutant
1
Je veux dire, bien que le code de votre réponse fasse une requête dans le fil d'arrière-plan, le fil principal est bloqué et attend lorsque la requête est terminée. Donc à la fin ce n'est pas beaucoup mieux que allowMainThreadQueries(). Fil principal toujours bloqué dans les deux cas
eugeneek
0

À mon avis, la bonne chose à faire est de déléguer la requête à un thread IO en utilisant RxJava.

J'ai un exemple de solution à un problème équivalent que je viens de rencontrer.

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            //Creating view model requires DB access
            homeViewModel = new ViewModelProvider(this, factory).get(HomeViewModel.class);
        }).subscribeOn(Schedulers.io())//The DB access executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    mAdapter = new MyAdapter(homeViewModel.getExams());
                    recyclerView.setAdapter(mAdapter);
                    ((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.INVISIBLE);
                },
                error -> error.printStackTrace()
        );

Et si nous voulons généraliser la solution:

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            someTaskThatTakesTooMuchTime();
        }).subscribeOn(Schedulers.io())//The long task executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    taskIWantToDoOnTheMainThreadWhenTheLongTaskIsDone();
                },
                error -> error.printStackTrace()
        );
Rotem Barak
la source