#android #kotlin
Вопрос:
Итак, Привет, ребята, я недавно работаю с MVVM, наблюдателем и с живыми данными. Позвольте мне описать вам, над чем я работаю, поэтому в основном предположим, что я работаю над приложением, предназначенным для мобильного магазина, и этот магазин предоставляет кредит клиентам, которые приходят купить товар в этом магазине. Это отработка приложения
Итак, я создал фрагмент, в котором будут представлены все детали продуктов, а также сведения о клиентах. Все работает нормально, пока я не дошел до раздела сведений о продукте. В разделе «Сведения о продукте» я создал поля для сведений о продукте, которые включают строку поиска, счетчик и другие поля для цены и другие.
ВОТ СКРИНШОТ ЭТОЙ ФОРМЫ
введите описание изображения здесь
СЛЕДУЮЩИЙ ШАГ — Приведенная выше форма, которую вы видите здесь, она загружается из адаптера recyclerview, как вы можете видеть кнопку «Добавить» в правом углу формы. Когда я нажму на кнопку «Добавить», под этой формой появится та же форма.
И ИНДЕКСАЦИЯ ВЫГЛЯДИТ ТАК
Индекс формы с кнопкой добавить равен — 0, а при нажатии на кнопку добавить Индекс формы, которая генерируется ниже, равен — 1 и с кнопкой удалить, чтобы я мог удалить и сформировать из любого индекса.
ВОТ СКРИНШОТ ЭТОГО ДЛЯ ВАШЕГО УДОБСТВА
введите описание изображения здесь
СЛЕДУЮЩИЙ ШАГ — Работа с данными, чтобы получить сведения о продукте из api и настроить их в указанных выше формах
ПРИМЕЧАНИЕ — ПОЗВОЛЬТЕ МНЕ СООБЩИТЬ ВАМ, ЧТО ЭТОТ СПИСОК ПРОДУКТОВ И НАСТРОЙКА ДАННЫХ В ФОРМЕ СВЕДЕНИЙ О ПРОДУКТЕ ВЫПОЛНЯЕТСЯ В АДАПТЕРЕ, И Я ПОКАЖУ ВАМ КОД ЭТОГО НИЖЕ….
- Предположим, у вас есть изменяемый список продуктов, подобных этому
private val tempList = mutableListOf<Product>()
- Работа в классе ViewHolder адаптера
В этом разделе я использую привязку, чтобы получить идентификатор виджета AutoCompleteTextView, и я добавляю doOnTextChanged в идентификатор AutoCompleteTextView
HERE IS THE CODE —
binding.search.doOnTextChanged { text, _, _, _ ->
productViewModel.getProduct(
itemView.context,
mapOf(
"category" to 0,
"brand" to 0,
"page" to 0,
"search" to text!!,
"from" to "create"
)
)
}
and fetch the data on this AutoCompleteTextView Widget from the ProductViewModel (My Model response which contain the Products Data)
- Next i add the clear function on the tempList of products so that i can clear the data before the observer observe it because i have to set the data
on the multiple forms if i click on the add button to generate new form for add some other product detailstempList.clear()
- I added the getObserveable() function to observe the data from the productViewModel Class (My Model response which contain the Products Data)
So, when i perform Step number 2 than it will observe the data from the productViewModel - After that i check the status of the data authenticated or not so, that i can give infotmation on the toast regarding that data available or not
a. Assume when status is authenticated i created the variable named as (val list) and in that variable i set the list of the products.
which is like this
val list = it.data?.data?.products ?: listOf()
b. Next i apply the for loop on that list so that i can get only the product name and the brand name and add the data on the new list called productNameList
private val productNamelist = mutableListOf<String>()
c. In the next step i add the whole data from the list of products in the list called named (tempList), you check on the Point number 1 above.
d. Next step i set the data on the AutoCompletetextView Widget as a simple_spinner_dropdown_item with setAdapter() function to show the selected data on it after search
AND HERE’S THE FULL CODE WHICH I EXPLAINED ABOVE
binding.search.doOnTextChanged { text, _, _, _ ->
productViewModel.getProduct(
itemView.context,
mapOf(
"category" to 0,
"brand" to 0,
"page" to 0,
"search" to text!!,
"from" to "create"
)
)
}
tempList.clear()
productViewModel.getObservable().observe(viewLifecycleOwner, Observer {
when (it.status) {
AuthResource.AuthStatus.LOADING -> {
binding.autoProgress = true
}
AuthResource.AuthStatus.AUTHENTICATED -> {
val list = it.data?.data?.products ?: listOf()
for (li in list) {
this.productNamelist.add("${li.productName} Brand: ${li.brand?.brandName} n")
}
tempList.addAll(list)
binding.search.apply {
setAdapter(ArrayAdapter(
itemView.context,
android.R.layout.simple_spinner_dropdown_item,
productNamelist
))
}
binding.autoProgress = false
}
AuthResource.AuthStatus.ERROR -> {
session.toast = it.message ?: ""
binding.autoProgress = false
}
AuthResource.AuthStatus.NOT_AUTHENTICATED -> {
//Log.e("TAG", "bindData: ${it.message}" )
// session.toast = it.message ?: ""
binding.autoProgress = false
}
}
})
e. In the next step i apply the setOnClickListener to get the selected position of the product from the AutoCompleteTextView or i can say search bar
HERE’S THE CODE FOR THAT
binding.search.setOnItemClickListener { _, _, pos, _ ->
try {
setAdapter(tempList[pos])
Log.e("Search", "Index: ${tempList[pos]}")
}catch (e:Exception){
Log.e("TAG", "bindData: ${e.message}" )
}
}
Now as you can see in the next step i sended the selected position data to the setAdapter function, so that i can set the data on the spinner on based the selected data from the search bar
f. Next i send the data to the setAdapter() and i sended the full Product object according to the selected position
HERE’S THE CODE FOR THAT
private fun setAdapter(product: Product) {
Log.e("Product Index", "Data: $product" )
val varName = mutableListOf<String>()
val variant = product.variant ?: listOf()
for (vari in variant) {
varName.add(vari.colorName ?: "NA")
}
binding.variantSpinner.apply {
adapter =
ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, varName)
setAdapter(adapter)
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Log.e("Spinner", "Index: $position")
val vari = variant[position]
Log.e("Variants", "onItemSelected: $vari")
binding.cases?.caseProduct?.caseVariant = vari
binding.cases?.casePrice = vari.saleAtPrice.toString()
binding.cases?.totalAmount = vari.saleAtPrice ?: 0.0
binding.cases?.advanceAmount = vari.saleAtPrice ?: 0.0
binding.cases?.quantity = "1"
binding.variants = variant.get(position)
addProduct.onSelect(
Soldproduct(
0,
binding.cases?.caseId?.toInt(),
product.brandId,
product.categoryId,
product.id,
vari.id,
vari.buyAtPrice ?: 0.0,
vari.saleAtPrice ?: 0.0,
0.0,
vari.serialNumber,
vari.modelNumber,
vari.hsn,
vari.sgst.toString(),
vari.cgst.toString(),
vari.igst.toString(),
1,
)
)
binding.invalidateAll()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
}
}
Now let me explained what i did in the above code, as you can see i sended the whole Product Model object as a parameter in the setAdapter(product: Product) function
next i made local list of type String
val varName = mutableListOf<String>()
next i fetch the variant details according to the selected product from the search bar above to set in the spinner
val variant = product.variant ?: listOf()
for example i have product details like this [productName = Redmi Note 10, Price = 12000, Variant = [White,Black,Red,Green]]
and note that the variant is itself a list inside of the list of the products and to set the variants on the spinner i apply the for loop on that
for (vari in variant) {
varName.add(vari.colorName ?: "NA")
}
and add the data on varName list
after that i set the varName list on the spinner with the help of the setAdapter you can see in the above section f. but i will write some code down here for your ease
binding.variantSpinner.apply {
adapter =
ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, varName)
setAdapter(adapter)
}
after the setting the data on the spinner i apply the onItemSelectedListener, and as the varaint selected i set all the there product detials according to the variant in the product details form fields which added the screen shot above
HERE I’M SETTING THE DATA FOR PRODUCT ACCORDING TO THE VARIANTS
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
Log.e("Spinner", "Index: $position")
val vari = variant[position]
Log.e("Variants", "onItemSelected: $vari")
binding.cases?.caseProduct?.caseVariant = vari
binding.cases?.casePrice = vari.saleAtPrice.toString()
binding.cases?.totalAmount = vari.saleAtPrice ?: 0.0
binding.cases?.advanceAmount = vari.saleAtPrice ?: 0.0
binding.cases?.quantity = "1"
binding.variants = variant.get(position)
addProduct.onSelect(
Soldproduct(
0,
binding.cases?.caseId?.toInt(),
product.brandId,
product.categoryId,
product.id,
vari.id,
vari.buyAtPrice ?: 0.0,
vari.saleAtPrice ?: 0.0,
0.0,
vari.serialNumber,
vari.modelNumber,
vari.hsn,
vari.sgst.toString(),
vari.cgst.toString(),
vari.igst.toString(),
1,
)
)
binding.invalidateAll()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
Теперь все это работает нормально, пока в какой-то момент мое приложение не выйдет из строя и не выдаст мне ошибку IndexOutOfBoundExcpetion.
Вот как
- Я выбираю первый продукт и устанавливаю данные в spinner, и в этом случае spinner установит данные в других полях, что отлично работает
- Когда я нажимаю на кнопку Добавить в форме сведений о продукте, она создаст новую ту же форму, ниже которой я объяснял ранее. Теперь отсюда все пошло не так,потому что, когда я ищу некоторые другие продукты, данные извлекаются нормально, но не могут быть установлены в файлах формы, что дает мне значение IndexOutOfBoundExcpetion здесь….
Другими словами, я могу задать данные только один раз, прежде чем добавлять новые продукты для покупки, но я хочу, чтобы, если вы решите добавить более одной информации о продукте для одного клиента, мои данные легко настроят все другие несколько форм….
ПОЖАЛУЙСТА, ПОМОГИТЕ МНЕ ЗДЕСЬ, Я ЗАСТРЯЛ НА ЭТОМ ПРИМЕРНО НА 4 ДНЯ, И Я ИЩУ ВСЕ, НО НЕ МОГУ РЕШИТЬ ЭТУ ПРОБЛЕМУ
ЛЮБАЯ ПОМОЩЬ БУДЕТ ПРИЗНАТЕЛЬНА ЗАРАНЕЕ СПАСИБО
Комментарии:
1. Я ценю, что вы приложили усилия, чтобы подробно описать свою проблему, но, боюсь, это просто слишком много для того, чтобы кто-то мог переварить. Я предполагаю, что невозможно опубликовать исходный код всего приложения, чтобы другие люди могли запустить его на своем компьютере. Не могли бы вы предоставить трассировку стека? В какой именно строке возникает эта ошибка? Вы пытались проанализировать ситуацию с помощью отладчика? Например, установите точку останова в строке, которая вызывает исключение, посмотрите, что такое индекс и что находится в массиве/списке, затем вернитесь и попытайтесь понять, что произошло.
2. Похоже, что место, где вы устанавливаете
tempList[pos]
, создает исключение, так как вpos
индексеtempList
значения нет никаких данных. Где-то я тоже видел, как вы расчищаетеtempList
. Прежде чем обращаться к каким-либо данным с некоторым индексом, проверьте размер коллекции и убедитесь, что данные по данному индексу действительно существуют.3. @Varsha Kulkarni, Varsha права, я получаю исключение в тот момент, когда я устанавливаю данные для еще одного продукта в форме, и я также проверяю точки останова, и в этом случае я получаю данные из вышеупомянутого tempList[pos] и отправляю в функцию spinner без каких-либо трудностей, и это временное исключение приходит сюда. Там написано java.lang. Исключение IndexOutOfBoundException: Индекс:1, Размер:1
4. @broot спасибо за ответ, я сделал то, что вы сказали, и проверил точки останова, и в этом случае я получаю данные из вышеупомянутого tempList[pos] и отправляю в функцию spinner без каких-либо трудностей, и это исключение времени приходит сюда. Там написано java.lang. Исключение IndexOutOfBoundException: Индекс:1, Размер:1
5. Как вы можете видеть, ваш
tempList
содержит только один предмет, и вы пытаетесь приобрести 2-й предмет. Это приводит к ошибке. Разве он не должен содержать 2 элемента, если вы говорите, что это происходит после того, как вы использовали кнопку «Добавить»? Если это так, то добавьте точки останова или журналы везде, где вы изменяете содержимоеtempList
, и попытайтесь проанализировать, почему это только 1, а не 2.