Sala de Android: consulta de selección simple: no se puede acceder a la base de datos en el hilo principal

126

Estoy probando una muestra con Room Persistence Library . Creé una entidad:

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

Creó una clase 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);
}

Creó la clase de base de datos:

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

Base de datos expuesta usando la siguiente subclase en 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()
    }
}

Implementado a continuación la función en mi actividad:

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

Desafortunadamente, al ejecutar el método anterior, se bloquea con el seguimiento de la pila inferior:

    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) 

Parece que ese problema está relacionado con la ejecución de la operación db en el hilo principal. Sin embargo, el código de prueba de muestra proporcionado en el enlace anterior no se ejecuta en un hilo separado:

@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));
    }

¿Me estoy perdiendo algo por aquí? ¿Cómo puedo hacer que se ejecute sin fallar? Por favor recomiende.

Devarshi
fuente
1
Aunque escrito para Kotlin, este artículo explica muy bien el problema subyacente.
Peter Lehnhardt
Eche un vistazo a stackoverflow.com/questions/58532832/…
nnyerges
Mira esta respuesta. Esta respuesta funciona para mí stackoverflow.com/a/51720501/7655085
Somen Tushir

Respuestas:

59

El acceso a la base de datos en el hilo principal que bloquea la interfaz de usuario es el error, como dijo Dale.

Cree una clase anidada estática (para evitar la pérdida de memoria) en su actividad extendiendo 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();
        }
    }
}

O puede crear una clase final en su propio archivo.

Luego ejecútelo en el método signUpAction (View view):

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

En algunos casos, es posible que también desee mantener una referencia a AgentAsyncTask en su actividad para poder cancelarla cuando se destruya la actividad. Pero tendría que interrumpir las transacciones usted mismo.

Además, tu pregunta sobre el ejemplo de prueba de Google ... Dicen en esa página web:

El enfoque recomendado para probar la implementación de su base de datos es escribir una prueba JUnit que se ejecute en un dispositivo Android. Debido a que estas pruebas no requieren la creación de una actividad, deberían ser más rápidas de ejecutar que las pruebas de IU.

Sin actividad, sin interfaz de usuario.

--EDITAR--

Para las personas que se preguntan ... Tienes otras opciones. Recomiendo echar un vistazo a los nuevos componentes ViewModel y LiveData. LiveData funciona muy bien con Room. https://developer.android.com/topic/libraries/architecture/livedata.html

Otra opción es RxJava / RxAndroid. Más potente pero más complejo que LiveData. https://github.com/ReactiveX/RxJava

--EDIT 2--

Dado que muchas personas pueden encontrar esta respuesta ... La mejor opción hoy en día, en términos generales, es Kotlin Coroutines. Room ahora lo admite directamente (actualmente en versión beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

mcastro
fuente
31
¿Se supone que debo realizar esta enorme tarea asíncrona (que ha propuesto en el ejemplo de código) cada vez que necesito acceder a la base de datos? Es una docena de líneas de código en lugar de una para obtener algunos datos de db. Propuso también crear una nueva clase, pero ¿significa que necesito crear una nueva clase AsyncTask para cada llamada de base de datos de inserción / selección?
Piotrek
7
Tienes otras opciones, sí. Es posible que desee echar un vistazo a los nuevos componentes ViewModel y LiveData. Al usar LiveData no necesita AsyncTask, el objeto será notificado cada vez que algo cambie. developer.android.com/topic/libraries/architecture/… developer.android.com/topic/libraries/architecture/… También está AndroidRx (aunque hace prácticamente lo que hace LiveData) y Promises. Al usar AsyncTask, puede realizar la arquitectura de tal manera que podría incluir varias operaciones en una AsyncTask o separar cada una.
mcastro
@Piotrek: Kotlin ahora tiene async incorporado (aunque está marcado como experimental). Vea mi respuesta que es comparativamente trivial. La respuesta de Samuel Robert cubre Rx. No veo una respuesta de LiveData aquí, pero esa podría ser la mejor opción si desea un observable.
AjahnCharles
El uso de AsyncTask regular ni siquiera parece funcionar ahora, aún
recibe la
@Piotrek, ¿me estás diciendo que estás acostumbrado a ejecutar el acceso a la base de datos en el hilo principal en toda tu experiencia?
mr5
142

No se recomienda, pero puede acceder a la base de datos en el hilo principal con allowMainThreadQueries()

MyApp.database =  Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build()
mpolat
fuente
9
Room no permite acceder a la base de datos en el hilo principal a menos que haya llamado allowMainThreadQueries()al constructor porque podría bloquear la interfaz de usuario durante un largo período de tiempo. Las consultas asincrónicas (consultas que devuelven LiveDatao RxJava Flowable) están exentas de esta regla, ya que ejecutan de forma asincrónica la consulta en un hilo en segundo plano cuando es necesario.
pRaNaY
4
Gracias, esto es muy útil para la migración, ya que quiero probar que Room funciona como se esperaba antes de convertir de
Loaders
5
@JideGuruTheProgrammer No, no debería. En algunos casos, podría ralentizar mucho la aplicación. Las operaciones deben realizarse de forma asincrónica.
Alex
@Alex ¿Nunca hay un caso para realizar una consulta en el hilo principal?
Justin Meiners
2
@JustinMeiners es una mala práctica, estará bien si lo hace siempre que la base de datos se mantenga pequeña.
lasec0203
53

Corutinas de Kotlin (claras y concisas)

AsyncTask es realmente torpe. Las corrutinas son una alternativa más limpia (solo agregue un par de palabras clave y su código de sincronización se volverá asíncrono).

// 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
    }
}

