• Почему даже при самом простом RecyclerViewAdapter лагает?

    @UBERS Автор вопроса
    Павел, Тут дело точно не в количестве данных, т.к. я специально протестировал отображение загрузкой лишь одной записи в adapter. А вот насчет загрузки данных на основном потоке я не уверен. До этого пробовал запускать и инициализировать адаптер с помощью корутин различными способами, с применением разных диспатчеров - не помогло. НО возможно я делал что-либо не так, использовал lifecycleScope и CoroutineScope. Но насколько я видел, все пишут код, относящийся к адаптеру и загрузке в него данных не асинхронным методом, а обычным прямым кодом. Да и тем более ведь данные из БД загружаются через запрос в MainViewModel - а там обращение к DAO асинхронно, с помощью viewModelScope.
    Возможно все-таки нужно загружать данные в адаптер из корутины, только я использовал неправильный подход?

    Сколько ищу информации в интернете, никаких примеров для моего случая не нахожу, весь код написан по мануалам, и RcView самый простой написал для теста - все равно подлагивает при открытии...
    Не понимаю в чем дело, ссылаюсь на то, что все же делаю сам что-то где-то не то.
    Написано
  • Почему даже при самом простом RecyclerViewAdapter лагает?

    @UBERS Автор вопроса
    Да, до переписывания адаптера на самый простой, я использовал ListAdapter с DiffUtills и ситуация была точно такая же. Вот код того адаптера:
    class RecyclerViewAdapter(
        private val listener: Listener, private val context: Context
    ) : ListAdapter<ItemsList, RecyclerViewAdapter.ItemHolder>(AsyncDifferConfig.Builder(ItemComparator()).build()) {
    
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
            return ItemHolder.create(parent)
        }
    
        override fun onBindViewHolder(holder: ItemHolder, position: Int) {
            if (position != 0) {
                val previousItem = getItem(position - 1)
                holder.setData(getItem(position), listener, context, position, previousItem)
            }
    
            else {
                holder.setData(getItem(position), listener, context, position, null)
            }
        }
    
        fun onSwipedRc(position: Int) {
            val item = getItem(position)
            Log.d("MyLog", "Deleted item was $item")
    
            listener.deleteItem(item!!, position)
        }
    
        class ItemHolder(view: View) : ViewHolder(view) {
            private val binding = ListItemBinding.bind(view)
            private var isAnim = false
    
            fun setData(note: ItemsList, listener: Listener, context: Context,
                        position: Int, previousItem: ItemsList?) = with(binding) {
                val name = note.categoryName!!
                val nameResID = R.string::class.java.getField(name).getInt(null)
    
                tvCategoryTitle.setText(nameResID)
                Glide.with(context).load(R.drawable::class.java.getField(formatIcPath(name)).getInt(null))
                    .error(R.drawable.ic_others)
                    .placeholder(R.drawable.ic_others).into(categoryIv)
                tvPurchaseTitle.text = note.purchaseName
                tvDescription.text = note.description
                if (tvDescription.text.isNotEmpty()) {
                    ivArrow.visibility = View.VISIBLE
                }
                tvAmount.text = formatTrim(String.format("%.2f", note.amount))
    
                //Make date string and set day of week
                val date = note.displayed_date
                if (date != null) {
                    if (position == 0 || date != previousItem?.displayed_date) {
                        tvDay.setText(getDayOfWeek(date))
                        tvDate.text = date.substring(0, 2)
                        binding.tvDate.visibility = View.VISIBLE
                        binding.tvDay.visibility = View.VISIBLE
                    } else {
                        binding.tvDate.visibility = View.GONE
                        binding.tvDay.visibility = View.GONE
                    }
                } else {
                    Log.d("MyLog", "Date is null")
                    binding.tvDate.visibility = View.GONE
                    binding.tvDay.visibility = View.GONE
                }
    
                if (!note.description.isNullOrEmpty()) {
                    itemView.setOnClickListener {
                        Log.d("MyLog", "Clicked on item")
                        if (isAnim) {
                            return@setOnClickListener
                        }
    
                        if (BottomList.visibility == View.GONE && tvDescription.text.isNotEmpty()) {
                            Log.d("MyLog", "VISIBLE description")
                            val expItem = Item
                            val expHeight = BottomList
    
                            expandView(expItem, expHeight, context)
    
                            val animRotation = RotateAnimation(
                                0f, 180f,
                                Animation.RELATIVE_TO_SELF, 0.5f,
                                Animation.RELATIVE_TO_SELF, 0.5f
                            )
                            animRotation.duration = 500
                            animRotation.fillAfter = true
    
                            ivArrow.startAnimation(animRotation)
                        } else if (BottomList.visibility == View.VISIBLE) {
                            Log.d("MyLog", "GONE description")
    
                            val collItem = Item
                            val collHeight = BottomList
    
                            collHeight.visibility = View.GONE
                            collapseView(collItem, collHeight)
    
                            val animRotation = RotateAnimation(
                                180f, 0f,
                                Animation.RELATIVE_TO_SELF, 0.5f,
                                Animation.RELATIVE_TO_SELF, 0.5f
                            )
                            animRotation.duration = 250
                            animRotation.fillAfter = true
    
                            ivArrow.startAnimation(animRotation)
                        }
                    }
                }
    
                itemView.setOnLongClickListener {
                    listener.onLongClickItem(note, itemView)
    
                    true
                }
            }
    
    
            private fun expandView(expItem: View, expHeight: View, context: Context) {
                isAnim = true
    
                val initHeight = expItem.height
                expHeight.measure(
                    View.MeasureSpec.makeMeasureSpec(expItem.width, View.MeasureSpec.EXACTLY),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
                )
                val targetHeight = initHeight + expHeight.measuredHeight
    
                val animator = ValueAnimator.ofInt(initHeight, targetHeight)
                animator.addUpdateListener { animation ->
                    val value = animation.animatedValue as Int
                    val layoutParams = expItem.layoutParams
                    layoutParams.height = value
                    expItem.layoutParams = layoutParams
                }
                animator.duration = 200
    
                animator.start()
    
                animator.addListener(object: AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
                        super.onAnimationEnd(animation)
                        isAnim = false
                        val animFadeIn = AnimationUtils.loadAnimation(context, R.anim.fade_in)
                        expHeight.startAnimation(animFadeIn)
                        expHeight.visibility = View.VISIBLE
                    }
                })
            }
    
            private fun collapseView(collItem: View, collHeight: View) {
                isAnim = true
    
                val initHeight = collItem.height
                collHeight.measure(
                    View.MeasureSpec.makeMeasureSpec(collItem.width, View.MeasureSpec.EXACTLY),
                    View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
                )
                val targetHeight = initHeight - collHeight.measuredHeight
    
                val animator = ValueAnimator.ofInt(initHeight, targetHeight)
                animator.addUpdateListener { animation ->
                    val value = animation.animatedValue as Int
                    val layoutParams = collItem.layoutParams
                    layoutParams.height = value
                    collItem.layoutParams = layoutParams
                }
                animator.duration = 200
    
                animator.start()
    
                animator.addListener(object: AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
                        super.onAnimationEnd(animation)
                        isAnim = false
                    }
                })
            }
    
            private fun getDayOfWeek(date: String): Int {
                val calendar = Calendar.getInstance()
                calendar.set(Calendar.YEAR, date.substring(7, 10).toInt())
                calendar.set(Calendar.MONTH, date.substring(4, 5).toInt())
                calendar.set(Calendar.DAY_OF_MONTH, date.substring(0, 2).toInt())
    
                val numDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
                val daysOfWeek = arrayListOf(
                    "day1",
                    "day2",
                    "day3",
                    "day4",
                    "day5",
                    "day6",
                    "day7"
                )
    
                val dayResID = R.string::class.java.getField(daysOfWeek[numDayOfWeek - 1])
                    .getInt(null)
    
                return dayResID
            }
    
            private fun formatTrim(numString: String): String {
                val index = numString.indexOf(',')
                val sumTrim: String
    
                if (index > 4) {
                    sumTrim = numString.substring(0, 4) + ".." + numString.substring(index)
                    Log.d("MyLog", "index = $index; sum = $numString; sum trim = $sumTrim")
                } else {
                    sumTrim = numString
                }
    
                return sumTrim
            }
    
            private fun formatIcPath(name: String): String {
                val index = name.indexOf('_')
                val nameCategory = name.substring(0, index).lowercase()
    
                return "ic_$nameCategory"
            }
    
            companion object {
                fun create(parent: ViewGroup): ItemHolder {
                    return ItemHolder(
                        LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
                    )
                }
            }
        }
    
        class ItemComparator : DiffUtil.ItemCallback<ItemsList>() {
            override fun areItemsTheSame(oldItem: ItemsList, newItem: ItemsList): Boolean {
                return oldItem.id == newItem.id
            }
    
            override fun areContentsTheSame(oldItem: ItemsList, newItem: ItemsList): Boolean {
                return oldItem == newItem
            }
        }
    
        interface Listener {
            fun deleteItem(note: ItemsList, pos: Int)
            fun onLongClickItem(note: ItemsList, view: View)
        }
    }
    Написано
  • Почему даже при самом простом RecyclerViewAdapter лагает?

    @UBERS Автор вопроса
    Олег, Также я заметил, что если я ограничу подаваемый список в RcViewAdapter до 1 элемента:
    adapter.transactions = it.subList(0, 1)
    То фрагмент открывается значительно быстрее, и практически без подлагивания (но оно все еще есть даже с одним элементом), будто пропускается лишь пара кадров. Может дело в нагруженной разметке элемента, на основе которого строится список?
    Написано
  • Почему даже при самом простом RecyclerViewAdapter лагает?

    @UBERS Автор вопроса
    Олег, Да, спасибо за замечание по поводу запроса, исправил, теперь использую обычное сравнение.
    Но это не является главной причиной задержки (подлагивания) во время открытия фрагмента, т.к. когда я просто закомментировал строчку adapter.transactions = it - лаг при открытии фрагмента полностью исчез и все стало работать настолько плавно, на сколько должно и на сколько я бы хотел.
    Исходя из этого - предполагаю, что главная проблема именно в загрузке списка? Или здесь есть еще какие-то нюансы, или же я смотрю не туда? Ведь у всех со схожим кодом все работает хорошо, без лагов.
    Написано
  • Почему даже при самом простом RecyclerViewAdapter лагает?

    @UBERS Автор вопроса
    Олег, Да, данные извлекаю из БД, запросом из фрагмента в MainViewModel таким образом:
    private suspend fun observer(month: String, currentMonth: String) {
            Log.d("MyLog", "Observer - HistoryFrag")
            mainViewModel.getMonthNotes(month).observe(viewLifecycleOwner) {
                if (it == null && month == currentMonth) {
                    Log.d("MyLog", "List is empty")
                    binding.rcViewHistory.visibility = View.GONE
    
                    binding.EmptyView.emptyView.visibility = View.VISIBLE
                    val openAnim = AnimationUtils.loadAnimation(context, R.anim.enter_empty_view)
                    binding.EmptyView.emptyView.startAnimation(openAnim)
                } else {
                    binding.rcViewHistory.visibility = View.VISIBLE
                    binding.EmptyView.emptyView.visibility = View.GONE
                    Log.d("MyLog", "Submitting list...")
                    Log.d("MyLog", "$it")
                    adapter.transactions = it
                    //adapter.submitList(it)
                }
            }
        }

    Так же пробовал такой вариант (вариант с Flow, а не LiveData):
    private suspend fun observer(month: String, currentMonth: String) {
            Log.d("MyLog", "Observer - HistoryFrag")
            mainViewModel.getMonthNotes(month).collect {
                if (it == null && month == currentMonth) {
                    Log.d("MyLog", "List is empty")
                    binding.rcViewHistory.visibility = View.GONE
    
                    binding.EmptyView.emptyView.visibility = View.VISIBLE
                    val openAnim = AnimationUtils.loadAnimation(context, R.anim.enter_empty_view)
                    binding.EmptyView.emptyView.startAnimation(openAnim)
                } else {
                    binding.rcViewHistory.visibility = View.VISIBLE
                    binding.EmptyView.emptyView.visibility = View.GONE
                    Log.d("MyLog", "Submitting list...")
                    Log.d("MyLog", "$it")
                    adapter.transactions = it
                    //adapter.submitList(it)
                }
            }
        }

    А вот код самого обращения к DAO в MainViewModel:
    fun getMonthNotes(month: String) : LiveData<List<ItemsList>> {
            return dao.getMonthNotes(month).distinctUntilChanged()
        }

    А это запрос в DAO:
    @Query ("SELECT * FROM List_of_Items WHERE assignable_date LIKE :month ORDER BY id DESC")
        fun getMonthNotes(month: String): LiveData<List<ItemsList>>


    Насчет разрешения выполнения запросов на главном потоке не совсем понял. Я пробовал использовать корутины во фрагменте, но это не помогало, все равно были лаги. Подскажите пожалуйста, в чем же я все-таки ошибся здесь?
    Написано
  • Как изменить язык приложения?

    @UBERS Автор вопроса
    AlexVWill, я пробовал следующие подходы:
    private fun setLang(context: Context = applicationContext) {
        val lang = defPreferences.getString("key_language", "en")
        val locale = Locale(lang)
        Locale.setDefault(locale)
    
        val resources = context.resources
        val configuration = Configuration(resources.configuration)
        configuration.setLocale(locale)
    
        val displayMetrics = resources.displayMetrics
        resources.updateConfiguration(configuration, displayMetrics)
    
        currentLang = lang.toString()
        recreate()
    }

    И похожие на него, с незначительными изменениями, тут суть одна

    Такое пробовал:
    private fun setLang(context: Context = applicationContext) {
    val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags("en-US")
    AppCompatDelegate.setApplicationLocales(appLocale)
    recreate()
    }

    private fun setLang(context: Context = applicationContext) {
    val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags(Locale.ENGLISH.toLanguageTag())
    AppCompatDelegate.setApplicationLocales(appLocale)
    recreate()
    }

    Мне нужен код, который может менять языковой ресурс приложения и чтобы изменения были видны после перезапуска активити. Все что выше - у меня не сработало, проверил все что смог
    Написано
  • Как изменить язык приложения?

    @UBERS Автор вопроса
    Да, но там нет конктреного объяснения, я не понимаю как я могу конкретно изменить язык моего приложения, как должен выглядеть этот код? Я перепробовал все, что написано в там, в мануале, и то, что пишут на сайтах - ничего не работает.
    Написано
  • Почему не определяется флешка и как это исправить?

    @UBERS Автор вопроса
    Да, ты прав, скорее всего не имеет смысла тратить на это время, спасибо за ответ!
  • Почему при выборке элементов SQLite в Android studio суммируемые числа округляются до целого?

    @UBERS Автор вопроса
    mayton2019, Спасибо за вопрос, показывает "text" во всех строках. Наверное проблема в типе данных, с которым я записываю данные в эту колонну?
  • Что с округлением Excel?

    @UBERS Автор вопроса
    Не согласен, по правилам округления число округляется в меньшую сторону при значении цифры от 0 - 4, в большую при значении от 5 - 9. В данном случае округление должно происходить следующим образом:
    0.7488888...9
    0.75
    0.8
    В случае, если бы значение было, к примеру, 0.74333...9 - округление происходило бы в порядке:
    0.74333...9
    0.74
    0.7

    Тем более, в добавок, схожие значения в моих таблицах в других ячейках давали верный результат, соответствующий правилам округления.