I have RecyclerView, which has another items inside. Each RecyclerView item has list of other items which are shown bellow as subitems if I click item in RecyclerView. To avoid nested RecyclerView thing, I iterate over these items in onBindViewHolder() and I add them to empty LinearLayout inflating subitem layout.
OutOfMemory error occurs as I scroll down, because there can be 1000 items and each item could have 1000 subitems. In my app its list of orders and if I click item from this list, list of ordered parts are shown one by one.
How to fix this issue. Also scrolling became laggy. I'm using Glide API to cache images, but this error still occurs.
recyclerView = view.findViewById<RecyclerView>(R.id.order_recycler_view).apply {
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
//set cache for rv
setItemViewCacheSize(50)
isDrawingCacheEnabled = true
drawingCacheQuality = View.DRAWING_CACHE_QUALITY_HIGH
}
inside RVAdapter onBindViewHolder():
for(orderItem in it.itemList){
val contentLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.ordered_list_item_layout, null, false)
fillItemView(contentLayout, orderItem, res)
holder.orderContentLayout.addView(contentLayout)
}
FillItemView method:
private fun fillItemView(contentLayout: View, orderItem: OrderedItem, res: Resources){
val orderItemImage: ImageView = contentLayout.findViewById(R.id.orderPartImage)
val orderItemName: TextView = contentLayout.findViewById(R.id.orderPartName)
val orderItemWeight: TextView = contentLayout.findViewById(R.id.orderPartWeight)
val orderPartPhoto: String? = orderItem.item.itemPhoto.optString(ctx.getString(R.string.part_photo))
setOrderedPartLogo(orderItemImage, res, orderPartPhoto)
val itemPrice: String = String.format("%.2f", orderItem.item.itemPrice)
val spanText = SpannableString(foodPrice + " €" + " " + "(" + orderItem.quantity.toString() + "x" + ")" + orderItem.item.itemName)
spanText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, itemPrice.length + 2, 0)
orderItemName.text = spanText
orderItemWeight.text = orderItem.item.itemWeigth
}
private fun setOrderedPartLogo(orderImageView: ImageView, resources: Resources, imageURL: String?) {
val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.no_photo)
val roundedBitMapDrawable: RoundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap)
roundedBitMapDrawable.isCircular = true
val requestOptions: RequestOptions = RequestOptions()
.circleCrop()
.placeholder(roundedBitMapDrawable)
.error(roundedBitMapDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
Glide.with(orderImageView.context)
.load(imageURL)
.apply(requestOptions)
.into(orderImageView)
}
Whole Adapter:
class OrderListAdapter(private var mActivity: FragmentActivity,
private var orderList: ArrayList<Order>, private var fragment: OrderListFragment):
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var expandedPosition = -1
private lateinit var mRecyclerView: RecyclerView
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
//order item views
val orderName: TextView = itemView.findViewById(R.id.orderName)
val orderWaitTime: TextView = itemView.findViewById(R.id.orderWaitTime)
val orderAddress: TextView = itemView.findViewById(R.id.orderAddress)
val orderDate: TextView = itemView.findViewById(R.id.orderDate)
//details expandable layout
val orderDetailsExpandable: LinearLayout = itemView.findViewById(R.id.orderDetails)
val orderContentLayout: LinearLayout = itemView.findViewById(R.id.contentLayout)
val orderLayout: ConstraintLayout = itemView.findViewById(R.id.mainLayout)
}
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): RecyclerView.ViewHolder {
// create a new view
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.order_recyclerview_item_layout, parent, false)
return ViewHolder(itemView)
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
mRecyclerView = recyclerView
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolder) {
val res = holder.itemView.context.resources
val ctx = holder.itemView.context
orderList[position].let {
val orderPrice: Int = it.orderJSON.getInt(ctx.getString(R.string.order_total_price))
val orderSupplierName: String? = it.orderSupplier?.json?.getString(ctx.getString(R.string.sup_name))
val orderDate: String = it.orderJSON.getString(ctx.getString(R.string.order_date))
val orderPaymentType: Int = it.orderJSON.getInt(ctx.getString(R.string.order_payment_type))
var orderPaymentTypeString = "unknown"
val orderDeliveryType: Int = it.orderJSON.getInt(ctx.getString(R.string.order_delivery_type))
val orderDeliveryPrice: Int = it.orderJSON.getInt(ctx.getString(R.string.order_delivery_price))
val orderJSONObject: JSONObject = it.orderJSON
val orderItemList: ArrayList<OrderedItem> = it.partsList
//OrderDate -> hours, minutes, day, month, year
val formattedOrderDate: OrderDate = getOrderDate(orderDate)
when(orderPaymentType){
1 -> orderPaymentTypeString = "credit"
2 -> orderPaymentTypeString = "credit"
3 -> orderPaymentTypeString = "money"
4 -> orderPaymentTypeString = "voucher"
}
//set order price, name and type
val orderPriceString: String = convertCentsToFloat(orderPrice)
if (orderSupplierName == null){
val spannableText = SpannableString(orderPriceString + " € " + ctx.getString(R.string.default_sup_name) + " - " + orderPaymentTypeString)
spannableText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, orderPriceString.length + 3, 0)
holder.orderName.text = spannableText
} else {
val spannableText = SpannableString(orderPriceString + " € " + orderSupplierName + " - " + orderPaymentTypeString)
spannableText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, orderPriceString.length + 3, 0)
holder.orderName.text = spannableText
}
//set order wait time
holder.orderWaitTime.text = formattedOrderDate.dateHours + ":" + formattedOrderDate.dateMinutes
//set order address
//holder.orderAddress.text = it.orderAddress
//set order date
holder.orderDate.text = formattedOrderDate.dateDay + "." + formattedOrderDate.dateMonth + "." + formattedOrderDate.dateYear
holder.orderContentLayout.removeAllViews()
//create layout for order items
for(orderItem in it.itemList){
val contentLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.ordered_list_item_layout, null, false)
fillItemView(contentLayout, orderItem, res, ctx)
holder.orderContentLayout.addView(contentLayout)
}
//create footer delivery
val deliveryLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.order_delivery_footer_layout, null, false)
fillDeliveryFooter(deliveryLayout, orderDeliveryType, orderDeliveryPrice, res, ctx)
holder.orderContentLayout.addView(deliveryLayout)
//create footer orderRepeat Button
val orderRepeatLayout: View = LayoutInflater.from(holder.itemView.context).inflate(R.layout.order_repeat_order_layout, null, false)
holder.orderContentLayout.addView(orderRepeatLayout)
orderRepeatLayout.setOnClickListener {
fragment.switchToOrderCartActivity(orderItemList)
}
//expanding order view on click
val isExpanded = position == expandedPosition
holder.orderDetailsExpandable.visibility = if (isExpanded) View.VISIBLE else View.GONE
holder.itemView.isActivated = isExpanded
holder.orderLayout.setOnClickListener {
createLog("expPos ", position.toString())
orderList[position].let {
if(expandedPosition != position){
if(expandedPosition != -1){
val myLayout: View? = mRecyclerView.layoutManager.findViewByPosition(expandedPosition)
createLog("myLayout", myLayout.toString())
createLog("OrderExp", "Expanding layout")
if(myLayout != null){
myLayout.findViewById<LinearLayout>(R.id.orderDetails).visibility = View.GONE
}
}
createLog("expPosSet ", position.toString())
expandedPosition = position
} else {
expandedPosition = -1
}
notifyItemChanged(position)
scrollToTop(holder.itemView)
}
}
}
}
}
override fun getItemCount() = orderList.size
private fun scrollToTop(v: View) {
val itemToScroll = mRecyclerView.getChildAdapterPosition(v)
val centerOfScreen = mRecyclerView.width / 2 - v.width / 2
fragment.getRecyclerViewManager().scrollToPositionWithOffset(itemToScroll, centerOfScreen)
}
private fun fillItemView(contentLayout: View, orderItem: OrderedItem, res: Resources){
val orderItemImage: ImageView = contentLayout.findViewById(R.id.orderPartImage)
val orderItemName: TextView = contentLayout.findViewById(R.id.orderPartName)
val orderItemWeight: TextView = contentLayout.findViewById(R.id.orderPartWeight)
val orderPartPhoto: String? = orderItem.item.itemPhoto.optString(ctx.getString(R.string.part_photo))
setOrderedPartLogo(orderItemImage, res, orderPartPhoto)
val itemPrice: String = String.format("%.2f", orderItem.item.itemPrice)
val spanText = SpannableString(foodPrice + " €" + " " + "(" + orderItem.quantity.toString() + "x" + ")" + orderItem.item.itemName)
spanText.setSpan(ForegroundColorSpan(res.getColor(R.color.colorRed)), 0, itemPrice.length + 2, 0)
orderItemName.text = spanText
orderItemWeight.text = orderItem.item.itemWeigth
}
private fun fillDeliveryFooter(deliveryLayout: View, deliveryType: Int, deliveryPrice: Int, res: Resources, ctx: Context){
val deliveryImageIcon: ImageView = deliveryLayout.findViewById(R.id.deliveryIconImage)
val deliveryPriceTextView: TextView = deliveryLayout.findViewById(R.id.deliveryLabelText)
//set delivery icon
when(deliveryType){
1 -> deliveryImageIcon.setImageResource(R.drawable.ico_summary_delivery)
2 -> deliveryImageIcon.setImageResource(R.drawable.ico_summary_pickup)
else -> deliveryImageIcon.setImageResource(R.drawable.restauracia_no_photo)
}
//set delivery price, name label
val deliveryPriceString: String = convertCentsToFloat(deliveryPrice)
val deliverySpannable = SpannableString(deliveryPriceString + " € Doprava / Vyzdvihnutie")
deliverySpannable.setSpan(ForegroundColorSpan(res.getColor(R.color.colorPrice)), 0, deliveryPriceString.length + 2, 0)
deliveryPriceTextView.text = deliverySpannable
}
private fun setOrderedPartLogo(orderImageView: ImageView, resources: Resources, imageURL: String?) {
val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.no_photo)
val roundedBitMapDrawable: RoundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources, bitmap)
roundedBitMapDrawable.isCircular = true
val requestOptions: RequestOptions = RequestOptions()
.circleCrop()
.placeholder(roundedBitMapDrawable)
.error(roundedBitMapDrawable)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.priority(Priority.HIGH)
Glide.with(orderImageView.context)
.load(imageURL)
.apply(requestOptions)
.into(orderImageView)
}
private fun convertCentsToFloat(centPrice: Int): String {
val centOnlyPrice: Int = centPrice % 100
val euroPrice: Int = (centPrice - centOnlyPrice) / 100
if (centOnlyPrice < 10) {
val finalPrice: String = euroPrice.toString() + ".0" + centOnlyPrice.toString()
return finalPrice
} else {
val finalPrice: String = euroPrice.toString() + "." + centOnlyPrice.toString()
return finalPrice
}
}
private fun getOrderDate(date: String): OrderDate{
val rawDate: List<String> = date.split("T")
val dateOnly: String = rawDate[0]
val dateFormat: List<String> = dateOnly.split("-")
val timeOnly: String = rawDate[1]
val timeFormat: List<String> = timeOnly.split(":")
val finalDate = OrderDate(timeFormat[0], timeFormat[1], dateFormat[2], dateFormat[1], dateFormat[0])
return finalDate
}
fun createLog(tag: String, msg: String){
Log.i(tag, msg)
}
fun refreshOrder(orderListRefreshed: ArrayList<Order>){
orderList = orderListRefreshed
notifyDataSetChanged()
if(AndroidAssets.getInstance(mActivity).orderList.isEmpty()){
mRecyclerView.visibility = View.GONE
fragment.showFooterLayout()
} else{
mRecyclerView.visibility = View.VISIBLE
fragment.hideFooterLayout()
}
fragment.hideProgressBar()
}
}
Image from AndroidProfiler on Memory Usage(memory usage increased around 25s - this is where I've started scrolling RecyclerView... and after that it dropped).
UPDATE: With better analysis I found out that one SubItem has 2.5MB in memory. If i will have 5 orders containing 20 items each it will allocate 250MB of space in RAM. And this is with Glide caching images.
UPDATE 2: Is there any way how to load only visible views? So as user scrolls it will load new views and top one which will be off the display will be removed from memory. I thought that recyclerview is doing that by default by recycling item layout views.
UPDATE 3: I've implemented new recyclerview and adapter initialization for inner list. This rv and adapter is initialized when view in onBindViewHolder() is marked as expanded and if it's not expanded RV and Adapter is set to null. So I've implemented Nested RecyclerView. Problem is that my inner recyclerview is not scrolling at all. I have to set scrolling and set RV height to fixed size (for example 400dp), because if I leave it match_parent or wrap_content, it will throw OutOfMemoryError if more than 20 items are inside -> it is not recycling views. How to achieve both recyclerviews to scroll vertically?
Layout Visualization:
Your groups (each containing 1000 items) are simply too big.
You should flatten the hierarchy -- that is, expose all the items individually to the RecyclerView. Your adapter will need to override getItemViewType() to return different values for different view types, and you'll need to return different types of view holders depending on the item at each position.
This way, you'll only ever inflate as many views as can fit on the screen (plus a few extra that RecyclerView pre-requests to avoid too much inflation at scroll time).
Related
Updating value of recyclerview but unable to update corresponding data in model class
Model classes
#Parcelize
class GetStockListData : ArrayList<GetStockListDataItem>(), Parcelable
#Parcelize
data class GetStockListDataItem(
var Qty:#RawValue Double,
var selectedQty: Double
): Parcelable
able to change recyclerview element using alertbox as follows
private fun showAlertDialog(stockListData: GetStockListData, position: Int) {
val layoutInflater = LayoutInflater.from(context)
val customView =
layoutInflater.inflate(R.layout.change_qty_dialog, null)
val myBox: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder(context)
myBox.setView(customView)
val dialog = myBox.create()
dialog.show()
val etQuantity = customView.findViewById<AppCompatEditText>(R.id.et_quantity)
if (stockListData[position].Qty < args.getListDetailsByRNumberModelItem.ReqQty) {
val df = DecimalFormat("#.##")
df.roundingMode = RoundingMode.CEILING
etQuantity.setText(df.format(stockListData[position].Qty).toString())
} else
etQuantity.setText(args.getListDetailsByRNumberModelItem.ReqQty.toString())
etQuantity.setSelection(etQuantity.text.toString().length)
etQuantity.requestFocus()
requireContext().showKeyboard()
customView.findViewById<Button>(R.id.btnDone).setOnClickListener {
if(!etQuantity.text.isNullOrEmpty()) {
val qtyStr = etQuantity.text.toString().trim()
var qtyDouble = qtyStr.toDouble()
stockListData[position].selectedQty = qtyDouble
adapter.notifyDataSetChanged()
dialog.dismiss()
}
}
}
for (i in 0 until stockListData.size){
sum += stockListData[i].selectedQty
}
here if user edit Recyclerview list item multiple times, each value added to list. Finally if i try to retrieve sum of all recyclerview elements getting wrong value because in model class values are added when i try to replace element.
Instead of passing whole list as parameter to showAlertDialog() method, you could just pass single item which has to be updated. And one more thing, you should not call adapter.notifyDataSetChanged() for single item updation, rather call adapter.notifyItemChanged(position). Look at below code, I am getting correct sum :
private fun showRecipeMeasureDialog(recipeItem: RecipeItem?,position: Int){
val dialogView = LayoutInflater.from(context).inflate(R.layout.add_recipe_measure_dialog, null)
val dialog = AlertDialog.Builder(context, R.style.RoundedCornersDialog).setView(dialogView).show()
dialog.setCancelable(false)
val displayRectangle = Rect()
val window = activity?.window
window?.decorView?.getWindowVisibleDisplayFrame(displayRectangle)
dialog.window?.setLayout(
(displayRectangle.width() * 0.5f).toInt(),
dialog.window!!.attributes.height
)
context?.resources?.let {
dialogView.findViewById<TextView>(R.id.recipe_measure_title).text = java.lang.String.format(it.getString(R.string.addRecipeMeasure), unitsArray[currentMeasurementUnits - 1])
}
val doneBtn = dialogView.findViewById<ImageButton>(R.id.recipe_measure_done_btn)
val closeBtn = dialogView.findViewById<ImageButton>(R.id.close_btn_add_recipe)
val conversionEditText = dialogView.findViewById<ClearFocusEditText>(R.id.recipe_conversion_tie)
doneBtn.isEnabled = false
if (recipeItem != null ){
conversionEditText.setText("${recipeItem.conversionRatio}")
}
closeBtn.setOnClickListener {
context?.hideKeyboard(it)
dialog.dismiss() }
doneBtn.setOnClickListener {
context?.hideKeyboard(it)
val conversionRatio = conversionEditText.text.toString().toFloat()
if (recipeItem != null){
recipeItem.conversionRatio = conversionRatio
recipeItemList[position] = recipeItem
recipeAdapter.notifyItemChanged(position)
}else{
recipeItemList.add(0,RecipeItem(0,0,conversionRatio,0)) // Use your data class in place of RecipeItem
recipeAdapter.notifyItemInserted(0) // new Item is added to index zero, so adapter has to be updated at index zero
}
// calculating sum
val sum = recipeItemList.map { it.conversionRatio }.sum()
Log.d("tisha==>>","Conversion ratio sum = $sum")
dialog.cancel()
}
}
I am trying to return a list from inside firestore function based on if a condition is true.I want to return different lists when different categories are selected.
I tried:
putting the return statement out of firestore function which did not work and returned empty list due to firestore async behaviour.
creating my own callback to wait for Firestore to return the data using interface as I saw in some other questions but in that case how am i supposed to access it as my function has a Int value(i.e.private fun getRandomPeople(num: Int): List<String>)?
What could be the way of returning different lists for different categories based on firestore conditions?
My code(Non Activity class):
class Board// Create new game
(private val context: Context, private val board: GridLayout) {
fun newBoard(size: Int) {
val squares = size * size
val people = getRandomPeople(squares)
createBoard(context, board, size, people)
}
fun createBoard(context: Context, board: GridLayout, size: Int, people: List<String>) {
destroyBoard()
board.columnCount = size
board.rowCount = size
var iterator = 0
for(col in 1..size) {
for (row in 1..size) {
cell = RelativeLayout(context)
val cellSpec = { GridLayout.spec(GridLayout.UNDEFINED, GridLayout.FILL, 1f) }
val params = GridLayout.LayoutParams(cellSpec(), cellSpec())
params.width = 0
cell.layoutParams = params
cell.setBackgroundResource(R.drawable.bordered_rectangle)
cell.gravity = Gravity.CENTER
cell.setPadding(5, 0, 5, 0)
text = TextView(context)
text.text = people[iterator++]
words.add(text.text as String)
text.maxLines = 5
text.setSingleLine(false)
text.gravity = Gravity.CENTER
text.setTextColor(0xFF000000.toInt())
cell.addView(text)
board.addView(cell)
cells.add(GameCell(cell, text, false, row, col) { })
}
}
}
private fun getRandomPeople(num: Int): List<String> {
val mFirestore: FirebaseFirestore=FirebaseFirestore.getInstance()
val mAuth: FirebaseAuth=FirebaseAuth.getInstance()
val currentUser: FirebaseUser=mAuth.currentUser!!
var validIndexes :MutableList<Int>
var chosenIndexes = mutableListOf<Int>()
var randomPeople = mutableListOf<String>()
mFirestore.collection("Names").document(gName).get().addOnSuccessListener(OnSuccessListener<DocumentSnapshot>(){ queryDocumentSnapshot->
var categorySelected:String=""
if (queryDocumentSnapshot.exists()) {
categorySelected= queryDocumentSnapshot.getString("selectedCategory")!!
print("categoryselected is:$categorySelected")
Toast.makeText(context, "Got sel category from gameroom:$categorySelected", Toast.LENGTH_LONG).show()
when(categorySelected){
"CardWords"->{
for (i in 1..num) {
validIndexes=(0..CardWords.squares.lastIndex).toMutableList()
val validIndexIndex = (0..validIndexes.lastIndex).random()
val peopleIndex = validIndexes[validIndexIndex]
chosenIndexes.add(peopleIndex)
val person = CardWords.squares[peopleIndex]
randomPeople.add(person)
validIndexes.remove(peopleIndex)
peopleIndexes = chosenIndexes.toList()
}
}
else->{}
}
}
else {
Toast.makeText(context, "Sel category does not exist", Toast.LENGTH_LONG).show()
}
}).addOnFailureListener(OnFailureListener { e->
val error=e.message
Toast.makeText(context,"Error:"+error, Toast.LENGTH_LONG).show()
})
return randomPeople.toList()
}
}
Activity A:
Board(this, gridLay!!)
I want to check if data that comes from the server is null or not. If is not null then populate the recycler view item in my case being a CardView and if it is null don't display the CardView at all.
In my recycler view adapter i have this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
var firstName = itemsList[position].firstName
var lastName = itemsList[position].lastName
if (firstName.isNullOrEmpty() && lastName.isNullOrEmpty()) {
//...what shoud i write here ?
} else {
holder.name?.text = firstName + " " + lastName
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val name = itemView.name
}
you should do something like this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
var firstName = itemsList[position].firstName
var lastName = itemsList[position].lastName
if (firstName.isNullOrEmpty() && lastName.isNullOrEmpty()) {
yourCardView.visibility = View.INVISIBLE //or GONE if you do not want to keep its space
holder.name?.text = ""
} else {
yourCardView.visibility = View.VISIBLE
holder.name?.text = firstName + " " + lastName
}
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val name = itemView.name
}
but if cardview is your root item and you do not want to show the list item at all,you should change your main list with a code like this in your adapter constructor:
filteredList = itemsList?.filter {
(it.firstname.isNullOrEmpty() || it.lastName.isNullOrEmpty()).not()
}
Even if you change the visibility of the CardView the gap in the RecyclerView will be there.
It's better to remove the empty items from the list, prior to setting the adapter:
list.removeIf { it.firstName.isNullOrEmpty() && it.lastName.isNullOrEmpty() }
I have a listView that has a custom layout that contains a fragment. The problem I am having is when I add the fragment to the listView row I get a never ending loop that keeps going through the getView code.
I have only been doing Android coding for a couple months so any help would be great. Please let me know if there is any more of my code I need to post
This is my adapter code:
class AdapterReply(
private val context: Context,
private val ph: Phan,
private val replies: ArrayList<Reply> ) : BaseAdapter() {
override fun getCount(): Int {
return replies.size
}
override fun getItem(position: Int): Reply {
return replies[position]
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getView(position: Int, convertView: View?, viewGroup: ViewGroup?): View? {
val rowMain: View?
val rowHolder: ListRowHolder
val contacts = ph.contacts
val reply = replies[position]
Log.d("AdapterReply", "Binding reply: Position $position ${reply.id} Detail: ${reply.detail}")
if (convertView == null) {
val layoutInflater = LayoutInflater.from(viewGroup!!.context)
rowMain = layoutInflater.inflate(R.layout.reply_item_row, viewGroup, false)
rowHolder = ListRowHolder(rowMain)
rowMain.tag = rowHolder
Log.d("AdapterReply", "New Item")
} else {
rowMain = convertView
rowHolder = rowMain.tag as ListRowHolder
Log.d("AdapterReply", "Old item from memory")
}
rowHolder.itemDetail.text = Helpers.anonymizeContent(reply.detail, ph.anonymousMetadata, ph.isViewerMember())
rowHolder.itemLastActive.text = Helpers.getFormattedDate(reply.lastActive())
if(reply.attachments.size > 0){
val imageAttachment = reply.attachments.filter { attachment: Attachment -> attachment.type == 0 }[0]
var imageUrl = Constants.BASE_URL + imageAttachment.url
if(imageAttachment.anonymous()){
imageUrl = Constants.BASE_URL + imageAttachment.anonUrl
}
Picasso.with(rowMain!!.context).load(imageUrl).into(rowHolder.itemImage)
}
var poster: Contact? = reply.contact
if(contacts.size > 0) {
val posterList = contacts.filter { contact: Contact -> contact.id == reply.rlContactID }
if (posterList.isNotEmpty()) {
poster = posterList[0]
}
}
if(poster != null) {
if(poster.anonymizedName.isNotEmpty()) {
rowHolder.itemPoster.text = poster.anonymizedName
} else {
val posterName = "${poster.firstName} ${poster.lastName}"
rowHolder.itemPoster.text = posterName
}
//Code the causes the problem
val manager = (rowMain!!.context as AppCompatActivity).supportFragmentManager
val posterAvatarFragment =
AvatarFragment.newInstance(poster)
manager.beginTransaction()
.add(R.id.reply_avatar_fragment, posterAvatarFragment, "ReplyAvatarFragment")
.commit()
//End Code the causes the problem
}
bindReplies(rowMain, ph, reply.replies)
return rowMain
}
internal class ListRowHolder(row: View?) {
var itemDetail : TextView = row!!.reply_view_detail
var itemImage : ImageView = row!!.reply_view_image
var itemPoster : TextView = row!!.reply_view_posterName
var itemLastActive : TextView= row!!.reply_view_lastActive
var itemReplyReplies: ListView = row!!.reply_view_replyList
}
private fun bindReplies(viewRow: View?, ph: Phan, replies : ArrayList<Reply>){
if(replies.isNotEmpty()) {
val myObject = AdapterReply(context, ph, replies)
val replyListView = viewRow!!.reply_view_replyList
Log.d("AdapterReply", "Binding Replies: ${ph.encodedId} ${replies.size}")
replyListView.adapter = myObject
}
}
}
manager.beginTransaction()
.add(R.id.reply_avatar_fragment, posterAvatarFragment, "ReplyAvatarFragment")
.commit()
Man, I'm not sure do you know, what function performs adapter class in listview. Adapter class is used to fill listview by rows passed in array inside class constructor. getView method is called for every row in array, so for example, if you have an array with 10 rows, this code will fire ten times. If you do automatically change fragment to another, and during filling an old view you will change layout to a another layout with the same data, your code will make an infinity loop, because you will repeat all time the same cases. You should avoid actions, which will dynamically change layout during load him. In my sugestion, if you want to use a multiple layouts inside one adapter, there are two special methods to set layout for row, based on some conditions: getItemViewType and getViewTypeCount. In first one you can use your conditions to check, which layout should be used for row. Second one is to set number of layouts, which will be used in your adapter class. Let search some examples of usage.
I need to pick a new item from a list that hasn't been picked already until there are no more.
Here is my code:
private var quizQuestionList: ArrayList<Quiz>
private var pickedItems: ArrayList<Int>
private var random: Random = Random()
private fun pickItem(): Quiz {
var index = random?.nextInt(quizQuestionList!!.size)
if (pickedItems.contains(index)) {
index = random?.nextInt(quizQuestionList!!.size)
pickedItems.add(index)
} else {
pickedItems.add(index)
}
val item = quizQuestionList!!.get(index!!)
return item
}
Please suggest any solution that gives me a new item every time. I used an int list for all previously picked items and check every time when picked new item but I didn't get success.
It isn't obvious what you are looking for, but it looks like you want to show different Quiz question from ArrayList. In condition of, when that question is shown, no more same question will be shown. Here is how you should do, I will give you just the logic you could try it yourself:
import java.util.Random
fun main(args: Array<String>) {
val random = Random()
var randomInt: Int
var pickedInt: MutableSet<Int> = mutableSetOf()
fun rand(from: Int, to: Int): Int{
do{
randomInt = random.nextInt(to - from) + from
}while(pickedInt.contains(randomInt))
pickedInt.add(randomInt)
return randomInt
}
while(pickedInt.size < 9){
var differentNumber = rand(1,11)
println(differentNumber)
}
}
This will print nine different Number. The way I choosing MutableSet is because it will only have unique values, no duplicated value. Hope it helps!
here is code for same::
val arrayList = ArrayList<String>()
arrayList.add("a")
arrayList.add("b")
arrayList.add("c")
arrayList.add("d")
arrayList.add("e")
arrayList.add("f")
arrayList.add("g")
arrayList.add("h")
arrayList.add("i")
arrayList.add("j")
arrayList.add("k")
random = Random()
Low = 0
High = arrayList.size
val generateRandom = findViewById<View>(R.id.generateRandom) as Button
generateRandom.setOnClickListener {
val Result = random.nextInt(High - Low) + Low
Log.v("check", arrayList[Result])
}
Please let me know if need more!!
Try this way
class MainActivity : AppCompatActivity() {
private var arrayList = ArrayList<String>()
private var lastItem = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
for (i in 0..9) {
arrayList.add(i.toString())
}
btnData.setOnClickListener {
Log.e("RANDOM_NUMBER", "" + getRandomItemFromList())
}
}
private fun getRandomItemFromList(): Int {
val randomValue = Random().nextInt(arrayList.size)
return if (randomValue != lastItem) {
lastItem = randomValue
randomValue
} else {
getRandomItemFromList()
}
}
}
I made this extension for the ArrayList class in Kotlin you can use it multiple times in only one line.
fun <T> ArrayList<T>.generateRandomItems(numberOfItems: Int): ArrayList<T> {
val range = if(numberOfItems > this.size){this.size}else{numberOfItems}
val randomItems = ArrayList<T>()
for (i in 0..range) {
var randomItem: T
do {
randomItem = this.random()
} while (randomItems.contains(randomItem))
randomItems.add(randomItem)
}
return randomItems
}
Usage:
val randomUsersList = usersList.generateRandomItems(20)
Note: the usersList is the list that has items to generate random items from.