Android Room: ottieni l'ID della nuova riga inserita con la generazione automatica


138

Ecco come sto inserendo i dati nel database usando Room Persistence Library:

Entità:

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

Oggetto di accesso ai dati:

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

È possibile restituire l'id dell'utente una volta completato l'inserimento nel metodo sopra descritto senza scrivere una query di selezione separata?


1
Hai provato a utilizzare into longanziché voidcome risultato @Insertdell'operazione?
MatPag,

Non ancora. Ci proverò!
SpiralDev,

ho aggiunto anche una risposta perché ho trovato il riferimento nella documentazione e sono abbastanza sicuro che funzionerà;)
MatPag

3
non sarà fatto con un aSyncTask? come stai restituendo il valore dalla tua funzione di repository?
Nimitz14

Risposte:


191

Basato sulla documentazione qui (sotto lo snippet di codice)

Un metodo annotato con l' @Insertannotazione può restituire:

  • long per l'operazione di inserimento singolo
  • long[]o Long[]o List<Long>per più operazioni di inserimento
  • void se non ti interessano gli ID inseriti

5
perché nella documentazione dice int per tipo id ma restituisce long? supponendo che l'id non sarà mai abbastanza grande da essere lungo? quindi l'id riga e l'id di generazione automatica sono letteralmente la stessa cosa?
Michael Vescovo,

11
In SQLite il più grande ID chiave primaria che puoi avere è un intero con segno a 64 bit, quindi il valore massimo è 9.223.372.036.854.775.807 (solo positivo perché è un ID). In java un int è un numero con segno a 32 bit e il valore positivo massimo è 2.147.483.647, quindi non è in grado di rappresentare tutti gli ID. È necessario utilizzare un Java long il cui valore massimo è 9.223.372.036.854.775.807 per rappresentare tutti gli ID. La documentazione è solo per esempio, ma l'API è stata progettata pensando a questo (ecco perché sta tornando a lungo e non int o double)
MatPag,

2
ok quindi dovrebbe davvero essere lungo. ma forse per la maggior parte dei casi non ci saranno 9 miliardi di righe in un database sqlite, quindi usano int come esempio per l'utenteId poiché richiede meno memoria (o è un errore). Questo è quello che prendo da questo. Grazie per la spiegazione sul perché ritorni a lungo.
Michael Vescovo,

3
Hai ragione, ma le API di Room dovrebbero funzionare anche nello scenario peggiore e devono seguire le specifiche di SQlite. L'uso di un int più a lungo per questo caso specifico è praticamente la stessa cosa, il consumo di memoria aggiuntiva è trascurabile
MatPag,

1
@MatPag Il tuo link originale non includeva più una conferma di questo comportamento (e purtroppo, il riferimento API per l'annotazione Inserisci della room ). Dopo un po 'di ricerca ho trovato questo e aggiornato il link nella tua risposta. Spero che persista un po 'meglio dell'ultimo in quanto si tratta di un po' di informazioni piuttosto significative.
CodeClown42

27

@Insertfunzione può restituire void, long, long[]o List<Long>. Per favore, prova questo.

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

 // Insert multiple items
 @Insert(onConflict = OnConflictStrategy.REPLACE)
  long[] insert(User... user);

5
return Single.fromCallable(() -> dbService.YourDao().insert(mObject));
Murt

8

Il valore restituito dell'inserimento per un record sarà 1 se l'istruzione viene eseguita correttamente.

Nel caso in cui desideri inserire un elenco di oggetti, puoi andare con:

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

Ed eseguilo 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.

        }
    });

1
"Il valore di ritorno dell'inserimento per un record sarà 1 se la tua affermazione è andata a buon fine" -> Secondo questa documentazione: developer.android.com/training/data-storage/room/accessing-data "Se il metodo @Insert riceve solo 1 parametro, può restituire long, ovvero il nuovo rowId per l'elemento inserito. Se il parametro è un array o una raccolta, dovrebbe invece restituire long [] o List <Long> . "
CodeClown42

4

Ottieni l'ID riga dal seguente sniplet. Utilizza callable su un ExecutorService con 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;
 }

Rif: Java Executor Service Tutorial per ulteriori informazioni su Callable.


3

Nel tuo Dao, la query di inserimento restituisce Longcioè il rowId inserito.

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

Nella tua classe Model (Repository): (MVVM)

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

Nella tua classe ModelView: (MVVM) Gestisci LiveData con DisposableSingleObserver.
Riferimento sourcer di lavoro: https://github.com/SupriyaNaveen/CookingRecipes


1

Dopo molte lotte, sono riuscito a risolverlo. Ecco la mia soluzione usando l'architettura 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)
                    }

                })
        )
    }
}

Nel frammento:

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

Ho usato DataBinding. Ecco il mio 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>

Ho faticato molto per ottenere questo risultato con asynctask poiché l'operazione di inserimento ed eliminazione della stanza deve essere eseguita in un thread separato. Finalmente in grado di farlo con Single type osservabile in RxJava.

Ecco le dipendenze Gradle per rxjava:

implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.0.3' 

0

Secondo le funzioni di documentazione annotate con @Insert, è possibile restituire rowId.

Se il metodo @Insert riceve solo 1 parametro, può restituire long, ovvero il nuovo rowId per l'elemento inserito. Se il parametro è un array o una raccolta, dovrebbe invece restituire long [] o List <Long>.

Il problema che ho con questo è che restituisce rowId e non l'id e non ho ancora scoperto come ottenere l'id usando rowId.

Purtroppo non posso ancora commentare, perché non ho 50 reputazione, quindi sto pubblicando questa come risposta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.