Kotlin FragmentStatePageAdapter destroy view on third swipe if it has more than 2 items - kotlin

I have set the FragmentStatePageAdapter to swipe through fragments of the same layout and different data (users profiles).
When I give it list of 2 items all work well.
If I give it 3 or more items it creates views for fragments, but on third swipe contents turn blank, regardless to which direction I swipe (forward then forward, forward then backward). If I accurately slide to third item when still holding the screen I can see it's valid contents, but when I finish slide releasing the screen all disappears.
Page Adapter is set like below. (swipeList is list of users' ids, swipeEntityType == 0, each User contents is loaded by "uid" passed through bundle)
class SwipePagerFragment : Fragment() {
private lateinit var parent: MainActivity
private lateinit var state: State
private lateinit var ctx: Context
private lateinit var swipeList: MutableList<Int>
private lateinit var pager: ViewPager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_swipe_pager, container, false)
parent = activity as MainActivity
state = parent.state
ctx = state.ctx
swipeList = state.swipeList
pager = view.findViewById(R.id.fragmentSwipePager_pager)
val pagerAdapter = ScreenSlidePagerAdapter(childFragmentManager)
pager.adapter = pagerAdapter
return view
}
private inner class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
override fun getCount(): Int = swipeList.size
override fun getItem(position: Int): Fragment = when (state.swipeEntityType) {
0 -> {
makeUserFragment(swipeList[position])
}
else -> {
makeEventFragment(swipeList[position])
}
}
}
private fun makeUserFragment(uid: Int): UserFragment {
val bundle = Bundle()
bundle.putInt("uid", uid)
val frag = UserFragment()
frag.arguments = bundle
return frag
}
private fun makeEventFragment(eid: Int): EventFragment {
val bundle = Bundle()
bundle.putInt("eid", eid)
val frag = EventFragment()
frag.arguments = bundle
return frag
}
}

Setting
pager.offscreenPageLimit = swipeList.size
solves my problem

Related

Obtain a crash message-App keeps stopping

