How to dispose a view holder linked to certain card in RecyclerView? - android

I am working on an android project in which I want to remove the item from vertical recycler view by swiping an item to right or left. As far as it comes to deleting the item, I am able to do that correctly. The issue arises after I delete the item and call notifyDataSetChanged().
Now while refreshing items, Adapter uses previous view holders to display cards. But while deleting an item I displaced some of its layouts to left and right. So when new item occupies the same view holder all the translation is preserved and
some views of the new item are thus created out of bounds of the screen(as I displaced that view holder while deleting the previous item that occupied that spot).
So my question is (solution to any one of the following will solve the issue I am facing),
Is there "free" function like c++ in Java? So that I can free that view holder.
How to make sure that recycler view doesn't reuse particular view holder?
How to reset layout of to the original state before all animations were done? So that I can just reset all translations
Thank you.
Edits:
Below are the codes for adapter and view holder if someone wants to have a look at them.
As view holder is too large to understand directly, SO here is summery for functions used in it:
init -> just set on click listener.
updateUI -> sets some more listener and set text and other fields from card.
pressDown -> is called when MotionEvent.ACTION_DOWN.
pressUP -> is called when MotionEvent.ACTION_UP.
animateViewDisappear -> listener for animation.
archive -> delete an element and tell adapter data is change.
pressMove -> sense when motion happens after clicking button.
toggleShowHideView -> change visibility of some view.
closeOpenedLayout -> Close some expanded layouts.
Adapter:
class LocalAdapter(var localCardsInfo : ArrayList<LocalModel>?,var fragment: LocalListingFragment) : RecyclerView.Adapter<LocalHolder>() {
fun refreshDataOnOrientationChange(mLocalCards : ArrayList<LocalModel>?){
if (localCardsInfo!!.size>0)
localCardsInfo!!.clear()
localCardsInfo = mLocalCards
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return localCardsInfo!!.size
}
override fun onBindViewHolder(holder: LocalHolder, position: Int) {
if (localCardsInfo!=null)
holder.updateUI(position,localCardsInfo!![position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocalHolder {
val card_a : View = LayoutInflater.from(parent.context).inflate(R.layout.card_local_a,parent,false)
return LocalHolder(card_a,fragment)
}
}
view holder:
class LocalHolder(itemView : View,val fragment: LocalListingFragment) : RecyclerView.ViewHolder(itemView),OpenedLayoutManagerLocal{
private val TAG = "iotcontrollerapp.#Debug"
private var _xDelta: Float = 0f
private var originalDelta : Float = 0f
private var directionOut1 = false
private var directionOut = false
private var previousX = 0f
var isFavourite : Boolean = false
var isPropertyPanelOpen : Boolean = false
val deviceName : TextView = itemView.findViewById(R.id.deviceNameLocal)
val deviceRoom : TextView = itemView.findViewById(R.id.deviceRoomLocal)
val deviceOnOff : TextView = itemView.findViewById(R.id.deviceOnOffLocal)
val showHideLayout : LinearLayout = itemView.findViewById(R.id.showHideLayoutLocal)
val showHideProperties : TextView = itemView.findViewById(R.id.showHidePropertiesLocal)
val showHideButton : ImageView = itemView.findViewById(R.id.showHideButtonLocal)
val favouriteButton : ImageButton = itemView.findViewById(R.id.imageFavouriteButtonLocal)
val moveButton : MoveableViewImageButton = itemView.findViewById(R.id.imageMoveButtonLocal)
val changeFragmentToDetail : Button = itemView.findViewById(R.id.changePropertiesLocal)
val layoutForProperties : LinearLayout = itemView.findViewById(R.id.layoutForPropertyLocal)
val baseForProperties : LinearLayout = itemView.findViewById(R.id.baseForPropertyLocal)
private var model : LocalModel? = null
init {
itemView.elevation = 0f
showHideLayout.setOnClickListener({
isPropertyPanelOpen = !isPropertyPanelOpen
if (isPropertyPanelOpen){
if (openedLayoutManagerLocal != this)
openedLayoutManagerLocal?.closeOpenedLayout()
openedLayoutManagerLocal = this
showHideProperties.text = fragment.getString(R.string.close_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_local)
}
else{
showHideProperties.text = fragment.getString(R.string.open_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_reverse_local)
}
val mDrawable = showHideButton.drawable
if (mDrawable is Animatable)
{
(mDrawable as Animatable).start()
}
toggleShowHideView()
})
}
fun changeFavouriteButtonState(localModel: LocalModel){
isFavourite = !isFavourite
if (isFavourite){
favouriteButton.setImageResource(R.drawable.avd_heart_fill)
val a = favouriteButton.drawable
if(a is Animatable){
a.start()
}
ALL_STATIC_CONSTANTS_AND_METHODS.saveIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName,true)
}
else{
favouriteButton.setImageResource(R.drawable.avd_heart_break)
val a = favouriteButton.drawable
if(a is Animatable){
a.start()
}
ALL_STATIC_CONSTANTS_AND_METHODS.saveIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName,false)
}
}
fun updateUI(position : Int , localModel: LocalModel){
itemView.elevateLayoutLocal.visibility = View.VISIBLE
itemView.archiveLayout.visibility = View.VISIBLE
model = localModel
originalDelta = itemView.elevateLayoutLocal.x
changeFragmentToDetail.setOnClickListener({
fragment.handlerForDetail.sendEmptyMessage(position)
})
favouriteButton.setOnClickListener({
changeFavouriteButtonState(localModel)
})
moveButton.setOnTouchListener({v: View?, event: MotionEvent? ->
if (v == null || event == null) return#setOnTouchListener true
when (event.action){
MotionEvent.ACTION_UP -> {
v.performClick()
pressUP(v,event)
}
MotionEvent.ACTION_DOWN -> {
pressDown(v,event)
}
MotionEvent.ACTION_MOVE -> {
pressMove(v,event)
}
else -> {
Log.e(TAG,"Something happened")
}
}
return#setOnTouchListener true
})
isFavourite = (ALL_STATIC_CONSTANTS_AND_METHODS.getIsFavourite(fragment.activity as Context,localModel.roomName.trim() + "$" + localModel.deviceName) ?: false)
favouriteButton.setImageResource(if (isFavourite) R.drawable.vd_trimclip_heart_full else R.drawable.vd_trimclip_heart_empty)
deviceRoom.text = localModel.roomName.split("_").dropLast(1).toTypedArray().joinToString(" ")
deviceName.text = localModel.deviceName.split("_").dropLast(1).toTypedArray().joinToString(" ")
deviceOnOff.text = if (localModel.isON) fragment.getString(R.string.device_on_off_on) else fragment.getString(R.string.device_on_off_off)
val components = localModel.componentName.split(" ")
val value = localModel.value.split(" ")
val maxValue = localModel.maxValue.split(" ")
val minValue = localModel.minValue.split(" ")
if (layoutForProperties.childCount > 0)
layoutForProperties.removeAllViews()
if (components.size == value.size && minValue.size == maxValue.size && components.size == minValue.size){
for (i in 0 until components.size){
val layout : LinearLayout = LinearLayout.inflate(fragment.activity , R.layout.card_local_b , null) as LinearLayout
layout.findViewById<TextView>(R.id.propertyLocal).text = components[i].replace("_"," ")
layout.findViewById<TextView>(R.id.valueLocal).text = value[i]
layout.findViewById<TextView>(R.id.rangeLocal).text = minValue[i] + " to " + maxValue[i]
layoutForProperties.addView(layout)
}
}
}
private fun pressDown(imageButton: View, event: MotionEvent){
fragment.mLayoutManager?.setScrollEnabled(false)
openedLayoutManagerLocal?.closeOpenedLayout()
_xDelta = itemView.elevateLayoutLocal.x - event.rawX
}
private fun pressUP(imageButton: View, event: MotionEvent){
Log.e(TAG,"itemView.elevateLayoutLocal.x :: ${(itemView.elevateLayoutLocal.width / 3.toFloat()) - itemView.elevateLayoutLocal.x}")
val status = (itemView.elevateLayoutLocal.width / 3.toFloat()) < itemView.elevateLayoutLocal.x
val status1 = (itemView.elevateLayoutLocal.width / 2.toFloat()) < itemView.elevateLayoutLocal.x
itemView.elevateLayoutLocal.animate()
.x(if ((directionOut && status) || (status1 && directionOut1)) itemView.elevateLayoutLocal.width.toFloat() else originalDelta)
.setDuration(100)
.setListener(object : Animator.AnimatorListener{
override fun onAnimationCancel(animation: Animator?) {
if ((directionOut && status) || (status1 && directionOut1)) {
itemView.elevateLayoutLocal.visibility = View.GONE
val anim = ValueAnimator.ofInt(itemView.archiveLayout.measuredHeight, 0)
anim.addUpdateListener { valueAnimator ->
val `val` = valueAnimator.animatedValue as Int
val layoutParams = itemView.archiveLayout.layoutParams
layoutParams.height = `val`
itemView.archiveLayout.layoutParams = layoutParams
}
anim.addListener(animateViewDisappear(this#LocalHolder))
anim.duration = 1000
anim.start()
}else{
fragment.mLayoutManager?.setScrollEnabled(true)
}
}
override fun onAnimationEnd(animation: Animator?) {
if ((directionOut && status) || (status1 && directionOut1)) {
itemView.elevateLayoutLocal.visibility = View.GONE
val anim = ValueAnimator.ofInt(itemView.archiveLayout.measuredHeight, 0)
anim.addUpdateListener { valueAnimator ->
val `val` = valueAnimator.animatedValue as Int
val layoutParams = itemView.archiveLayout.layoutParams
layoutParams.height = `val`
itemView.archiveLayout.layoutParams = layoutParams
}
anim.addListener(animateViewDisappear(this#LocalHolder))
anim.duration = 1000
anim.start()
}else{
fragment.mLayoutManager?.setScrollEnabled(true)
}
}
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
}).start()
}
class animateViewDisappear(var i : LocalHolder) :Animator.AnimatorListener {
override fun onAnimationCancel(animation: Animator?) {
i.itemView.archiveLayout.visibility = View.GONE
i.itemView.elevateLayoutLocal.visibility = View.GONE
i.itemView.elevateLayoutLocal.x = i.originalDelta
val layoutParams = i.itemView.archiveLayout.layoutParams
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
i.itemView.archiveLayout.layoutParams = layoutParams
i.archived()
}
override fun onAnimationEnd(animation: Animator?) {
i.itemView.archiveLayout.visibility = View.GONE
i.itemView.elevateLayoutLocal.visibility = View.GONE
i.itemView.elevateLayoutLocal.x = i.originalDelta
val layoutParams = i.itemView.archiveLayout.layoutParams
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
i.itemView.archiveLayout.layoutParams = layoutParams
i.archived()
}
override fun onAnimationRepeat(animation: Animator?) {
}
override fun onAnimationStart(animation: Animator?) {
}
}
private fun archived(){
fragment.mLayoutManager?.setScrollEnabled(true)
if (model != null) {
ALL_STATIC_CONSTANTS_AND_METHODS.addToArchive(fragment.activity!!, model!!.roomName, model!!.deviceName)
fragment.mAdapter?.refreshDataOnOrientationChange(LocalDataService.ourInstance.getNonArchivedItems(fragment.activity!!))
}
}
private fun pressMove(imageButton: View, event: MotionEvent){
directionOut1 = itemView.elevateLayoutLocal.x >= (previousX)
directionOut = itemView.elevateLayoutLocal.x >= (previousX + 20)
previousX = itemView.elevateLayoutLocal.x
fragment.mLayoutManager?.setScrollEnabled(false)
itemView.elevateLayoutLocal.animate()
.x(event.rawX + _xDelta)
.setDuration(0)
.start()
}
fun toggleShowHideView(){
changeFragmentToDetail.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
layoutForProperties.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
baseForProperties.visibility = if (isPropertyPanelOpen) View.VISIBLE else View.GONE
}
override fun closeOpenedLayout() {
if (isPropertyPanelOpen){
isPropertyPanelOpen = !isPropertyPanelOpen
showHideProperties.text = fragment.getString(R.string.open_property_panel)
showHideButton.setImageResource(R.drawable.animated_more_button_reverse_local)
val mDrawable = showHideButton.drawable
if (mDrawable is Animatable)
{
(mDrawable as Animatable).start()
}
toggleShowHideView()
}
}
companion object {
var openedLayoutManagerLocal : OpenedLayoutManagerLocal? = null
}
}

Related

expand all items in a expandable recyclerview using single button press

this is the code for expanding/collapsing an item. how can I Expand all items in a recycler view with a single button click?
private fun expandParentRow(position: Int){ \\to expand an item
val currentBoardingRow = list[position]
val services = currentBoardingRow.gameDetes
currentBoardingRow.isExpanded = true
var nextPosition = position
if(currentBoardingRow.type == Constants.PARENT) {
services?.forEach { service ->
val parentModel = IndividualReport()
parentModel.type = Constants.CHILD
val subList: ArrayList<GameDete> = ArrayList()
subList.add(service)
parentModel.gameDetes = subList
list.add(++nextPosition, parentModel)
}
notifyDataSetChanged()
}
}
private fun collapseParentRow(position: Int){ \\ to collapse an item
val currentBoardingRow = list[position]
val services = currentBoardingRow.gameDetes
list[position].isExpanded = false
if(list[position].type==Constants.PARENT){
services?.forEach { _ ->
list.removeAt(position + 1)
}
notifyDataSetChanged()
}
}
Just simply do a forEach or a for loop to iterate all items of the recycle view, set the .isExpanded to true and finally call notifyDataSetChanged().
For example:
fun expandAllItems() {
val currentList = list.map { it.copy() }
currentList.forEachIndexed { index, item ->
item.isExpanded = true
val services = item.gameDetes
if(item.type == Constants.PARENT) {
services?.forEach { service ->
val parentModel = IndividualReport()
parentModel.type = Constants.CHILD
val subList: ArrayList<GameDete> = ArrayList()
subList.add(service)
parentModel.gameDetes = subList
list.add(++index, parentModel)
}
}
}
notifyDataSetChanged()
}
This should do the job.
you can expand with animation (new animate() api) with this code block:
private fun RecyclerView.showAnimation(
duration: Long,
startFrom: Int = 0,
itemsDelay: Long = 50,
fromAlpha: Float = 1.0f,
toAlpha: Float = 1.0f,
fromScaleY: Float = 1.0f,
toScaleY: Float = 1.0f,
onAnimationStart: () -> Unit = {},
onAnimationEnd: () -> Unit = {}
) {
viewTreeObserver.addOnPreDrawListener(
object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
viewTreeObserver.removeOnPreDrawListener(this)
for (i in startFrom until childCount) {
val v: View = getChildAt(i)
v.alpha = fromAlpha
v.scaleY = fromScaleY
v.pivotY = 0f
v.animate().apply {
scaleY(toScaleY)
alpha(toAlpha)
setDuration(duration)
startDelay = (i * itemsDelay)
withStartAction(onAnimationStart)
withEndAction(onAnimationEnd)
start()
}
}
return true
}
})
}

How to deal with recyclerview showing empty row

I am building a movies app that uses "The Movie DB" API.
My app currently displaying a recyclerview filled with movies.
The problem is that some movies don't have the values I need to display the movie and it looks like this:
https://ibb.co/5FBCP5v
My question is how can I deal with it the correct way ?
Should I use recyclerview with 2 view types ?
Is there any way I can prevent the row to inflate empty data in the first place ?
This is the recyclerView Adapter:
class MainRecyclerViewAdapter(var moviesList: List<Movie>,var listener : MainRecyclerViewAdapter.MovieListener) :
RecyclerView.Adapter<MainRecyclerViewAdapter.MovieHolder>() {
inner class MovieHolder(var itemView : View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
fun bindViews(movie : Movie){
val imageBaseUrl = "https://image.tmdb.org/t/p/w185"
val movieTitle = itemView.findViewById<TextView>(R.id.movieTitle)
val movieYear = itemView.findViewById<TextView>(R.id.movieYear)
val movieImage = itemView.findViewById<ImageView>(R.id.movieImage)
//Rating Bar
val ratingBar = itemView.findViewById<RatingBar>(R.id.ratingBar)
val ratingInNumbers = itemView.findViewById<TextView>(R.id.ratingInNumbers)
val peopleRated = itemView.findViewById<TextView>(R.id.peopleRated)
itemView.setOnClickListener(this)
if (movie.movieName != "null" && movie.movieVoteCount != "null" && movie.movieVoteCount != "null") {
// Movie Name
movieTitle.text = movie.movieName
// Movie Year
val inputFormat = SimpleDateFormat("yyyy", Locale.getDefault())
if (movie.movieReleasedDate != null && movie.movieReleasedDate.isNotEmpty()){
val date = inputFormat.parse(movie.movieReleasedDate)
val movieYearString: String = inputFormat.format(date?.time)
movieYear.text = movieYearString
}
// Image
Glide.with(itemView.context).load(imageBaseUrl + movie.moviePoster).into(movieImage)
Log.e("ImageuRL", "URL: " + movie.moviePoster)
// Rating Bar
if (movie.movieVotesAverge != null){
val ratingDivider = movie.movieVotesAverge.toDouble() / 2
ratingBar.rating = ratingDivider.toFloat()
ratingInNumbers.text = ratingDivider.toString()
}
// Rating
if (movie.movieVoteCount != null){
movie.movieVoteCount = when {
abs(movie.movieVoteCount.toDouble() / 1000000) > 1 -> {
(movie.movieVoteCount.toDouble() / 1000000).toString().toString() + "m"
}
abs(movie.movieVoteCount.toDouble() / 1000) > 1 -> {
(movie.movieVoteCount.toDouble() / 1000).toString().toString() + "k"
}
else -> {
movie.movieVoteCount.toInt().toString()
}
}
}
val ratedPeopleString = "(${movie.movieVoteCount})"
peopleRated.text = ratedPeopleString
}
}
override fun onClick(view: View?) {
val position = adapterPosition
listener.onMovieClickedListener(moviesList[position])
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieHolder {
val rootView = LayoutInflater.from(parent.context).inflate(R.layout.recycler_view_row_change,parent,false)
return MovieHolder(rootView)
}
override fun onBindViewHolder(holder: MovieHolder, position: Int) {
holder.bindViews(moviesList[position])
}
override fun getItemCount(): Int {
return moviesList.size
}
fun changeCurrentList(newMovieList : List<Movie>) {
this.moviesList = newMovieList
notifyDataSetChanged()
}
interface MovieListener{
fun onMovieClickedListener(movie:Movie)
}
}
Thank you!
Filter your items before passing it to recycler view in your activity or your fragment like below
moviesList.filter{ movie ->
movie != null &&
!movie.movieName.isNullOrEmpty() &&
!movie.movieVoteCount.isNullOrEmpty() &&
!movie.movieVoteCount.isNullOrEmpty() &&
!movie.moviePoster.isNullOrEmpty()
}

Retaining expandable recyclerview's layout while retrieving and updating data from API

I currently have a fully functioning expandable RecyclerView (with two distinct views: one for the header and the other for the contents of each header). The contents of each card under a header is retrieved from an API call to my back end.
Today, I managed to implement swipe to refresh functionality but the issue is that when I do the refresh, the groups all collapse. From a UI/UX perspective, this isn't very appealing and adds extra steps for the user. As such, what I'm looking for is a way to perform the swipe to refresh without causing the groups to collapse (i.e. the contents of the card get updated but the cards remain at the same position as when the refresh is called). I've looked at using onSaveInstanceState and onRestoreInstanceState as detailed in this answer here and here but to no avail.
My code for the adapter is as follows:
class BusStopsServicesArrivalRVAdapter :
RecyclerView.Adapter<RecyclerView.ViewHolder>(), BusStopNoSectionHeaderViewHolder.HeaderViewHolderCallback {
private val SERVICE_TYPE = 1
private val STOP_TYPE = 2
private var busStopServiceList: ArrayList<BusStopService>? = null
private var busStopList: ArrayList<String>? = null
private var busArrivalViewTypes: SparseArray<BusArrivalViewType>? = null
private var headerExpandTracker: SparseIntArray? = null
lateinit var context: Context
var nineOneCount = 0
var threeOneCount = 0
var threeNineCount = 0
var fiveNineCount = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view: View
when (viewType) {
SERVICE_TYPE -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.card_bus_stop_service, parent, false)
return BusStopServiceHolder(view)
}
STOP_TYPE -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.card_bus_stop_section, parent, false)
return BusStopNoSectionHeaderViewHolder(view, this)
}
else -> {
view = LayoutInflater.from(parent.context)
.inflate(R.layout.card_bus_stop_service, parent, false)
return BusStopServiceHolder(view)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemViewType = getItemViewType(position)
val viewType = busArrivalViewTypes!!.get(position)
if (itemViewType == SERVICE_TYPE) {
bindBusStopServiceViewHolder(holder, viewType)
} else {
bindHeaderViewHolder(holder, position, viewType)
}
}
private fun bindHeaderViewHolder(holder: RecyclerView.ViewHolder, position: Int, busArrivalViewType: BusArrivalViewType) {
val dataIndex = busArrivalViewType.dataIndex
val headerViewHolder = holder as BusStopNoSectionHeaderViewHolder
when(busStopList!![dataIndex]) {
"D" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_D)
}
"C" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_C)
}
"B" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_B)
}
"A" -> {
headerViewHolder.busStopNoTextView.text =
context.resources.getString(R.string.bus_stop_services_description_A)
}
}
// headerViewHolder.busStopNoTextView.text = busStopList!![dataIndex]
if (isExpanded(position)) {
headerViewHolder.busStopNoTextView
.setCompoundDrawablesWithIntrinsicBounds(null, null, headerViewHolder.arrowUp, null)
} else {
headerViewHolder.busStopNoTextView
.setCompoundDrawablesWithIntrinsicBounds(null, null, headerViewHolder.arrowDown, null)
}
}
private fun bindBusStopServiceViewHolder(holder: RecyclerView.ViewHolder, busArrivalViewType: BusArrivalViewType) {
val dataIndex = busArrivalViewType.dataIndex
(holder as BusStopServiceHolder).bindData(busStopServiceList!![dataIndex], context)
}
override fun getItemCount(): Int {
var count = 0
if (busStopList != null && busStopServiceList != null) {
busArrivalViewTypes!!.clear()
var collapsedCount = 0
for (i in busStopList!!.indices) {
busArrivalViewTypes!!.put(count, BusArrivalViewType(i, STOP_TYPE))
count += 1
val userType = busStopList!![i]
val childCount = getChildCount(userType)
if (headerExpandTracker!!.get(i) != 0) {
// Expanded State
for (j in 0 until childCount) {
busArrivalViewTypes!!.put(count, BusArrivalViewType(count - (i + 1) + collapsedCount, SERVICE_TYPE))
count += 1
}
} else {
// Collapsed
collapsedCount += childCount
}
}
}
return count
}
override fun getItemViewType(position: Int): Int {
return if (busArrivalViewTypes!!.get(position).type === STOP_TYPE) {
STOP_TYPE
} else {
SERVICE_TYPE
}
}
private fun getChildCount(type: String): Int {
when (type) {
"D" -> return nineOneCount
"C" -> return threeOneCount
"B" -> return threeNineCount
"A" -> return fiveNineCount
else -> return 0
}
}
fun setUserListAndType(busStopServiceList: ArrayList<BusStopService>?, busStopNoList: ArrayList<String>?, c: Context) {
if (busStopServiceList != null && busStopNoList != null) {
this.busStopServiceList = busStopServiceList
this.busStopList = busStopNoList
this.context = c
busArrivalViewTypes = SparseArray<BusArrivalViewType>(busStopServiceList.size + busStopNoList.size)
headerExpandTracker = SparseIntArray(busStopNoList.size)
notifyDataSetChanged()
for (i in busStopServiceList.indices) {
when(busStopServiceList[i].busStopCode) {
"D" -> {
nineOneCount += 1
}
"C" -> {
threeOneCount += 1
}
"B" -> {
threeNineCount += 1
}
"A" -> {
fiveNineCount += 1
}
}
}
}
}
override fun onHeaderClick(position: Int) {
val viewType = busArrivalViewTypes!!.get(position)
val dataIndex = viewType.dataIndex
val userType = busStopList!![dataIndex]
val childCount = getChildCount(userType)
if (headerExpandTracker!!.get(dataIndex) == 0) {
headerExpandTracker!!.put(dataIndex, 1)
notifyItemRangeInserted(position + 1, childCount)
} else {
headerExpandTracker!!.put(dataIndex, 0)
notifyItemRangeRemoved(position + 1, childCount)
}
}
override fun isExpanded(position: Int): Boolean {
val dataIndex = busArrivalViewTypes!!.get(position).dataIndex
return headerExpandTracker!!.get(dataIndex) == 1
}
}

