Avoid duplicate entries in room Database - android

I am trying to make a function which will check whether this id is already present or not in the database, But it is not working as required.It take the insert value in database.
private fun CheckDuplicateId(id:String,donorDao:DonorDao):Boolean{
var result=true
lifecycleScope.launch {
donorDao.fetchAllDonor().collect(){
var Donorlist=ArrayList(it)
for(item in Donorlist){
if (item.id==id){
result=false
}
}
}
}
return result
}
I am using this function in while inserting the entry
private fun addRecord(donorDao:DonorDao){
val Id:String=binding?.etDonorId?.text.toString()
val bloodGr=binding?.etDonorBloodgroup?.text.toString()
if(Id.isNotEmpty() && bloodGr.isNotEmpty() && CheckDuplicateId(Id,donorDao)){
lifecycleScope.launch {
donorDao.insert(DonorEntity(id = binding?.etDonorId?.text.toString(), bloodGroup = bloodGr))
Toast.makeText(applicationContext, "Record saved", Toast.LENGTH_SHORT).show()
}
}else{
Toast.makeText(this,"duplicate value",Toast.LENGTH_SHORT).show()
}
}
here donor entity
#Entity(tableName = "donor-table")
data class DonorEntity(
#PrimaryKey(autoGenerate = true)
var sr_no:Int=0,
var id:String="",
var bloodGroup:String="")
here donor dao
#Dao
interface DonorDao {
#Insert
suspend fun insert(donorEntity:DonorEntity)
#Update
suspend fun update(donorEntity:DonorEntity)
#Delete
suspend fun delete(donorEntity:DonorEntity)
#Query("SELECT*FROM `donor-table`")
fun fetchAllDonor(): Flow<List<DonorEntity>>
#Query("SELECT*FROM `donor-table` Where id=:id")
fun fetchDonorById(id:Int): Flow<DonorEntity>
}
here the database
#Database(entities = [DonorEntity::class], version = 2)
abstract class DonorDatabase:RoomDatabase() {
abstract fun donorDao():DonorDao
companion object{
#Volatile
private var INSTANCE:DonorDatabase?=null
fun getInstance(context: Context):DonorDatabase{
synchronized(this){
var instance=INSTANCE
if (instance==null){
instance=Room.databaseBuilder(context.applicationContext,
DonorDatabase::class.java,"donor_database")
.fallbackToDestructiveMigration().build()
}
INSTANCE=instance
return instance
}
}
}
}

Your method returning true immediately:
private fun CheckDuplicateId(id:String,donorDao:DonorDao):Boolean{
var result=true
lifecycleScope.launch {
donorDao.fetchAllDonor().collect(){
var Donorlist=ArrayList(it)
for(item in Donorlist){
if (item.id==id){
result=false
}
}
}
}
return result // return true immediately
}
So, do it inside
private fun addRecord(donorDao:DonorDao){
val Id:String=binding?.etDonorId?.text.toString()
val bloodGr=binding?.etDonorBloodgroup?.text.toString()
if(Id.isNotEmpty() && bloodGr.isNotEmpty()){
lifecycleScope.launch {
// check here CheckDuplicateId(Id,donorDao) synchronously
donorDao.insert(DonorEntity(id = binding?.etDonorId?.text.toString(), bloodGroup = bloodGr))
Toast.makeText(applicationContext, "Record saved", Toast.LENGTH_SHORT).show()
}
}else{
Toast.makeText(this,"duplicate value",Toast.LENGTH_SHORT).show()
}
}

One way to do that is at DB level is using index. This will only insert if table as no with same id value. You can avoid select * query which would affect performance if you have many data in you case.
#Entity(tableName = "donor-table",indices = [Index(value = ["id"], unique = true)])
data class DonorEntity(
#PrimaryKey(autoGenerate = true)
var sr_no:Int=0,
#ColumnInfo(name = "id")
var id:String="",
var bloodGroup:String="")

