Sala Android - obtenha o ID da nova linha inserida com geração automática

138

É assim que estou inserindo dados no banco de dados usando a Biblioteca de Persistência de Sala:

Entidade:

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

Objeto de acesso a dados:

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

É possível retornar o ID do usuário depois que a inserção for concluída no próprio método acima sem escrever uma consulta de seleção separada?

SpiralDev
fonte
1
Você já tentou usar intou em longvez de voidcomo resultado da @Insertoperação?
precisa saber é
Ainda não. Eu vou dar um tiro!
SpiralDev
Também adicionei uma resposta porque encontrei a referência na documentação e estou bastante confiante de que funcionará;)
MatPag
3
isso não será feito com um aSyncTask? como você está retornando o valor da sua função de repositório?
Nimitz14

Respostas:

191

Com base na documentação aqui (abaixo do snippet de código)

Um método anotado com a @Insertanotação pode retornar:

  • long para operação de inserção única
  • long[] ou Long[] ou List<Long>para várias operações de inserção
  • void se você não se importa com os IDs inseridos
MatPag
fonte
4
por que na documentação diz int para o tipo de identificação, mas retorna por muito tempo? está assumindo que o ID nunca será grande o suficiente para ser longo? então o ID da linha e o ID de geração automática são literalmente a mesma coisa?
Michael Vescovo
11
No SQLite, o maior ID de chave primária que você pode ter é um número inteiro assinado de 64 bits; portanto, o valor máximo é 9.223.372.036.854.775.807 (apenas positivo porque é um ID). Em java, um int é um número assinado de 32 bits e seu valor máximo positivo é 2.147.483.647, portanto, não é capaz de representar todos os IDs. Você precisa usar um Java longo, cujo valor máximo é 9.223.372.036.854.775.807 para representar todos os IDs. A documentação é apenas para exemplo, mas a API foi projetado com isso em mente (é por isso que ele está retornando longa e não int ou double)
MatPag
2
ok, então realmente deve ser um longo. mas talvez na maioria dos casos não haja 9 bilhões de linhas em um banco de dados sqlite, portanto eles usam int como um exemplo para o userId, pois isso requer menos memória (ou é um erro). É isso que retiro disso. Obrigado pela explicação sobre por que ele retorna por muito tempo.
Michael Vescovo
3
Você está certo, mas as APIs do Room devem funcionar mesmo no pior cenário e devem seguir as especificações do SQlite. Usando um int durante um longo para este caso específico é praticamente a mesma coisa, o consumo de memória adicional é insignificante
MatPag
1
@ MatPag Seu link original não incluía mais uma confirmação desse comportamento (e infelizmente a referência da API também não faz parte da anotação Insert da sala ). Depois de um pouco de pesquisa, encontrei este e atualizei o link na sua resposta. Espero que persista um pouco melhor do que o último, pois essa é uma informação bastante significativa.
precisa saber é o seguinte
27

@Insertfunção pode retornar void, long, long[]ou List<Long>. Por favor, tente isso.

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

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

O valor de retorno da inserção para um registro será 1 se sua instrução for bem-sucedida.

Caso deseje inserir uma lista de objetos, você pode:

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

E execute-o com 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
fonte
1
"O valor de retorno da inserção para um registro será 1 se sua instrução for bem-sucedida" -> De acordo com esta documentação: developer.android.com/training/data-storage/room/accessing-data "Se o método @Insert receber apenas 1, ele pode retornar um long, que é o novo rowId do item inserido. Se o parâmetro for uma matriz ou uma coleção, ele retornará long [] ou List <Long> . "
precisa saber é o seguinte
4

Obtenha o ID da linha pelo seguinte sniplet. Ele usa callable em um 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 do Java Executor Service para obter mais informações sobre Callable.

Hardian
fonte
3

No seu Dao, a consulta de inserção retorna, Longou seja, o rowId inserido.

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

Na sua classe Model (Repository): (MVVM)

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

Na sua classe ModelView: (MVVM) Manipule LiveData com DisposableSingleObserver.
Referência do sourcing de trabalho: https://github.com/SupriyaNaveen/CookingRecipes

Supriya Naveen
fonte
1

Depois de muita luta, consegui resolver isso. Aqui está minha solução usando a arquitetura 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)
                    }

                })
        )
    }
}

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

Eu usei DataBinding. Aqui está o meu 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>

Eu lutei muito para fazer isso com o asynctask, pois a operação de inserção e exclusão de sala deve ser feita em um thread separado. Finalmente capaz de fazer isso com o tipo Single observável no RxJava.

Aqui estão as dependências Gradle para rxjava:

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

De acordo com a documentação, as funções anotadas com o @Insert podem retornar o rowId.

Se o método @Insert receber apenas 1 parâmetro, ele poderá retornar um longo, que é o novo rowId do item inserido. Se o parâmetro for uma matriz ou uma coleção, ele retornará long [] ou List <Long>.

O problema que tenho com isso é que ele retorna o rowId e não o id, e ainda não descobri como obter o id usando o rowId.

Infelizmente ainda não posso comentar, porque não tenho 50 reputação, por isso estou publicando isso como resposta.

AndroidKotlinNoob
fonte