Android Room - Obtenez l'ID de la nouvelle ligne insérée avec la génération automatique

138

Voici comment j'insère des données dans la base de données à l'aide de la bibliothèque de persistance de la pièce:

Entité:

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

Objet d'accès aux données:

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

Est-il possible de renvoyer l'identifiant de l'utilisateur une fois l'insertion terminée dans la méthode ci-dessus elle-même sans écrire une requête de sélection séparée?

SpiralDev
la source
1
Avez-vous essayé d'utiliser intou à la longplace de voidsuite à l' @Insertopération?
MatPag
Pas encore. Je vais essayer!
SpiralDev
J'ai aussi ajouté une réponse car j'ai trouvé la référence dans la documentation et je suis assez confiant que cela fonctionnera;)
MatPag
3
cela ne sera-t-il pas fait avec un aSyncTask? comment renvoyez-vous la valeur de votre fonction de référentiel?
Nimitz14

Réponses:

191

Basé sur la documentation ici (ci-dessous l'extrait de code)

Une méthode annotée avec l' @Insertannotation peut renvoyer:

  • long pour une opération d'insertion simple
  • long[]ou Long[]ou List<Long>pour plusieurs opérations d'insertion
  • void si vous ne vous souciez pas des identifiants insérés
MatPag
la source
5
pourquoi dans la documentation dit-il int pour le type d'id mais renvoie long? est-ce en supposant que l'identifiant ne sera jamais assez grand pour être long? donc l'id de ligne et l'id de génération automatique sont littéralement la même chose?
Michael Vescovo
11
Dans SQLite, le plus grand identifiant de clé primaire que vous pouvez avoir est un entier signé de 64 bits, la valeur maximale est donc de 9 223 372 036 854 775 807 (uniquement positive car il s'agit d'un identifiant). En java, un int est un nombre signé de 32 bits et sa valeur positive maximale est de 2 147 483 647, il n'est donc pas en mesure de représenter tous les identifiants. Vous devez utiliser un long Java dont la valeur maximale est de 9 223 372 036 854 775 807 pour représenter tous les ID. La documentation n'est fournie qu'à titre d'exemple, mais l'api a été conçue dans cet esprit (c'est pourquoi elle renvoie long et non int ou double)
MatPag
2
ok donc ça devrait vraiment être long. mais peut-être que dans la plupart des cas, il n'y aura pas 9 milliards de lignes dans une base de données sqlite, donc ils utilisent int comme exemple pour userId car cela prend moins de mémoire (ou c'est une erreur). C'est ce que j'en retire. Merci pour l'explication sur la raison pour laquelle il revient longtemps.
Michael Vescovo
3
Vous avez raison, mais les API de Room devraient fonctionner même dans le pire des cas et doivent suivre les spécifications de SQlite. Utiliser un int sur une longue période pour ce cas spécifique est pratiquement la même chose, la consommation de mémoire supplémentaire est négligeable
MatPag
1
@MatPag Votre lien d' origine non plus inclus une confirmation de ce comportement (et , malheureusement, ne fait la référence API pour l'annotation Insérer de chambre ). Après quelques recherches, j'ai trouvé ceci et mis à jour le lien dans votre réponse. Espérons que cela persiste un peu mieux que le dernier car c'est une information assez importante.
CodeClown42
27

@Insertfonction peut retourner void, long, long[]ou List<Long>. Veuillez essayer ceci.

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

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

La valeur de retour de l'insertion pour un enregistrement sera 1 si votre instruction réussit.

Si vous souhaitez insérer une liste d'objets, vous pouvez utiliser:

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

Et exécutez-le avec 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
la source
1
"La valeur de retour de l'insertion pour un enregistrement sera 1 si votre instruction réussit" -> Selon cette documentation: developer.android.com/training/data-storage/room/accessing-data "Si la méthode @Insert reçoit uniquement 1, il peut renvoyer un long, qui est le nouveau rowId pour l'élément inséré. Si le paramètre est un tableau ou une collection, il doit renvoyer long [] ou List <Long> à la place. "
CodeClown42
4

Obtenez l'ID de ligne à l'aide du sniplet suivant. Il utilise appelable sur un ExecutorService avec 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;
 }

Réf: Tutoriel Java Executor Service pour plus d'informations sur Callable.

Hardian
la source
3

Dans votre Dao, la requête d'insertion renvoie Longie le rowId inséré.

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

Dans votre classe Model (Repository): (MVVM)

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

Dans votre classe ModelView: (MVVM) Gérez LiveData avec DisposableSingleObserver.
Référence du fournisseur de travail: https://github.com/SupriyaNaveen/CookingRecipes

Supriya Naveen
la source
1

Après beaucoup de difficultés, j'ai réussi à résoudre ce problème. Voici ma solution utilisant l' architecture 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)
                    }

                })
        )
    }
}

Dans le fragment:

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

J'ai utilisé DataBinding, voici mon 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>

J'ai beaucoup de mal à accomplir cela avec asynctask car l'opération d'insertion et de suppression de pièce doit être effectuée dans un thread séparé. Enfin capable de le faire avec Single type observable dans RxJava.

Voici les dépendances Gradle pour rxjava:

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

Selon la documentation, les fonctions annotées avec @Insert peuvent renvoyer le rowId.

Si la méthode @Insert ne reçoit qu'un seul paramètre, elle peut renvoyer un long, qui est le nouveau rowId pour l'élément inséré. Si le paramètre est un tableau ou une collection, il doit renvoyer long [] ou List <Long> à la place.

Le problème que j'ai avec ceci est qu'il renvoie le rowId et non l'identifiant et je n'ai toujours pas trouvé comment obtenir l'identifiant en utilisant le rowId.

Malheureusement, je ne peux pas encore faire de commentaire, car je n'ai pas 50 réputation, donc je publie ceci comme une réponse à la place.

AndroidKotlinNoob
la source