recyclerview Drag and drop to different View - android

I have a recyclerview in my project, where a user will be able to drag and drop it to a different view(card view).
I am following mvvm pattern and using dataBinding. I use the longclick function from within the activity,i created an interface in adapter class for this.
inorder to use the drag and drop i need to get the adapter position of the item in activity, which i am unable to get.
here is the adapter class
class StatementAdapter(
private val context: Context,
private val statementList: ArrayList<Statement>
) :
RecyclerView.Adapter<StatementAdapter.StatementViewHolder>() {
private var mListener: OnItemLongClickListener? = null
interface OnItemLongClickListener {
fun onItemLongClick(statement: Statement)
}
fun setOnItemLongClickListener(listener: OnItemLongClickListener) {
mListener = listener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatementViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val statementBinding: StatementBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.statement_list, parent, false)
return StatementViewHolder(statementBinding, mListener)
}
override fun getItemCount(): Int {
return statementList.size
}
override fun onBindViewHolder(holder: StatementViewHolder, position: Int) {
val statementViewModel = statementList[position]
holder.bind(statementViewModel)
}
class StatementViewHolder(
private val statementBinding: StatementBinding,
private val listener: OnItemLongClickListener?
) : RecyclerView.ViewHolder(statementBinding.root) {
fun bind(statementViewModel: Statement) {
this.statementBinding.statementModel = statementViewModel
itemView.setOnLongClickListener {
listener?.onItemLongClick(
statementViewModel
)
true
}
statementBinding.executePendingBindings()
}
}
}
Here is the activity
class TrueOrFalseActivity : AppCompatActivity(), StatementAdapter.OnItemLongClickListener {
private lateinit var trueOrFalseBinding: ActivityTrueOrFalseBinding
private var rvStatement: RecyclerView? = null
private var statementAdapter: StatementAdapter? = null
private val dragMessage = "Added"
private val inBucket = "in bucket"
private val offBucket = "not in bucket"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
trueOrFalseBinding = ActivityTrueOrFalseBinding.inflate(layoutInflater)
setContentView(trueOrFalseBinding.root)
rvStatement = findViewById(R.id.rvStatement)
val statementViewModel = ViewModelProvider(this).get(StatementViewModel::class.java)
statementViewModel.generateStatement()
statementViewModel.newMStatementList.observe(this) {
statementAdapter = StatementAdapter(this#TrueOrFalseActivity, it)
rvStatement!!.layoutManager = LinearLayoutManager(this#TrueOrFalseActivity)
rvStatement!!.adapter = statementAdapter
statementAdapter?.setOnItemLongClickListener(this)
}
}
override fun onItemLongClick(statement: Statement) {
val item = ClipData.Item(dragMessage)
val dragData = ClipData(
dragMessage,
arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
item
)
val myShadow = MyDragShadowBuilder(this)
// Here where i need to get the item position
}
}
class MyDragShadowBuilder(trueOrFalseActivity: TrueOrFalseActivity) {
}
I am following the following documentation
https://developer.android.com/guide/topics/ui/drag-drop.html#AboutDragging
in this, the "view" is the parameter, but in my case it is arecyclerview item.
how do i solve this?

Related

List is not updated after notifyDataSetChanged what should I do?

I am fetching JSON data from API and passing it in recycler view but if I want to fetch new data and display it in recycler view then I have to clear the list and then add new data in that list and notify the adapter that the data is changed but it is not updated what should I do?
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var recipeViewModel: RecipeViewModel
private lateinit var mainBinding: ActivityMainBinding
private lateinit var recipeAdapter: RecipeAdapter
private lateinit var recipeItemList: ArrayList<Hit>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeItemList.addAll(recipeItems.hits)
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
private fun searchRecipeName() {
mainBinding.searchRecipeFabBtn.setOnClickListener {
val view = layoutInflater.inflate(R.layout.recipe_search_layout, null)
val searchRecipeET = view.findViewById<EditText>(R.id.searchRecipeET)
val searchRecipeBtn = view.findViewById<Button>(R.id.searchRecipeBtn)
val bottomSheetDialog = BottomSheetDialog(this)
bottomSheetDialog.apply {
this.setContentView(view)
this.show()
}
searchRecipeBtn.setOnClickListener {
val recipeName = searchRecipeET.text.toString()
searchRecipeName(recipeName, searchRecipeET, bottomSheetDialog)
}
}
}
private fun searchRecipeName(
recipeName: String,
searchRecipeET: EditText,
bottomSheetDialog: BottomSheetDialog
) {
if (recipeName.isEmpty()) {
searchRecipeET.error = "Please enter recipe name"
} else {
recipeViewModel.getRecipes(recipeName)
bottomSheetDialog.dismiss()
}
}
}
RecipeAdapter.kt
class RecipeAdapter(val context: Context) : RecyclerView.Adapter<RecipeAdapter.RecipeViewHolder>() {
private val recipesList: ArrayList<Hit> = arrayListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = recipesList[position]
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
override fun getItemCount(): Int {
return recipesList.size
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList.clear()
Log.d("RECIPE SIZE", "${recipesList.size}")
recipesList.addAll(newRecipesList)
notifyDataSetChanged()
}
}
This may be helpful.
Be careful of this :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mainBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(mainBinding.root)
recipeViewModel =
ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory
.getInstance(application)
)[RecipeViewModel::class.java]
recipeItemList = arrayListOf()
mainBinding.recyclerView.layoutManager = LinearLayoutManager(this)
mainBinding.recyclerView.hasFixedSize()
recipeAdapter = RecipeAdapter(this)
mainBinding.recyclerView.adapter = recipeAdapter
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
// You're adding items here but never clear the list
// list will be bigger every time you'll be notified
// recipeItemList.addAll(recipeItems.hits
// recipeAdapter.updateRecipes(recipeItemList)
// Do this instead
recipeItemList = recipeItems.hits
recipeAdapter.updateRecipes(recipeItemList)
Log.d("RESPONSE", recipeItems.toString())
Log.d("List size", recipeAdapter.itemCount.toString())
})
searchRecipeName()
}
Also, here: It's a little better to do this (https://stackoverflow.com/a/10298038/4221943)
fun updateRecipes(newRecipesList: ArrayList<Hit>){
recipesList = newRecipesList
Log.d("RECIPE SIZE", "${recipesList.size}")
notifyDataSetChanged()
}
BTW it will always be more efficient to use the more specific change events if you can. Rely on notifyDataSetChanged() as a last resort. It is also good practice to use notifyItemInserted(mItems.size() - 1) for "easier" solution.
You could convert the RecyclerView.Adapter into a ListAdapter:
class RecipeAdapter(val context: Context) : ListAdapter<Hit, RecipeAdapter.RecipeViewHolder>(RecipeDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder {
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.recipe_items_layout, null, false)
return RecipeViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int) {
val currentItem = getItem(position)
holder.recipeImageView.load(currentItem.recipe.image)
holder.recipeNameText.text = currentItem.recipe.label
}
class RecipeViewHolder(itemView: View) :RecyclerView.ViewHolder(itemView) {
val recipeImageView: ImageView = itemView.findViewById(R.id.recipeImageView)
val recipeNameText: TextView = itemView.findViewById(R.id.recipeNameText)
}
}
class RecipeDiffCallback : DiffUtil.ItemCallback<Hit>() {
// Change this condition based on the attribute of `Hit` that will change
override fun areItemsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Hit, newItem: Hit): Boolean = oldItem == newItem
}
Then update its content with the submitList method.
Every item not satisfying the RecipeDiffCallback conditions will be automatically updated:
recipeViewModel.recipeLiveData.observe(this, Observer { recipeItems ->
recipeAdapter.submitList(recipeItems.hits)
})

