Search code examples
androidsqlitekotlinmvvmandroid-room

Problem with saving faviorites items to room database, saved dublicate items and unable to check if it's saved too


While I updating some parts of my app, I think I doing some mistake or something is missing that causes this problem, the problem is happening when I tried to save any item to the favorites table, and after I saved it I open the same item (fragment details again) I should see the favorite icon (heart icon) filled with red color which means the item is already saved and If I click on it again it should be removed and so on...

now I see the item saved but when I click the back button and click on the item again the icon doesn't change and that's meaning the user can save it again (which causes duplicated items in recyclerView)

Problem in GIF

here's my code

FavoritesEntity class

@Parcelize
@Entity(tableName = "favorites")
data class FavoritesEntity(@PrimaryKey(autoGenerate = true) val id: Int, val item: Item) : Parcelable

Database class

@Database(
    entities = [Item::class, FavoritesEntity::class],
    version = 1,
    exportSchema = false
) //@TypeConverters(Converters.class)

//@TypeConverters(Converters.class)
//@TypeConverters(Converters.class)
@TypeConverters(ItemTypeConverter::class)
abstract class ItemsDatabase : RoomDatabase() {

    //    private static ItemsDatabase INSTANCE;
    abstract val itemDAO: ItemDAO
    
    companion object {

        @Volatile
        private var INSTANCE: ItemsDatabase? = null


        @Synchronized
        fun getInstance(context: Context): ItemsDatabase {


            if (INSTANCE == null) {

                INSTANCE = Room.databaseBuilder(
                    context.applicationContext,
                    ItemsDatabase::class.java,
                    "items_database"
                ).fallbackToDestructiveMigration()
                    .build()

                return INSTANCE!!
            }
            return INSTANCE as ItemsDatabase
        }
    }
}

ItemDAO

@Dao
interface ItemDAO {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertItem(item: Item)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertFavorites(favoritesEntity: FavoritesEntity)

    @get:Query("SELECT * FROM FAVORITES")
    val getAllFavorites: Flow<List<FavoritesEntity>>

    @Delete
    suspend fun deleteFavorite(favoritesEntity: FavoritesEntity)

    @Query("DELETE FROM FAVORITES")
    suspend fun deleteAllFavorites()

    @Query("SELECT * FROM item_table order by datetime(published) DESC")
    fun getAlItems(): Flow<List<Item>>

    @Query("SELECT * FROM item_table WHERE title like '%' || :keyword || '%' ")
    suspend fun getItemsBySearch(keyword: String?): List<Item>
}

DetailsViewModel

@HiltViewModel
class DetailsViewModel @Inject constructor(
    private val mainRepository: MainRepository,
    private val dataStoreRepository: DataStoreRepository
) : ViewModel() {

    private val _favoritesPostList = MutableLiveData<List<FavoritesEntity>>()
    val favoritesPostList: LiveData<List<FavoritesEntity>> get() = _favoritesPostList


    fun getFavoritePosts() {
        viewModelScope.launch {
            mainRepository.localDataSource.getAllFavorites().collect {
                _favoritesPostList.value = it
            }
        }
    }

    val readRecyclerViewPortraitLayout =
        dataStoreRepository.readRecyclerViewPortraitLayout.asLiveData()
    val readRecyclerViewLandscapeLayout =
        dataStoreRepository.readRecyclerViewLandscpaeLayout.asLiveData()

    fun saveRecyclerViewPortraitLayout(layout: String) {
        viewModelScope.launch {
            dataStoreRepository.saveRecyclerViewPortraitLayout(layout)
        }
    }

    fun saveRecyclerViewLandscapeLayout(layout: String) {
        viewModelScope.launch {
            dataStoreRepository.saveRecyclerViewLandscapeLayout(layout)
        }
    }

    fun insertFavorites(favoritesEntity: FavoritesEntity) {
        viewModelScope.launch(Dispatchers.Default) {
            mainRepository.localDataSource.insertFavorites(favoritesEntity)
        }
    }

    fun deleteFavoritePost(favoritesEntity: FavoritesEntity) {
        viewModelScope.launch(Dispatchers.IO) {
            mainRepository.localDataSource.deleteFavorite(favoritesEntity)
        }
    }

    fun deleteAllFavorites() {
        viewModelScope.launch(Dispatchers.IO) {
            mainRepository.localDataSource.deleteAllFavorites()
        }
    }
}

