What I am trying to do is that when the user completes the editText codigoTxt
, the function initViewModel
sent with the parameters of codigoTxt
and a is called 0
, with this the spinner is filled. Later, when the user chooses an option of the spinner, I want it to be called again, initViewModel
sending the parameters of the spinner codigoTxt
and the selected option with this, it fills some textview according to the selected option.
The problem is that when the user selects an element, the method onItemSelected
is repeated the number of times that the elements that the spinner contains. Example if there are 15 elements in the spinner and I choose option 2, the method is repeated 15 times for option 2
class Nuevo : Fragment(){
private lateinit var viewModel: ViewModel
private lateinit var codigoTxt:EditText
private lateinit var mySpinner: Spinner
var options: List<Int> = mutableListOf<Int>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
val view= inflater.inflate(R.layout.fragment_nuevo, container, false)
codigoTxt= view.findViewById<EditText>(R.id.codigo)
codigoTxt.addTextChangedListener(object:TextWatcher{
override fun afterTextChanged(s: Editable?) = Unit
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
try {
//aqui se llena el spinner cuando el usuario escribe algo en un EditText codigoTxt
initViewModel(codigoTxt.text.toString(),0)
}
catch(e: Exception){
}
}
})
mySpinner = view.findViewById<Spinner>(R.id.mySpinner)
if(mySpinner!=null) {
mySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
var count:Int=0
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("not implemented")
}
override fun onItemSelected(parent: AdapterView<*>?,view: View?,position: Int,id: Long) {
if (count >= 1) {
var optionSelected=options.get(position)
initViewModel(codigoTxt.text.toString(), optionSelected)
}
count++
}
}
}
val repository= Repository()
val viewModelFactory= MainViewModelFactory(repository)
viewModel= ViewModelProvider(this,viewModelFactory).get(ViewModel::class.java)
return view
}
private fun initViewModel(codigo:String,cod_Campo:Int){
if(codigo.length==5)
{
viewModel.makeApiCallCampos(cod_Prod, cod_Campo)
viewModel.myresponsecampos.observe(this, Observer { response ->
response.clone().enqueue(object : Callback<List<MyDataItem>?> {
override fun onFailure(call: Call<List<MyDataItem>?>, t: Throwable) {
Toast.makeText(activity, "Error: " + t.message, Toast.LENGTH_LONG).show()
}
override fun onResponse(call: Call<List<MyDataItem>?>,response: Response<List<MyDataItem>?>) {
val responseBody: List<MyDataItem>? = response.body()
if (responseBody != null)
{
if(codigo.length==5 && cod_Campo==0) {
options = mutableListOf<Int>()
for (x in responseBody) {
nombreTxt.setText(x.nombre)
options += x.cod_Campo
}
val adapter = ArrayAdapter(
activity!!,
android.R.layout.simple_spinner_item,
options
)
mySpinner.adapter = adapter
optionSelected = adapter.getItem(0)!!
}
else{
for (x in responseBody)
{
telefonoTxt.setText(x.telefono)
localidadTxt.setText(x.ubicacion)
tipoTxt.setText(x.tipo)
productoTxt.setText(x.producto)
}
}
}
}
})
})
}
else {
codigoTxt.setError("El código debe ser de 5 digitos, rellene con 0 al principio")
codigoTxt.requestFocus()
}
}
ViewModel
class ViewModel (private val repository: Repository): ViewModel() {
val myresponsecampos:MutableLiveData<Call<List<MyDataItem>>> = MutableLiveData()
fun makeApiCall(codigo: String, cod_Campo: Int) {
viewModelScope.launch {
val response= repository.getCampos(codigo, cod_Campo)
myresponse.value=response
}
}
}
Among the many errors in your code, one of the most serious is in the
initViewModel
. Apart from being very badly named, every time you call it it is adding a new observer to the liveDatamyresponsecampos
The first time you call it with the code
0
and anObserver
inside is created whichcod_Campo
will always be valid0
because that is the value it had at the time you created it. Then you select an item from the spinner and the method is called again. Now another observer is created and both react to the change inmyresponsecampos
. That is to say that all the code insideObserver { }
will be executed 2 times, once for the observer wherecod_Campo
it is 0 and another for the new observer.The first observer will continue to unnecessarily create a new list of options and a new adapter each time it observes a change in the LiveData. Assigning this new adapter to the spinner changes the selected item and then executes
onItemSelected
, which callsinitViewModel
. Then another observer is created and the loop keeps repeating until either the device runs out of memory or your app crashes for some other reason caused by the infinite loop.The solution is to stop creating unnecessary objects. The first thing is to have a single observer observing the liveData. For that you can move this part to
onCreateView
Your spinner only needs one adapter so there is no point in creating more. For example you could store it in a property
That adapter must be assigned to the spinner during the
onCreateView
Then in the
onResponse
, remove the code to create another adapter and simply change the elements of the existing adapterThis way
onItemSelected
it doesn't run and therefore you don't enter the infinite loop.The variable
options
is also unnecessary because you could replace itoptions.get(position)
withadapter.getItem(position)
.