Sala de Android: obtenga la identificación de la nueva fila insertada con generación automática

138

Así es como estoy insertando datos en la base de datos usando la Biblioteca de persistencia de sala:

Entidad:

@Entity
class User {
    @PrimaryKey(autoGenerate = true)
    public int id;
    //...
}

Objeto de acceso a datos:

@Dao
public interface UserDao{
    @Insert(onConflict = IGNORE)
    void insertUser(User user);
    //...
}

¿Es posible devolver la identificación de usuario una vez que se completa la inserción en el método anterior sin escribir una consulta de selección por separado?

SpiralDev
fuente
1
¿Has intentado usar into en longlugar de voidcomo resultado de la @Insertoperación?
MatPag
Aún no. Voy a dar un tiro!
SpiralDev
También agregué una respuesta porque encontré la referencia en la documentación y estoy bastante seguro de que funcionará;)
MatPag
3
¿No se hará esto con un aSyncTask? ¿Cómo devuelve el valor de su función de repositorio?
Nimitz14

Respuestas:

191

Según la documentación aquí (debajo del fragmento de código)

Un método anotado con la @Insertanotación puede devolver:

  • long para operación de inserción única
  • long[]o Long[]o List<Long>para operaciones de inserción múltiple
  • void si no te importan los id insertados
MatPag
fuente
44
¿Por qué en la documentación dice int para el tipo de identificación pero devuelve largo? está asumiendo que la identificación nunca será lo suficientemente grande como para ser larga? Entonces, ¿la ID de fila y la ID de generación automática son literalmente la misma cosa?
Michael Vescovo
11
En SQLite, la identificación de clave principal más grande que puede tener es un entero con signo de 64 bits, por lo que el valor máximo es 9.223.372.036.854.775.807 (solo positivo porque es una identificación). En java, un int es un número con signo de 32 bits y su valor positivo máximo es 2,147,483,647, por lo que no puede representar todos los identificadores. Necesita usar un Java largo cuyo valor máximo es 9.223.372.036.854.775.807 para representar todos los identificadores. La documentación es solo por ejemplo, pero la API se diseñó teniendo esto en cuenta (es por eso que está regresando mucho y no int o doble)
MatPag
2
ok, así que realmente debería ser largo. pero tal vez para la mayoría de los casos no habrá 9 mil millones de filas en un sqlite db, por lo que usan int como ejemplo para el ID de usuario, ya que requiere menos memoria (o es un error). Eso es lo que tomo de esto. Gracias por la explicación sobre por qué regresa mucho.
Michael Vescovo
3
Tiene razón, pero las API de Room deberían funcionar incluso en el peor de los casos y deben seguir las especificaciones de SQlite. Usar un int durante mucho tiempo para este caso específico es prácticamente lo mismo, el consumo de memoria adicional es insignificante
MatPag
1
@MatPag Su enlace original ya no incluía una confirmación de este comportamiento (y lamentablemente, tampoco lo hace la referencia API para la anotación Insertar de la sala ). Después de un poco de búsqueda, encontré esto y actualicé el enlace en su respuesta. Con suerte, persiste un poco mejor que el anterior, ya que esta es una información bastante significativa.
CodeClown42
27

@Insertfunción puede devolver void, long, long[]o List<Long>. Por favor intente esto.

 @Insert(onConflict = OnConflictStrategy.REPLACE)
  long insert(User user);

 // Insert multiple items
 @Insert(onConflict = OnConflictStrategy.REPLACE)
  long[] insert(User... user);
Quang Nguyen
fuente
55
return Single.fromCallable(() -> dbService.YourDao().insert(mObject));
murt
8

El valor de retorno de la inserción para un registro será 1 si su estado de cuenta es exitoso.

En caso de que desee insertar una lista de objetos, puede ir con:

@Insert(onConflict = OnConflictStrategy.REPLACE)
public long[] addAll(List<Object> list);

Y ejecútelo con Rx2:

