Recyclerview faces massive scroll lag when loading large number of items - java

In app i'm having a activity which hosts a collapsing toolbar layout and a recycler view i got working properly however here issue is when i try to scroll my recyclerview it produces massive lags which in turn results in UI/UX shuttering here i'm trying to load large number of images in recycler view say 600 to 800 images are all loaded asynchronously with glide and couroutines inspite of this if i remove the images loading call of glide still recyclerview causes same lags like before.
I'm really unable to interpret the root cause this problem! Any help will be appreciated!
Here is my Adapter Class:
class MainFolderImagesAdapter :
RecyclerView.Adapter<RecyclerView.ViewHolder> {
private var thumbPaths = ArrayList<String>()
private val tempThumbPath = ArrayList<String>()
private var context: Activity? = null
private var albumName: String? = null
private val al_selected_photo = ArrayList<Int>()
private var mItemCLickAlbumImageListener: ItemCLickAlbumImageListener? = null
private var mDatabaseHelper: DatabaseHelper? = null
private var mBean: Bean? = null
constructor(thumbPaths: ArrayList<String>, albumName: String, databaseHelper: DatabaseHelper, applicationContext: Activity, bean: Bean) {
this.mBean = bean
this.context = applicationContext
this.albumName = albumName
this.mDatabaseHelper = databaseHelper
Share.selected_image_no.clear()
this.thumbPaths = thumbPaths
}
protected inner class LoadingVH(itemView: View) : RecyclerView.ViewHolder(itemView) {
val mProgressBar: ProgressBar
init {
mProgressBar = itemView.findViewById(R.id.pbar)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecyclerView.ViewHolder {
var viewHolder1: RecyclerView.ViewHolder? = null
val inflater = LayoutInflater.from(viewGroup.context)
when (i) {
ITEM -> {
val view1 = LayoutInflater.from(viewGroup.context).inflate(R.layout.item_show_image, viewGroup, false)
viewHolder1 = ViewHolder(view1)
return ViewHolder(view1)
}
LOADING -> {
val viewLoading = inflater.inflate(R.layout.custom_loading_list_item, viewGroup, false)
viewHolder1 = LoadingVH(viewLoading)
}
}
// View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.item_show_image, viewGroup, false);
return viewHolder1!!
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any>) {
super.onBindViewHolder(holder, position, payloads)
when (getItemViewType(position)) {
ITEM -> {
val movieVH = holder as ViewHolder
if (payloads.contains("maulik")) {
movieVH.progress.visibility = View.GONE
movieVH.iv_complete.visibility = View.VISIBLE
} else if (payloads.contains("123")) {
movieVH.iv_background.visibility = View.GONE
movieVH.iv_selected.visibility = View.GONE
}
}
LOADING -> {
val loadingVH = holder as LoadingVH
loadingVH.mProgressBar.visibility = View.VISIBLE
}
}
}
override fun onBindViewHolder(holder1: RecyclerView.ViewHolder, i: Int) {
//holder1.setIsRecyclable(false)
when (getItemViewType(i)) {
ITEM -> {
val holder = holder1 as ViewHolder
var uniqueID: Int?=null
if (Share.checkUserType(context)) {
GlobalScope.async(Dispatchers.Default) {
uniqueID = mDatabaseHelper!!.getImagesUniqueID(albumName,
getOriginalFilename(thumbPaths[i]), DatabaseHelper.IMAGES_TABLE)
}
} else {
GlobalScope.async(Dispatchers.Default) {
uniqueID = mDatabaseHelper!!.getImagesUniqueIDGuest(albumName,
getOriginalFilename(thumbPaths[i]), DatabaseHelper.IMAGES_TABLE)
}
}
if (Share.checkUserType(context)) {
// if (SharedPrefs.getBoolean(context, SharedPrefs.AutoUploadEnable)) {
if (uniqueID!! > 0) {
holder.progress.visibility = View.GONE
holder.iv_complete.visibility = View.VISIBLE
} else {
holder.progress.visibility = View.VISIBLE
holder.iv_complete.visibility = View.GONE
}
} else {
holder.progress.visibility = View.GONE
holder.iv_complete.visibility = View.GONE
}
if (!SharedPrefs.getBoolean(context, SharedPrefs.AutoUploadEnable)) {
holder.progress.visibility = View.GONE
}
if (al_selected_photo.size > 0) {
if (al_selected_photo.contains(uniqueID)) {
holder.iv_selected.visibility = View.VISIBLE
holder.iv_background.visibility = View.VISIBLE
} else {
holder.iv_selected.visibility = View.GONE
holder.iv_background.visibility = View.GONE
}
} else {
holder.iv_selected.visibility = View.GONE
holder.iv_background.visibility = View.GONE
}
try {
GlobalScope.async(Dispatchers.Default) {
var arr:ByteArray?=null
val op = GlobalScope.async(Dispatchers.Default){
arr = ImageEncryptDecrypt.decrypt(context, thumbPaths[i])
}
op.await()
withContext(Dispatchers.Main) {
Glide.with(context)
.load(arr)
.asBitmap()
.diskCacheStrategy(DiskCacheStrategy.ALL)
.thumbnail(0.9f)
.skipMemoryCache(false)
.centerCrop()
.placeholder(R.drawable.dummy_header_bg)
.listener(object : RequestListener<ByteArray, Bitmap> {
override fun onException(e: Exception, model: ByteArray, target: Target<Bitmap>, isFirstResource: Boolean): Boolean {
return false
}
override fun onResourceReady(resource: Bitmap, model: ByteArray, target: Target<Bitmap>, isFromMemoryCache: Boolean, isFirstResource: Boolean): Boolean {
Log.e("Digan--->>", "onResourceReady: " + "redyyyyy")
if (MainFolderImagesActivity.mDownloadImageDialog != null && MainFolderImagesActivity.mDownloadImageDialog!!.isShowing) {
MainFolderImagesActivity.mDownloadImageDialog!!.dismiss()
MainFolderImagesActivity.mDownloadImageDialog = null
}
Glide.get(context).clearMemory()
return false
}
})
.into(holder.all_image_iv_view)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
holder.itemView.setOnClickListener(object : OnSingleClickListener() {
override fun onSingleClick(v: View) {
if (Share.downloadComplete) {
if (uniqueID!! > 0) {
if (al_selected_photo.size > 0) {
if (!al_selected_photo.contains(uniqueID!!)) {
if (al_selected_photo.size < 20) {
al_selected_photo.add(uniqueID!!)
Share.selected_image_no.add(uniqueID)
holder.iv_background.visibility = View.VISIBLE
holder.iv_selected.visibility = View.VISIBLE
if (mItemCLickAlbumImageListener != null) {
if (al_selected_photo.size == 0) {
mItemCLickAlbumImageListener!!.itemClicked(i, al_selected_photo.size, holder.iv_selected)
}
MainFolderImagesActivity.tv_count.text = Share.selected_image_no.size.toString() + " Selected"
}
} else {
Toast.makeText(context!!.applicationContext, "you can select maximum 20 images", Toast.LENGTH_SHORT).show()
}
} else {
val pos = al_selected_photo.indexOf(uniqueID!!)
al_selected_photo.removeAt(pos)
Share.selected_image_no.removeAt(pos)
holder.iv_background.visibility = View.GONE
holder.iv_selected.visibility = View.GONE
if (mItemCLickAlbumImageListener != null) {
if (al_selected_photo.size == 0) {
mItemCLickAlbumImageListener!!.itemClicked(i, al_selected_photo.size, holder.iv_selected)
}
MainFolderImagesActivity.tv_count.text = Share.selected_image_no.size.toString() + " Selected"
}
}
} else {
mBean!!.thumbPaths = thumbPaths
val intent = Intent(context, ViewImageActivity::class.java)
intent.putExtra("pos", holder.adapterPosition)
intent.putExtra("fromWhere", "albumImages")
context!!.startActivity(intent)
}
}
}
}
})
holder.itemView.setOnLongClickListener { v ->
if (Share.downloadComplete) {
//int uniqueID1 = mDatabaseHelper.getImagesUniqueID(albumName, getOriginalFilename(thumbPaths.get(i)), DatabaseHelper.IMAGES_TABLE);
var uniqueID1: Int?=null
if (Share.checkUserType(context)) {
GlobalScope.async(Dispatchers.Default) {
uniqueID1 = mDatabaseHelper!!.getImagesUniqueID(albumName,
getOriginalFilename(thumbPaths[i]), DatabaseHelper.IMAGES_TABLE)
}
} else {
GlobalScope.async(Dispatchers.Default) {
uniqueID1 = mDatabaseHelper!!.getImagesUniqueIDGuest(albumName,
getOriginalFilename(thumbPaths[i]), DatabaseHelper.IMAGES_TABLE)
}
System.gc()
Runtime.getRuntime().gc()
}
if (uniqueID!! > 0) {
if (al_selected_photo.size == 0) {
if (!al_selected_photo.contains(uniqueID1)) {
if (al_selected_photo.size < 20) {
Share.selected_image_no.add(uniqueID1)
al_selected_photo.add(uniqueID1!!)
holder.iv_selected.visibility = View.VISIBLE
holder.iv_background.visibility = View.VISIBLE
if (mItemCLickAlbumImageListener != null) {
mItemCLickAlbumImageListener!!.itemClicked(i, al_selected_photo.size, holder.iv_selected)
}
MainFolderImagesActivity.tv_count.text = Share.selected_image_no.size.toString() + " Selected"
} else {
Toast.makeText(context!!.applicationContext, "you can select maximum 20 images", Toast.LENGTH_SHORT).show()
}
} else {
val pos = al_selected_photo.indexOf(uniqueID1)
Share.selected_image_no.removeAt(pos)
al_selected_photo.removeAt(pos)
holder.iv_selected.visibility = View.GONE
holder.iv_background.visibility = View.GONE
if (mItemCLickAlbumImageListener != null) {
mItemCLickAlbumImageListener!!.itemClicked(i, al_selected_photo.size, holder.iv_selected)
}
MainFolderImagesActivity.tv_count.text = Share.selected_image_no.size.toString() + " Selected"
}
}
}
}
true
}
}
LOADING -> {
val loadingVH = holder1 as LoadingVH
loadingVH.mProgressBar.visibility = View.GONE
}
}
}
override fun getItemViewType(position: Int): Int {
return if (isLoadingAdded) {
if (isLoadingAdded) ITEM else LOADING
} else {
if (position == thumbPaths.size - 1) LOADING else ITEM
}
}
private fun getOriginalFilename(path: String?): String {
return (if (path != null) {
File(path).name
} else path)!!
}
fun setItemClickListener(itemClickListener: ItemCLickAlbumImageListener) {
this.mItemCLickAlbumImageListener = itemClickListener
}
override fun getItemCount(): Int {
Log.e("getItemCount", "getItemCount: " + thumbPaths.size)
return thumbPaths.size
// /*return thumbPaths.size();*/
}
fun unSelectAll() {
for (i in al_selected_photo.indices) {
GlobalScope.async(Dispatchers.Default) {
val path = mDatabaseHelper!!.getImagesStringFromUniqID(al_selected_photo[i], null, DatabaseHelper.IMAGES_TABLE)
notifyItemChanged(thumbPaths.indexOf(path), "123")
}
}
Share.selected_image_no.clear()
al_selected_photo.clear()
}
fun removeItems(selectedItemsThumbs: ArrayList<String>) {
for (i in selectedItemsThumbs.indices) {
thumbPaths.remove(selectedItemsThumbs[i])
notifyItemRemoved(thumbPaths.indexOf(selectedItemsThumbs[i]))
notifyItemRangeChanged(thumbPaths.indexOf(selectedItemsThumbs[i]), itemCount)
}
(context as MainFolderImagesActivity).changeCount(thumbPaths.size)
al_selected_photo.clear()
notifyDataSetChanged()
}
fun updateResult(imageItems: ArrayList<String>) {
thumbPaths.addAll(imageItems)
notifyItemRangeInserted(0, itemCount)
(context as MainFolderImagesActivity).changeCount(thumbPaths.size)
}
fun updateResult1(imageItems: ArrayList<String>) {
thumbPaths.addAll(imageItems)
notifyItemRangeInserted(itemCount, thumbPaths.size)
}
internal inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var all_image_iv_view: ImageView
var iv_selected: ImageView
var iv_background: ImageView
var iv_complete: ImageView
var progress: ProgressBar
init {
all_image_iv_view = itemView.findViewById(R.id.all_image_iv_view)
iv_background = itemView.findViewById(R.id.iv_background)
iv_selected = itemView.findViewById(R.id.iv_selected)
progress = itemView.findViewById(R.id.progress)
iv_complete = itemView.findViewById(R.id.iv_complete)
val displayMetrics = DisplayMetrics()
context!!.windowManager.defaultDisplay.getMetrics(displayMetrics)
val width = displayMetrics.widthPixels
all_image_iv_view.requestLayout()
all_image_iv_view.layoutParams.height = width / 3
all_image_iv_view.layoutParams.width = width / 3
iv_background.setBackgroundColor(mBean!!.themeColor)
}
}
fun add(response: String) {
if (!thumbPaths.contains(response)) {
thumbPaths.add(0, response)
notifyItemInserted(0)
notifyItemRangeChanged(0, itemCount)
(context as MainFolderImagesActivity).rcv_images.scrollToPosition(0)
(context as MainFolderImagesActivity).changeCount(thumbPaths.size)
} else {
Logs.e("add", "add: ")
notifyItemChanged(thumbPaths.indexOf(response), "maulik")
}
}
fun setValue(thumbPath: String, path: String) {
if (thumbPaths.contains(thumbPath)) {
Logs.e("setValue", "setValue: " + thumbPaths.indexOf(thumbPath))
thumbPaths[thumbPaths.indexOf(thumbPath)] = path
notifyItemChanged(thumbPaths.indexOf(path), "maulik")
}
}
fun addAll(postItems: List<String>) {
for (response in postItems) {
add(response)
}
}
companion object {
private var isLoadingAdded = false
// View Types
private val ITEM = 0
private val LOADING = 1
}
}
Note : I've already added these flags in recyclerview
rcv_images.setHasFixedSize(true)
rcv_images.isNestedScrollingEnabled = false
rcv_images.setItemViewCacheSize(20)
Here's the link to xml code:
https://pastebin.com/F9wrrYpA

Related

The RecyclerView scrolls to downward when a new message is added

I have a chat app. I used RecyclerView and I set stackFromEnd true and reverseLayout false to LinearlayoutManager. when a new message is added at the bottom I mean at the end of the list the recyclerView starts auto scroll downward instead of upward. the adapter is notified by notifyItemInserted().
Expected Behaviour: When a new message is added to the list it should scroll to the bottom or upward. Any help is appreciated. Thanks,
Here is adapter:
class ChatMessagesListAdapter(
var chatMessages: ChatMessagesDataModel
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
// we set the header as 0 so we can add more types to the ConversationItem enum
private const val ITEM_TYPE_HEADER = 0
}
override fun getItemViewType(position: Int): Int {
return when {
chatMessages.hasMoreItems -> chatMessages.messages[position].getItemType()
position == 0 -> ITEM_TYPE_HEADER
else -> chatMessages.messages[position - 1].getItemType()
}
}
// return +1 to draw the header if there are no items to load
override fun getItemCount() = chatMessages.messages.size + getContentIndex()
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
ITEM_TYPE_HEADER -> {
MessagesHeaderViewHolder(inflater.inflate(R.layout.item_chat_messages_header,
parent, false))
}
ChatItemDataModel.TYPE_ADDED_USER,
ChatItemDataModel.TYPE_VIDEO_NOTIFICATION -> {
MessagesInfoViewHolder(inflater.inflate(R.layout.item_chat_messages_info,
parent, false))
}
else -> {
MessagesMessageViewHolder(inflater.inflate(R.layout.item_chat_messages_message,
parent, false))
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
(holder as? MessagesHeaderViewHolder)?.bind()
(holder as? MessagesMessageViewHolder)?.bind(position, chatMessages.messages[getRealPosition(position)])
(holder as? MessagesInfoViewHolder)?.bind(chatMessages.messages[getRealPosition(position)])
}
private fun getContentIndex() = if (chatMessages.hasMoreItems) {
0
} else {
1
}
private fun getRealPosition(position: Int) = if (chatMessages.hasMoreItems) {
position
} else {
position - 1
}
private fun notifyChanges() {
if (chatMessages.numberOfItemsInserted == chatMessages.messages.size || isPreview ||
chatMessages.hadPendingMessages) {
notifyDataSetChanged()
} else {
// +1 because of the header
notifyItemRangeInserted(chatMessages.insertionIndex + getContentIndex(),
chatMessages.numberOfItemsInserted)
}
}
fun updateConversation(newChatMessages: ChatMessagesDataModel) {
chatMessages = newChatMessages
notifyChanges()
}
fun updateMessage(newMessage: ChatItemDataModel, isRemote: Boolean) {
if (newMessage.message.hashIdentifier.isNullOrBlank()) {
addNewMessage(newMessage)
return
}
val messageIndex = chatMessages.messages.indexOfFirst { it.message.hashIdentifier == newMessage.message.hashIdentifier }
if (messageIndex != -1) {
val localMessage = chatMessages.messages[messageIndex]
chatMessages.messages[messageIndex] = newMessage
if (failedMessages.contains(localMessage.message.sid)) {
if (isRemote) {
failedMessages.remove(localMessage.message.sid)
}
notifyItemChanged(messageIndex + getContentIndex())
}
}
else {
addNewMessage(newMessage)
}
}
private fun addNewMessage(newMessage: ChatItemDataModel) {
val oldCount = chatMessages.messages.size
chatMessages.messages.add(newMessage)
notifyItemInserted(oldCount + getContentIndex())
}
fun addLocalMessage(
sharedPrefsStorage: SharedPrefsStorage,
message: String, imageUri: Uri?, hashIdentifier: String
) {
val userMessage = UserMessage(messageBody = message, firstName = sharedPrefsStorage.firstName,
lastName = sharedPrefsStorage.lastName, isFromLoggedUser = true, imageUri = imageUri,
hashIdentifier = hashIdentifier, files = null, reactions = null)
val newMessage = ChatItemDataModel(userMessage, sharedPrefsStorage.profileImageUrl, sharedPrefsStorage.userId.toString())
val oldCount = chatMessages.messages.size
chatMessages.messages.add(newMessage)
notifyItemRangeInserted(oldCount + getContentIndex(), 1)
}
....
}
Here is Fragment:
class ChatRoomMessagesFragment : Fragment() {
#Inject
lateinit var sharedPrefsStorage: SharedPrefsStorage
private var adapter: ChatMessagesListAdapter? = null
......
override fun onMessagesReceived(chatMessages: ChatMessagesDataModel) {
if (adapter == null) {
adapter = ChatMessagesListAdapter(chatMessages)
adapter?.setHasStableIds(true)
binding.recyclerview.adapter = adapter
} else {
adapter?.updateConversation(chatMessages)
}
}
override fun onUserMessageRetrieved(newMessage: ChatItemDataModel, isRemote: Boolean) {
adapter?.updateMessage(newMessage, isRemote)
}
private fun setupUI() {
binding.apply {
recyclerview.apply {
layoutManager = LinearLayoutManager(requireContext()).apply {
reverseLayout = false
stackFromEnd = true
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
showScrollToBottomButtonIfNeeded(findFirstCompletelyVisibleItemPosition())
}
})
}
addOnScrollListener(object : PaginationScrollListener(
layoutManager as LinearLayoutManager,
ScrollDirection.BOTTOM
) {
override fun loadMoreItems() {
presenter.loadMoreMessages()
}
override fun isLastPage() = !presenter.hasMoreItems()
override fun isLoading() = presenter.isLoadingInProgress()
})
// prevent the list from scrolling when the keyboard opens
addOnLayoutChangeListener { _, _, _, _, bottom, _, _, _, oldBottom ->
if (bottom < oldBottom) {
scrollChatToBottom()
}
}
}
....
adapter?.addLocalMessage(sharedPrefsStorage, message, imageUri, hashIdentifier)
presenter.sendMessage("", message, imageUri, hashIdentifier)
}
private fun scrollChatToBottom() {
binding.apply {
recyclerview.post {
recyclerview.smoothScrollToPosition(recyclerview.adapter?.itemCount ?: 0)
}
}
}
}
Let's suppose your adapter is populated by objects called messages and you have a source of data called messageList (could be a Collection, etc) that is passed to your adapter. You could do something like this:
int position = messageList.size() > 0 ? (messageList.size()-1) : 0;
mRecyclerView.smoothScrollToPosition(position);
Just ensure your adapter is not null and actually gets data from messageList since this is in your Activity/Fragment class and not in the adapter class.
You can scrolldown progrmatically using code below
recyclerView.scrollToPosition(chatList.size() - 1);
You can also try another solution if above one doesn't works
setStackFromEnd(true) or setReverseLayout(true)
yourAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
this#EventDetails.binding.rvData.scrollToPosition(positionStart)
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
super.onItemRangeChanged(positionStart, itemCount)
this#EventDetails.binding.rvData.scrollToPosition(positionStart)
}
})
one of these method will work i think 1st one, try
and
try to avoid notifyDataSetChange() or intemChange() call by your self use DiffUtils or ListAdapter(Extension of recycler view)

