androidkotlinspinnerdropdownandroid-moxy

Why are two items showed in Spinner at once?


Items in one of my dropdown lists are starting to double themselves after orientation change for some reason.

Here are two pictures: one with the bug (after changing to horizontal and back) and one without it.

Bug picture How it should be

Here's my code:

Activity class:

class ScheduleActivity : AppCompatActivity(), GroupPickerActivity {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_lessons_list)

    val groupPickerFragment = GroupPickerFragment()

    fragmentManager.beginTransaction()
            .add(R.id.central_frame, groupPickerFragment)
            .commit()
}

override fun finishPicking() {
    Toast.makeText(applicationContext, "Finished picking", Toast.LENGTH_SHORT).show()
}
}

Fragment class:

class GroupPickerFragment : MvpFragment(), GroupPickerView {

    val TAG = "GroupPickerFragment"

    @InjectPresenter
    lateinit var groupPickerPresenter: GroupPickerPresenter

    var noCitySelected = true
    var noSchoolSelected = true
    var noGroupSelected = true

    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        return inflater!!.inflate(R.layout.fragment_group_picker, container, false)
    }


    override fun onStart() {
        super.onStart()

        groupPickerPresenter.start(activity.applicationContext)

        makeCitySpinnerClickable()
    }

    private fun makeCitySpinnerClickable() {
        /*
        * Set onItemSelectedListener on City picker spinner
        * */

        pick_your_city_tv.visibility = View.VISIBLE
        city_picker_spinner.visibility = View.VISIBLE

        city_picker_spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View?, pos: Int, id: Long) {
                if (!noCitySelected) {
                    val selectedItem = parentView.getItemAtPosition(pos)

                    if (pos != 0) {
                        onCitySelected(selectedItem.toString())
                    } else {
                        onCreateNewCitySelected()
                    }
                } else {
                    noCitySelected = false
                }

            }

            override fun onNothingSelected(parentView: AdapterView<*>?) {}
        }
    }

    private fun onCreateNewCitySelected() {

        val textbox = EditText(activity.applicationContext)

        val alertDialogBuilder = AlertDialog.Builder(activity,
                R.style.AppTheme_myDialogueWindow)

        alertDialogBuilder.setTitle(R.string.enter_new_city_name)
                .setCancelable(true)
                .setView(textbox)
                .setPositiveButton(R.string.create_new, { _, _ ->
                    val newCityName = textbox.text.toString()

                    if (newCityName != "") {
                        groupPickerPresenter.createNewCity(newCityName)
                    } else {
                        Toast.makeText(activity.applicationContext,
                                R.string.city_name_cannot_be_empty,
                                Toast.LENGTH_SHORT).show()
                    }
                })
                .show()
    }

    override fun onCitiesDownloaded(cityList: ArrayAdapter<String>) {
        val spinner = city_picker_spinner

        val adapter = cityList
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)

        spinner.adapter = adapter

    }

    fun onCitySelected(cityName: String) {
        Log.v(TAG, "Get cities by name")

        groupPickerPresenter.onCitySelected(cityName)
        makeSchoolSpinnerClickable()

    }

    override fun onCityAdded(newCityId: String) {
        groupPickerPresenter.start(activity.applicationContext)
    }

    override fun onSchoolsDownloaded(schoolsList: ArrayAdapter<String>) {
        val spinner = school_picker_spinner

        val adapter = schoolsList
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)

        spinner.adapter = adapter

    }

    private fun makeSchoolSpinnerClickable() {
        /*
        * Set onItemSelectedListener on School picker spinner
        * */

        pick_your_school_tv.visibility = View.VISIBLE
        school_picker_spinner.visibility = View.VISIBLE

        school_picker_spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View?, pos: Int, id: Long) {
                if (!noSchoolSelected) {
                    val selectedItem = parentView.getItemAtPosition(pos)

                    if (pos != 0) {
                        onSchoolSelected(selectedItem.toString())
                    } else {
                        onCreateNewSchoolSelected()
                    }
                } else {
                    noSchoolSelected = false
                }

            }

            override fun onNothingSelected(parentView: AdapterView<*>?) {}
        }
    }

    private fun onCreateNewSchoolSelected() {
        val textbox = EditText(activity.applicationContext)

        val alertDialogBuilder = AlertDialog.Builder(activity,
                R.style.AppTheme_myDialogueWindow)

        alertDialogBuilder.setTitle(R.string.enter_new_school_name)
                .setCancelable(true)
                .setView(textbox)
                .setPositiveButton(R.string.create_new, { _, _ ->
                    val newSchoolName = textbox.text.toString()

                    if (newSchoolName != "") {
                        val cityName = city_picker_spinner.selectedItem.toString()

                        groupPickerPresenter.createNewSchool(newSchoolName, cityName)

                    } else {
                        Toast.makeText(activity.applicationContext,
                                R.string.school_name_cannot_be_empty,
                                Toast.LENGTH_SHORT).show()
                    }
                })
                .show()
    }

    private fun onSchoolSelected(schoolName: String) {
        Log.v(TAG, "Get schools by name")

        groupPickerPresenter.onSchoolSelected(schoolName)

        makeGroupSpinnerClickable()
    }

    override fun onSchoolAdded(newCityId: String) {
        //todo Когда новая школа добавлена, надо обновить список школ
    }

    override fun onGroupsDownloaded(groupsList: ArrayAdapter<String>) {
        val spinner = group_picker_spinner

        val adapter = groupsList
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)

        spinner.adapter = adapter

    }

    private fun makeGroupSpinnerClickable() {
        /*
        * Set onItemSelectedListener on School picker spinner
        * */

        pick_your_group_tv.visibility = View.VISIBLE
        group_picker_spinner.visibility = View.VISIBLE


        group_picker_spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
            override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View?, pos: Int, id: Long) {
                if (!noGroupSelected) {
                    val selectedItem = parentView.getItemAtPosition(pos)

                    if (pos != 0) {
                        onGroupSelected(selectedItem.toString())
                    } else {
                        onCreateNewGroupSelected()
                    }
                } else {
                    noGroupSelected = false
                }

            }

            override fun onNothingSelected(parentView: AdapterView<*>?) {}
        }
    }

    private fun onGroupSelected(groupName: String) {
        groupPickerPresenter.onGroupSelected(groupName)
    }

    private fun onCreateNewGroupSelected() {
        val textbox = EditText(activity.applicationContext)

        val alertDialogBuilder = AlertDialog.Builder(activity,
                R.style.AppTheme_myDialogueWindow)

        alertDialogBuilder.setTitle(R.string.enter_new_group_name)
                .setCancelable(true)
                .setView(textbox)
                .setPositiveButton(R.string.create_new, { _, _ ->
                    val newGroupName = textbox.text.toString()

                    if (newGroupName != "") {
                        val schoolName = school_picker_spinner.selectedItem.toString()

                        groupPickerPresenter.createNewGroup(newGroupName, schoolName)

                    } else {
                        Toast.makeText(activity.applicationContext,
                                R.string.group_name_cannot_be_empty,
                                Toast.LENGTH_SHORT).show()
                    }
                })
                .show()
    }

    override fun onGroupAdded(updatedSchoolId: String) {
        //todo обновить список групп
    }

    override fun letUserContinue() {
        group_picker_next_btn.isEnabled = true
        group_picker_next_btn.setOnClickListener({
            val selectedSchoolName = school_picker_spinner.selectedItem.toString()
            val selectedGroupName = group_picker_spinner.selectedItem.toString()

            groupPickerPresenter.pick(selectedSchoolName, selectedGroupName)

            group_picker_progressbar.visibility = View.VISIBLE
        })
    }

    override fun finishPicking() {
        group_picker_progressbar.visibility = View.GONE
        (activity as GroupPickerActivity).finishPicking()
    }
}