Dependencias (agrega alcances de corrutina para componentes de arco):

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

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

- Actualizaciones:
08-May-2019: Room 2.1 ahora es compatible con suspend
13-Sep-2019: actualizado para usar el alcance de los componentes de Arquitectura

AjahnCharles
fuente
1
¿Tiene algún error de compilación con el @Query abstract suspend fun count()uso de la palabra clave suspend? ¿Puede consultar esta pregunta similar ?: stackoverflow.com/questions/48694449/…
Robin
@Robin - Sí, lo hago. Mi error; Estaba usando suspender en un método DAO público (sin anotar) que llamaba a una función protegida sin suspensión @Query. Cuando agrego la palabra clave suspend al @Querymétodo interno también, de hecho, no se compila. Parece lo inteligente bajo el capó para suspender y choque de sala (como mencionaste en tu otra pregunta, la versión compilada de suspender devuelve una continuación que Room no puede manejar).
AjahnCharles
Tiene mucho sentido. En su lugar, lo llamaré con funciones de rutina.
Robin
1
@Robin - Para su información, agregaron soporte para suspender en la Sala 2.1 :)
AjahnCharles
Aparentemente ya no hay launchpalabra clave, se GlobalScope.launch
inicia
48

Para todos los RxJava o RxAndroid o RxKotlin amantes hacia fuera allí

Observable.just(db)
          .subscribeOn(Schedulers.io())
          .subscribe { db -> // database operation }
Samuel Robert
fuente
3
Si pongo este código dentro de un método, ¿cómo devolver el resultado de la operación de la base de datos?
Eggakin Baconwalker
@EggakinBaconwalker tengo override fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }donde applySchedulers()acabo de hacerfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman
Esto no funcionará para IntentService. Porque IntentService se realizará una vez que se complete su hilo.
Umang Kothari
1
@UmangKothari No obtiene la excepción si está activado IntentService#onHandleIntentporque este método se ejecuta en el hilo de trabajo, por lo que no necesitaría ningún mecanismo de subprocesamiento allí para realizar la operación de la base de datos de la sala
Samuel Robert
@SamuelRobert, sí de acuerdo mi mal. Se me fué de la mente.
Umang Kothari
27

No puede ejecutarlo en el subproceso principal, en su lugar, use controladores, subprocesos asíncronos o de trabajo. Un código de muestra está disponible aquí y lea el artículo sobre la biblioteca de salas aquí: Biblioteca de salas de 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 desea ejecutarlo en el hilo principal, que no es la forma preferida.

Puede utilizar este método para lograr en el hilo principal Room.inMemoryDatabaseBuilder()

Rizvan
fuente
Si utilizo este método para obtener los datos (solo getAllUsers () en este caso), ¿cómo devolver los datos de este método? Aparece un error, si pongo la palabra "retorno" dentro del "ejecutar".
Eggakin Baconwalker
1
cree un método de interfaz en algún lugar y agregue una clase anónima para obtener datos de aquí.
Rizvan
1
Esta es la solución más sencilla para insertar / actualizar.
Beer Me
12

Con lambda es fácil de ejecutar con AsyncTask

 AsyncTask.execute(() -> //run your query here );
Afjalur Rahman Rana
fuente
2
Esto es conveniente, gracias. Por cierto, Kotlin es aún más fácil: AsyncTask.execute {}
alexrnov
1
pero ¿cómo se obtiene el resultado con este método?
leeCoder
11

Con la biblioteca Jetbrains Anko, puede usar el método doAsync {..} para ejecutar automáticamente llamadas a la base de datos. Esto soluciona el problema de verbosidad que parecía haber tenido con la respuesta de mcastro.

Uso de ejemplo:

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

Utilizo esto con frecuencia para inserciones y actualizaciones, sin embargo, para consultas seleccionadas, recomiendo usar el flujo de trabajo RX.

Arsala Bangash
fuente
7

Simplemente realice las operaciones de la base de datos en un hilo separado. Así (Kotlin):

Thread {
   //Do your database´s operations here
}.start()
Ángel Enrique Herrera Crespo
fuente
6

Tienes que ejecutar la solicitud en segundo plano. Una forma sencilla podría ser utilizar Ejecutores :

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}
Phil
fuente
¿Cómo devuelves el resultado?
leeCoder
5