To make certain fields or groups of fields in a database unique, you can enforce this uniqueness property by setting the unique property of an #Index annotation to true.
#Entity(indices = [Index(value = ["first_name", "last_name"],
unique = true)])
data class User(
#PrimaryKey val id: Int,
#ColumnInfo(name = "first_name") val firstName: String?,
#ColumnInfo(name = "last_name") val lastName: String?,
#Ignore var picture: Bitmap?
)
Here is the Official Docs

Related

How can I add data into Room database in a manner where it is appended to the top row

Image for reference
I am facing a situation whenever I add a new article into the database it will add the new article to the bottom of the RecyclerView, how can I implement it in such a way that it updates the top row instead?
Is it something got to do with the Adapter or do I have to tweak the Doa?
Room Database Implementation
**//ViewModel**
fun saveArticle(article: Article) = viewModelScope.launch {
newsRepository.upsert(article)
}
**
//NewsRepository**
suspend fun upsert(article: Article) = db.getArticleDao().upsert(article)
**//Implementation of the Database**
#Database(
entities = [Article::class],
version = 1
)
#TypeConverters(Converters::class)
abstract class ArticleDatabase : RoomDatabase() {
abstract fun getArticleDao(): ArticleDao
companion object {
#Volatile
private var instance: ArticleDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: createDatabase(context).also { instance = it }
}
private fun createDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
ArticleDatabase::class.java,
"article_db.db"
).build()
}
}
**//DOA**
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(article: Article): Long //returns the id's of the article
**//Entity**
#Entity(
tableName = "articles", indices = [Index(value = ["url","title"], unique = true)]
)
#Parcelize
data class Article(
#PrimaryKey(autoGenerate = true)
var id: Int? =null,
val author: String?,
val description: String?,
val source: Source?,
val title: String?,
val url: String?,
val urlToImage: String?,
val publishedAt: String?,
val content: String?
): Parcelable {
override fun hashCode(): Int {
var result = id.hashCode()
if(url.isNullOrEmpty()){
result = 31 * result + url.hashCode()
}
return result
}
}
Expecting the top row to be updated instead of the bottom row, the Room Database docs did not talk much about changing the way the article is inserted, tried to reference other people's work, couldn't find the solution

Android Room - ColumnInfo Cannot find getter for field. private final ... = null;