How to sort encrypted ArrayList and show this in a RecyclerView

My target is to sort in alphabetical order data from an ArrayList with encrypted data and put correct order into a RecyclerView.
I'm able to show decrypted data into RecyclerView, but in the same order witch I use to add item into the ArrayList (see code in adapter).
I tried to sort with sortby{}without success (see code in activity). The result is an partial order and onItemClick not return correct position.
My activity that extend adapter:
class HomeActivity : AppCompatActivity(),DocumentAdapter.OnItemClickListener {
companion object{
lateinit var dbHandler: DBHandler
}
private var documentslist = ArrayList<PassItem>()
private lateinit var adapter : RecyclerView.Adapter<*>
private lateinit var rv : RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
dbHandler = DBHandler(this, null, null,1)
viewDocuments()
val fab = findViewById<FloatingActionButton>(R.id.fabAdd)
fab.setOnClickListener {
val i = Intent(this, AddDocument :: class.java)
startActivity(i)
}
}
#SuppressLint("WrongConstant")
private fun viewDocuments(){
documentslist = dbHandler.getDocuments(this)
//documentslist.sortBy { it.PassItemApp.toString() } ---> this sort encrypted data then not in alphabetic order and onItemClick return uncorrect position
adapter = DocumentAdapter(this,documentslist,this)
rv = findViewById(R.id.recyclerView)
rv.layoutManager = LinearLayoutManager(this, LinearLayout.VERTICAL,false)
rv.adapter = adapter
}
override fun onItemClick(position: Int) {
val intent = Intent(this#HomeActivity,DocActivity::class.java)
intent.putExtra("clickedPosition", position)
startActivity(intent)
}
override fun onResume() {
viewDocuments()
super.onResume()
}
and my adapter:
class DocumentAdapter(val mCtx: Context, private val documents: ArrayList<PassItem>, val listener: OnItemClickListener) : RecyclerView.Adapter<DocumentAdapter.ViewHolder>(){
interface OnItemClickListener{
fun onItemClick(position : Int)
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView), View.OnClickListener{
val txtDocApp : TextView = itemView.findViewById(R.id.txtDocApp)
init {
itemView.setOnClickListener(this)
}
override fun onClick(v: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION){
listener.onItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DocumentAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.item_pass_layout,parent,false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val document : PassItem = documents[position]
//Decryption of item showed in Recyclerview
val initialisationVector = document.PassItemIvApp
val bytesIV = Base64.decode(initialisationVector, Base64.NO_WRAP)
val oisIV = ObjectInputStream(ByteArrayInputStream(bytesIV))
val initializationVectorIV = oisIV.readObject() as ByteArray
val encryptedText = document.PassItemApp
val bytesText = Base64.decode(encryptedText, Base64.NO_WRAP)
val oisText = ObjectInputStream(ByteArrayInputStream(bytesText))
val textByteArray = oisText.readObject() as ByteArray
val key = document.PassItemKeyApp
val keyText = Base64.decode(key, Base64.NO_WRAP)
val oisKey = ObjectInputStream(ByteArrayInputStream(keyText))
val keyToDecrypt = oisKey.readObject() as SecretKey
val aesDecrypt = Decrypted(keyToDecrypt,initializationVectorIV)
val decryptedByteArray = aesDecrypt.decrypt(textByteArray)
val textAfterDecryption = decryptedByteArray.toString(charset("UTF_8"))
//Data in Recyclerview
holder.txtDocApp.text = textAfterDecryption
}
override fun getItemCount(): Int {
return documents.size
}
}

Handling button clicks in recyclerview adapter (Kotlin)?

I have an adapter in which the items each have 3 buttons, that generate a dialog that then performs an action. I have a sense that this should be removed from the adapter (I have view models available), but it works and I am wondering: Should I move logic to the fragment, to the view model, do I need to move it at all (is the code below bad practice and if so why)? Any help/input would be greatly appreciated.
Here is the adapter code:
class ViewRecipesAdapter(val context: Context, private val recipes: List<Recipe>, private val parentFragment: Fragment) :
RecyclerView.Adapter<ViewRecipesAdapter.RecipeViewHolder>()
{
private var listToUse: List<Recipe> = recipes
private lateinit var recipesViewModel: RecipesViewModel
private var isView = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding: ViewRecipesItemBinding =
DataBindingUtil.inflate(layoutInflater, R.layout.view_recipes_item, parent, false)
return RecipeViewHolder(binding, context)
}
override fun getItemCount() = listToUse.size
override fun onBindViewHolder(holder: RecipeViewHolder, position: Int)
{
val recipe = listToUse[position]
// to delete and edit items
val dao = RecipesDatabase.getInstance(context).recipeDao()
val repository = RecipeRepository(dao)
recipesViewModel = RecipesViewModel(repository)
//display data on list item
holder.bind(recipe)
Glide.with(context).load(recipe.imageOne)
.into(holder.binding.imageViewItemImage)
//tried to handle clicks here through the viewModel but I could not get it working from fragment
//the function call after viewModel calls is what works and it seems to work well
holder.binding.imageButtonItemdelete.setOnClickListener {
recipesViewModel.setIsDelete(true)
recipesViewModel.setPositionFromAdapter(position)
startDeleteDialog(position)
}
holder.binding.imageButtonItemedit.setOnClickListener {
recipesViewModel.setIsView(false)
recipesViewModel.setPositionFromAdapter(position)
isView = false
startEditOrViewDialog(position)
}
holder.binding.imageButtonItemview.setOnClickListener {
recipesViewModel.setIsView(true)
recipesViewModel.setPositionFromAdapter(position)
isView = true
startEditOrViewDialog(position)
}
}
fun setList(newList: List<Recipe>)
{
listToUse = newList
}
//dialog functions for the edit, delete, and view buttons on each item
private fun startDeleteDialog(position: Int)
{
AlertDialog.Builder(context)
.setTitle("Delete recipe?")
.setPositiveButton("Yes") { _, _ ->
recipesViewModel.deleteRecipe(recipes[position])
notifyItemRemoved(position)
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
private fun startEditOrViewDialog(position: Int)
{
when (isView)
{
true ->
{
AlertDialog.Builder(context).setTitle("View recipe?")
.setPositiveButton("Yes") { _, _ ->
//get relevant data from current recipe
val recipe = recipes[position]
//create a dialog that shows this data in an inflated layout
val viewDialog = AlertDialog.Builder(context)
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.fragment_edit_or_view, null)
view.editText_editrecipe_directions.setText(recipe.directions)
view.editText_editrecipe_ingredients.setText(recipe.ingredients)
view.editText_editrecipe_notes.setText(recipe.notes)
view.editText_editrecipe_title.setText(recipe.title)
view.textView_date_edit.text = recipe.date
view.editText_editrecipe_title.keyListener = null
view.editText_editrecipe_directions.keyListener = null
view.editText_editrecipe_ingredients.keyListener = null
view.editText_editrecipe_notes.keyListener = null
if (recipe.rating != null)
{
view.ratingBar_edit.rating = recipe.rating
}
Glide.with(context)
.load(recipe.imageOne)
.into(view.imageView_addphoto_edit)
viewDialog.setView(view).show()
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
false ->
{
AlertDialog.Builder(context).setTitle("Edit recipe?")
.setPositiveButton("Yes") { _, _ ->
//get relevant data from current recipe
val recipe = recipes[position]
val idString = recipe.id.toString()
recipesViewModel.setId(idString)
recipesViewModel.getRecipeById2(idString)
notifyDataSetChanged()
val controller = parentFragment.findNavController()
controller.navigate(
ViewRecipesFragmentDirections.actionNavViewrecipesToNavAddrecipe(
recipe.id.toString()
)
)
}
.setNegativeButton("No") { dialog, _ ->
dialog.dismiss()
}.show()
}
}
}
override fun getItemId(position: Int): Long
{
return position.toLong()
}
override fun getItemViewType(position: Int): Int
{
return position
}
class RecipeViewHolder(val binding: ViewRecipesItemBinding, val context: Context) :
RecyclerView.ViewHolder(binding.root)
{
fun bind(recipe: Recipe)
{
if (recipe.isLeftover == true)
{
binding.tvIsLeftovers.visibility = View.VISIBLE
}
binding.textViewItemTitle.text = recipe.title
if (recipe.date != null)
{
binding.textViewItemDate.text = recipe.date
}
if (recipe.rating != null)
{
binding.ratingBar2.rating = recipe.rating
}
binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
duration = 1000
}
}
}
}
This is the view model, with live data variables set up that I could not get working in the fragment that this RecyclerView is in:
class RecipesViewModel(private val repository: RecipeRepository) : ViewModel()
{
val recipesList = repository.getAllRecipes()
private val _isView = MutableLiveData<Boolean>()
val isView: MutableLiveData<Boolean> = _isView
private val _isEdit = MutableLiveData<Boolean>()
val isEdit: MutableLiveData<Boolean> = _isEdit
private val _positionFromAdapter = MutableLiveData<Int>()
val positionFromAdapter: MutableLiveData<Int> = _positionFromAdapter
private val _isDelete = MutableLiveData<Boolean>()
val isDelete: MutableLiveData<Boolean> = _isDelete
private val _recipesListFromSearch = MutableLiveData<List<Recipe>>()
val recipesListFromSearch: LiveData<List<Recipe>> = _recipesListFromSearch
private val _recipe = MutableLiveData<Recipe>()
val recipe: LiveData<Recipe> = _recipe
lateinit var searchString: String
val savedId = MutableLiveData<String>()
fun setPositionFromAdapter(position: Int)
{
_positionFromAdapter.value = position
}
fun setIsView(isView: Boolean)
{
_isView.value = isView
}
fun setIsDelete(isDelete: Boolean)
{
_isView.value = isDelete
}
fun setIsEdit(isEdit: Boolean)
{
_isEdit.value = isEdit
}
fun setId(id: String)
{
savedId.value = id
}
fun insertRecipe(recipe: Recipe)
{
CoroutineScope(Dispatchers.IO).launch {
repository.insertRecipe(recipe)
}
}
fun getRecipesFromQuery(query: String)
{
CoroutineScope(Dispatchers.IO).launch {
val list = repository.getRecipesSearch(query)
MainScope().launch { _recipesListFromSearch.value = list }
}
}
fun saveUserRecipeToDb(
title: String?,
ingredients: String?,
directions: String?,
notes: String?,
uriToSave: String?,
rating: Float?,
date: String?,
isLeftover: Boolean,
loadedId: String
): Boolean
{
val recipeToSave = Recipe(
title,
ingredients,
directions,
notes,
uriToSave,
null,
null,
rating,
date,
isLeftover
)
if (loadedId != "666")
{
recipeToSave.id = loadedId.toInt()
}
insertRecipe(recipeToSave)
return false
}
fun getRecipeById2(id: String) = repository.getRecipeByIdLive(id)
fun deleteRecipe(recipe: Recipe)
{
CoroutineScope(Dispatchers.IO).launch {
repository.deleteRecipe(recipe)
}
}
}
How to implement onClick in the RecyclerView. Let's assume that in Your Recycler every view is a visualization of some item and when You click on it You want to do something with that item:
Create class: ClickListener:
class ClickListener(
val clickListener: (itemId: Int) -> Unit,
)
{
fun onClick(item: ItemClass) = clickListener(item.id)
}
Now in Your RecylerViewAdapter pass as an argument this Listener:
class RecylerViewAdapter(
private val clickListener: ClickListener
)
In onBindViewHolder pass this Listenner as argument
override fun onBindViewHolder(holder: ViewHolder, position: Int)
{
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
{
return ViewHolder.from(
parent
)
}
In Your ViewHolder class:
class ViewHolder private constructor(private val binding: ItemRecyclerBinding) :
RecyclerView.ViewHolder(binding.root)
{
companion object
{
fun from(parent: ViewGroup): ViewHolder
{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemRecyclerBinding.inflate(layoutInflater, parent, false)
return ViewHolder(
binding
)
}
}
fun bind(
item : Item,
clickListener: ClickListener
)
{
binding.item = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
}
In Your item layout (which has to be converted to data binding layout) add this:
<data>
<variable
name="item"
type="com.example.sth.database.Item" /> // path to `Item`
<variable
name="clickListener"
type="com.example.sth.ui.adapter.ClickListener" /> // Path to `ClickListener`
</data>
Now You can add onClick method to Button:
android:onClick="#{() -> clickListener.onClick(item)}"
When You create Adapter in fragment or Activity You have to pass clickListenner as a parameter. In this way You can handle everything from fragment and RecyclerView doesn't care about what You do in this function.
val clickListenner = ClickListenner(
{ id -> viewModel.clickItemWithid(id) }, // click. This function from ViewModel will be executed when You click on item in recycler View
)
val adapter = RecylerViewAdapter (
clickListenner
)
This method is based on Google developers codelabs on Udacity.
Here You can check whole codelabs. It is free.
And here is just one video with implementing click listenner
These are the changes that are working for me now:
class ClickListener(val clickListener: (itemId: Int, itemPosition: Int, dialogInt: Int) -> Unit) {
fun onClickDelete(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
fun onClickEdit(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
fun onClickView(recipe: Recipe, position: Int, dialogInt: Int) = clickListener(recipe.id, position, dialogInt)
}
In the adapter:
class RecipeViewHolder private constructor(val binding: ViewRecipesItemBinding) :
RecyclerView.ViewHolder(binding.root) {
companion object {
fun from(parent: ViewGroup): RecipeViewHolder{
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ViewRecipesItemBinding.inflate(layoutInflater, parent, false)
return RecipeViewHolder(binding)
}
}
fun bind(recipe: Recipe, clickListener: ClickListener) {
binding.recipe = recipe
binding.imageButtonItemdelete.setOnClickListener {
clickListener.onClickDelete(recipe, adapterPosition, 1)
}
binding.imageButtonItemedit.setOnClickListener {
clickListener.onClickEdit(recipe, adapterPosition,2)
}
binding.imageButtonItemview.setOnClickListener {
clickListener.onClickView(recipe, adapterPosition,3)
}
binding.executePendingBindings()
binding.root.animation = AlphaAnimation(0.0f, 1.0f).apply {
duration = 1000
}
}
In the fragment holding the RecyclerView :
private fun initRecyclerView() {
recipesViewModel.recipesList.observe(viewLifecycleOwner, Observer {
//update recyclerview
val list = it
listForFragment = it
clickListener = ClickListener { id, position, dialogInt ->
recipesViewModel.apply {
setPositionFromAdapter(position)
setDialogRecipe(id)
}
when (dialogInt) {
1 -> startDeleteDialog(position)
2 -> startEditDialog(position)
3 -> startViewDialog(position)
}
}
rv_viewrecipes.adapter = ViewRecipesAdapter(requireContext(), list, this, clickListener)
rv_viewrecipes.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
})
}
I couldn't figure out how to pass the position and int for the when statement through the xml onClick, but for now at least the adapter class has the view logic removed.

How to update my Recyclerview using kotlin android?

I have an actitvity with Recyclerview which display data. Now I want to update my RecyclerView once got a new data.For now each time I close and reopen my app the new data will be displayed. but i want it without close to update my view.
I have tried this,
but nothing will work,
fun setupViewPager(viewPager: ViewPager, it: List<TransactionEntity>, incoming: TransactionAdapterDirection, mainActivity: MainActivity) {
val cc: Context = mainActivity.applicationContext
if(adapter.count < 2) {
if (incoming.equals(OUTGOING)) {
val gson = Gson()
val gson1 = GsonBuilder().create()
val model = it
val IT = gson.toJson(model)
val pref = cc.applicationContext.getSharedPreferences("MyPrefSend", 0)
val editor = pref.edit()
editor.putString("NEWIT_SEND", IT)
editor.apply()
adapter.addFragment(SendingFragment(),"SEND")
adapter.notifyDataSetChanged()
} else if (incoming.equals(INCOMING)) {
val gson = Gson()
val gson1 = GsonBuilder().create()
val model = it
val IT = gson.toJson(model)
val pref = cc.applicationContext.getSharedPreferences("MyPrefRec", 0)
val editor = pref.edit()
editor.putString("NEWIT_REC", IT)
editor.apply()
adapter.addFragment(ReceiveFragment(), "RECEIVE")
adapter.notifyDataSetChanged()
}
viewPager.adapter = adapter
}
}
class ViewPagerAdapter(manager: FragmentManager) : FragmentStatePagerAdapter(manager)
{
private val mFragmentList: ArrayList<Fragment> = ArrayList<Fragment>()
private val mFragmentTitleList: ArrayList<String> = ArrayList<String>()
override fun getCount(): Int {
return mFragmentList.size
}
override fun getItem(position: Int): Fragment? {
var fragment: Fragment? = null
if (position == 0) {
fragment = SendingFragment()
} else if (position == 1) {
fragment = ReceiveFragment()
}
return fragment
}
fun addFragment(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList[position]
}
override fun getItemPosition(fragItem: Any): Int {
var position = 0
if (fragItem is ReceiveFragment) {
position = 0
} else if (fragItem is SendingFragment) {
position = 1
}
return if (position >= 0) position else PagerAdapter.POSITION_NONE
}
}
**Framgnet.kt**
class ReceiveFragment: Fragment()
{
private var linearLayoutManager: LinearLayoutManager? = null
fun fromJson(jsonString: String, type: Type): Any {
return Gson().fromJson(jsonString, type)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val pref = context!!.getSharedPreferences("MyPrefRec", 0)
val mFragIT = pref.getString("NEWIT_REC", "")
val NewIT = fromJson(mFragIT,
object : TypeToken<List<TransactionEntity>>() {
}.type) as List<TransactionEntity>
val activity = activity as MainActivity
val myAppDatabaseData = activity.getAppDatabaseData()
val myNetwrk = activity.getNwtwrkData()
// Inflate the layout for this fragment
val rootView = inflater.inflate(R.layout.receive_fragment, container, false)
val recyclerView = rootView.findViewById<RecyclerView>(R.id.transaction_recycler_in) as RecyclerView
linearLayoutManager = LinearLayoutManager(activity, LinearLayout.VERTICAL, false)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = TransactionRecyclerAdapter(NewIT,myAppDatabaseData,TransactionAdapterDirection.INCOMING,myNetwrk)
recyclerView.setHasFixedSize(true)
return rootView
}
}
I have tried this, mAdapter.notifyDataSetChanged();
If you want to update data from activity/fragment than you can make one function inside adapter as below:
public void update(ArrayList<String> modelList){
//string arraylist is for example pass your data
// replace your adapter data with argument data
mAdapter.notifyDataSetChanged();
}
For kotlin:
fun update(modelList:ArrayList<String){
myList = modelList
myAdapter!!.notifyDataSetChanged()
}
Call this function from activity/fragment as below :
mAdapter.update(response.getList());
You can do it with a function in your ItemAdapter:
/**
* Refresh the whole data set of items.
*/
fun refreshDataset() {
mDataset = parseItems(mAppCtx)
notifyDataSetChanged()
}
and then, any time you receive a new set of data, you can call such function directly.
If you want to do in more Kotlin style do like this:
1) Create an abstract class for Adapter:
abstract class BaseRecyclerAdapter<Type, ViewHolder : BaseViewHolder<Type>>(list: List<Type> = mutableListOf()) : RecyclerView.Adapter<ViewHolder>() {
var items: MutableList<Type> = list.toMutableList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount() = items.size
enter code here
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position]) }
2) Create an abstract class for ViewHolder:
abstract class BaseViewHolder<in T>(override val containerView: View): RecyclerView.ViewHolder(containerView), LayoutContainer {
abstract fun bind(item: T) }
3) How to use:
class MyActivity : AppCompatActivity() { private var adapter: MyAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
//This is your data which you set first time
val initData = mutableListOf<String>()
adapter = MyAdapter(initData)
myRecyclerView.layoutManager = adapter
myRecyclerView.layoutManager = LinearLayoutManager(this)
}
// Call this function when you need to update adapter
private fun notifyAdapter(list: MutableList<String>){
adapter.items = list
}}
Create MyAdapter:
private class MyAdapter(list: MutableList<String>) : BaseRecyclerAdapter<String, ViewHolder>(list) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder.newInstance(parent)
}
Create ViewHolder for MyAdapter:
private class ViewHolder(containerView: View) : BaseViewHolder<String>(containerView) {
companion object {
fun newInstance(parent: ViewGroup) = ViewHolder(parent.inflate(R.layout.item))
}
override fun bind(item: String) {
title.text = item
}}
Create item for ViewHolder:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
If you have any questions please ask
You should try LiveData
LiveData is a data holder class that can be observed within a given
lifecycle. This means that an Observer can be added in a pair with a
LifecycleOwner, and this observer will be notified about modifications
of the wrapped data only if the paired LifecycleOwner is in active
state.
Documentation: https://developer.android.com/reference/android/arch/lifecycle/LiveData

Item Onclick RecyclerView Kotlin Android

I have RecyclerView adapter in Kotlin and when a user clicks on categoryPhoto, I want to open a new activity. How should I implement this?
class CategoryAdapter(private val categoryList: List<Category>, private val context: Context) : RecyclerView.Adapter<CategoryAdapter.MyViewHolder>() {
class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var categoryName = view.text_view_category_name
var categoryPhoto = view.image_view_category
var cardView = view.card_view_category
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = MyViewHolder(parent.inflate(R.layout.category_list_row))
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val category = categoryList[position]
// Set height of cardview based on screen width
val displayMetrics = context.resources.displayMetrics
val finalHeight = displayMetrics.widthPixels / 2
holder.cardView.layoutParams.height = finalHeight
holder.categoryName.text = category.oc
holder.categoryPhoto.loadUrl(category.icon)
}
override fun getItemCount(): Int {
return categoryList.size
}}
Just add click listener as parameter to constructor of your adapter.
class CategoryAdapter(
private val categoryList: List<Category>,
private val context: Context,
private val onClickListener: (View, Category) -> Unit
) : RecyclerView.Adapter<CategoryAdapter.MyViewHolder>() {
...
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val category = categoryList[position]
// Set height of cardview based on screen width
...
holder.itemView.setOnClickListener { view ->
onClickListener.invoke(view, category)
}
}
...
}
Then you can use as following:
fun initList() {
adapter = CategoryAdapter(
categoryList = ...,
context = ...,
onClickListener = { view, category -> openActivity(view, category) }
}
Off-top. Some optional improvements for code above
Create typealias for lambda. Make your code more readable.
typealias MyCategoryClickListener = (View, Category) -> Unit
class CategoryAdapter(
private val categoryList: List<Category>,
private val context: Context,
private val onClickListener: MyCategoryClickListener
) : RecyclerView.Adapter<CategoryAdapter.MyViewHolder>() {
Omit invoke call of listener. Lambda can be called just like function.
holder.itemView.setOnClickListener { view ->
onClickListener(view, category)
}
Replace lambda with reference when creating adapter
fun initList() {
adapter = CategoryAdapter(
categoryList = ...,
context = ...,
onClickListener = this::openActivity)
}
fun openActivity(view: View, category: Category) {
...
}
You can do it in your onBindViewHolder(...)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val category = categoryList[position]
// Set height of cardview based on screen width
val displayMetrics = context.resources.displayMetrics
val finalHeight = displayMetrics.widthPixels / 2
holder.cardView.layoutParams.height = finalHeight
holder.categoryName.text = category.oc
holder.categoryPhoto.loadUrl(category.icon)
holder.categoryPhoto.setOnClickListener { view ->
// categoryPhoto clicked.
// start your activity here
}
}
Do like this
class RecyclerListAdapter: RecyclerView.Adapter {
var context: Context? = null
var listData: ArrayList? = null
Step 1: Activity ref..................................
var activityref:MainActivity?=null
constructor(context: Context?, listData: ArrayList<ItemDetails>?, activityref: MainActivity?) : super() {
this.context = context
this.listData = listData
this.activityref = activityref
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewsHolder {
val view = LayoutInflater.from(context).inflate(R.layout.row_list, parent, false)
return ViewsHolder(view)
}
override fun getItemCount(): Int {
return listData!!.size
}
override fun onBindViewHolder(holder: ViewsHolder?, position: Int) {
holder?.item=listData?.get(position)
holder!!.first!!.setText(holder.item?.First)
holder.second!!.setText(holder.item?.Second)
holder.third!!.setText(holder.item?.Third)
Step 2 OnClick on item.....................
holder.third!!.setOnClickListener{
activityref?.OnItemClicked(holder.item!!)
}
}
class ViewsHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
var item:ItemDetails?=null
var first: TextView? = null;
var second: TextView? = null;
var third: TextView? = null;
init {
first = itemView?.findViewById(R.id.first)
second = itemView?.findViewById(R.id.second)
third = itemView?.findViewById(R.id.third)
}
}
}

Categories

Resources