Nested recycler view's unable to show two independent lists

I have a vertical chat recycler-view i am trying to show multiple horizontal recycler-view's in which contain a "carousel" of card views. I am able to inflate the horizontal views but when i try to add two of the same kind of horizontal view as independent items in the parent view i either get the new data added to the original and then displayed twice or if i clear the old array the new data replaces the old data in the recycler-view and a second one is not added.
I am unsure if this is due to me not generating individual adapters per horizontal view or some array issue.
My adapter generation, called in the onBindViewHolder of the parent recycler-view:
fun generateAdapter(holder: ViewHolder): CarouselAdapter{
val layoutManager = LinearLayoutManager(appContext, LinearLayoutManager.HORIZONTAL, false )
holder.carouselView.layoutManager = layoutManager
val adapterCarousel = CarouselAdapter()
holder.carouselView.adapter = adapterCarousel
snapHelper.attachToRecyclerView(holder.carouselView)
holder.carouselView.addItemDecoration(ItemOffsetDecoration(12))
holder.carouselView.isNestedScrollingEnabled = false
layoutManager.isSmoothScrollbarEnabled = true
layoutManager.stackFromEnd = true
return adapterCarousel
}
Calling of data onto child recycler-view:
"rates" -> {
if (items[i].ratesData != null) {
val adapter = generateAdapter(holder)
for (j in 0 until (items[i].ratesData!!.size)) {
adapter.addCarousel(ChatNew(items[i].msgTime, null, null, null, items[i].ratesData!![j], null, null, null, null))
}
}
//ParseHelper.clearArrays()
}
Child adapter:
class CarouselAdapter : RecyclerView.Adapter<ViewHolderCarousel>() {
private val RATES = 1
private val IMG_BUTTON = 2
private val IMG_NO_BUTTON = 3
private val BLUE = 4
private val NO_IMG_BUTTON = 5
private val ERROR = 0
lateinit var type : String
var items: ArrayList<ChatNew> = ArrayList()
fun addCarousel(carousel: ChatNew){
items.add(carousel)
notifyItemInserted(itemCount)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolderCarousel {
return when(viewType) {
RATES -> RatesViewHolderNew(LayoutInflater.from(appContext).inflate(R.layout.carousel_rate, parent, false))
IMG_BUTTON -> ImgButtonViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_img_btn, parent, false))
IMG_NO_BUTTON -> ImgNoButtonViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_img_no_btn, parent, false))
NO_IMG_BUTTON-> NoImgButtonViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_no_img_btn, parent, false))
else -> BlueViewHolder(LayoutInflater.from(appContext).inflate(R.layout.carousel_noimg_nobtn, parent, false))
}
}
override fun getItemCount(): Int {
return items.size
}
override fun onBindViewHolder(holder: ViewHolderCarousel, i: Int) {
type = findType(holder)
when (type){
"rates" -> {
val ri = items[i]
//for (i in 0 until (ri.ratesData!!.size)){
holder.pnView.text = ri.ratesData?.pn
holder.mpView.text =ri.ratesData?.mp
holder.itView.text = ri.ratesData?.it
holder.brView.text = ri.ratesData?.br
holder.pfView.text = ri.ratesData?.pf
holder.aprcView.text = ri.ratesData?.aprc
holder.tpView.text = ri.ratesData?.tp
holder.headDivider!!.setBackgroundColor(ContextCompat.getColor(appContext!!, R.color.NWBlue))
// }
}
"carousel button" -> {
val ci = items[i]
holder.imgView.setImageResource(ci.carouselData?.img!!)
holder.number.text = ci.carouselData?.number
holder.title.text = ci.carouselData?.title
holder.content.text = ci.carouselData?.content
holder.magicString.text = ci.carouselData?.magicString
}
"img no button" -> {
val ci = items[i]
holder.imgViewNoBtn.setImageResource(ci.carouselData?.img!!)
holder.numberNoBtn.text = ci.carouselData?.number
holder.titleNoBtn.text = ci.carouselData?.title
holder.contentNoBtn.text = ci.carouselData?.content
holder.noButtonString.text = ci.carouselData?.noButtonString
}
"no img no button" -> {
val ci = items[i]
holder.imgViewNoImgNoBtn.setBackgroundColor(ContextCompat.getColor(appContext!!,R.color.NWBlue))
holder.numberNoImgNoBtn.text = ci.carouselData?.number
holder.titleNoImgNoBtn.text = ci.carouselData?.title
holder.contentNoImgNoBtn.text = ci.carouselData?.content
}
"no img button" -> {
val ci = items[i]
holder.imgViewNoImg.setBackgroundColor(ContextCompat.getColor(appContext!!,R.color.NWBlue))
holder.numberNoImg.text = ci.carouselData?.number
holder.titleNoImg.text = ci.carouselData?.title
holder.contentNoImg.text = ci.carouselData?.content
holder.magicStringNoImg.text = ci.carouselData?.magicString
}
}
}
override fun getItemViewType(i: Int): Int {
return when (items.isNotEmpty()) {
true -> {
when {
items[i].ratesData != null -> RATES
items[i].carouselData != null -> {
when (items[i].carouselData?.img != null) {
true -> {
return if (items[i].carouselData?.magicString != null) {
//has image and button
IMG_BUTTON
} else {
//has image but no button
IMG_NO_BUTTON
}
}
false -> {
return if (items[i].carouselData?.magicString != null) {
//has no image but button
NO_IMG_BUTTON
} else {
//has no image and no button
BLUE
}
}
}
}
else -> ERROR
}
}
false -> ERROR
}
}
fun findType(holder: ViewHolderCarousel): String {
return when (holder) {
is RatesViewHolderNew -> "rates"
is ImgButtonViewHolder -> "carousel button"
is ImgNoButtonViewHolder -> "img no button"
is NoImgButtonViewHolder -> "no img button"
is BlueViewHolder -> "no img no button"
else -> "error"
}
}
}
open class ViewHolderCarousel(view: View) : RecyclerView.ViewHolder(view) {
// looks at the view and finds components on that view
// carousel for rates
val headDivider: LinearLayout? = view.head_divider
val pnView = view.pn_view
val mpView = view.mp_view
val itView = view.it_view
val brView = view.br_view
val pfView = view.pf_view
val aprcView = view.aprc_view
val tpView = view.tp_view
//TODO: set up links for "r" sections
//carousel with img and button
val imgView = view.imageView_btn
val number = view.number_btn
val title = view.title_btn
val content = view.content_btn
val magicString = view.carousel_button_btn
//carousel with img but no button
val imgViewNoBtn = view.imageView_no_btn
val numberNoBtn = view.number_no_btn
val titleNoBtn = view.title_no_btn
val contentNoBtn = view.content_no_btn
val noButtonString = view.magic_string_no_btn
//carousel with no img or button
val imgViewNoImgNoBtn = view.imageView_no_img_no_btn
val numberNoImgNoBtn = view.number_no_img_no_btn
val titleNoImgNoBtn = view.title_no_img_no_btn
val contentNoImgNoBtn = view.content_no_img_no_btn
val imgViewNoImg = view.imageView_no_img
val numberNoImg = view.number_no_img
val titleNoImg = view.title_no_img
val contentNoImg = view.content_no_img
val magicStringNoImg = view.carousel_button_no_img
}
class RatesViewHolderNew(itemView: View) : ViewHolderCarousel(itemView)
class ImgButtonViewHolder(itemView: View) : ViewHolderCarousel(itemView)
class ImgNoButtonViewHolder(itemView: View) : ViewHolderCarousel(itemView)
class NoImgButtonViewHolder(itemView: View) : ViewHolderCarousel(itemView)
class BlueViewHolder(itemView: View) : ViewHolderCarousel(itemView)
Any ideas on how i could ensure that each time i add a new set of carousel data it gets added as an independent item in the parent view would be appreciated.
It turns out that you can fix this by having each addition to the parent adapter/view as an async task. Am unsure why this works but am not complaining.
doAsync {
adapter.addChatMessage(Chat(time, null, null, null, contentRates, null, null, null, null))
}