Observable.fromCallable(new Callable<Object>() {
        @Override
        public Object call() throws Exception {
            return yourDao.addAll(list<Object>);
        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Object>() {
        @Override
        public void accept(@NonNull Object o) throws Exception {
           // the o will be Long[].size => numbers of inserted records.

        }
    });
Cuong Vo
fuente
1
"El valor de retorno de la inserción para un registro será 1 si su extracto es exitoso" -> De acuerdo con esta documentación: developer.android.com/training/data-storage/room/accessing-data "Si el método @Insert solo recibe 1 parámetro, puede devolver un valor largo, que es el nuevo rowId para el elemento insertado. Si el parámetro es una matriz o una colección, debería devolver long [] o List <Long> en su lugar ".
CodeClown42
4

Obtenga la ID de fila por el siguiente fragmento de código. Utiliza invocable en un ExecutorService with Future.

 private UserDao userDao;
 private ExecutorService executorService;

 public long insertUploadStatus(User user) {
    Callable<Long> insertCallable = () -> userDao.insert(user);
    long rowId = 0;

    Future<Long> future = executorService.submit(insertCallable);
     try {
         rowId = future.get();
    } catch (InterruptedException e1) {
        e1.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    return rowId;
 }

Ref: Tutorial de Java Executor Service para obtener más información sobre Callable.

Hardian
fuente
3

En su Dao, la consulta de inserción devuelve, Longes decir, el rowId insertado.

 @Insert(onConflict = OnConflictStrategy.REPLACE)
 fun insert(recipes: CookingRecipes): Long

En su clase de Modelo (Repositorio): (MVVM)

fun addRecipesData(cookingRecipes: CookingRecipes): Single<Long>? {
        return Single.fromCallable<Long> { recipesDao.insertManual(cookingRecipes) }
}

En su clase ModelView: (MVVM) Maneje LiveData con DisposableSingleObserver.
Referencia del proveedor de trabajo: https://github.com/SupriyaNaveen/CookingRecipes

Supriya Naveen
fuente
1

Después de mucha lucha, logré resolver esto. Aquí está mi solución usando la arquitectura MMVM:

Student.kt

@Entity(tableName = "students")
data class Student(
    @NotNull var name: String,
    @NotNull var password: String,
    var subject: String,
    var email: String

) {

    @PrimaryKey(autoGenerate = true)
    var roll: Int = 0
}

StudentDao.kt

interface StudentDao {
    @Insert
    fun insertStudent(student: Student) : Long
}

StudentRepository.kt

    class StudentRepository private constructor(private val studentDao: StudentDao)
    {

        fun getStudents() = studentDao.getStudents()

        fun insertStudent(student: Student): Single<Long>? {
            return Single.fromCallable(
                Callable<Long> { studentDao.insertStudent(student) }
            )
        }

 companion object {

        // For Singleton instantiation
        @Volatile private var instance: StudentRepository? = null

        fun getInstance(studentDao: StudentDao) =
                instance ?: synchronized(this) {
                    instance ?: StudentRepository(studentDao).also { instance = it }
                }
    }
}

StudentViewModel.kt

class StudentViewModel (application: Application) : AndroidViewModel(application) {

var status = MutableLiveData<Boolean?>()
private var repository: StudentRepository = StudentRepository.getInstance( AppDatabase.getInstance(application).studentDao())
private val disposable = CompositeDisposable()

fun insertStudent(student: Student) {
        disposable.add(
            repository.insertStudent(student)
                ?.subscribeOn(Schedulers.newThread())
                ?.observeOn(AndroidSchedulers.mainThread())
                ?.subscribeWith(object : DisposableSingleObserver<Long>() {
                    override fun onSuccess(newReturnId: Long?) {
                        Log.d("ViewModel Insert", newReturnId.toString())
                        status.postValue(true)
                    }

                    override fun onError(e: Throwable?) {
                        status.postValue(false)
                    }

                })
        )
    }
}

En el fragmento:

class RegistrationFragment : Fragment() {
    private lateinit var dataBinding : FragmentRegistrationBinding
    private val viewModel: StudentViewModel by viewModels()

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initialiseStudent()
        viewModel.status.observe(viewLifecycleOwner, Observer { status ->
            status?.let {
                if(it){
                    Toast.makeText(context , "Data Inserted Sucessfully" , Toast.LENGTH_LONG).show()
                    val action = RegistrationFragmentDirections.actionRegistrationFragmentToLoginFragment()
                    Navigation.findNavController(view).navigate(action)
                } else
                    Toast.makeText(context , "Something went wrong" , Toast.LENGTH_LONG).show()
                //Reset status value at first to prevent multitriggering
                //and to be available to trigger action again
                viewModel.status.value = null
                //Display Toast or snackbar
            }
        })

    }

    fun initialiseStudent() {
        var student = Student(name =dataBinding.edName.text.toString(),
            password= dataBinding.edPassword.text.toString(),
            subject = "",
            email = dataBinding.edEmail.text.toString())
        dataBinding.viewmodel = viewModel
        dataBinding.student = student
    }
}

He usado DataBinding. Aquí está mi XML:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="student"
            type="com.kgandroid.studentsubject.data.Student" />

        <variable
            name="listener"
            type="com.kgandroid.studentsubject.view.RegistrationClickListener" />

        <variable
            name="viewmodel"
            type="com.kgandroid.studentsubject.viewmodel.StudentViewModel" />

    </data>


    <androidx.core.widget.NestedScrollView
        android:id="@+id/nestedScrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        tools:context="com.kgandroid.studentsubject.view.RegistrationFragment">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/constarintLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:isScrollContainer="true">

            <TextView
                android:id="@+id/tvRoll"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:gravity="center_horizontal"
                android:text="Roll : 1"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <EditText
                android:id="@+id/edName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="16dp"
                android:ems="10"
                android:inputType="textPersonName"
                android:text="Name"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/tvRoll" />

            <TextView
                android:id="@+id/tvName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginEnd="16dp"
                android:text="Name:"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintBaseline_toBaselineOf="@+id/edName"
                app:layout_constraintEnd_toStartOf="@+id/edName"
                app:layout_constraintStart_toStartOf="parent" />

            <TextView
                android:id="@+id/tvEmail"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Email"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintBaseline_toBaselineOf="@+id/edEmail"
                app:layout_constraintEnd_toStartOf="@+id/edEmail"
                app:layout_constraintStart_toStartOf="parent" />

            <EditText
                android:id="@+id/edEmail"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="16dp"
                android:ems="10"
                android:inputType="textPersonName"
                android:text="Name"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edName" />

            <TextView
                android:id="@+id/textView6"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Password"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintBaseline_toBaselineOf="@+id/edPassword"
                app:layout_constraintEnd_toStartOf="@+id/edPassword"
                app:layout_constraintStart_toStartOf="parent" />

            <EditText
                android:id="@+id/edPassword"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="16dp"
                android:ems="10"
                android:inputType="textPersonName"
                android:text="Name"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edEmail" />

            <Button
                android:id="@+id/button"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="32dp"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="32dp"
                android:background="@color/colorPrimary"
                android:text="REGISTER"
                android:onClick="@{() -> viewmodel.insertStudent(student)}"
                android:textColor="@android:color/background_light"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edPassword" />
        </androidx.constraintlayout.widget.ConstraintLayout>


    </androidx.core.widget.NestedScrollView>
</layout>

He luchado mucho para lograr esto con asynctask ya que la operación de inserción y eliminación de la sala debe hacerse en un hilo separado. Finalmente capaz de hacer esto con Single type observable en RxJava.

Aquí están las dependencias de Gradle para rxjava:

implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.0.3' 
kgandroid
fuente
0

De acuerdo con la documentación, las funciones anotadas con @Insert pueden devolver el rowId.

Si el método @Insert recibe solo 1 parámetro, puede devolver un largo, que es el nuevo rowId para el elemento insertado. Si el parámetro es una matriz o una colección, debería devolver long [] o List <Long> en su lugar.

El problema que tengo con esto es que devuelve el Id. De fila y no el ID y todavía no he descubierto cómo obtener el ID utilizando el ID de fila.

Lamentablemente no puedo comentar todavía, porque no tengo 50 reputación, por lo que estoy publicando esto como una respuesta.

AndroidKotlinNoob
fuente