In my application i want use multiple CountDownerTimer for show offer times into RecyclerView.
For write this application i used Kotlin language.
I write below codes, but when scrolling on recyclerView's items start again this timer!
My mean is, timer started from 4:19sec, when scrolling on items and after 10sec show me 4:19 again instead of 4:09!
Activity codes:
class MainActivity : AppCompatActivity() {
private lateinit var apisList: ApisList
private lateinit var retrofit: Retrofit
private lateinit var todayAdapter: AuctionsTodayAdapter
private val todayModel: MutableList<Today> = mutableListOf()
private lateinit var layoutManager: RecyclerView.LayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Initialize
retrofit = ApiClient.instance
apisList = retrofit.create(ApisList::class.java)
todayAdapter = AuctionsTodayAdapter(themedContext, todayModel)
layoutManager = LinearLayoutManager(themedContext)
//RecyclerView
main_list.setHasFixedSize(true)
main_list.layoutManager = layoutManager
main_list.adapter = todayAdapter
if (isNetworkAvailable()) getData(1, 10)
}
private fun getData(page: Int, limit: Int) {
main_loader.visibility = View.VISIBLE
val call = apisList.getAuctionsToday(page, limit)
call.let {
it.enqueue(object : Callback<AuctionsTodayResponse> {
override fun onFailure(call: Call<AuctionsTodayResponse>, t: Throwable) {
main_loader.visibility = View.GONE
Log.e("auctionsTodayList", t.message)
}
override fun onResponse(call: Call<AuctionsTodayResponse>, response: Response<AuctionsTodayResponse>) {
if (response.isSuccessful) {
response.body()?.let { itBody ->
main_loader.visibility = View.GONE
if (itBody.toString().isNotEmpty()) {
todayModel.clear()
todayModel.addAll(itBody.res.today)
todayAdapter.notifyDataSetChanged()
}
}
}
}
})
}
}
}
Adapter codes:
class AuctionsTodayAdapter(val context: Context, val model: MutableList<Today>) :
RecyclerView.Adapter<AuctionsTodayAdapter.MyHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyHolder {
val view = LayoutInflater.from(context).inflate(R.layout.row_main_list, parent, false)
val holder = MyHolder(view)
//holder.setIsRecyclable(false)
return holder
}
override fun getItemCount(): Int {
return model.size
}
override fun onBindViewHolder(holder: MyHolder, position: Int) {
val modelUse = model[position]
holder.setData(modelUse)
if (holder.newCountDownTimer != null) {
holder.newCountDownTimer!!.cancel()
}
var timer = modelUse.calculateEnd
timer = timer * 1000
holder.newCountDownTimer = object : CountDownTimer(timer, 1000) {
override fun onTick(millisUntilFinished: Long) {
var seconds = (millisUntilFinished / 1000).toInt()
val hours = seconds / (60 * 60)
val tempMint = seconds - hours * 60 * 60
val minutes = tempMint / 60
seconds = tempMint - minutes * 60
holder.rowMain_timer.rowMain_timer.text =
String.format("%02d", hours) + ":" + String.format(
"%02d",
minutes
) + ":" + String.format("%02d", seconds)
}
override fun onFinish() {
holder.rowMain_timer.text = "00:00:00"
}
}.start()
}
inner class MyHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var newCountDownTimer: CountDownTimer? = null
lateinit var rowMain_timer: TextView
init {
rowMain_timer = itemView.findViewById(R.id.rowMain_timer)
}
fun setData(model: Today) {
model.image.let {
Glide.with(context)
.load(Constants.MAIN_BASE_URL + it)
.apply(RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.RESOURCE))
.into(itemView.rowMain_img)
}
model.title.let { itemView.rowMain_title.text = it }
}
}
How can I fix it?
How can I fix it?
ViewHolder is destroyed after you scroll it down or up (making not visible). You should store timer value when it is destroyed and restore value when it is visible again.
Welcome to the recyclerview architecture!
First of all you ought to understand that onBindViewHolder can be called multiple times when a view is scrolled out and in from your screen, and you are starting a timer in every call.
What you should do is to save the timer remaining millis in onTick in the viewHolder data and start a timer with this remaining time.
Update:
In your viewHolder class, use a long variable that represents how much time left for coutDown, lets call it timeLeft. In onTick method type holder.timeLeft = millisUntilFinished and in onFinish method type holder.timeLeft = -1 .
Now, when initiating a new timer, after checking the timer is not null check,
if (holder.leftTime > 0) {
holder.newCountDownTimer = object : CountDownTimer(holder.timeLeft, 1000) {
...
}
}
If the timeLeft is -1, decide what to do with the timer.
Also, you might need to change the ticking from every 1000 millis to smaller number, like 100 millis, for better accurate timing when saving holder.timeLeft.
Hope this will answer your question.
Related
My goal is to have button inside each RecyclerView item that will appear on click so Im changing the buttons visibility. On the first click it works fine but on clicking again or in any other item it crashes the app.
This is my Adapter.kt
class Adapter (private val orders:ArrayList<Order>, private val listener : OnItemClickListener) : RecyclerView.Adapter<Adapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_pedido,parent,false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = orders[position]
holder.nrPedido.text = currentItem.id.toString()
holder.distancia.text = currentItem.distancia.toString()+"km"
holder.estado.text = currentItem.estado
}
override fun getItemCount(): Int {
return orders.size
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener{
val nrPedido : TextView = itemView.findViewById(R.id.textViewIDPedido)
val distancia : TextView = itemView.findViewById(R.id.textViewDistancia)
val estado : TextView = itemView.findViewById(R.id.textViewEstado)
val buttonEstado : Button = itemView.findViewById(R.id.buttonEstado)
val buttonAceitarRecusar : Button = itemView.findViewById(R.id.buttonAceitarRecusar)
val buttonDados : Button = itemView.findViewById(R.id.buttonDados)
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val holder = ViewHolder(itemView)
val order = orders[adapterPosition]
val position = adapterPosition
if(position != RecyclerView.NO_POSITION && order != null) {
listener.onItemClick(order,position,holder)
}
}
}
interface OnItemClickListener{
fun onItemClick(order: Order,position: Int,holder: ViewHolder)
}
}
This Is my mainActivity where the onItemClick Method is being called
override fun onItemClick(order: Order,position:Int,holder: Adapter.ViewHolder) {
visible = visible?.not()
if(arrayListPedidos.contains(order)){
val clickedItem = arrayListPedidos[position]
if(visible==true){
holder.buttonDados.visibility = View.VISIBLE
}else{
holder.buttonDados.visibility = View.INVISIBLE
}
}else if(arrayListMeusPedidos.contains(order)){
val clickedItem = arrayListMeusPedidos[position]
if(visible==true){
holder.buttonDados.visibility = View.VISIBLE
}else{
holder.buttonDados.visibility = View.INVISIBLE
}
}
}
I think it could be something to do with not notifying that the item is being updated, but it could also be the way im sending the holder.
When I click one time on the item it changed the visibility to visible. If I click again (to make it invisible) or if I click in any other item it crashed the app.
Think about the ViewHolder as a highly volatile view, you don't want to keep any references about it that you use outside the Adapter – I see that you pass the ViewHolder back into the MainActivity if I understand correctly what you are trying to achieve could be done by doing something like this:
class Adapter(
private val orders: ArrayList<Order>,
// Note: Modern callbacks are declared like this, don't worry about passing the
// OnItemClickListener
private val listener: ((Order) -> Unit)
) : RecyclerView.Adapter<ViewHolder> { // Note: Try to use your own ViewHolders
// In situations like this you can use a map or some other data structure to keep track of what
// order button is supposed to be visible or not. You could also keep track of this in the Order
// class itself.
private val isVisibleMap = mutableMapOf<Int, Boolean>().apply {
orders.forEachIndexed { index, _ -> put(index, false) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_pedido,parent,false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val currentItem = orders[position]
// You must set the visibility during the binding
holder.buttonDados.visibility = if (isVisibleMap[position]) {
View.VISIBLE
} else {
View.GONE
}
holder.nrPedido.text = currentItem.id.toString()
holder.distancia.text = currentItem.distancia.toString()+"km"
holder.estado.text = currentItem.estado
holder.itemView.setOnClickListener {
// Here we update the visibility
isVisibleMap[position] = !isVisibleMap[position]
listener.invoke(currentItem)
}
}
override fun getItemCount(): Int {
return orders.size
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
val nrPedido : TextView = itemView.findViewById(R.id.textViewIDPedido)
val distancia : TextView = itemView.findViewById(R.id.textViewDistancia)
val estado : TextView = itemView.findViewById(R.id.textViewEstado)
val buttonEstado : Button = itemView.findViewById(R.id.buttonEstado)
val buttonAceitarRecusar : Button = itemView.findViewById(R.id.buttonAceitarRecusar)
val buttonDados : Button = itemView.findViewById(R.id.buttonDados)
}
}
Keep in mind that things like this, changing the visibility of an item from the adapter, should be handled within the adapter itself. Hope it helps!
Edit: If you want to change the button visibility programmatically from outside the Adapter, you can always do something like:
class Adapter ... {
fun changeButtonVisibility(isVisible: Boolean, pos: Int) {
isVisibleMap[pos] = isVisible
notifyItemChanged(pos)
}
}
There is a list of operations that I display in the recyclerview through the ListAdapter. The size of the RecyclerView is 2 elements.enter image description here
ListAdapter:
class OperationAdapter(private val onItemClicked: (Operation) -> Unit) :
ListAdapter<Operation, OperationAdapter.OperationViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OperationViewHolder {
val viewHolder = OperationViewHolder(
OperationItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
return viewHolder
}
#SuppressLint("ResourceAsColor", "SetTextI18n")
override fun onBindViewHolder(holder: OperationViewHolder, position: Int) {
holder.bind(getItem(position))
}
class OperationViewHolder(private var binding: OperationItemBinding): RecyclerView.ViewHolder(binding.root) {
/*val imageOperation:ImageView = view.findViewById(R.id.imageViewItem)
val nameOperation:TextView = view.findViewById(R.id.name_operation)
val balanceOperation:TextView = view.findViewById(R.id.textSum)*/
fun bind(operation: Operation){
if(operation.receive == ACCOUNT.number){
binding.imageViewItem.setImageResource(R.drawable.ic_type_recieve)
binding.nameOperation.text = operation.send
binding.nameOperation.setTextColor(Color.rgb(35, 135, 0))
val sum = NumberFormat.getCurrencyInstance(Locale("en", "US")).format(operation.sum)
binding.textSum.text = sum
binding.textSum.setTextColor(Color.rgb(35, 135, 0))
}else{
binding.imageViewItem.setImageResource(R.drawable.ic_type_sent)
binding.nameOperation.text = operation.receive
binding.nameOperation.setTextColor(Color.rgb(231, 223,255))
val sum = NumberFormat.getCurrencyInstance(Locale("en", "US")).format(operation.sum)
binding.textSum.text = "-$sum"
binding.textSum.setTextColor(Color.rgb(231, 223,255))
}
}
}
companion object{
private val DiffCallback = object: DiffUtil.ItemCallback<Operation>(){
override fun areItemsTheSame(oldItem: Operation, newItem: Operation): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Operation, newItem: Operation): Boolean {
return oldItem == newItem
}
}
}
}
I retrieve list from Room by courotine:
GlobalScope.launch(Dispatchers.IO){
sharedViewModel.getOperationsAll(ACCOUNT.number).collect(){ it ->
operationAdapter.submitList(it.sortedByDescending { it.time })
}
}
I cannot output recycler view with only two items, where user can scroll it.
You must not use GlobalScope for this, because it will leak your Activity and/or Fragment for the entire lifetime of your app. Every time the screen changes, it'll leak another copy of it until your app runs out of memory.
Dispatchers.IO is unnecessary. You are not calling any blocking functions in this coroutine.
To limit to two items, you can use take(2) on the list before you pass it to the adapter:
lifecycleScope.launch {
sharedViewModel.getOperationsAll(ACCOUNT.number).collect { opList ->
operationAdapter.submitList(opList.sortedByDescending { it.time }.take(2))
}
}
If this is a Fragment, you should use viewLifecycleOwner.lifecycleScope instead.
I'm uploading some photos to my server, and want to view the progress of individual uploads. However, my RecyclerView isn't updating.
I know the progress is being updated, as I can see the progress in the my upload task, and the photo is uploaded to the server. I thought the viewholder would be what I would use to update the view, but my progress listener isn't' called.
Does anyone see what I have done wrong? Or could do better?
The PhotoUploadItem is the object that gets updated with the progress.
class PhotoUploadItem(var photoStore: PhotoStore) {
interface PhotoUploadProgressListener {
fun onProgressChanged(progress: Int)
}
private var uploadProgress = 0
private var listener: PhotoUploadProgressListener? = null
fun setUploadProgress(uploadProgress: Int) {
this.uploadProgress = uploadProgress
listener?.onProgressChanged(uploadProgress)
}
fun subscribe(listener: PhotoUploadProgressListener) {
this.listener = listener
}
fun unsubscribe() {
this.listener = null
}
fun progress() = uploadProgress
fun identifier(): Int = photoStore.key
}
The PhotoUploadsAdapter takes a list of them.
class PhotoUploadsAdapter : RecyclerView.Adapter<PhotoUploadsAdapter.CustomViewHolder>() {
inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val differCallback = object : DiffUtil.ItemCallback<PhotoUploadItem>() {
override fun areItemsTheSame(oldItem: PhotoUploadItem, newItem: PhotoUploadItem) = oldItem.identifier() == newItem.identifier()
override fun areContentsTheSame(oldItem: PhotoUploadItem, newItem: PhotoUploadItem) = oldItem.hashCode() == newItem.hashCode()
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
return CustomViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.row_photo_upload, parent, false))
}
override fun getItemCount() = differ.currentList.size
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val item = differ.currentList[position]
holder.itemView.apply {
///..... set other fields
tvDisplayName?.text = item.photoStore.displayName
// THIS DOESN'T WORK
item.subscribe(object : PhotoUploadItem.PhotoUploadProgressListener{
override fun onProgressChanged(progress: Int) {
Log.i("UploadViewHolder", "${item.identifier()} has progress: $progress")
progressBar?.progress = progress
tvProgressAmount?.text = String.format(Locale.ROOT, "%d%%", progress)
}
})
}
}
}
I want the next item in the RecyclerView can be clicked. For example, if item 1 has score > 10, then the item 2 can be clicked, but the item 3,4,5,6... cannot be clicked. If item 2 has scrore > 10,then the item 3 can be clicked,but the item 4,5,6,7... cannot be clicked. If item 3 has score > 10, then the item 4 can be clicked, but the item 5,6,7,8... cannot be clicked.
class RecyclerView
class MathCategoryDetailActivity : AppCompatActivity() {
private var progressBar: ProgressBar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_math_category_detail)
progressBar = findViewById(R.id.progressBar)
recycler_math_category_detail.layoutManager = GridLayoutManager(this, 2)
recycler_math_category_detail.setHasFixedSize(true)
val adapter = MathCategoryDetailAdapter(
this,
DBHelperOther.getInstance(this).allCategoryDetail(Common.selectedCategory!!.id)
)
Common.selectedCategory!!.id
recycler_math_category_detail.addItemDecoration(
SplacesItemDecoration(
4
)
)
recycler_math_category_detail.adapter = adapter
}
}
Adapter of RecyclerView
class MathCategorySpecifyDetailAdapter(
internal var context: Context,
internal var categoryDetailList: List<MathCategorySpecifyDetail>) :
RecyclerView.Adapter<MathCategorySpecifyDetailAdapter.MyViewHolder1>() {
private var dialog: Dialog? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder1 {
val itemView =
LayoutInflater.from(context).inflate(R.layout.activity_math_category_specify_detail_adapter, parent, false)
return MyViewHolder1(itemView)
}
override fun getItemCount(): Int {
return categoryDetailList.size
}
override fun onBindViewHolder(holder: MyViewHolder1, position: Int) {
holder.card_category_detail_2.isEnabled = false
holder.txt_category_specify_detail_2_name.isEnabled = false
//if(the id =1 , item can be clicked)
if(categoryDetailList[position].id == 1) {
holder.card_category_detail_2.isEnabled = true
holder.txt_category_specify_detail_2_name.isEnabled = true
if(categoryDetailList[position].highScore> 10) {
//I added code here, but it doens't work
categoryDetailList[position].id++
holder.card_category_detail_2.isEnabled = true
}
}
holder.txt_category_specify_detail_2_name.text = categoryDetailList[position].name
holder.score.text = categoryDetailList[position].highScore.toString()
dialog = Dialog(context)
dialog!!.setContentView(R.layout.activity_lesson)
holder.card_category_detail_2.setOnClickListener {
var txtLesson: TextView = dialog!!.findViewById(R.id.txtLesson)
txtLesson.text = categoryDetailList[position].lesson
var txtDescribe: TextView = dialog!!.findViewById(R.id.txtDescribe)
txtDescribe.text = categoryDetailList[position].nameOfTypeDetail
var txtType: TextView = dialog!!.findViewById(R.id.txtType)
txtType.text = categoryDetailList[position].mathCategoryDetail
var btnPlay: Button =dialog!!.findViewById(R.id.btnPlay)
btnPlay.setOnClickListener{
Common2.selectedCategoryId = categoryDetailList[position]
val intent = Intent(context, QuestionActivity::class.java)
context.startActivity(intent)
}
var window : Window? = dialog!!.window
window!!.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)
dialog!!.show()
}
}
inner class MyViewHolder1(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener {
internal val txt_category_specify_detail_2_name: TextView
internal val score: TextView
internal val card_category_detail_2: CardView
internal lateinit var iOnRecyclerViewItemClickListener2: IOnRecyclerViewItemClickListener2
fun setiOnRecyclerViewItemClickListener2(iOnRecyclerViewItemClickListener2: IOnRecyclerViewItemClickListener2) {
this.iOnRecyclerViewItemClickListener2 = iOnRecyclerViewItemClickListener2
}
init {
txt_category_specify_detail_2_name =
itemView.findViewById(R.id.txt_category_specify_detail_2_name) as TextView
score = itemView.findViewById(R.id.score) as TextView
card_category_detail_2 = itemView.findViewById(R.id.card_category_detail_2) as CardView
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
iOnRecyclerViewItemClickListener2.onClick(view, adapterPosition)
}
}
}
MathCategorySpecifyDetail
class MathCategorySpecifyDetail(
var id: Int,
var idMathCategoryDetail: Int,
var name: String,
var lesson: String,
var nameOfTypeDetail: String,
var mathCategoryDetail: String,
var highScore: Int
)
Common1
object Common1 {
var selectedCategoryIdDetail: MathCategoryDetail? = null
}
I have tried in many ways but it doesn't work. Please help me
Try creating a method inside the adapter that will enable/disable the items.
For example:
fun toggleEnabled(enabled: Boolean){
//enable or disable the items according to the score
}
Use RxJava or LiveData for observing the score. It will check and trigger the toggleEnabled() every time when the score is changed.
p.s. You can get items by position (f.e. if the score is between 10 and 20 you will get the item with index of 1 from your adapter and call toggleEnabled() for that item.)
Blockquote
i am trying to achieve auto scrolling infinite recyclerview like hotstar have it in hotstar VIP subscription page.
i have tried given code.
For auto scrolling recycler:-
binding.rvPartyOfWeek.addOnScrollListener(CustomScrollListener())
private val SCROLLING_RUNNABLE = object : Runnable {
override fun run() {
val duration = 10
val pixelsToMove = 22
if (!isPartyOfWeekScrolling) {
binding.rvPartyOfWeek.smoothScrollBy(pixelsToMove, 0)
}
mHandler.postDelayed(this, duration.toLong())
}
}
For infinite scroll:-
binding.rvPartyOfWeek.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView2: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView2, dx, dy)
val totalItemCount = layoutManagerparty.itemCount
val lastVisibleItem = layoutManagerparty.findLastVisibleItemPosition()
if (totalItemCount <= (lastVisibleItem + 3)) {
if (totalItemCount > 22) {
for (i in 0..10) {
listParty.removeAt(0)
}
}
listParty.addAll(listPartySingle)
adapterpartyofweek.notifyDataSetChanged()
Log.i("Helllo listParty", listParty.size.toString())
}
}
})
It is not scrolling smoothly in some device and crashing in some old devices.
I did as follows:
Creating auto scroll for RecyclerView
`
private fun autoScroll() {
scrollCount = 0;
var speedScroll: Long = 1200;
val runnable = object : Runnable {
override fun run() {
if (layoutManager.findFirstVisibleItemPosition() >= imageArrayList.size / 2) {
adapter.load()
}
recyclerView.smoothScrollToPosition(scrollCount++)
Log.e(TAG, "run: $scrollCount")
handler.postDelayed(this, speedScroll)
}
}
handler.postDelayed(runnable, speedScroll)
}
`
Create Smooth scroll automatically
`
layoutManager = object : LinearLayoutManager(this#MainActivity) {
override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State?, position: Int) {
val smoothScroller = object : LinearSmoothScroller(this#MainActivity) {
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
return 5.0f;
}
}
smoothScroller.targetPosition = position
startSmoothScroll(smoothScroller)
}
}
`
For source Code, check out the GitHub project link
https://github.com/Mahesh2318/AutoScrollRecyclerView