List/grid switchable recyclerview doesn't work

I'm trying to create a recyclerview which can change layout (a list or a grid), for that I modified my adapter to determine the which layout it should use according to the number of spans. But when I try to modify the information from the holder nothing is happening. Does anyone have an idea?
here's my adapter:
class FileAdapter(
private val context: Context,
private val layoutManager: GridLayoutManager,
private var items:MutableList<File>,
private val isFolder:Boolean = false
) : RecyclerView.Adapter<FileAdapter.FileViewHolder>() {
companion object {
private val LIST = 1
private val GRID = 3
private val VIEW_TYPE_LIST = 1
private val VIEW_TYPE_GRID = 2
}
override fun getItemViewType(position: Int): Int {
val spanCount: Int = layoutManager.spanCount
return if (spanCount == LIST) {
VIEW_TYPE_LIST
} else {
VIEW_TYPE_GRID
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType : Int):FileViewHolder{
val view : View
if(!isFolder) {
if (viewType == VIEW_TYPE_GRID) {
view = LayoutInflater.from(parent.context).inflate(R.layout.file_element, parent, false)
} else {
view = LayoutInflater.from(parent.context).inflate(R.layout.file_list_element, parent, false)
}
} else
view = LayoutInflater.from(parent.context).inflate(R.layout.folder_element, parent, false)
return FileViewHolder(view, viewType)
}
class FileViewHolder(view : View, viewType: Int) : RecyclerView.ViewHolder(view) {
var fileName: TextView? = null
var fileImg: ImageView? = null
var fileSize: TextView? = null
init {
if (viewType == VIEW_TYPE_GRID) {
val fileName = view.findViewById<TextView>(R.id.file_name)
var fileImg = view.findViewById<ImageView>(R.id.file_img)
} else {
val fileName = view.findViewById<TextView>(R.id.file_name)
var fileImg = view.findViewById<ImageView>(R.id.file_img)
var fileSize = view.findViewById<TextView>(R.id.file_size)
}
}
}
override fun onBindViewHolder(holder: FileViewHolder, position: Int) {
val currentFile = items[position]
holder.fileName?.text = currentFile.name
if(currentFile.isDirectory)
//holder.fileImg.setImageResource(getDrawable(R.drawable.ic_folder))
else if(currentFile.extension=="pdf"){
//holder.fileImg.setImageResource(R.drawable.ic_pdf)
CoroutineScope(Dispatchers.Default).launch {
val bitmapPdf = pdfToBitmap(currentFile)
CoroutineScope(Dispatchers.Main).launch {
holder.fileImg?.setImageBitmap(bitmapPdf)
}
}
}
else if(currentFile.extension=="docx")
holder.fileImg?.setImageResource(R.drawable.ic_docx)
else if(currentFile.extension=="xlsx")
holder.fileImg?.setImageResource(R.drawable.ic_xlsx)
else
holder.fileImg?.setImageResource(R.drawable.ic_file)
}
override fun getItemCount() = items.size
}
Here's the result:

Pagination Duplicating Values in RecyclerView in MVVM

I am new in Kotlin MVVM also, I tried to achieved Pagination with legacy approach and stucked in a issue with my RecyclerView, whenever I scroll it the data duplicated, I tried DiffUtils but no help.
I Logged the data in VIEWMODEL class the data is not repeating
but, when I logged in Activity where I am observing it is showing duplicate values
SEARCHRESULTACTIVITY.KT
class SearchResultActivity : AppCompatActivity() {
private lateinit var layoutManager: LinearLayoutManager
private lateinit var recyclerView: RecyclerView
private lateinit var pullAdapter: CustomAdapter
private var pageNumber = 1
private var totalItemsCount = 0
private var firstVisibleItemsCount = 0
private var visibleItemsCount = 0
private var previousTotal = 0
private var loading = true
private var fillPullList: ArrayList<RepoPull> = ArrayList()
private var userName: String = ""
private var repoName: String = ""
private var isEnd = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initialize()
getDataPull(userName, repoName)
loadNextData()
}
private fun initialize() {
setContentView(R.layout.activity_search_result)
recyclerView = findViewById(R.id.repoRecView)
layoutManager = LinearLayoutManager(this)
getSearchQuery()
}
private fun getSearchQuery() {
userName = intent.getStringExtra("owner").toString()
repoName = intent.getStringExtra("repo").toString()
populatePullRv()
}
private fun populatePullRv() {
recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
recyclerView.layoutManager = layoutManager
pullAdapter = CustomAdapter(this, fillPullList)
recyclerView.adapter = pullAdapter
progressBar.visibility = View.VISIBLE
}
private fun loadNextData() {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val mLayoutManger = recyclerView.layoutManager as LinearLayoutManager
visibleItemsCount = mLayoutManger.childCount
totalItemsCount = mLayoutManger.itemCount
firstVisibleItemsCount = mLayoutManger.findFirstVisibleItemPosition()
if (loading) {
if (totalItemsCount > previousTotal) {
previousTotal = totalItemsCount
pageNumber++
loading = false
progressBar.visibility = View.GONE
}
}
if (!loading && (firstVisibleItemsCount + visibleItemsCount) >= totalItemsCount) {
getDataPull(userName, repoName)
loading = true
Log.d("PAGE", pageNumber.toString())
}
}
})
}
private fun getDataPull(username: String?, reponame: String?) {
val myViewModel = ViewModelProviders.of(this).get(PullVM::class.java)
myViewModel.endofList.observe(this, {
if (it == true) {
isEnd = true
progressBar.visibility = View.GONE
Toast.makeText(this#SearchResultActivity, "All PR Fetched", Toast.LENGTH_SHORT)
.show()
}
})
myViewModel.status.observe(this, {
if (it == false) {
showError(getString(R.string.no_net))
}
})
myViewModel.getPullDataFromVM().observe(this, {
if (it != null) {
listRepos(it) **//DUPLICATE VALUE COMING**
} else {
showError(getString(R.string.nothing_found))
}
})
myViewModel.getPullList(username.toString(), reponame.toString(), pageNumber)
}
private fun showError(s: String) {
progressBar.visibility = View.GONE
val theView =
this#SearchResultActivity.findViewById<View>(android.R.id.content)
Snackbar.make(
theView,
s,
Snackbar.LENGTH_LONG
).show()
}
#SuppressLint("NotifyDataSetChanged")
fun listRepos(repos: List<RepoPull>) {
if (!isEnd) {
progressBar.visibility = View.GONE
fillPullList.addAll(repos)
pullAdapter.notifyDataSetChanged()
}
}}
PULLVM(View Model).kt
class PullVM : ViewModel() {
var pullList: MutableLiveData<List<RepoPull>>
var status = MutableLiveData<Boolean?>()
var endofList = MutableLiveData<Boolean?>()
init {
pullList = MutableLiveData()
}
fun getPullDataFromVM(): MutableLiveData<List<RepoPull>> {
return pullList
}
fun getPullList(ownerName: String, repoName: String, pgNo: Int) {
val retriever = GitHubRetriever
val callback = object : Callback<List<RepoPull>> {
override fun onFailure(call: Call<List<RepoPull>>, t: Throwable) {
status.value = false
}
override fun onResponse(
call: Call<List<RepoPull>>,
response: Response<List<RepoPull>>
) {
if (response.body()?.size == 0) {
endofList.value = true
}
if (response.code() == 404) {
pullList.postValue(null)
} else {
status.value = true
val repos = response.body()
if (repos != null) {
pullList.postValue(repos)
}
}
}
}
retriever.userRepos(
callback,
ownerName,
repoName,
pgNo
)
}
Try moving your viewModel instantiation and observer settings to onCreate so you don't have to create a new viewModel instance and set a new observable to your LiveDatas.
Declare myViewModel as a lateinit property of your Activity and move this part to onCreate
myViewModel = ViewModelProviders.of(this).get(PullVM::class.java)
myViewModel.endofList.observe(this, {
if (it == true) {
isEnd = true
progressBar.visibility = View.GONE
Toast.makeText(this#SearchResultActivity, "All PR Fetched", Toast.LENGTH_SHORT)
.show()
}
})
myViewModel.status.observe(this, {
if (it == false) {
showError(getString(R.string.no_net))
}
})
myViewModel.getPullDataFromVM().observe(this, {
if (it != null) {
listRepos(it) **//DUPLICATE VALUE COMING**
} else {
showError(getString(R.string.nothing_found))
}
})
And
private fun getDataPull(username: String?, reponame: String?)
should only contain
myViewModel.getPullList(username.toString(), reponame.toString(), pageNumber)

cant use handler to start fragment with delay

I want to set SplashScreen(fragment) in my app and because its a online shopping application I should check for internet connectivity.
I have 4 main fragment and one main activity in this app.
I used progress bar ,textview and a refresh button in splash fragment.
lets see part of code!...
val networkConnection = NetworkConnection(requireContext())
networkConnection.observe(requireActivity()) { isConnected ->
if (isConnected) {
binding.refresh.setOnClickListener {
binding.apply {
progress.visibility = View.VISIBLE
progress.animate().start()
connectivityText.visibility = View.INVISIBLE
refresh.visibility = View.INVISIBLE
go()
}
}
} else {
binding.apply {
progress.visibility = View.GONE
connectivityText.visibility = View.VISIBLE
refresh.visibility = View.VISIBLE
}
}
}
this part is for when i am not connected to the internet and I start the app.
textview appears with this text " you are not connected to Internet " and refresh button appears to refresh the fragment after connection. It works fine.
the problem is when I am connected to Internet and start the app. Lets see the code.
val s = S(requireContext())
when (s.isOnline()) {
true -> {
binding.apply {
progress.visibility = android.view.View.VISIBLE
progress.animate().start()
connectivityText.visibility = android.view.View.INVISIBLE
refresh.visibility = android.view.View.INVISIBLE
}
}
false -> {
binding.apply {
progress.visibility = View.GONE
connectivityText.visibility = View.VISIBLE
refresh.visibility = View.VISIBLE
}
}
}
"S" is a file that has a isOnline() function. i want to use below code in "if(isOnline)"statement
Handler(Looper.getMainLooper()).postDelayed({
val action =
SplashFragmentDirections.actionSplashFragmentToHomeFragment2()
findNavController().navigate(action)
CoroutineScope(Dispatchers.Main).launch {
delay(4)
}
}, 4000)
when i set this code in "if(isonline)" statement I see this error:
failed to connect to /192.168.1.36 (port 80) from /:: (port 0) and when i delete it from there
app works fine with no error .
NetworkConnectionClass:
class NetworkConnection( private val context: Context) : LiveData<Boolean>() {
private var connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private lateinit var networkCallback: ConnectivityManager.NetworkCallback
override fun onActive() {
super.onActive()
updateConnection()
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> {
connectivityManager.registerDefaultNetworkCallback(ConnectivityManagerCallback())
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> {
lollipopNetworkRequest()
}
else -> {
context.registerReceiver(
networkReciever,
IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
)
}
}
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onInactive() {
super.onInactive()
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun lollipopNetworkRequest() {
val requestBuilder = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
connectivityManager.registerNetworkCallback(
requestBuilder.build(),
ConnectivityManagerCallback()
)
}
private fun ConnectivityManagerCallback(): ConnectivityManager.NetworkCallback {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onLost(network: Network) {
super.onLost(network)
postValue(false)
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
postValue(true)
}
}
} else {
throw IllegalAccessError("ERROR")
}
return networkCallback
}
private var networkReciever = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
updateConnection()
}
}
private fun updateConnection() {
val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
if (activeNetwork?.isConnected == true) {
postValue(true)
} else {
postValue(false)
}
}
}
It took me a week and still I have no idea . help me please
any help will be appreciated.
I changed my splash fragment to an activity and also i delete it from graph then I used Thread instead of handler like this for that part where is was connected to internet
var ms: Long = 0
val splashTime = 2000
val splashActive = true
val paused = false
//use go() method here instead of this thread.check below to see go() method
Thread {
try {
while (splashActive && ms < splashTime) {
if (!paused)
ms += 100
Thread.sleep(100)
}
} catch (e: Exception) {
e.fillInStackTrace()
} finally {
if (!s.isOnline()) {
} else {
startActivity(Intent(this, MainActivity::class.java))
}
}
}.start()
I Changed that part where i was not connected to internet like this:
val networkConnection = NetworkConnection(this)
networkConnection.observe(this) { isConnected ->
if (isConnected) {
binding.refresh.setOnClickListener {
binding.apply {
progress.visibility = View.VISIBLE
progress.animate().start()
connectivityText.visibility = View.INVISIBLE
refresh.visibility = View.INVISIBLE
}
//use go() method here instead of this thread.check below to see go() method
Thread {
Thread.sleep(2000)
startActivity(Intent(this, MainActivity::class.java))
}.start()
}
} else {
binding.apply {
progress.visibility = View.GONE
connectivityText.visibility = View.VISIBLE
refresh.visibility = View.VISIBLE
}
}
}
UPDATE
put first part code where i was connected to internet to one method then use that method anywhere you want like this:
fun go() {
val s = S(this)
Thread {
try {
while (splashActive && ms < splashTime) {
if (!paused)
ms += 100
Thread.sleep(100)
}
} catch (e: Exception) {
e.fillInStackTrace()
} finally {
if (!s.isOnline()) {
} else {
startActivity(Intent(this, MainActivity::class.java))
}
}
}.start()
}
please tell me if you have better solution