DetailsActivity


@AndroidEntryPoint
class DetailsActivity : AppCompatActivity() {
    private var _binding: ActivityDetailsBinding? = null
    private val binding get() = _binding!!
    private var url: String? = null
    private var title: String? = null
    private var content: String? = null
    private var youtubeThumbnailImageSrc: String? = null
    private var youTubeLink: String? = null
    private var youtubeThumbnailImageSetVisibility: Int? = null

    private var html: Spannable? = null

    private val detailsViewModel: DetailsViewModel by viewModels()

    private var postItem: Item? = null

    private var postFavoritesSaved = false
    private var postFavoritesSavedId = 0
    private var menuItem: MenuItem? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityDetailsBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setDisplayShowHomeEnabled(true)
        binding.progressBar.visibility = View.VISIBLE
        Log.d(
            TAG,
            "onCreate checkSavedFavoritesItems: $postFavoritesSavedId"
        )


        postItem = if (intent.extras!!.containsKey("postItem")) {
            intent.getParcelableExtra("postItem")!!
        } else {
            val favoriteItem: FavoritesEntity = intent.getParcelableExtra("favoriteItem")!!
            favoriteItem.item
        }
  
    }

    override fun onDestroy() {
        super.onDestroy()

        changeMenuItemIcon(menuItem, R.drawable.ic_favorite_border)
        binding.fab.setOnClickListener(null)
        adRequest = null
        _binding = null
    }

    private fun changeMenuItemIcon(menuItem: MenuItem?, icFavoriteBorder: Int) {
        menuItem?.setIcon(icFavoriteBorder)
    }

        private fun checkSavedFavoritesItems(menuItem: MenuItem?) {
        detailsViewModel.favoritesPostList.observe(this) { favoritesEntity ->
            try {
                
                favoritesEntity.forEach {
                    if (it.item.id == postItem?.id) {
                        changeMenuItemIcon(menuItem, R.drawable.ic_favorite)
                        postFavoritesSavedId = it.id
                        Log.d(TAG, "checkSavedFavoritesItems: $postFavoritesSavedId")
                        postFavoritesSaved = true
                    }
                }
                
            } catch (exception: Exception) {
                Log.e(TAG, "checkSavedFavoritesItems: " + exception.message)
            }
        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.details_menu, menu)
        menuItem = menu.findItem(R.id.action_add_to_favorites)
        checkSavedFavoritesItems(menuItem)
        return true
    }

    override fun onOptionsItemSelected(menuItem: MenuItem): Boolean {
        when {
            menuItem.itemId == R.id.action_add_to_favorites && !postFavoritesSaved -> {
                saveTogFavorites(menuItem)
            }
            menuItem.itemId == R.id.action_add_to_favorites && postFavoritesSaved -> {
                removePostFromFavorites(menuItem)
            }

            menuItem.itemId == android.R.id.home -> this.finish()
        }
        return super.onOptionsItemSelected(menuItem)
    }


    private fun saveTogFavorites(menuItem: MenuItem) {
        val favoritesEntity = postItem?.let {
            FavoritesEntity(0, it)
        }
        if (favoritesEntity != null) {
            detailsViewModel.insertFavorites(favoritesEntity)
        }
        menuItem.setIcon(R.drawable.ic_favorite)
        Snackbar.make(binding.root, "Saved", Snackbar.LENGTH_LONG).show()
        postFavoritesSaved = true
    }

    private fun removePostFromFavorites(menuItem: MenuItem) {
        val favoritesEntity = postItem?.let { FavoritesEntity(postFavoritesSavedId, it) }
        Log.d(
            TAG, "checkSavedFavoritesItems: $postFavoritesSavedId"
        )
        if (favoritesEntity != null) {
            detailsViewModel.deleteFavoritePost(favoritesEntity)
        }
        menuItem.setIcon(R.drawable.ic_favorite_border)
        Snackbar.make(
            binding.root,
            "Post deleted from favorites", Snackbar.LENGTH_LONG
        ).show()
        postFavoritesSaved = false
    }

I tried to check for postFavoritesSavedId in the log and I got 0 eachtime, so I tried to change it to get the item itself long ID instead favoritesEntity incremental id but doesn't work


Solution

  • in this method checkSavedFavoritesItems you observsed in favoritesEntity but you forgot to call getFavoritePosts method because of that this problem happend