When I am trying to insert around 400 rows of data in DB using Room , its inserting only 90 rows of data. Please find below my code :
#Entity(tableName = "data_info")
data class DataInfo(
#PrimaryKey(autoGenerate = true) val uid: Int,
val body: String?,
val title:String?) {
}
DataInfoDao.kt
#Dao
interface DataInfoDao{
#Transaction #Insert
fun insertAll(resultModel:MutableList<DataInfo> )
#Transaction #Query("SELECT * FROM data_info ORDER BY uid COLLATE NOCASE ASC")
fun allDataByName(): DataSource.Factory<Int, DataInfo>
}
DataInfoRoomDataBase.kt:
#Database(entities = arrayOf(DataInfo::class), version = 3)
abstract class DataInfoRoomDataBase : RoomDatabase() {
abstract fun dataInfoDao(): DataInfoDao
val context:Context?=null
companion object {
private var INSTANCE: DataInfoRoomDataBase? = null
fun getDatabase(context: Context): DataInfoRoomDataBase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
DataInfoRoomDataBase::class.java,
"datainfo_page_database"
) .fallbackToDestructiveMigration()
.addCallback(SRoomDatabaseCallback())
.build()
INSTANCE = instance
return instance
}
}
}
private class SRoomDatabaseCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
/*PopulateDbAsyncTask(INSTANCE)
.execute()*/
fillInDb(INSTANCE)
}
private fun fillInDb(db: DataInfoRoomDataBase?) {
// inserts in Room are executed on the current thread, so we insert in the background
ioThread {
db?.dataInfoDao()?.insertAll(prepareDummyData())
/* db?.dataInfoDao()?.insertAll(
CHEESE_DATA.map{DataInfo(uid = 0, body="Info" , title = it)}.toMutableList())*/
}
}
private fun prepareDummyData():MutableList<DataInfo>{
val dummyList = mutableListOf<DataInfo>()
for(i in 0..400){
dummyList.add(DataInfo(uid = 0, body="Info" , title = "Title $i"))
}
return dummyList
}
}
In the above code in the method "prepareDummyData()" I am prepare dummy data for 400 rows and trying to insert to DB. But only 90 records are getting inserted.
My View model is as below:
class DataInfoViewModel(application: Application) : AndroidViewModel(application) {
var allInfo: LiveData<PagedList<DataInfo>> = MutableLiveData()
lateinit var dataInfoDao:DataInfoDao
init{
dataInfoDao = DataInfoRoomDataBase.getDatabase(application).dataInfoDao()
allInfo = dataInfoDao.allDataByName().toLiveData(Config(pageSize = 30,
enablePlaceholders = true
,maxSize = 200))
}
fun remove(dataInfo: DataInfo) = ioThread {
dataInfoDao.delete(dataInfo)
}
}
Please find my View and Adapter classes as below :
class PagingRoomDataBaseFragment:Fragment() {
var fragmentView: View? = null
// private var listInfoAdapter:ListInfoAdapter?=null
private var roomDbInfoListFragmentLayoutBinding:RoomDbInfoListFragmentLayoutBinding?=null
// val dataInfoViewModel: DataInfoViewModel by lazy { ViewModelProviders.of(this).get(DataInfoViewModel::class.java) }
var mContainerID:Int = -1
private val dataInfoViewModel by viewModels<DataInfoViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
roomDbInfoListFragmentLayoutBinding = DataBindingUtil.inflate(inflater, R.layout.room_db_info_list_fragment_layout, container, false)
roomDbInfoListFragmentLayoutBinding?.lifecycleOwner = this
fragmentView = roomDbInfoListFragmentLayoutBinding?.root
container?.let{
mContainerID = container.id
}
// fragmentView?.floatingActionButton?.visibility.apply { 8 }
// initAdapter()
intRecyclerView()
setAdapter()
// initSwipeToDelete()
//listInfoAdapter?.refreshList()
return fragmentView
}
fun intRecyclerView(){
fragmentView?.dbrecyclerView?.apply {
layoutManager = LinearLayoutManager(activity)
addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL))
}
}
fun setAdapter(){
val listInfoAdapter =
ListInfoAdapter()
// dataInfoViewModel.allInfo.observe(this, Observer(listInfoAdapter::submitList))
fragmentView?.dbrecyclerView?.adapter = listInfoAdapter
dataInfoViewModel.allInfo.observe(this#PagingRoomDataBaseFragment.requireActivity(), Observer { data ->
// Update the cached copy of the words in the adapter.
data?.let {
//view.textView2.text = it.toString()
// Log.d("RoomDBFragment","ViewModel Observer is called")
Log.d("Fragm","it:::"+it.toString())
listInfoAdapter?.setAdapterList(it)
}
})
}
Adapter class:
class ListInfoAdapter : PagedListAdapter<DataInfo,ListInfoAdapter.ViewHolder>(diffCallback) {
private var list: List<DataInfo> = emptyList<DataInfo>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: PageDbListItemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.page_db_list_item, parent, false)
return ListInfoAdapter.ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d("ADapter", "Info:::$position" + list.get(position))
holder.bind(list.get(position)/*getItem(position)*/)
}
fun setAdapterList(list: PagedList<DataInfo>) {
this.list = list
notifyDataSetChanged()
}
fun refreshList(){
notifyDataSetChanged()
}
override fun getItemCount(): Int = list.size
class ViewHolder(val binding: PageDbListItemBinding) : RecyclerView.ViewHolder(binding.root) {
var data : DataInfo? = null
fun bind(data: DataInfo?) {
this.data = data
binding.setVariable(BR.dataInfo, data) // BR - generated class; BR.item - 'item' is variable name declared in layout
binding.executePendingBindings()
}
}
companion object {
/**
* This diff callback informs the PagedListAdapter how to compute list differences when new
* PagedLists arrive.
* <p>
* When you add a Cheese with the 'Add' button, the PagedListAdapter uses diffCallback to
* detect there's only a single item difference from before, so it only needs to animate and
* rebind a single view.
*
* #see android.support.v7.util.DiffUtil
*/
private val diffCallback = object : DiffUtil.ItemCallback<DataInfo>() {
override fun areItemsTheSame(oldItem: DataInfo, newItem: DataInfo): Boolean =
oldItem.uid == newItem.uid
/**
* Note that in kotlin, == checking on data classes compares all contents, but in Java,
* typically you'll implement Object#equals, and use it to compare object contents.
*/
override fun areContentsTheSame(oldItem: DataInfo, newItem: DataInfo): Boolean =
oldItem == newItem
}
}
}
Related
I am using flow to get data after any changes in my room database ,my app is note and it use SELECT by searching and filter by priority [High,Normal,Low]. and it has delete and update and insert too. I get flows from model and in viewModel by search and filter I Collect my data from flows and post it to my single liveData and in the last I get them in my Observer in the view
my problem is after in SELECT when I use UPDATE or INSERTT or DELETE , all of my old SELECT(search and filter) repeat in the moment. it means if I search 1000 once, and use filter to my notes 99 once; I will collect 1099 flows in the moment again and my observer in the my view will be bombing!!!
My Dao:
#Dao
interface NoteDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun saveNote(entity : NoteEntity)
#Delete
suspend fun deleteNote(note : NoteEntity)
#Update
suspend fun updateNote(note : NoteEntity)
#Query("SELECT * FROM $NOTE_TABLE")
fun getAllNotes() : Flow<MutableList<NoteEntity>>
#Query("SELECT * FROM $NOTE_TABLE WHERE id == :id")
fun getNote(id : Int) : Flow<NoteEntity>
#Query("SELECT * FROM $NOTE_TABLE WHERE priority == :priority")
fun fileNote(priority : String) : Flow<MutableList<NoteEntity>>
#Query("SELECT * FROM $NOTE_TABLE WHERE title LIKE '%' || :title || '%'")
fun searchNote(title : String) : Flow<MutableList<NoteEntity>>
}
My Repository:
class MainRepository #Inject constructor(private val dao : NoteDao) {
fun getAllNotes() = dao.getAllNotes()
fun getNotesByPriority(priority : String) = dao.fileNote(priority)
fun getNotesBySearch(title : String) = dao.searchNote(title)
suspend fun deleteNote(note: NoteEntity) = dao.deleteNote(note)
}
My ViewModel:
#HiltViewModel
class MainViewModel #Inject constructor(private val repository: MainRepository) : ViewModel() {
val notesData = object : MutableLiveData<DataStatus<MutableList<NoteEntity>>>(){
override fun postValue(value: DataStatus<MutableList<NoteEntity>>?) {
super.postValue(value)
Log.e("TAGJH", "postValue: ${value?.data?.size}")
}
}
fun getAllNotes() = viewModelScope.launch {
repository.getAllNotes().collect {
Log.e("TAGJH", "getAll: ${it.size}")
notesData.postValue(DataStatus.success(it, it.isEmpty()))
}
}
fun getNoteByPriority(priority: String) = viewModelScope.launch {
repository.getNotesByPriority(priority).collect {
Log.e("TAGJH", "priority ${it.size }->$priority")
notesData.postValue(DataStatus.success(it, it.isEmpty()))
}
}
fun getNoteBySearch(characters: String) = viewModelScope.launch {
repository.getNotesBySearch(characters).collect {
Log.e("TAGJH", "collect: ${it.size}")
notesData.postValue(DataStatus.success(it, it.isEmpty()))
}
}
fun deleteNote(note: NoteEntity) = viewModelScope.launch {
repository.deleteNote(note)
}
}
My View:
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private var selectedItem: Int = 0
private var _binding: ActivityMainBinding? = null
private val binding get() = _binding
#Inject
lateinit var noteAdapter: NoteAdapter
#Inject
lateinit var noteEntity: NoteEntity
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding?.root)
binding?.apply {
setSupportActionBar(notesToolbar)
addNoteBtn.setOnClickListener {
NoteFragment().show(supportFragmentManager, NoteFragment().tag)
}
viewModel.notesData.observe(this#MainActivity) {
showEmpty(it.isEmpty)
noteAdapter.setData(it.data!!)
noteList.apply {
layoutManager =
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
adapter = noteAdapter
}
}
viewModel.getAllNotes()
notesToolbar.setOnMenuItemClickListener {
when (it.itemId) {
R.id.actionFilter -> {
filterByProperty()
return#setOnMenuItemClickListener true
}
else -> return#setOnMenuItemClickListener false
}
}
deleteAndUpdateListener()
}
}
private fun deleteAndUpdateListener() =
noteAdapter.onItemClickListener { note, title ->
when (title) {
DELETE -> {
noteEntity.id = note.id
noteEntity.title = note.title
noteEntity.des = note.des
noteEntity.priority = note.priority
noteEntity.category = note.category
viewModel.deleteNote(noteEntity)
}
EDIT -> {
val noteFragment = NoteFragment()
val bundle = Bundle()
bundle.putInt(BUNDLE_ID, note.id)
noteFragment.arguments = bundle
noteFragment.show(supportFragmentManager, NoteFragment().tag)
}
}
}
private fun showEmpty(isShown: Boolean) {
binding?.apply {
if (isShown) {
emptyShowing.visibility = View.VISIBLE
noteList.visibility = View.GONE
} else {
emptyShowing.visibility = View.GONE
noteList.visibility = View.VISIBLE
}
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_toolbar, menu)
val search = menu.findItem(R.id.actionSearch)
val searchView = search.actionView as SearchView
searchView.queryHint = getString(R.string.search)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String): Boolean {
viewModel.getNoteBySearch(newText)
Log.e("TAGJH", "searching")
return true
}
})
return super.onCreateOptionsMenu(menu)
}
private fun filterByProperty() {
val builder = AlertDialog.Builder(this)
val priories = arrayOf(ALL, HIGH, NORMAL, LOW)
builder.setSingleChoiceItems(priories, selectedItem) { dialog, item ->
when (item) {
0 -> {
viewModel.getAllNotes()
}
in 1..3 -> {
viewModel.getNoteByPriority(priories[item])
}
}
selectedItem = item
dialog.dismiss()
}
val dialog: AlertDialog = builder.create()
dialog.show()
}
}
I want to see my note after any update or delete or insert is updating in my last SELECT from room database
I am building a movies app which uses Paging3 to page from the network and from a local database at the same time using Remote Mediator.
which gets the data from TMDB api
and save them to room database.
But I am experiencing some blinking or flickers in the recycler view
when I scroll down some items change their position and jumps up and down.
here's a video of the issue : https://youtu.be/TzV9Mf85uzk
Working with paging source only for api or database works fine.
but when using remote mediator the blinking happens after inserting any page data from api to the database.
I don't know what is causing this hopefully I can find a solution.
Here's some of my code snippets:
RemoteMediator
class MovieRemoteMediator(
private val query: String ="",
private val repository: MovieRepository
) :
RemoteMediator<Int, Movie>() {
companion object {
private const val STARTING_PAGE_INDEX = 1
}
private val searchQuery = query.ifEmpty { "DEFAULT_QUERY" }
override suspend fun initialize(): InitializeAction {
// Require that remote REFRESH is launched on initial load and succeeds before launching
// remote PREPEND / APPEND.
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Movie>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> STARTING_PAGE_INDEX
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val remoteKey = getRemoteKeyForLastItem(state)
val nextPage = remoteKey?.nextPage
?: return MediatorResult.Success(endOfPaginationReached = remoteKey != null)
nextPage
}
}
val response = repository.getMoviesFromApi(page)
if (response is NetworkResult.Success) {
val movies = response.data.results ?: emptyList()
val nextPage: Int? =
if (response.data.page < response.data.totalPages) response.data.page + 1 else null
val remoteKeys: List<MovieRemoteKey> = movies.map { movie ->
MovieRemoteKey(searchQuery, movie.id, nextPage)
}
repository.insertAndDeleteMoviesAndRemoteKeysToDB(
searchQuery,
movies,
remoteKeys,
loadType
)
return MediatorResult.Success(
endOfPaginationReached = nextPage == null
)
} else {
val error = (response as NetworkResult.Error).errorMessage
return MediatorResult.Error(Exception(error))
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Movie>): MovieRemoteKey? {
return state.pages.lastOrNull() { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { movie ->
repository.getMovieRemoteKey(movie.id.toInt(), searchQuery)
}
}
}
Repository
class MovieRepository #Inject constructor(
private val apiClient: ApiService,
private val movieDao: MovieDao,
private val movieRemoteKeyDao: MovieRemoteKeyDao
) {
companion object {
private const val PAGE_SIZE =20
val config = PagingConfig(pageSize = PAGE_SIZE,
enablePlaceholders = false)
}
#OptIn(ExperimentalPagingApi::class)
fun getPagingMovies() = Pager(config,
remoteMediator = MovieRemoteMediator(repository = this)
) {
getPagedMoviesFromDB(SortType.DEFAULT, "")
}.flow
suspend fun insertAndDeleteMoviesAndRemoteKeysToDB(
query: String,
movies: List<Movie>,
remoteKeys: List<MovieRemoteKey>,
loadType: LoadType
)= withContext(Dispatchers.IO) {
movieRemoteKeyDao.insertAndDeleteMoviesAndRemoteKeys(query,movies, remoteKeys, loadType)
}
suspend fun getMovieRemoteKey(itemId:Int,query:String):MovieRemoteKey? {
return movieRemoteKeyDao.getRemoteKey(itemId,query)
}
MovieDao
fun getSortedMovies(sortType: SortType, searchQuery: String) : Flow<List<Movie>> =
when(sortType){
SortType.ASC -> getSortedMoviesASC(searchQuery)
SortType.DESC -> getSortedMoviesDESC(searchQuery)
SortType.DEFAULT -> getMovies()
}
fun getPagedMovies(sortType: SortType, searchQuery: String) : PagingSource<Int,Movie> =
when(sortType){
SortType.ASC -> getPagedSortedMoviesASC(searchQuery)
SortType.DESC -> getPagedSortedMoviesDESC(searchQuery)
SortType.DEFAULT -> getDefaultPagedMovies(searchQuery.ifEmpty { "DEFAULT_QUERY" })
}
#Query("SELECT * FROM movies ORDER BY popularity DESC")
fun getMovies(): Flow<List<Movie>>
#Query("SELECT * FROM movies WHERE title LIKE '%' || :search || '%'" +
" OR originalTitle LIKE :search" +
" ORDER BY title ASC")
fun getSortedMoviesASC(search:String): Flow<List<Movie>>
#Query("SELECT * FROM movies WHERE title LIKE '%' || :search || '%'" +
" OR originalTitle LIKE :search" +
" ORDER BY title DESC")
fun getSortedMoviesDESC(search:String): Flow<List<Movie>>
#Transaction
#Query("SELECT * FROM movies" +
" INNER JOIN movie_remote_key_table on movies.id = movie_remote_key_table.movieId" +
" WHERE searchQuery = :search" +
" ORDER BY movie_remote_key_table.id")
fun getDefaultPagedMovies(search:String): PagingSource<Int,Movie>
#Query("SELECT * FROM movies WHERE title LIKE '%' || :search || '%'" +
" OR originalTitle LIKE :search" +
" ORDER BY title ASC")
fun getPagedSortedMoviesASC(search:String): PagingSource<Int,Movie>
#Query("SELECT * FROM movies WHERE title LIKE '%' || :search || '%'" +
" OR originalTitle LIKE :search" +
" ORDER BY title DESC")
fun getPagedSortedMoviesDESC(search:String): PagingSource<Int,Movie>
#Query("SELECT * FROM movies WHERE id = :id")
fun getMovieById(id: Int): Flow<Movie>
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMovie(movie: Movie)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMovies(movies: List<Movie>)
#Query("DELETE FROM movies")
fun deleteAllMovies()
#Query("DELETE FROM movies WHERE id = :id")
fun deleteMovieById(id: Int)
}
RemoteKeyDao
#Dao
interface MovieRemoteKeyDao {
#Query("SELECT * FROM movie_remote_key_table WHERE movieId = :movieId AND searchQuery = :query LIMIT 1")
suspend fun getRemoteKey(movieId: Int, query: String): MovieRemoteKey?
#Query("DELETE FROM movie_remote_key_table WHERE searchQuery = :query")
suspend fun deleteRemoteKeys(query: String)
#Transaction
#Query("DELETE FROM movies WHERE id IN ( SELECT movieId FROM movie_remote_key_table WHERE searchQuery = :query)")
suspend fun deleteMoviesByRemoteKeys(query: String)
#Transaction
suspend fun insertAndDeleteMoviesAndRemoteKeys(
query: String,
movies: List<Movie>,
remoteKeys: List<MovieRemoteKey>,
loadType: LoadType
) {
if (loadType == LoadType.REFRESH) {
Timber.d("REMOTE SOURCE DELETING:")
deleteMoviesByRemoteKeys(query)
deleteRemoteKeys(query)
}
Timber.d("REMOTE SOURCE INSERTING ${movies.size} Movies and ${remoteKeys.size} RemoteKeys :")
insertMovies(movies)
insertRemoteKey(remoteKeys)
}
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertRemoteKey(movieRemoteKey: List<MovieRemoteKey>)
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertMovies(movies: List<Movie>)
}
MoviesViewModel
#HiltViewModel
class MoviesViewModel #Inject constructor(
private val repository: MovieRepository, private val preferenceManger: PreferenceManger
) : ViewModel() {
private val searchFlow = MutableStateFlow("")
private val sortFlow = preferenceManger.preferencesFlow
val movies = repository.getPagingMovies().cachedIn(viewModelScope)
//
// val movies: StateFlow<Resource<List<Movie>>> = sortFlow.combine(searchFlow) { sort, search ->
// Pair(sort, search)
// } //For having timeouts for search query so not overload the server
// .debounce(600)
// .distinctUntilChanged()
// .flatMapLatest { (sort, search) ->
// repository.getMovies(sort, search)
// }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), Resource.Loading())
//
fun setSearchQuery(query: String) {
searchFlow.value = query
}
fun saveSortType(type: SortType) {
viewModelScope.launch {
preferenceManger.saveSortType(type)
}
}
private val _currentMovie = MutableLiveData<Movie?>()
val currentMovie: LiveData<Movie?>
get() = _currentMovie
fun setMovie(movie: Movie?) {
_currentMovie.value = movie
}
}
MovieFragment
#AndroidEntryPoint
class MoviesFragment : Fragment(), MovieClickListener {
private lateinit var moviesBinding: FragmentMoviesBinding
private lateinit var pagingMovieAdapter: PagingMovieAdapter
private val viewModel: MoviesViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
moviesBinding = FragmentMoviesBinding.inflate(inflater, container, false)
return moviesBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupUI()
getMovies()
}
private fun setupUI() {
pagingMovieAdapter = PagingMovieAdapter(this)
moviesBinding.moviesRv.layoutManager = GridLayoutManager(context, 3)
moviesBinding.moviesRv.adapter = pagingMovieAdapter
moviesBinding.moviesRv.setHasFixedSize(true)
setHasOptionsMenu(true)
}
private fun getMovies() {
// repeatOnLifeCycle(pagingMovieAdapter.loadStateFlow) { loadStates ->
// val state = loadStates.refresh
// moviesBinding.loadingView.isVisible = state is LoadState.Loading
//
// if (state is LoadState.Error) {
// val errorMsg = state.error.message
// Toast.makeText(context, errorMsg, Toast.LENGTH_LONG).show()
// }
//
// }
lifecycleScope.launchWhenCreated{
viewModel.movies.collectLatest { pagingMovieAdapter.submitData(it) }
}
// repeatOnLifeCycle(viewModel.movies,pagingMovieAdapter::submitData)
// //scroll to top after updating the adapter
// repeatOnLifeCycle(pagingMovieAdapter.loadStateFlow
// .distinctUntilChangedBy { it.refresh }
// .filter { it.refresh is LoadState.NotLoading }
// ) {
// moviesBinding.moviesRv.scrollToPosition(0)
// }
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.main, menu)
val searchView = menu.findItem(R.id.action_search).actionView as SearchView
searchView.onQueryTextChanged() { query ->
viewModel.setSearchQuery(query)
}
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_sort_asc -> {
viewModel.saveSortType(SortType.ASC)
true
}
R.id.action_sort_desc -> {
viewModel.saveSortType(SortType.DESC)
true
}
R.id.action_sort_default -> {
viewModel.saveSortType(SortType.DEFAULT)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun onMovieClickListener(movie: Movie?) {
Toast.makeText(context, movie?.title, Toast.LENGTH_SHORT).show()
viewModel.setMovie(movie)
movie?.id?.let {
val action = MoviesFragmentDirections.actionMoviesFragmentToDetailsFragment2(it)
findNavController().navigate(action)
}
}
}
PagingMovieAdapter
class PagingMovieAdapter(private val movieClickListener: MovieClickListener)
: PagingDataAdapter<Movie, PagingMovieAdapter.PagingMovieViewHolder>(diffUtil) {
companion object{
val diffUtil = object : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
return oldItem == newItem
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PagingMovieViewHolder {
return PagingMovieViewHolder.from(parent,movieClickListener)
}
override fun onBindViewHolder(holder: PagingMovieViewHolder, position: Int) {
val movie = getItem(position)
holder.bind(movie)
}
class PagingMovieViewHolder(private val movieBinding: ItemMovieBinding,private val movieClickListener: MovieClickListener) :
RecyclerView.ViewHolder(movieBinding.root) , View.OnClickListener{
init {
movieBinding.root.setOnClickListener(this)
}
fun bind(movie: Movie?) {
movie.let { movieBinding.movie = movie }
}
companion object {
fun from(parent: ViewGroup, movieClickListener: MovieClickListener): PagingMovieViewHolder {
val inflater = LayoutInflater.from(parent.context)
val movieBinding = ItemMovieBinding.inflate(inflater, parent, false)
return PagingMovieViewHolder(movieBinding,movieClickListener)
}
}
override fun onClick(p0: View?) {
val movie = movieBinding.movie
movieClickListener.onMovieClickListener(movie)
}
}
}
Thanks.
For anyone that might face this problem,
The blinking was because my diffUtil callback returning false on areContentTheSame because I had a long array parameter on my data model class
and kotlin data class equals method compare arrays based on their reference not the value so I had to manually override equals method.
and for items moving in their position, it was because I was disabling placeholders
on the paging config
which made the paging library to return a wrong offset after updating the database
so making enablePlaceholders = false solves the issue.
also of the order of data coming from the api not the same of the data coming from the database might cause this issue.
Thanks
I am trying to display data from IconFinder API. It seems to be ItemKeyedDataSource for me and I used Paging3 to display the data as it's mentioned in the official docs.
Here is code, please check if there're any issues with the implementation I have done and where is the mistake.
IconSetsRemoteMediator
#OptIn(ExperimentalPagingApi::class)
class IconSetsRemoteMediator(
private val query: String?,
private val database: IconsFinderDatabase,
private val networkService: IconFinderAPIService
) : RemoteMediator<Int, IconSetsEntry>() {
private val TAG: String? = IconSetsRemoteMediator::class.simpleName
private val iconSetsDao = database.iconSetsDao
private val remoteKeysDao = database.remoteKeysDao
override suspend fun initialize(): InitializeAction {
// Load fresh data when ever the app is open new
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, IconSetsEntry>
): MediatorResult {
val iconSetID = when (loadType) {
LoadType.REFRESH -> {
null
}
LoadType.PREPEND -> {
return MediatorResult.Success(
endOfPaginationReached = true
)
}
LoadType.APPEND -> {
Log.d(TAG, "LoadType.APPEND")
val lastItem = state.lastItemOrNull()
if (lastItem == null) {
return MediatorResult.Success(
endOfPaginationReached = true
)
}
// Get the last item from the icon-sets list and return its ID
lastItem.iconset_id
}
}
try {
// Suspending network load via Retrofit.
val response = networkService.getAllPublicIconSets(after = iconSetID)
val iconSets = response.iconsets
val endOfPaginationReached = iconSets == null || iconSets.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
// Delete the data in the database
iconSetsDao.deleteAllIconSets()
//remoteKeysDao.deleteRemoteKeys()
}
Log.d(TAG, "iconSets = ${iconSets?.size}")
Log.d(TAG, "endOfPaginationReached = $endOfPaginationReached")
Log.d(TAG, "state.anchorPosition = ${state.anchorPosition}")
Log.d(TAG, "state.pages = ${state.pages.size}")
val time = System.currentTimeMillis()
/*val remoteKeys = iconSets!!.map {
RemoteKeysEntry(it.iconset_id, time)
}*/
// Insert new IconSets data into database, which invalidates the current PagingData,
// allowing Paging to present the updates in the DB.
val data = iconSets!!.mapAsIconSetsEntry()
iconSetsDao.insertAllIconSets(data)
// Insert the remote key values which set the time at which the data is
// getting updated in the DB
//remoteKeysDao.insertRemoteKeys(remoteKeys)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
}
IconFinderRepository
class IconFinderRepository(
private val service: IconFinderAPIService,
private val database: IconsFinderDatabase
) {
private val TAG: String? = IconFinderRepository::class.simpleName
fun getPublicIconSets(): Flow<PagingData<IconSetsEntry>> {
Log.d(TAG, "New Icon Sets query")
val pagingSourceFactory = { database.iconSetsDao.getIconSets() }
#OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(pageSize = NUMBER_OF_ITEMS_TO_FETCH, enablePlaceholders = false),
remoteMediator = IconSetsRemoteMediator(
query = null,
database,
service
),
pagingSourceFactory = pagingSourceFactory
).flow
}
companion object {
const val NUMBER_OF_ITEMS_TO_FETCH = 20
}
}
IconSetViewHolder
class IconSetViewHolder private constructor(val binding: RecyclerItemIconSetBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(iconSetsEntry: IconSetsEntry?) {
if (iconSetsEntry == null) {
//Show the Loading UI
} else {
binding.model = iconSetsEntry
binding.executePendingBindings()
}
}
companion object {
fun from(parent: ViewGroup): IconSetViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RecyclerItemIconSetBinding.inflate(layoutInflater, parent, false)
return IconSetViewHolder(binding)
}
}
}
IconSetAdapter
class IconSetAdapter : PagingDataAdapter<UiModel.IconSetDataItem, ViewHolder>(UI_MODEL_COMPARATOR) {
companion object {
private val UI_MODEL_COMPARATOR =
object : DiffUtil.ItemCallback<UiModel.IconSetDataItem>() {
override fun areContentsTheSame(
oldItem: UiModel.IconSetDataItem,
newItem: UiModel.IconSetDataItem
): Boolean {
return oldItem.iconSetsEntry.iconset_id == newItem.iconSetsEntry.iconset_id
}
override fun areItemsTheSame(
oldItem: UiModel.IconSetDataItem,
newItem: UiModel.IconSetDataItem
): Boolean =
oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == R.layout.recycler_item_icon_set) {
IconSetViewHolder.from(parent)
} else {
IconSetViewHolder.from(parent)
}
}
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is UiModel.IconSetDataItem -> R.layout.recycler_item_icon_set
null -> throw UnsupportedOperationException("Unknown view")
else -> throw UnsupportedOperationException("Unknown view")
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val uiModel = getItem(position)
uiModel.let {
when (uiModel) {
is UiModel.IconSetDataItem -> (holder as IconSetViewHolder).bind(uiModel.iconSetsEntry)
}
}
}
}
HomeFragmentViewModel
class HomeFragmentViewModel(application: Application) : AndroidViewModel(application) {
private val TAG: String? = HomeFragmentViewModel::class.simpleName
private val repository: IconFinderRepository = IconFinderRepository(
IconFinderAPIService.create(),
IconsFinderDatabase.getInstance(application)
)
private var iconSetsQueryResult: Flow<PagingData<UiModel.IconSetDataItem>>? = null
fun iconSetsQuery(): Flow<PagingData<UiModel.IconSetDataItem>> {
val newResult: Flow<PagingData<UiModel.IconSetDataItem>> = repository.getPublicIconSets()
.map { pagingData -> pagingData.map { UiModel.IconSetDataItem(it) } }
.cachedIn(viewModelScope)
iconSetsQueryResult = newResult
return newResult
}
/**
* Factory for constructing HomeFragmentViewModel
*/
class Factory(private val application: Application) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(HomeFragmentViewModel::class.java)) {
return HomeFragmentViewModel(application) as T
}
throw IllegalArgumentException("Unable to construct ViewModel")
}
}
}
sealed class UiModel {
data class IconSetDataItem(val iconSetsEntry: IconSetsEntry) : UiModel()
}
IconSetFragment: This is one of the fragments implemented as part of ViewPager. Its parent is a Fragment in an Activity.
class IconSetFragment : Fragment() {
private val TAG: String = IconSetFragment::class.java.simpleName
/**
* Declaring the UI Components
*/
private lateinit var binding: FragmentIconSetBinding
private val viewModel: HomeFragmentViewModel by viewModels()
private val adapter = IconSetAdapter()
private var job: Job? = null
companion object {
fun newInstance(): IconSetFragment {
return IconSetFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Get a reference to the binding object
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_icon_set, container, false)
Log.d(TAG, "onCreateView")
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initAdapter()
job?.cancel()
job = viewLifecycleOwner.lifecycleScope.launch {
viewModel.iconSetsQuery().collectLatest {
adapter.submitData(it)
Log.d(TAG, "collectLatest $it")
}
}
}
private fun initAdapter() {
binding.rvIconSetList.adapter = adapter
/*.withLoadStateHeaderAndFooter(
header = LoadStateAdapter(), // { adapter.retry() },
footer = LoadStateAdapter { adapter.retry() }
)*/
}
}
IconSetsDao
#Dao
interface IconSetsDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAllIconSets(iconSets: List<IconSetsEntry>)
#Query("SELECT * FROM icon_sets_table")
fun getIconSets(): PagingSource<Int, IconSetsEntry>
#Query("DELETE FROM icon_sets_table")
suspend fun deleteAllIconSets()
}
This is the Logcat screenshot, the load() method is being invoked without any scrolling action.
I have the similar issue, seems the recursive loading issue is fixed by setting the recyclerView.setHasFixedSize(true)
I am using the following DAO
#Dao
interface GroceryListDao {
#Insert
fun insert(list: GroceryList)
#Update
fun update(list: GroceryList)
#Query("Delete from grocery_list_table")
fun clear()
#Query ("Select * from grocery_list_table")
fun getAllItems(): LiveData<List<GroceryList>>
#Query ("Select * from grocery_list_table where itemId = :item")
fun getItem(item: Long): GroceryList?
#Query ("Select * from grocery_list_table where item_status = :status")
fun getItemsBasedOnStatus(status: Int): List<GroceryList>
}
And my database has 3 columns groceryId(Long - autogenerated), groceryName(String) and groceryStatus(Int - 1/0).
When I am using getItemsBasedOnStatus(status: Int) without using LiveData I am able to retrieve the data. But when it is wrapped with LiveData I am getting null.
The other issue is when I get a list of items from a database without wrapping with LiveData and assigning to MutableLiveData in ViewModel, then the assigned MutableLiveData is displaying null values. I see lot of questions on this but no answer.
Adding code for my viewModel and Fragment
ViewModel
class GroceryListViewModel(
val database: GroceryListDao,
application: Application
) : AndroidViewModel(application) {
private var viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
var grocerylistItems = database.getAllItems()
var groceryItem = MutableLiveData<GroceryList>()
var groceryitems = MutableLiveData<List<GroceryList>>()
init {
getAllItemsFromDatabase()
}
fun insertIntoDatabase(item: GroceryList) {
uiScope.launch {
insert(item)
}
}
private suspend fun insert(item: GroceryList) {
withContext(Dispatchers.IO) {
database.insert(item)
}
}
fun updateGrocerylist(itemId: Long, status: Int) {
uiScope.launch {
groceryItem.value = getItem(itemId)
groceryItem.value?.itemStatus = status
groceryItem.value?.let { update(it) }
}
}
private suspend fun update(item: GroceryList) {
withContext(Dispatchers.IO) {
database.update(item)
}
}
private suspend fun getItem(itemId: Long): GroceryList? {
return withContext(Dispatchers.IO) {
var item = database.getItem(itemId)
item
}
}
fun getAllItemsFromDatabase() {
uiScope.launch {
getAllItems()
}
}
private suspend fun getAllItems() {
withContext(Dispatchers.IO) {
grocerylistItems = database.getAllItems()
}
}
fun getPendingItemsFromDatabase(status: Int) {
uiScope.launch {
getPendingItems(status)
}
}
private suspend fun getPendingItems(status: Int) {
withContext(Dispatchers.IO) {
val items = database.getItemsBasedOnStatus(status)
groceryitems.postValue(items)
Log.i("Grocery List", "Pending Items:" + items.size)
}
}
fun getDoneItemsFromDatabase(status: Int) {
uiScope.launch {
getDoneItems(status)
}
}
private suspend fun getDoneItems(status: Int) {
withContext(Dispatchers.IO) {
val items = database.getItemsBasedOnStatus(status)
groceryitems.postValue(items)
Log.i("Grocery List", "Done Items:" + items.size)
}
}
fun clearAllItemsFromDatabase() {
uiScope.launch {
clearItems()
}
}
private suspend fun clearItems() {
withContext(Dispatchers.IO) {
database.clear()
getAllItemsFromDatabase()
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
Fragment
class GroceryLIstFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val binding = FragmentGroceryLIstBinding.inflate(inflater,container,false)
val application = requireNotNull(this.activity).application
val dataSource = GroceryDatabase.getInstance(application)?.groceryListDatabaseDao
val viewModelFactory = dataSource?.let { GroceryListViewModelFactory(it, application) }
val viewModel = viewModelFactory?.let {
ViewModelProvider(
this,
it
).get(GroceryListViewModel::class.java)
}
viewModel?.grocerylistItems?.observe(this , Observer {
binding.grocerylistView.removeAllViews() // is it correct ?
for (item in it){
Log.i("Grocery List","Grocery Id=" + item.itemId+" ,Grocery Name=" + item.itemName +", Grocery status="+item.itemStatus)
addItemToScrollbar(item, binding, viewModel)
}
})
binding.addGrocery.setOnClickListener {
val imm = context?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view?.windowToken, 0)
val item = binding.groceryitemField.text.toString()
if (!item.isNullOrBlank()) {
val newItem = GroceryList(itemName = item)
viewModel?.insertIntoDatabase(newItem)
if (viewModel != null) {
addItemToScrollbar(newItem, binding,viewModel)
}
binding.groceryitemField.text.clear()
}
}
binding.doneCheckbox.setOnClickListener {
if (binding.doneCheckbox.isChecked)
viewModel?.getDoneItemsFromDatabase(1)
else
viewModel?.getAllItemsFromDatabase()
}
binding.pendingCheckbox.setOnClickListener {
if (binding.pendingCheckbox.isChecked) {
viewModel?.getPendingItemsFromDatabase(0)
}
else
viewModel?.getAllItemsFromDatabase()
}
binding.clearGrocery.setOnClickListener {
viewModel?.clearAllItemsFromDatabase()
binding.grocerylistView.removeAllViews()
}
return binding.root
}
private fun addItemToScrollbar(
item: GroceryList,
binding: FragmentGroceryLIstBinding,
viewModel: GroceryListViewModel
) {
val itemBox = AppCompatCheckBox(context)
itemBox.text = item.itemName
itemBox.isChecked = item.itemStatus == 1
binding.grocerylistView.addView(itemBox)
itemBox.setOnClickListener {
val itemstatus: Int = if (itemBox.isChecked)
1
else {
0
}
viewModel?.updateGrocerylist(item.itemId,itemstatus)
}
}
}
Any help would be appreciated.
This most likely the same issue as here (read the linked answer). Due to way asynchronous way LiveData is working, it will return null when you call it. LiveData is meant to be used in conjunction with Observers, that will be triggered once changes to observed subject occur.
An Observer can look like this
database.getItemsBasedOnStatus(status).observe(viewLifecycleOwner, Observer { groceryList->
// Do cool grocery stuff here
})
If you want to retrieve your data inside your ViewModel you do not have a viewLifecycleOwner, you can then use "observeForever()", but then you have to remove the Observer explicitly, see this answer.
Same issue and answer also in this post
The task is to open an activity with notes attached to this diary when you select a single diary.
(one-to-many)
This is how entities in the database look like:
#Entity(tableName = "word_table")
data class Word(#ColumnInfo(name = "word") val word: String,
#ColumnInfo(name = "description") val description : String
)
{
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id : Long = 0
}
#Entity(tableName = "note_table")
data class Note(#ColumnInfo(name = "note_name") val note : String,
#ColumnInfo(name = "text") val text : String,
#ColumnInfo(name = "diaryId") val diaryId : Long
){
#PrimaryKey(autoGenerate = true)
var idNote : Long = 0
}
Using a data class in NoteRepository.kt
data class NotesAndWords (#Embedded val word : Word,
#Relation(parentColumn = "id", entityColumn = "diaryId")
val notes : List<Note>)
And a Query in WordDao.kt
#Transaction
#Query("SELECT * from word_table ")
fun getSomeNotes() : LiveData<List<NotesAndWords>>
I get the data and save it in the NoteRepository class:
class NoteRepository (private val wordDao : WordDao) {
var allNotes : LiveData<List<NotesAndWords>> = wordDao.getSomeNotes()
suspend fun insertNote(note : Note)
{
wordDao.insertNote(note)
}
}
Then via NoteViewModel.kt passing data to NoteActivity.kt:
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
val allNotes: LiveData<List<NotesAndWords>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = NoteRepository(wordsDao)
allNotes = repository.allNotes
}
fun insertNote(note: Note) = viewModelScope.launch {
repository.insertNote(note)
}
}
(NoteActivity.kt)
class NoteActivity : AppCompatActivity() {
private val newWordActivityRequestCode = 1
private lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_note)
val adapter = NoteListAdapter(this, intent.getLongExtra("tag", -1) ){
val intent = Intent(this, ClickedActivity::class.java)
intent.putExtra("tag", it.note)
startActivity(intent)
}
recyclerview1.adapter = adapter
recyclerview1.layoutManager = LinearLayoutManager(this)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteViewModel.allNotes.observe(this, Observer {
adapter.setNotes(it)
})
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK)
{
data?.getStringArrayListExtra(NewWordActivity.EXTRA_REPLY)?.let {
val note = Note(it[0], it[1], intent.getLongExtra("tag", -1))
noteViewModel.insertNote(note)
}
}
else
{
Toast.makeText(applicationContext, R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
Then, in the adapter, I use MutableMap to transform the list so that the key is the name id and the value is the notes selected on request (attached to a specific diary)
NoteListAdapter.kt:
class NoteListAdapter internal constructor(
context: Context,
val wordId: Long,
private val listener : (Note) -> Unit
) : RecyclerView.Adapter<NoteListAdapter.NoteViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
//private val mContext = context
private var notes = emptyList<NotesAndWords>() // Cached copy of words
private var notesMapped = mutableMapOf<Long, List<Note>>()
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val noteItemView: TextView = itemView.findViewById(R.id.textView1)
private val noteDescriptionView: TextView = itemView.findViewById(R.id.textView)
fun bindView(note: Note, listener : (Note) -> Unit) {
noteItemView.text = note.diaryId.toString()
noteDescriptionView.text = note.text
itemView.setOnClickListener {
listener(note)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_layout, parent,
false)
return NoteViewHolder(itemView)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.bindView(notesMapped[wordId]!![position], listener)
}
internal fun setNotes(notes: List<NotesAndWords>) {
this.notes = notes
for (i in this.notes) {
notesMapped[i.word.id] = i.notes
}
notifyDataSetChanged()
}
override fun getItemCount() = notesMapped[wordId]!!.size
}
Database:
#Database(entities = [Word::class, Note::class], version = 2, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(private val scope: CoroutineScope) : RoomDatabase.Callback()
{
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
//wordDao.deleteAll()
//wordDao.deleteAllNotes()
}
}
companion object {
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context, scope:CoroutineScope): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
val instance = Room.databaseBuilder(context.applicationContext,
WordRoomDatabase::class.java, "word_database")
.addCallback(WordDatabaseCallback(scope))
//.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
I've created several diaries and one note in each of them, using the buttons to create new diaries and notes. Now, if you select several diaries in turn, then on some attempt to select a diary, a NullPointerException is issued in the adapter, in this line:
override fun getItemCount() = notesMapped[wordId]!!.size
Why is this exception thrown if notesMapped always has the wordId key?
NoteActivity is called from another activity and the diary id is passed to it
This repository on GitHub: https://github.com/Lomank123/RoomDatabase
Edit:
noteViewModel.allNotes.observe(this, Observer {
var getList = emptyList<Note>()
for(i in it)
{
if(i.word.id == wordId)
{
getList = i.notes
break
}
}
adapter.setNotes(getList)
})
I've changed the Observer in NoteActivity and changed setNotes() method in adapter, but now it returns nothing. With for() I get the right notes and give them to adapter.setNotes(). If it doesn't work, how can I get the correct list of notes?
Hi initially the map is empty and the map is returning a null value and you are checking size on a null object.
Also as a good practice do not use a map instead use a list of notes only and pass the list directly.