Se debe utilizar una elegante solución RxJava / Kotlin Completable.fromCallable, que le dará un Observable que no devuelve un valor, pero que puede ser observado y suscrito en un hilo diferente.

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

O en Kotlin:

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

Puede observar y suscribirse como lo haría normalmente:

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

Puede permitir el acceso a la base de datos en el hilo principal, pero solo con fines de depuración, no debe hacer esto en producción.

He aquí la razón.

Nota: Room no admite el acceso a la base de datos en el subproceso principal a menos que haya llamado allowMainThreadQueries () en el constructor porque podría bloquear la interfaz de usuario durante un período de tiempo prolongado. Las consultas asincrónicas (consultas que devuelven instancias de LiveData o Flowable) están exentas de esta regla porque ejecutan de forma asincrónica la consulta en un subproceso en segundo plano cuando es necesario.

Nilesh Rathore
fuente
4

Simplemente puedes usar este código para resolverlo:

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

O en lambda puede usar este código:

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

Puede reemplazarlo appDb.daoAccess().someJobes()con su propio código;

Mostafa Rostami
fuente
4

Como asyncTask está en desuso, podemos usar el servicio ejecutor. O también puede usar ViewModel con LiveData como se explica en otras respuestas.

Para usar el servicio ejecutor, puede usar algo como a continuación.

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

Se utiliza Main Looper para que pueda acceder al elemento de la interfaz de usuario desde la onFetchDataSuccessdevolución de llamada.

Sasuke Uchiha
fuente
3

El mensaje de error

No se puede acceder a la base de datos en el subproceso principal ya que potencialmente puede bloquear la interfaz de usuario durante largos períodos de tiempo.

Es bastante descriptivo y preciso. La pregunta es cómo evitar el acceso a la base de datos en el hilo principal. Ese es un tema enorme, pero para comenzar, lea sobre AsyncTask (haga clic aquí)

-----EDITAR----------

Veo que tiene problemas cuando ejecuta una prueba unitaria. Tiene un par de opciones para solucionar este problema:

  1. Ejecute la prueba directamente en la máquina de desarrollo en lugar de en un dispositivo Android (o emulador). Esto funciona para pruebas que se centran en bases de datos y no les importa si se están ejecutando en un dispositivo.

  2. Use la anotación @RunWith(AndroidJUnit4.class) para ejecutar la prueba en el dispositivo Android, pero no en una actividad con una interfaz de usuario. Se pueden encontrar más detalles sobre esto en este tutorial.

Dale Wilson
fuente
Entiendo su punto, mi suposición es que el mismo punto es válido cuando intenta probar cualquier operación de db a través de JUnit. Sin embargo, en developer.android.com/topic/libraries/architecture/room.html, el método de prueba de muestra writeUserAndReadInList no invoca la consulta de inserción en el hilo de fondo. ¿Me estoy perdiendo algo por aquí? Por favor recomiende.
Devarshi
Siento haberme perdido el hecho de que esta era la prueba que tenía problemas. Editaré mi respuesta para agregar más información.
Dale Wilson
3

Si se siente más cómodo con la tarea 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
fuente
2

Actualización: también recibí este mensaje cuando intentaba crear una consulta usando @RawQuery y SupportSQLiteQuery dentro del 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

Solución: cree la consulta dentro de ViewModel y páselo al DAO.

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

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

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

        });
    }

O...

No debe acceder a la base de datos directamente en el hilo principal, por ejemplo:

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

Debe usar AsyncTask para actualizar, agregar y eliminar operaciones.

Ejemplo:

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 usa LiveData para operaciones seleccionadas, no necesita AsyncTask.

vive el amor
fuente
1

Para consultas rápidas, puede dejar espacio para ejecutarlo en el hilo de la interfaz de usuario.

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

En mi caso, tuve que averiguar que el usuario seleccionado en la lista existe en la base de datos o no. Si no es así, cree el usuario y comience otra actividad.

       @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
fuente
1

Puede utilizar Future y Callable. Por lo tanto, no se le pedirá que escriba una asynctask larga y puede realizar sus consultas sin agregar allowMainThreadQueries ().

Mi consulta dao: -

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

Mi método de repositorio: -

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();
}
principiante
fuente
Es por eso que estamos usando Callable / Future porque Android no permite que las consultas se ejecuten en el hilo principal. Como se preguntó en la pregunta anterior
principiante
1
Quiero decir, aunque el código de su respuesta hace una consulta en el hilo de fondo, el hilo principal está bloqueado y esperando cuando finaliza la consulta. Así que al final no es mucho mejor que allowMainThreadQueries(). El hilo principal todavía está bloqueado en ambos casos
eugeneek
0

En mi opinión, lo correcto es delegar la consulta a un hilo IO usando RxJava.

Tengo un ejemplo de una solución a un problema equivalente que acabo de encontrar.

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

Y si queremos generalizar la solución:

((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
fuente