error: Cannot find getter for field.
private final com.kbb.webviewolacakmi.model.content icerik = null;
I didn't manage to add the subparts of the json to the room.
Thanks to everyone who helped.
I would be very happy if you could write a clear code example.
Json File :
{
"date": "xxx",
"title": {
"rendered": "Title"
},
"content": {
"rendered": "content",
"protected": false
},
}
Data Class :
#Entity
data class Icerik(
#ColumnInfo(name="title")
#SerializedName("title")
val baslik:title?,
#ColumnInfo(name="content")
#SerializedName("content")
public val icerik:content?,
#ColumnInfo(name="date")
#SerializedName("date")
val tarih:String?,
#ColumnInfo(name="jetpack_featured_media_url")
#SerializedName("jetpack_featured_media_url")
val gorsel:String?,) {
#PrimaryKey(autoGenerate = true)
var uuid:Int=0
fun getIcerik(){
}
}
data class content(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
public val content: String?,
#ColumnInfo(name="protected")
#SerializedName("protected")
val bool: Boolean?,
){
#PrimaryKey(autoGenerate = true)
var uuid:Int=0
}
data class title(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
val ytitle:String?
){
#PrimaryKey(autoGenerate = true)
var uuid:Int=0
}
IcerikDatabase Class
#TypeConverters(value = [RoomTypeConverters::class])
#Database(entities = arrayOf(Icerik::class), version = 1)
abstract class IcerikDatabase:RoomDatabase() {
abstract fun icerikDao(): IcerikDAO
companion object {
#Volatile private var instance:IcerikDatabase? = null
private val lock=Any()
operator fun invoke(context: Context)= instance?: synchronized(lock){
instance?: databaseOlustur(context).also {
instance=it
}
}
private fun databaseOlustur(context: Context) = Room.databaseBuilder(
context.applicationContext, IcerikDatabase::class.java,
"icerikdatabase"
).build()
}
}
IcerikDao
interface IcerikDAO {
#Insert
suspend fun instertAll(vararg icerik:Icerik):List<Long>
#Query("SELECT * FROM icerik")
suspend fun getAllIcerik():List<Icerik>
#Query("SELECT * FROM icerik WHERE uuid=:icerikId ")
suspend fun getIcerik(icerikId:Int):Icerik
#Query("DELETE FROM icerik")
suspend fun deleteAllIcerik()
}
TypeConverter
class RoomTypeConverters {
#TypeConverter
fun fromTitleToJSONString(title: title?): String? {
return Gson().toJson(title)
}
#TypeConverter
fun toTitleFromJSONString(jsonString: String?): title? {
return Gson().fromJson(jsonString, title::class.java)
}
#TypeConverter
fun fromIcerikToJSONString(content: content?): String? {
return Gson().toJson(content)
}
#TypeConverter
fun toIcrerikFromJSONString(jsonString: String?): content? {
return Gson().fromJson(jsonString, content::class.java)
}
}
I believe that your issue is in regard, not to room, but with the JSON handling.
The JSON file that you have shown cannot directly build an Icerik object.
Rather you need to have an intermediate class that can be built with the JSON and then use that intermediate class to then build the IceRik object.
So you want an intermediate class something along the lines of:-
data class JsonIceRik(
val content: content,
val title: title,
val date: String
)
If the JSON is then amended to be:-
val myjson = "{\"date\": \"xxx\",\"title\": {\"rendered\": \"Title\"},\"content\": {\"rendered\": \"content\",\"protected\": false}}"
note the omission of the comma between the two closing braces
Then you could use:-
val m5 = Gson().fromJson(myjson,JsonIceRik::class.java)
To build the intermediate JsonIceRik object.
And then you could use:-
val i5 = Icerik(baslik = m5.title, icerik = m5.content, tarih = m5.date,gorsel = "whatever")
To build the Icerik from the intermediate JsonIceRik.
The result in the database would be:-
uuid in the title and content serve no purpose and will always be 0 if obtaining the data from JSON
the #PrimaryKey annotation only serves to introduce warnings -
A Table can only have 1 Primary Key ( a composite Primary Key can include multiple columns though (but you cannot use the #PrimaryKey annotation, you have to instead use the primarykeys parameter of the #Entity annotation) )
You might as well have :-
data class content(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
val content: String?,
#ColumnInfo(name="protected")
#SerializedName("protected")
val bool: Boolean?,
)
data class title(
#ColumnInfo(name="rendered")
#SerializedName("rendered")
val ytitle:String?
)
Otherwise, as you can see, the data according to the JSON has been correctly stored.

Is their a better way to populate ignored column from another table using room

I have two related tables item and purchase. The purchase class contains an ignored column itemName which I want to fill with itemName from item
Although I have a method but I am concerned if there is a better way
to achieve it because my way has to do with reading all items from the database then comparing purchase.itemOwnerID to item.itemID
Here is my populating code along with others
LaunchedEffect(key1 = true) {
sharedViewModel.requestAllPurchases()
sharedViewModel.requestAllItems() //requesting all items
}
val allPurchases by sharedViewModel.allPurchases.collectAsState()
val allItems by sharedViewModel.allItems.collectAsState() //getting all items requested
if (allPurchases is RequestState.Success && allItems is RequestState.Success) {
(allPurchases as RequestState.Success<List<Purchase>>).data.forEach { purchase ->
(allItems as RequestState.Success<List<Item>>).data.first { it.itemID == purchase.itemOwnerID }//comparism
.apply {
purchase.itemName = itemName
purchase.costPrice = salePrice
}
}
}
Model
#Entity(tableName = "items", indices = [Index(value = ["itemName"], unique = true)])
data class Item(
#PrimaryKey(autoGenerate = true)
var itemID: Int,
var categoryOwnerID: Int = 0,
var itemName: String,
var costPrice: Int,
var salePrice: Int,
) {
#Ignore
var desiredQuantity: Int = 0
}
#Entity(tableName = "purchases")
data class Purchase(
#PrimaryKey(autoGenerate = true)
var purchaseID: Int,
var itemOwnerID: Int,
var quantity: Int,
var soldPrice: Int,
) {
#Ignore
var itemName: String = ""
}
sealed class RequestState<out T> {
object Idle : RequestState<Nothing>()
object Loading : RequestState<Nothing>()
data class Success<T>(val data: T) : RequestState<T>()
data class Error(val error: Throwable) : RequestState<Nothing>()
}
#Dao
#Query("select * from items order by itemName asc")
fun getAllItems(): Flow<List<Item>>
#Query("select * from purchases order by purchaseID asc")
fun getAllPurchases(): Flow<List<Purchase>>
Repository
val getAllItems: Flow<List<Item>> = itemDao.getAllItems()
val getAllPurchases: Flow<List<Purchase>> = itemDao.getAllPurchases()
SharedViewModel
private var _allItems = MutableStateFlow<RequestState<List<Item>>>(RequestState.Idle)
val allItems: StateFlow<RequestState<List<Item>>> = _allItems
fun requestAllItems() {
_allItems.value = RequestState.Loading
try {
viewModelScope.launch {
repository.getAllItems.collect {
_allItems.value = RequestState.Success(it)
}
}
} catch (e: Exception) {
_allItems.value = RequestState.Error(e)
}
}
private var _allPurchases =
MutableStateFlow<RequestState<List<Purchase>>>(RequestState.Idle)
val allPurchases: StateFlow<RequestState<List<Purchase>>> = _allPurchases
fun requestAllPurchases() {
_allPurchases.value = RequestState.Loading
try {
viewModelScope.launch {
repository.getAllPurchases.collect {
_allPurchases.value = RequestState.Success(it)
}
}
} catch (e: Exception) {
_allPurchases.value = RequestState.Error(e)
}
}
First, create another data class based on the columns needed, then use a #Query to get the needed columns from the database or a join query for multiple tables like the one below
Query("SELECT P.purchaseID, P.itemOwnerID, P.quantity, P.soldPrice, I.itemName FROM purchases as P INNER JOIN items AS I ON I.itemID = P.itemOwnerID")
fun getAllPurchases(): Flow<List<Purchase>>
More info could be found here

Checking if entry exists

I'm trying to create a login app. I want it to do nothing more than either store the account info on a db if the username is not already used. Problem is, no matter what I try, I cannot check if the username is already in the db
I've tried to get both LiveData from my query, as well as String?, as shown below with no success
I have also tried setting an onConflict strategy other than ignore, and then putting my addUser function call in a try-catch block, but it crashes
#Entity(tableName = "Users_table")
data class User(
#PrimaryKey var username: String,
#ColumnInfo var firstName: String,
#ColumnInfo var lastName: String,
#ColumnInfo var email: String,
#ColumnInfo var password: String
)
#Dao
interface UsersDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun addUser(user: User)
#Query("Select username from users_table Where username = :username")
fun getUser(username: String): String?
}
#Database(entities = [User::class], version = 1)
abstract class UsersDatabase: RoomDatabase() {
abstract fun userDao(): UsersDao
companion object{
#Volatile
private var INSTANCE: UsersDatabase? = null
fun getDatabase(context: Context): UsersDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
UsersDatabase::class.java,
"users_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
class UsersRepository(private val usersDao: UsersDao) {
suspend fun addUser(user: User){
usersDao.addUser(user)
}
suspend fun getUser(username: String) {
usersDao.getUser(username)
}
}
class UsersViewModel(application: Application): AndroidViewModel(application) {
//private val getUser: LiveData<List<User>>
private val repository: UsersRepository
init {
val userDao = UsersDatabase.getDatabase(application).userDao()
repository = UsersRepository(userDao)
//getUser = repository.getUser
}
fun addUser(user: User) {
viewModelScope.launch(Dispatchers.IO) {
repository.addUser(user)
}
}
fun getUser(username: String) {
viewModelScope.launch(Dispatchers.IO) {
repository.getUser(username)
}
}
}
...
if (mUsersViewModel.getUser(binding.etRegUsername.text.toString()).toString() != "0") {
Toast.makeText(this, "Username already exists", Toast.LENGTH_SHORT).show()
} else {
mUsersViewModel.addUser(user)
Toast.makeText(this, "User successfully created", Toast.LENGTH_SHORT).show()
}
...
You can use this query to get count of users with given user name
#Query("Select count(*) from users_table Where username LIKE :username")
fun getUser(username: String): Int?
and use this to check if there are user
if (mUsersViewModel.getUser(binding.etRegUsername.text.toString()) != 0) {
Toast.makeText(this, "Username already exists", Toast.LENGTH_SHORT).show()
} else {
mUsersViewModel.addUser(user)
Toast.makeText(this, "User successfully created", Toast.LENGTH_SHORT).show()
}
You have two options:
Fetch all the data first and then compare it with your editText's data(username,email)
Set an query inside the UsersDao , to check whether an entry with same values exists or not.

Retrieve data from Room using Column Value - Room

I have two tables in my Room DB - Events and Notes. For each event I have displayed in the RecycleView - I have a link to launch a note for that event. On first click - Note is created. On the second time the note is clicked, I would like to retrieve the previous note and then edit. Also, I am using the same activity to already edit/create new notes by passing on appropriate values, which works but uses parcelized note.
For editing an existing event Note - I am sending across the event ID (which is also stored in the Note table - not as a Foreign key) using the putExtra method. DB structure below (assocId refers to eventId)
ViewModel
fun setNotesByAssocEventId(assocEventId: String): Note {
return dao.getByAssocEventId(assocEventId)
}
DAO
#Query("SELECT * FROM notes WHERE assocEventId = :assocEventId")
fun getByAssocEventId(assocEventId: String): Note
NoteEntity
#Entity(tableName = "notes")
#Parcelize
data class Note(
//PrimaryKey annotation to declare primary key with auto increment value
//ColumnInfo annotation to specify the column's name
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") var id: Int = 0,
#ColumnInfo(name = "assocEventId") var assocEventId: String = "",
#ColumnInfo(name = "title") var title: String = "",
#ColumnInfo(name = "label") var label: String = "",
#ColumnInfo(name = "date") var date: String = "",
#ColumnInfo(name = "time") var time: String = "",
#ColumnInfo(name = "updatedDate") var updatedDate: String = "",
#ColumnInfo(name = "updatedTime") var updatedTime: String = "",
#ColumnInfo(name = "body") var body: String = ""
) : Parcelable
I am using the below code to edit/create new notes. While I am able to create/Edit notes. I am unable to retrieve a node for a particular event using the eventId. One of the errors I am getting is Note object has not been initialized when I am assigning the note object returned from the ViewModel. What could be the issue?
assocID is the event ID obtained using putExtra and the corresponding event note is to be retrieved...
private lateinit var binding: ActivityEditNoteBinding
private lateinit var notesViewModel: NotesViewModel
private lateinit var note: Note
private var assocId: String? = ""
private var isUpdate = false
private val dateChange = DateChange()
var refUsers: DatabaseReference? = null
var firebaseUser: FirebaseUser? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEditNoteBinding.inflate(layoutInflater)
setContentView(binding.root)
assocId = intent.getStringExtra("eventId").toString()
initView()
initListener()
}
private fun initView() {
firebaseUser = FirebaseAuth.getInstance().currentUser
initViewModel()
if (assocId != null) {
findViewById<TextView>(R.id.editNote).text = "Edit Event Note"
Toast.makeText(this, "EvetnId received", Toast.LENGTH_SHORT).show()
isUpdate = true
binding.editNoteDelete.visibility = View.VISIBLE
notesViewModel.getNotes()
note = notesViewModel.setNotesByAssocEventId("%${assocId}%")
binding.editTextTitle.setText(note.title)
binding.editTextBody.setText(note.body)
binding.editTextTitle.setSelection(note.title.length)
//set spinner position
val compareValue = note.label
val adapter = ArrayAdapter.createFromResource(
this, R.array.NoteSpinnerVals,
android.R.layout.simple_spinner_item
)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spLabel.adapter = adapter
val spinnerPosition = adapter.getPosition(compareValue)
binding.spLabel.setSelection(spinnerPosition)
}
}
private fun initViewModel() {
notesViewModel = ViewModelProvider(this).get(NotesViewModel::class.java)
}
private fun initListener() {
// binding.editNoteBack.setOnClickListener(this)
binding.editNoteSave.setOnClickListener(this)
binding.editNoteDelete.setOnClickListener(this)
}
private fun deleteNote(note: Note) {
notesViewModel.deleteNote(note)
Toast.makeText(this#EditNote, "Note removed", Toast.LENGTH_SHORT).show()
}
private fun showDialog() {
AwesomeDialog.build(this)
.position(AwesomeDialog.POSITIONS.CENTER)
.title("Delete the note?")
.icon(R.drawable.ic_delete_black)
.background(R.drawable.background_dialog)
.onPositive(
"Yes, delete",
buttonBackgroundColor = R.drawable.button_bg,
textColor = ContextCompat.getColor(this, R.color.white)
) {
deleteNote(note)
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
.onNegative(
"Cancel",
buttonBackgroundColor = R.drawable.button_bg,
textColor = ContextCompat.getColor(this, R.color.white)
) {
}
}
Code of the ViewModel
class NotesViewModel(application: Application) : AndroidViewModel(application) {
private val context = getApplication<Application>().applicationContext
private val listNotes = MutableLiveData<ArrayList<Note>>()
private var dao: NoteDao
init {
val database = AppDatabase.getDatabase(context)
dao = database.getNoteDao()
}
fun setNotes() {
val listItems = arrayListOf<Note>()
listItems.addAll(dao.getAll())
listNotes.postValue(listItems)
}
fun setNotesByType(label: String) {
val listItems = arrayListOf<Note>()
listItems.addAll(dao.getByLabel(label))
listNotes.postValue(listItems)
}
fun setNotesByTitle(title: String) {
val listItems = arrayListOf<Note>()
listItems.addAll(dao.getByTitle(title))
listNotes.postValue(listItems)
}
fun setNotesByAssocEventId(assocEventId: String): Note {
return dao.getByAssocEventId(assocEventId)
}
fun insertNote(note: Note) {
dao.insert(note)
}
fun updateNote(note: Note) {
dao.update(note)
}
fun deleteNote(note: Note) {
dao.delete(note)
}
fun getNotes(): LiveData<ArrayList<Note>> {
return listNotes
}
}
The method in the DAO need to be changed a little
#Query("SELECT * FROM notes WHERE assocEventId = :assocEventId")
fun getByAssocEventId(assocEventId: String): Note
should be
#Query("SELECT * FROM notes WHERE assocEventId LIKE :assocEventId")
fun getByAssocEventId(assocEventId: String): LiveData<List<Note>>
In order to support wild character search, "%${assocId}%", LIKE keyword.
To get one Note only
#Query("SELECT * FROM notes WHERE assocEventId LIKE :assocEventId LIMIT 1")
fun getByAssocEventId(assocEventId: String): LiveData<Note>
in view model
fun setNotesByAssocEventId(assocEventId: String): LiveData<Note>{
return dao.getByAssocEventId(assocEventId)
}
in the activity
notesViewModel.setNotesByAssocEventId("%${assocId}%").observe(this, {
if(it!=null){
//if you using for single note only
}
//if(it.isNotEmpty()){
//if you using for list
//}
})

Categories

Resources