android viewmodel observer inside RecyclerView.Adapter.onBindViewHolder() update every item

I need to download images from AWS S3 and update ImageViews inside a recycler view. I am using ViewModel for downloading files from S3. A file object is observing inside onBindViewHolder() method and once receive the file my intention was to update the image belongs with that particular holder.
But the problem is if there were two rows, then two images need to download. So each view holder will attach with the observer. After downloading the first file, both the image views inside the recycler view become updated with the first image and after downloading the second image, both images updated with the second image. I am a little bit confuse about working. Please help me with this.
I cannot use Glide for this. Because S3 have authentication. So, I need to use S3 library for downloading files.
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
val postUrl = listData[position].postUrl
val createdAt = listData[position].createdAt
val postImage = listData[position].postImage
val postVideo = listData[position].postVideo
val postVideoThumbnail = listData[position].postVideoThumbnail
val groupId = listData[position].groupId
val postStatus = listData[position].postStatus
val postId = listData[position].postId
val userId = listData[position].userId
val postHeading = listData[position].postHeading
val postDescription = listData[position].postDescription
val updatedAt = listData[position].updatedAt
val profileName = listData[position].userName
val profileImage = listData[position].profileImage
val likeCount = listData[position].likeCount
val commentCount = listData[position].commentCount
var key = ""
if(!profileImage.isNullOrEmpty()){
Glide.with(activity).load(profileImage) to holder.imgProfile
}
holder.textName.text = profileName.substring(0, 1).toUpperCase() + profileName.substring(1).toLowerCase()
holder.textPostedDateTime.text = SimpleDateFormat(POST_LIST_DATE_FORMAT).format(Date(createdAt))
holder.textPostHeading.text = postHeading
if(postDescription.isNullOrEmpty() || postDescription == "null"){
holder.textPostDescription.visibility = View.GONE
} else{
holder.textPostDescription.visibility = View.VISIBLE
holder.textPostDescription.text = postDescription
}
if(postUrl.isNullOrEmpty() || postUrl == "null"){
holder.textPostLink.visibility = View.GONE
} else{
holder.textPostLink.visibility = View.VISIBLE
holder.textPostLink.text = postUrl
}
if(postVideoThumbnail.isNullOrEmpty() || postVideoThumbnail == "null"){
holder.imgPostVideoPreview.visibility = View.GONE
} else{
holder.imgPostVideoPreview.visibility = View.VISIBLE
loadImageToFile(holder.imgPostVideoPreview, postVideoThumbnail, position)
key = postVideoThumbnail
}
if(postImage.isNullOrEmpty() || postImage == "null"){
holder.imgPostImagePreview.visibility = View.GONE
} else{
holder.imgPostImagePreview.visibility = View.VISIBLE
loadImageToFile(holder.imgPostImagePreview, postImage, position)
key = postImage
}
holder.textLikeCount.text = likeCount.toString()
holder.textCommentCount.text = commentCount.toString()
if(!isSelfPosts){
holder.layoutCommentLikeShare.visibility = View.VISIBLE
holder.imgAddComment.setOnClickListener { }
holder.imgAddLike.setOnClickListener { }
holder.imgShare.setOnClickListener { }
}
holder.itemView.setOnClickListener {
moveTOPostDetails(postId, listData[position], Environment.getExternalStorageDirectory().path + "/" + EXTERNAL_STORAGE_FOLDER_NAME + "/" + key.substring(key.lastIndexOf("/")+1))
}
}
class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imgProfile: ImageView = itemView.imgProfile
val textName: TextView = itemView.textName
val textPostedDateTime: TextView = itemView.textPostedDateTime
val textPostHeading: TextView = itemView.textPostHeading
val textPostDescription: TextView = itemView.textPostDescription
val textPostLink: TextView = itemView.textPostLink
val imgPostVideoPreview: ImageView = itemView.imgPostVideoPreview
val imgPostImagePreview: ImageView = itemView.imgPostImagePreview
val textCommentCount: TextView = itemView.textCommentCount
val textLikeCount: TextView = itemView.textLikeCount
val layoutCommentLikeShare: LinearLayout = itemView.layoutCommentLikeShare
val imgAddComment: ImageView = itemView.imgAddComment
val imgAddLike: ImageView = itemView.imgAddLike
val imgShare: ImageView = itemView.imgShare
}
private fun moveTOPostDetails(postId: String, fetchPostsResponseModel: FetchPostsResponseModel, imageFileName: String){
val intent = Intent(activity, PostDetails::class.java)
intent.putExtra("POST_DETAILS", fetchPostsResponseModel)
intent.putExtra("IS_SELF_POST", isSelfPosts)
intent.putExtra("IMAGE_FILE_NAME", imageFileName)
activity.startActivity(intent)
}
private fun loadImageToFile(imageView: ImageView, key: String, position: Int){
var postListAdapterViewModel: PostListAdapterViewModel = ViewModelProviders.of(fragment).get(PostListAdapterViewModel::class.java)
postListAdapterViewModel.init(activity)
postListAdapterViewModel.getMediaFile()?.observe(fragment, Observer<File>{ file ->
var a=position
imageView.setImageURI(Uri.fromFile(file))
} )
postListAdapterViewModel.performDownload(key)
}
This is my viewmodel
class PostListAdapterViewModel(application: Application):AndroidViewModel(application){
private val file = MutableLiveData<File>()
private lateinit var context: Context
fun init(context: Context) {
this.context = context
AWSMobileClient.getInstance().initialize(context).execute()
}
fun performDownload(key : String){
val credentials = BasicAWSCredentials(AMAZON_S3_ACCESS_KEY, AMAZON_S3_SECRET_KEY)
val s3Client = AmazonS3Client(credentials)
val transferUtility = TransferUtility.builder()
.context(context)
.awsConfiguration(AWSMobileClient.getInstance().configuration)
.s3Client(s3Client)
.build()
val downloadObserver = transferUtility.download (
key,
File(Environment.getExternalStorageDirectory().path + "/" + EXTERNAL_STORAGE_FOLDER_NAME + "/" + key.substring(key.lastIndexOf("/")+1)))
// Attach a listener to get state updates
downloadObserver.setTransferListener(object : TransferListener {
override fun onStateChanged(id: Int, state: TransferState) {
if (state == TransferState.COMPLETED) {
// Handle a completed upload.
file.value = File(Environment.getExternalStorageDirectory().path + "/" + EXTERNAL_STORAGE_FOLDER_NAME + "/" + key.substring(key.lastIndexOf("/")+1))
}
}
override fun onProgressChanged(id: Int, current: Long, total: Long) {
try {
val done = (((current.toDouble() / total) * 100.0).toInt()) //as Int
Log.d("PostListAdapterVM", "DOWNLOAD - - ID: $id, percent done = $done")
}
catch (e: Exception) {
Log.e("PostListAdapterVM", "Trouble calculating progress percent", e)
}
}
override fun onError(id: Int, ex: Exception) {
Log.d("PostListAdapterVM", "DOWNLOAD ERROR - - ID: $id - - EX: ${ex.message.toString()}")
}
})
// If you prefer to poll for the data, instead of attaching a
// listener, check for the state and progress in the observer.
if (downloadObserver.state == TransferState.COMPLETED) {
// Handle a completed upload.
}
Log.d("PostListAdapterVM", "Bytes Transferrred: ${downloadObserver.bytesTransferred}")
}
fun getMediaFile(): MutableLiveData<File> {
return file
}
}

Categories

Resources