RecyclerView jump to the top after click even

I have been having this issue for a long time and have tried different suggestion on similar posts but all proved futile.
Currently, I have a recyclerview that implements double click for likes. Whenever I do that the recyclerview jumps to the top of the page.
Kindly advise on what to do to stop this.
Code
private fun setDataIntoAdapter(list: List<MovieEntity?>?) {
movieAdapter = MovieAdapter(list, object : MovieAdapter.OnMovieListener {
override fun onMovieDoubleClick(movieEntity: MovieEntity, view: View) {
val fav = view.findViewById<ImageView>(R.id.redFav)
// var favMovie = convertToFavourityEntity(movieEntity)
movieEntity.favourite = true
if (fav.visibility == View.GONE) {
fav.visibility = View.VISIBLE
CoroutineScope(Main).launch {
try {
insertAndUpdate(movieEntity)
FancyToast.makeText(
context,
"${movieEntity.title} is added to favourite",
FancyToast.LENGTH_LONG,
FancyToast.SUCCESS,
true
).show()
} catch (e: Exception) {
FancyToast.makeText(
context,
"Movie has been previously added \nto favorite",
FancyToast.LENGTH_LONG,
FancyToast.ERROR,
true
).show()
}
}
} else {
fav.visibility = View.GONE
movieEntity.favourite = false
CoroutineScope(Main).launch {
try {
deleteAndUpdate(movieEntity)
FancyToast.makeText(
context,
"${movieEntity.title} is removed from favourite",
FancyToast.LENGTH_LONG,
FancyToast.INFO,
true
).show()
} catch (e: Exception) {
// Toast.makeText(context, "Movie has been previously removed \nto favorite", Toast.LENGTH_SHORT).show()
val snackbar = Snackbar
.make(
view, "Movie has been previously removed \n" +
"to favorite", Snackbar.LENGTH_LONG
)
snackbar.show()
}
}
}
}
override fun onSingleClick(movieEntity: MovieEntity, view: View) {
gotoDetails(movieEntity)
// view.deleteIcon.setOnClickListener {
// singleDeletion(movieEntity)
// }
}
})
}
private suspend fun insertAndUpdate(movieEntity: MovieEntity) {
ServiceLocator.createLocalDataSource(context!!).movieDao?.update(movieEntity)
}
Adapter
class MovieAdapter<T>(private var movies:List<T?>?, private var listener:OnMovieListener):RecyclerView.Adapter<MovieAdapter.MovieHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.movie_recycler_items, parent, false)
return MovieAdapter.MovieHolder(itemView)
}
override fun getItemCount(): Int {
return movies!!.size
}
fun setMovie(movies: List<T?>?){
this.movies = movies
notifyDataSetChanged()
}
fun getMovieAt(position: Int):T?{
return movies?.get(position)
}
override fun onBindViewHolder(holder: MovieHolder, position: Int) {
// covert.drawCornerFlag(holder)
movies?.let{
val currentMovies = it[position]
holder.bind(it[position]!!, listener)
}
}
class MovieHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
var title = itemView.findViewById<TextView>(R.id.title)
var releaseDate = itemView.findViewById<TextView>(R.id.releaseDate)
var ratingBar = itemView.findViewById<RatingBar>(R.id.ratingBar)
var imageThmbnail = itemView.findViewById<ImageView>(R.id.thumbnail)
var fav = itemView.findViewById<ImageView>(R.id.favourite)
var redFav = itemView.findViewById<ImageView>(R.id.redFav)
var rating = itemView.findViewById<TextView>(R.id.rating)
fun <T> bind(movieEntity: T, listener: OnMovieListener){
var i = 0
if(movieEntity is MovieEntity){
if(movieEntity.favourite){
redFav.visibility = View.VISIBLE
}
else{
redFav.visibility = View.GONE
}
val calendar = Calendar.getInstance()
val dateReleased = movieEntity.releaseDate?.split("-")
val year = dateReleased?.get(0)?.toInt()
val month = dateReleased?.get(1)?.toInt()
val day = dateReleased?.get(2)?.toInt()
var newDate:Date?= null
if (year != null) {
if (month != null) {
if (day != null) {
calendar.set(year, month,day)
}
}
newDate = calendar.time
}
val displayDate = newDate.toString().substring(4..7) + year.toString()
title.setText(movieEntity.title)
releaseDate.setText(displayDate)
ratingBar.numStars = 5
val ratingNum = movieEntity.rating?.toFloat()?.div(2)
rating.setText("${ratingNum!!}")
ratingBar.rating = ratingNum
Picasso.get().load(movieEntity.movieImage).into(imageThmbnail)
itemView.setOnClickListener(DoubleClick(object :DoubleClickListener{
override fun onDoubleClick(view: View?) {
Log.i("Dob", "Double clicked")
listener.onMovieDoubleClick(movieEntity, itemView)
}
override fun onSingleClick(view: View?) {
Log.i("click", "Single click")
listener.onSingleClick(movieEntity, itemView)
}
}))
}
}
}
interface OnMovieListener{
fun onMovieDoubleClick(movieEntity: MovieEntity, view:View)
fun onSingleClick(movieEntity: MovieEntity, view: View)
}
}

Categories