I get the following trace error java.lang.ClassCastException: com.example.myhouse.MainActivity cannot be cast to com.brsthegck.kanbanboard.TasklistFragment$Callbacks at com.brsthegck.kanbanboard.TasklistFragment.onAttach(TasklistFragment.kt:52)at androidx.fragment.app.Fragment.performAttach(Fragment.java:2672) at androidx.fragment.app.FragmentStateManager.attach(FragmentStateManager.java:263) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1170) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
enter image description here
Tasklist Fragment
private const val ARG_TASKLIST_TYPE = "tasklist_type"
private const val TASKLIST_TYPE_TODO = 0
private const val TASKLIST_TYPE_DOING = 1
private const val TASKLIST_TYPE_DONE = 2
class TasklistFragment : Fragment(){
private lateinit var visibleColorPaletteViewList : List<View>
lateinit var taskRecyclerView: RecyclerView
private var tasklistType : Int = -1
private var adapter : TaskViewAdapter? = TaskViewAdapter(LinkedList<Task>())
private var colorPaletteIsVisible : Boolean = false
private var callbacks: Callbacks? = null
//Callback interface to delegate access functions in MainActivity
interface Callbacks{
fun addTaskToViewModel(task: Task, destinationTasklistType: Int)
fun deleteTaskFromViewModel(tasklistType: Int, adapterPosition: Int)
fun getTaskListFromViewModel(tasklistType: Int) : LinkedList<Task>
}
//Attach context as a Callbacks reference to the callbacks variable when fragment attaches to container
override fun onAttach(context: Context) {
super.onAttach(context)
callbacks = activity as Callbacks? // error is here
}
//Detach context (assign to null) when fragment detaches from container
override fun onDetach() {
super.onDetach()
callbacks = null
}
//ItemTouchHelper instance with custom callback, to move task card view positions on hold
private val itemTouchHelper by lazy{
val taskItemTouchCallback = object : ItemTouchHelper.SimpleCallback(UP or DOWN, 0){
override fun onMove(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean {
val adapter = recyclerView.adapter as TaskViewAdapter
val from = viewHolder.adapterPosition
val to = target.adapterPosition
adapter.moveTaskView(from, to)
adapter.notifyItemMoved(from, to)
return true
}
//Make taskview transparent while being moved
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if(actionState == ACTION_STATE_DRAG)
viewHolder?.itemView?.alpha = 0.7f
}
//Make taskview opaque while being
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
viewHolder.itemView.alpha = 1.0f
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { /* Not implemented on purpose. */ }
}
ItemTouchHelper(taskItemTouchCallback)
}
//Get the fragment arguments, tasklist type to be precise, and assign it to the member of this fragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
tasklistType = arguments?.getInt(ARG_TASKLIST_TYPE) as Int
}
//Inflate view of fragment, prepare recycler view and it's layout manager, and update the UI
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View?{
//Inflate the layout of this fragment, get recyclerview ref, set layout manager for recycler view
val view = inflater.inflate(R.layout.tasklist_fragment_layout, container,false)
taskRecyclerView = view.findViewById(R.id.task_recycler_view) as RecyclerView
taskRecyclerView.layoutManager = LinearLayoutManager(context)
itemTouchHelper.attachToRecyclerView(taskRecyclerView)
//Fill the recyclerview with data from viewmodel
updateInterface()
//Return the created view
return view
}
//Populate recyclerview and set up its adapter
private fun updateInterface(){
val tasks = callbacks!!.getTaskListFromViewModel(tasklistType)
adapter = TaskViewAdapter(tasks)
taskRecyclerView.adapter = adapter
}
Kanban class
class Kanban : Fragment(), TasklistFragment.Callbacks{
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private lateinit var viewPager: ViewPager2
private lateinit var tabLayout: TabLayout
private lateinit var taskViewModel: TaskViewModel
//When add action bar button is pressed
override fun onOptionsItemSelected(item: MenuItem) = when(item.itemId) {
R.id.action_new_task -> {
val currentTasklistType = viewPager.currentItem
val currentTasklist = when (currentTasklistType) {
TASKLIST_TYPE_TODO -> taskViewModel.todoTaskList
TASKLIST_TYPE_DOING -> taskViewModel.doingTaskList
TASKLIST_TYPE_DONE -> taskViewModel.doneTaskList
else -> throw Exception("Unrecognized tasklist type")
}
val currentFragment = (activity?.supportFragmentManager?.fragments?.get(currentTasklistType) as TasklistFragment)
addTaskToViewModel(Task(), currentTasklistType)
currentFragment.taskRecyclerView.scrollToPosition(currentTasklist.size - 1)
true
}
else -> super.onOptionsItemSelected(item)
}
//Inflate the action bar menu resource on options menu creation
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_action_bar, menu)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
//Get viewmodel
val view: View = inflater.inflate(R.layout.fragment_kanban2, container, false)
taskViewModel = ViewModelProvider(this).get(TaskViewModel::class.java)
//Read tasklist data from shared prefs and populate viewmodel lists with it
readSharedPrefsToViewModel()
//Get ref to viewpager and set up its adapter & attributes
viewPager = view.findViewById(R.id.view_pager)
val viewPagerAdapter = TasklistFragmentStateAdapter(this)
viewPager.apply {
adapter = viewPagerAdapter
offscreenPageLimit = 2
//Get ref to tablayout, set up & attach its mediator
tabLayout = view.findViewById(R.id.tab_layout)
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
tab.text = when (position) {
0 -> getString(R.string.tab_label_todo)
1 -> getString(R.string.tab_label_doing)
else -> getString(R.string.tab_label_done)
}
}.attach()
}
return view
}
override fun onStop() {
super.onStop()
writeViewModelToSharedPrefs()
}
//Adapter class for view pager
private inner class TasklistFragmentStateAdapter(fa: Kanban) : FragmentStateAdapter(fa){
override fun createFragment(position: Int): Fragment {
//Create argument bundle with task list type
val tasklistFragmentArguments = Bundle().apply{
putInt(ARG_TASKLIST_TYPE, position)
}
//Attach the argument bundle to new fragment instance and return the fragment
return TasklistFragment().apply{
arguments = tasklistFragmentArguments
}
}
override fun getItemCount(): Int = NUM_TASKLIST_PAGES
}
override fun addTaskToViewModel(task: Task, destinationTasklistType: Int) {
val destinationFragment = (activity?.supportFragmentManager?.fragments?.get(destinationTasklistType) as TasklistFragment)
val taskList = getTaskListFromViewModel(destinationTasklistType)
taskList.add(task)
destinationFragment.taskRecyclerView.adapter?.notifyItemInserted(taskList.size)
}
override fun deleteTaskFromViewModel(tasklistType: Int, adapterPosition: Int) {
val tasklistFragment = (activity?.supportFragmentManager?.fragments?.get(tasklistType) as TasklistFragment)
getTaskListFromViewModel(tasklistType).removeAt(adapterPosition)
tasklistFragment.taskRecyclerView.adapter?.notifyItemRemoved(adapterPosition)
}
override fun getTaskListFromViewModel(tasklistType: Int): LinkedList<Task> =
when(tasklistType){
TASKLIST_TYPE_TODO -> taskViewModel.todoTaskList
TASKLIST_TYPE_DOING -> taskViewModel.doingTaskList
TASKLIST_TYPE_DONE -> taskViewModel.doneTaskList
else -> throw Exception("Unrecognized tasklist type") }
private fun writeViewModelToSharedPrefs(){
val gson = Gson()
//Convert list to json string
val todoJSON = gson.toJson(taskViewModel.todoTaskList)
val doingJSON = gson.toJson(taskViewModel.doingTaskList)
val doneJSON = gson.toJson(taskViewModel.doneTaskList)
var sharedPref : SharedPreferences = requireActivity().getPreferences(Context.MODE_PRIVATE);
//Save json strings into shared preferences
activity?.getPreferences(MODE_PRIVATE)?.edit()?.apply {
putString(KEY_TODO_JSON, todoJSON)
putString(KEY_DOING_JSON, doingJSON)
putString(KEY_DONE_JSON, doneJSON)
}?.apply()
}
private fun readSharedPrefsToViewModel(){
val gson = Gson()
val sharedPrefs = activity?.getPreferences(MODE_PRIVATE)
val todoJSON = sharedPrefs?.getString(KEY_TODO_JSON, "[]")
val doingJSON = sharedPrefs?.getString(KEY_DOING_JSON, "[]")
val doneJSON = sharedPrefs?.getString(KEY_DONE_JSON, "[]")
val type = object: TypeToken<LinkedList<Task>>() {}.type //Gson requires type ref for generic types
taskViewModel.todoTaskList = gson.fromJson(todoJSON, type)
taskViewModel.doingTaskList = gson.fromJson(doingJSON, type)
taskViewModel.doneTaskList = gson.fromJson(doneJSON, type)
}
Main activity
public class MainActivity extends AppCompatActivity {
private TabLayout tabLayout;
private ViewPager2 viewPager2;
private MyFragment adapter;
private TextView register;
GridView contractorsGV;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tabLayout = findViewById(R.id.tabLayout);
viewPager2 = findViewById(R.id.viewPager2);
tabLayout.addTab(tabLayout.newTab().setText("Home"));
tabLayout.addTab(tabLayout.newTab().setText("Idea Book"));
tabLayout.addTab(tabLayout.newTab().setText("My Profile"));
FragmentManager fragmentManager = getSupportFragmentManager();
adapter = new MyFragment(fragmentManager , getLifecycle());
viewPager2.setAdapter(adapter);
4. Fragment manager
public class MyFragment extends FragmentStateAdapter {
public MyFragment(#NonNull FragmentManager fm,Lifecycle lifecycle) {
super(fm,lifecycle);
}
#NonNull
#Override
public Fragment createFragment(int position) {
if(position==1)
{
return new Kanban();
}
if(position==2) {
return new signOut();
}
return new Navigation_Bar();
}

Kotlin rotateAnimation is not working. Nothing is happening when the spinBtn is clicked

***I am trying to create af spinningWheel animation with my imageView spinning once the button is pressed. I found a youtube video with some java code i converted into kotlin. With the code below nothing happens when the button is pushed. I have used the rotateAnimation class and some calculations in the GameViewModel thus far, and call the spin() in the GameFragment class' onCreateView().
The code below are the two classes i use for the rotateAnimation but it doesn't seem to work. I am not sure which part is the problem. Does anyone know?***
class GameViewModel : ViewModel() {
private val fields = arrayOf("1","2","3","4","5","6","7")
private val fieldDegrees = IntArray(fields.size)
private var degree = 0
var isSpinning = false
fun getDegreesForSectors(){
val oneFieldsDegrees = 360/fields.size
for (i in fields.indices){
fieldDegrees[i] = (i+1) * oneFieldsDegrees
}
}
fun spin(imageView: ImageView){
isSpinning = true
degree = (0..fields.size).random()
val rotateAnimation = RotateAnimation(0.toFloat(),
(360 * fields.size + fieldDegrees[degree]).toFloat(),
RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f)
rotateAnimation.setDuration(3600)
rotateAnimation.setFillAfter(true)
rotateAnimation.setInterpolator(DecelerateInterpolator())
rotateAnimation.setAnimationListener(object : Animation.AnimationListener{
override fun onAnimationStart(animation: Animation) {}
override fun onAnimationEnd(animation: Animation) {
isSpinning = false
}
override fun onAnimationRepeat(animation: Animation) {}
})
imageView.startAnimation(rotateAnimation)
}
}
class GameFragment : Fragment() {
private lateinit var gameViewModel: GameViewModel
private var _binding: GameFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
gameViewModel =
ViewModelProvider(this).get(GameViewModel::class.java)
val spinBtn: Button? = view?.findViewById(R.id.spinBtn)
val wheelImage: ImageView? = view?.findViewById(R.id.Spinning_Wheel)
gameViewModel.getDegreesForSectors()
spinBtn?.setOnClickListener() {
if (!gameViewModel.isSpinning) {
gameViewModel.spin(wheelImage!!)
}
}
_binding = GameFragmentBinding.inflate(inflater, container, false)
val root: View = binding.root
return root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
You are accessing fragment view inside onCreateView(). This will return null since nothing has been inflated yet.
Use the binding variable to access the views.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
gameViewModel = ViewModelProvider(this).get(GameViewModel::class.java)
_binding = GameFragmentBinding.inflate(inflater, container, false)
gameViewModel.getDegreesForSectors()
binding.spinBtn.setOnClickListener() {
if (!gameViewModel.isSpinning) {
gameViewModel.spin(binding.spinningWheel)
}
}
return binding.root
}

I'm adding data to my list but it doesn't appear in RecyclerView (Kotlin, MVVM)

I am making a simple application. There are 2 screens in the application: List screen, Add screen.
Scenario: When the application is opened, there is a list page on the screen. The user goes to the Adding page with the help of the Floating Action Button on the list page. Here, he enters the items with the help of Edit Text and comes to the List page by pressing the Add Button.
I am using MVVM in this application and I am a beginner. So I can't find the problem. Here is the problem: I am adding new elements to my list in the Add Fragment, but these elements are not seems in the List Fragment.
Thanks in advance.
Model Class
data class Movie(
val movieName: String,
val releaseDate: String
)
ViewModel class
class MovieViewModel : ViewModel() {
val movies = MutableLiveData<ArrayList<Movie>>()
var movieList = arrayListOf<Movie>()
fun addMovie(movie: Movie) {
movieList.add(movie)
movies.value = movieList
}
}
Adapter Class
class MovieAdapter : RecyclerView.Adapter<MovieAdapter.MyViewHolder>() {
private var movieList = emptyList<Movie>()
class MyViewHolder(private val binding: RowItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(movie: Movie) {
binding.textViewMovieName.text = movie.movieName
binding.textViewReleaseDate.text = movie.releaseDate
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = RowItemBinding.inflate(layoutInflater, parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = movieList[position]
holder.bind(currentItem)
}
override fun getItemCount(): Int {
return movieList.size
}
fun setData(movie: List<Movie>) {
val movieDiffUtil = MovieDiffUtil(movieList, movie)
val movieDiffResult = DiffUtil.calculateDiff(movieDiffUtil)
this.movieList = movie
movieDiffResult.dispatchUpdatesTo(this)
}
}
Add Fragment
class AddFragment : Fragment() {
private var _binding: FragmentAddBinding? = null
private val binding get() = _binding!!
private val movieViewModel: MovieViewModel by viewModels<MovieViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentAddBinding.inflate(inflater, container, false)
binding.buttonAdd.setOnClickListener {
insertData()
}
return binding.root
}
private fun insertData() {
val movieName = binding.editTextMovieName.text.toString()
val releaseDate = binding.editTextReleaseDate.text.toString()
val movie = Movie(movieName, releaseDate)
movieViewModel.addMovie(movie)
findNavController().navigate(R.id.action_addFragment_to_listFragment)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
List Fragment
class ListFragment : Fragment() {
private var _binding: FragmentListBinding? = null
private val binding get() = _binding!!
private val adapter: MovieAdapter by lazy { MovieAdapter() }
private val movieViewModel: MovieViewModel by viewModels<MovieViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentListBinding.inflate(inflater, container, false)
setAdapter()
movieViewModel.movies.observe(viewLifecycleOwner, { data ->
adapter.setData(data)
binding.textView.setText(data.get(0).movieName)
})
binding.floatingActionButton.setOnClickListener {
findNavController().navigate(R.id.action_listFragment_to_addFragment)
}
return binding.root
}
private fun setAdapter() {
val recyclerView = binding.recyclerView
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireActivity())
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
After adding an item to a List<> you need to notify the Adapter that the data has changed.
You can do so by calling adapter.notifyDataSetChanged().
I added my List directly into the Adapter but you can create a function like in the following stackoverflow post.
How to update my Recyclerview using kotlin android?

RecyclerView doesn't display data but onBindViewHolder never called

I'm using recyclerView to display my data but anything is displaying. The onBindViewHolder is never called and the onCreateViewHolder also.
I initialize the adapter in the viewModel with the data which I got from room but anything is displaying. The data in the viewModel is there when I want give it to the adaptater.
There's the adaptater,
class CountryInfoAdapter : RecyclerView.Adapter<TextItemViewHolder>() {
var data = listOf<DevByteCountryProperty>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
val item = data[position]
holder.textView.text = item.name.toString()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
//The inflater knows how to create views from XML layout. The context is the context of the RecyclerView.
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.text_item_view, parent, false) as TextView
Timber.i("Data from adaptater : ${data.toString()}")
return TextItemViewHolder(view)
}
}
There is the Fragment,
class CountryInfoFragment : Fragment() {
private lateinit var viewModel: CountryInfoViewModel
private lateinit var viewModelFactory: CountryInfoViewModelFactory
private lateinit var binding: FragmentCountryInfoBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater,
R.layout.fragment_country_info,container,false)
val application = requireNotNull(this.activity).application
viewModelFactory = CountryInfoViewModelFactory(application)
viewModel = ViewModelProvider(this,viewModelFactory).get(CountryInfoViewModel::class.java)
binding.viewModel = viewModel
val adapter = CountryInfoAdapter()
viewModel.countriesProperty.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
binding.countryList.adapter = adapter
binding.lifecycleOwner = viewLifecycleOwner
setHasOptionsMenu(true)
return binding.root
}
}
Please try below code, setting up layout manager before assigning adapter to recyclerView
binding.countryList.layoutManager = LinearLayoutManager(this)
binding.countryList.adapter = adapter

Android Recycler View delete item

I have created a simple notes app that uses some of the Android Architecture components. I am using dataBinding to set data to my recycler view. One of the functionalities is bookmarking a note and displaying it in the Bookmarks fragment. In the Bookmarks fragment a user can tap the 'unbookmark' icon to remove a bookmarked note. I have used a simple on click listener on the icon inside my Bookmarks Recycler View Adapter to achieve this. I have a boolean property in my notes entity. Inside the Bookmarks fragment I update the bookmarks boolean value to false when the user taps the icon so that it changes to a false value(meaning it's not bookmarked and does not appear in the bookmarks fragment). However, when I click the 'unbookmark' icon the recycler view still displays the notes removed from the bookmarks.
Here is my Bookmarks Adapter :
class BookmarksAdapter(private var bookmarksList: List<Note>, var context: Context):
RecyclerView.Adapter<BookmarksAdapter.BookmarkViewHolder>(), CoroutineScope {
private lateinit var job: Job
var deletedId : Int? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookmarkViewHolder {
val layoutItemBinding: BookmarksLayoutItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.bookmarks_layout_item,
parent,
false)
job = Job()
return BookmarkViewHolder(layoutItemBinding.root)
}
override fun onBindViewHolder(holder: BookmarkViewHolder, position: Int) {
val bookmarkNote: Note = bookmarksList.get(position)
holder.bookmarkBinding?.setVariable(BR.bookmarkItem, bookmarkNote)
holder.bookmarkBinding?.executePendingBindings()
holder.bookmarkBinding?.imageRemoveBookmark?.setOnClickListener {
removeBookmark(bookmarkNote, position)
}
}
override fun getItemCount(): Int {
return bookmarksList.size
}
inner class BookmarkViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var bookmarkBinding: BookmarksLayoutItemBinding? = DataBindingUtil.bind(itemView)
}
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
fun deleteNote(note : Note) {
launch(Dispatchers.IO) {
note.isBookmarked = false
NotesDatabase(context).getDao().updateNote(note)
}
val pos = bookmarksList.toMutableList().indexOf(note)
bookmarksList.toMutableList().removeAt(pos)
notifyItemRemoved(pos)
}
// Remove a bookmark
fun removeBookmark(bookmarkNote: Note, removedPos: Int) {
bookmarkNote.isBookmarked = false
launch (Dispatchers.IO){
NotesDatabase(context).getDao().updateNote(bookmarkNote)
withContext(Dispatchers.Main) {
bookmarksList.toMutableList().removeAt(removedPos)
notifyItemRemoved(removedPos)
notifyItemRangeChanged(removedPos, 1)
}
}
}
}
Below is my Bookmarks View Model:
class BookmarksViewModel(application: Application) : AndroidViewModel(application) {
val myContext: Context = application.applicationContext
private var _bookmarksList = MutableLiveData<List<Note>>()
val bookmarksList : LiveData<List<Note>>
get() = _bookmarksList
init{
viewModelScope.launch {
_bookmarksList.value = getNotes()
}
}
suspend fun getNotes() : List<Note> = NotesDatabase(myContext).getDao().getBookmarkedNotes()
}
Here is my Bookmarks Fragment:
class BookmarksFragment : BaseFragment(){
private lateinit var bookmarksBinding: FragmentBookmarksBinding
private lateinit var bookmarksViewModel: BookmarksViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
bookmarksBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_bookmarks, container, false)
bookmarksViewModel = ViewModelProvider(this).get(BookmarksViewModel::class.java)
// Observe the list of bookmarks
bookmarksViewModel.bookmarksList.observe(viewLifecycleOwner, { bList ->
bookmarksBinding.listOfBookmarks = bList
})
return bookmarksBinding.root
}
}
Here is my Notes Entity:
val bookmarkSate: Boolean
get() = true
#Entity
data class Note(
val title : String,
val note : String,
var isBookmarked : Boolean = false
) : Serializable{
#PrimaryKey(autoGenerate = true)
var noteID : Int = 0
}
Here is my Notes DAO:
#Dao
interface NoteDao {
#Insert
suspend fun saveNote(note : Note)
#Query("SELECT * FROM note ORDER BY noteID DESC")
suspend fun getAllNotes() : List<Note>
#Query("SELECT * FROM note WHERE isBookmarked")
suspend fun getBookmarkedNotes() : List<Note>
// add multiple notes
#Insert
suspend fun addMultipleNotes(vararg note: Note)
#Update
suspend fun updateNote(note: Note)
#Delete
suspend fun deleteNote(note : Note)
}
I am stuck on how to achieve the desired functionality of 'unbookmarking' a note and making it 'disappear 'from the bookmarks fragment. Kindly anyone who can help out?
There are a few problems and possible solutions I see here:
1-) In your adapter, in your delete method, you have this piece of code:
val pos = bookmarksList.toMutableList().indexOf(note)
bookmarksList.toMutableList().removeAt(pos)
notifyItemRemoved(pos)
The problem here is that toMutableList() doesn't change your bookmarksList as mutable, it returns a new mutable list filled with the items in your bookmarkList, a mutable version of your list, but a new list! So you remove the item from this new list, and not from the original list. Simply correcting this could fix the problem. (You can define the bookmarksList as a mutable list from the beginning)
2-) You seem to observe the list from your viewmodel, it is livedata in your viewmodel. But it is not defined as livedata in your dao. So you are not observing the changes in the database. So an alternative solution could be to observe the changes from the database by wrapping your list within a livedata in your dao.
#Query("SELECT * FROM note WHERE isBookmarked")
suspend fun getBookmarkedNotes() : LiveData<List<Note>>
But this second one would require some additional changes in your code.