Fragment controller class:

@InjectViewState
class GroupPickerPresenter : MvpPresenter<GroupPickerView>() {
    val TAG = "GroupPickerPresenter"

    val cityModel = CityFirestoreModel()
    val schoolModel = SchoolFirestoreModel()

    lateinit var context: Context

    init {

    }

    fun start(context: Context) {
        this.context = context

        getCities()
    }

    private fun getCities() {
        cityModel.getAllCities {
            val cityTitlesList = mutableListOf<String>()

            // Adding 'Create new'
            cityTitlesList.add(this.context.resources.getString(R.string.create_new))

            it.forEach {
                cityTitlesList.add(it.title)
            }

            val arrayAdapter = ArrayAdapter<String>(this.context,
                    android.R.layout.simple_spinner_item, cityTitlesList)


            viewState.onCitiesDownloaded(arrayAdapter)
        }

    }

    fun createNewCity(newCityName: String) {
        cityModel.addCity(newCityName, { newCityId ->
            if (newCityId != null)
                viewState.onCityAdded(newCityId)
        })
    }

    fun onCitySelected(cityName: String) {
        val cityModel = CityFirestoreModel()

        cityModel.getCitiesByName(cityName, {
            getSchoolsByCity(it)
        })
    }

    private fun getSchoolsByCity(cities: MutableList<City>) {
        val schoolModel = SchoolFirestoreModel()

        if (cities.size > 0) {
            val city = cities[0]

            schoolModel.getSchoolsByCityName(city.title, {
                val schoolTitlesList = mutableListOf<String>()

                // Adding 'Create new'
                schoolTitlesList.add(this.context.resources.getString(R.string.create_new))

                it.forEach {
                    schoolTitlesList.add(it.title)
                }

                val arrayAdapter = ArrayAdapter<String>(this.context,
                        android.R.layout.simple_spinner_item, schoolTitlesList)

                viewState.onSchoolsDownloaded(arrayAdapter)
            })
        } else {
            Log.e(TAG, "Cities list is empty")

            val schoolTitlesList = mutableListOf<String>()

            // Adding 'Create new'
            schoolTitlesList.add(this.context.resources.getString(R.string.create_new))

            val arrayAdapter = ArrayAdapter<String>(this.context,
                    android.R.layout.simple_spinner_item, schoolTitlesList)

            viewState.onSchoolsDownloaded(arrayAdapter)
        }

    }

    fun onSchoolSelected(schoolName: String) {
        val schoolModel = SchoolFirestoreModel()

        schoolModel.getSchoolGroups(schoolName, {
            val groupTitlesList = mutableListOf<String>()

            // Adding 'Create new'
            groupTitlesList.add(this.context.resources.getString(R.string.create_new))

            it.forEach {
                groupTitlesList.add(it.title)
            }

            val arrayAdapter = ArrayAdapter<String>(this.context,
                    android.R.layout.simple_spinner_item, groupTitlesList)

            viewState.onGroupsDownloaded(arrayAdapter)
        })
    }

    fun createNewSchool(newSchoolName: String, cityName: String) {
        val city = City()
        city.title = cityName

        val teachers = mutableListOf<Teacher>()
        val subjects = mutableListOf<Subject>()
        val groups = mutableListOf<Group>()


        schoolModel.addSchool(newSchoolName, city, teachers, subjects, groups, { newCityId ->
            if (newCityId != null)
                viewState.onSchoolAdded(newCityId)
        })
    }

    fun onGroupSelected(groupName: String) {
        viewState.letUserContinue()
    }

    fun createNewGroup(newGroupName: String, schoolName: String) {
        schoolModel.getSchoolsByName(schoolName, {
            val school = it[0] //todo сделать какую-то проверку на уникальность, чтоб не было проблем

            val group = Group()
            group.title = newGroupName

            school.groups.add(group)

            schoolModel.updateSchool(school.school_id, school, {updatedSchoolId ->
                if (updatedSchoolId != null) {
                    viewState.onGroupAdded(updatedSchoolId)
                }
            })
        })
    }

    fun pick(schoolName: String, groupName: String) {
        val schoolModel = SchoolFirestoreModel()

        schoolModel.getSchoolGroup(schoolName, groupName, {group ->
            if (group != null) {
                val userFirestoreModel = UserFirestoreModel()
                val currentUserId = FirebaseAuth.getInstance().currentUser!!.uid
                userFirestoreModel.setGroup(currentUserId, group, {
                    viewState.finishPicking()
                })


            }
        })

    }
}

Before that I tried to use Fragment instead of FrameLayout in my activity xml file, but when I was using .commit() on fragmentManager, my application crashed on orientation change and also showed the same bug as here every time I entered it. But everything was okay when I didn't call .commit(). Orientation change worked fine (besides the fact it was reseting chosen values) and there was no item doubling.

My former activity code:

class ScheduleActivity : AppCompatActivity(), GroupPickerActivity {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_lessons_list)

        val groupPickerFragment = GroupPickerFragment()

        fragmentManager.beginTransaction()
                .replace(R.id.group_picker_fragment, groupPickerFragment)
    }
} 

Solution

  • My guess is when you change your orientation you are adding same fragment twice; In your code

    fragmentManager.beginTransaction()
            .add(R.id.central_frame, groupPickerFragment)
            .commit()
    

    change the add method with replace like this

    fragmentManager.beginTransaction()
            .replace(R.id.central_frame, groupPickerFragment)
            .commit()