Search code examples
androidandroid-studiokotlinandroid-activity

Why do button clicks happen twice in this app?


I have an app where one functionality is that you can choose a day of the week by clicking on it's cardview.

My current problem is that if you do the following sequence, you get an unexpected result:

Clear -> pick day -> add task -> submit -> back button to Main Activity

When you do this sequence, the db is cleared again unexpectedly after the initial clearing, or if you go to a day, add a task, and navigate back, it runs again and/or puts you back in the view of the day you just left.

What I have noticed and what I have tried:

The foreach loop I have does what it's supposed to: flip through the weekdays until it hits the right one...but then it does it again? I know it's happening too much from logs and from the Toast popping up again after it's finished (it says db cleared automatically followed by there's nothing to clear). Also I have tried to add breakpoints within the foreach loop, but I don't think that helps me as it only breaks the iteration of the loop that one time if I'm not mistaken?

I have tried an activity level boolean variable to make it stop and start when I need, but the problem ends up being that I then either need to press any given card twice, it doesn't do anything at all after the first item is chosen, or the problem remains the same.

I've used onClick in the xml for the button listeners, but I've also tried it programmed into the activity kotlin class and it didn't make a difference

Maybe I'm just missing an extra iteration of a loop somewhere, or a break in the proper place?

Main Activity XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <TextView
        android:id="@+id/top_box"
        android:layout_width="0dp"
        android:layout_height="90dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/clear_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@id/top_box"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/sunday_card"
        app:layout_constraintBottom_toTopOf="@id/wednesday_card">

        <TextView
            android:id="@+id/clear_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"
            />

    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/sunday_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@+id/top_box"
        app:layout_constraintLeft_toRightOf="@id/clear_card"
        app:layout_constraintRight_toLeftOf="@id/monday_card"
        app:layout_constraintBottom_toTopOf="@id/thursday_card">

        <TextView
            android:id="@+id/sunday_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"/>

    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/monday_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@id/top_box"
        app:layout_constraintLeft_toRightOf="@id/sunday_card"
        app:layout_constraintRight_toLeftOf="@id/tuesday_card"
        app:layout_constraintBottom_toTopOf="@id/friday_card">

        <TextView
            android:id="@+id/monday_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"/>

    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/tuesday_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@id/top_box"
        app:layout_constraintLeft_toRightOf="@id/monday_card"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toTopOf="@id/saturday_card">

        <TextView
            android:id="@+id/tuesday_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"/>

    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/wednesday_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@id/clear_card"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/thursday_card"
        app:layout_constraintBottom_toTopOf="@id/bottom_box">

        <TextView
            android:id="@+id/wednesday_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"/>

    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/thursday_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@id/sunday_card"
        app:layout_constraintLeft_toRightOf="@id/wednesday_card"
        app:layout_constraintRight_toRightOf="@id/friday_card"
        app:layout_constraintBottom_toTopOf="@id/bottom_box">

        <TextView
            android:id="@+id/thursday_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"/>

    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/friday_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@id/monday_card"
        app:layout_constraintLeft_toRightOf="@id/thursday_card"
        app:layout_constraintRight_toLeftOf="@id/saturday_card"
        app:layout_constraintBottom_toTopOf="@id/bottom_box">

        <TextView
            android:id="@+id/friday_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"/>

    </com.google.android.material.card.MaterialCardView>

    <com.google.android.material.card.MaterialCardView
        android:id="@+id/saturday_card"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        android:onClick="buttonClick"
        app:layout_constraintTop_toBottomOf="@id/tuesday_card"
        app:layout_constraintLeft_toRightOf="@id/friday_card"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toTopOf="@id/bottom_box">

        <TextView
            android:id="@+id/saturday_card_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:textSize="20sp"
            android:gravity="center"/>

    </com.google.android.material.card.MaterialCardView>

    <TextView
        android:id="@+id/bottom_box"
        android:layout_width="0dp"
        android:layout_height="90dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val plannerViewModel: PlannerViewModel by viewModels {
        PlannerViewModelFactory((application as PlannerApplication).repository)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val clearButtonText = binding.clearCardText
        val sundayButtonText = binding.sundayCardText
        val mondayButtonText = binding.mondayCardText
        val tuesdayButtonText = binding.tuesdayCardText
        val wednesdayButtonText = binding.wednesdayCardText
        val thursdayButtonText = binding.thursdayCardText
        val fridayButtonText = binding.fridayCardText
        val saturdayButtonText = binding.saturdayCardText

        // Setting day card names
        clearButtonText.text = "Clear"
        sundayButtonText.text = "Sun"
        mondayButtonText.text = "Mon"
        tuesdayButtonText.text = "Tue"
        wednesdayButtonText.text = "Wed"
        thursdayButtonText.text = "Thu"
        fridayButtonText.text = "Fri"
        saturdayButtonText.text = "Sat"
        sundayButtonText.text = "Sun"
    }

    private fun startWeekdayActivity(day: Weekday) {
        val intent = Intent(this, WeekdayActivity::class.java)
        intent.putExtra("dayId", day.id)
        this.startActivity(intent)
    }

    private fun clearDb() {
        val alertDialog: AlertDialog? = this?.let { outerIt ->
            val builder = AlertDialog.Builder(outerIt)
            builder.apply {
                setPositiveButton("Clear",
                        DialogInterface.OnClickListener { dialog, id ->

                            plannerViewModel.allTasks.observe(outerIt, {
                                if (it.count() == 0) {
                                    Toast.makeText(context, "No tasks to clear", Toast.LENGTH_SHORT).show()
                                }
                                else {
                                    plannerViewModel.deleteAllTasks()
                                    Toast.makeText(context, "Tasks cleared", Toast.LENGTH_SHORT).show()
                                }
                            })
                            })
                setNegativeButton("Cancel",
                        DialogInterface.OnClickListener { dialog, id ->
                            // User cancelled the dialog
                        })
            }
                    .setTitle("Clear tasks?")
                    .setMessage("Are you sure you want to clear the weeks tasks?")

            // Create the AlertDialog
            builder.create()
        }

                alertDialog?.show()
    }

    private fun checkDay(dayIn: String) {
        var dayOut: Weekday? = null
        plannerViewModel.allWeekdays.observe(this, { weekdays ->
            weekdays?.let {
                weekdays.forEach {
                        if (dayIn == "clear_card" && it.day == "Clear") {
                                clearDb()
                                dayOut = it
                        }
                    else {
                        val dayInAbr = dayIn.substring(0, 3).toLowerCase(Locale.ROOT)
                        val dayOutAbr = it.day.substring(0,3).toLowerCase(Locale.ROOT)

                        if (dayInAbr == dayOutAbr) {
                            dayOut = it
                            dayOut?.let { startWeekdayActivity(it) }
                        }
                    }

                    if (dayOut != null) {
                        return@let
                    }
                }
            }
        })
    }

    fun buttonClick(view: View) {
        when(view.id) {
            R.id.clear_card -> checkDay(view.context.resources.getResourceEntryName(R.id.clear_card).toString())
            R.id.sunday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.sunday_card).toString())
            R.id.monday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.monday_card).toString())
            R.id.tuesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.tuesday_card).toString())
            R.id.wednesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.wednesday_card).toString())
            R.id.thursday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.thursday_card).toString())
            R.id.friday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.friday_card).toString())
            R.id.saturday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.saturday_card).toString())
        }
    }
}

Solution

  • Question answered by Jay in the comments

    If you know a way for me to credit them for the SO points and reputation, let me know!

    I needed to stop calling more instances of observer every time checkDay() ran. Moving the observers to onCreate() so that they only run one time seems to have fixed my issue!

    Working Code for Main Activity

    class MainActivity : AppCompatActivity() {
        private lateinit var binding: ActivityMainBinding
        private val plannerViewModel: PlannerViewModel by viewModels {
            PlannerViewModelFactory((application as PlannerApplication).repository)
        }
    
        lateinit var weekdayList: List<Weekday>
        lateinit var taskList: List<Task>
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            val clearButtonText = binding.clearCardText
            val sundayButtonText = binding.sundayCardText
            val mondayButtonText = binding.mondayCardText
            val tuesdayButtonText = binding.tuesdayCardText
            val wednesdayButtonText = binding.wednesdayCardText
            val thursdayButtonText = binding.thursdayCardText
            val fridayButtonText = binding.fridayCardText
            val saturdayButtonText = binding.saturdayCardText
    
            // Setting day card names
            clearButtonText.text = "Clear"
            sundayButtonText.text = "Sun"
            mondayButtonText.text = "Mon"
            tuesdayButtonText.text = "Tue"
            wednesdayButtonText.text = "Wed"
            thursdayButtonText.text = "Thu"
            fridayButtonText.text = "Fri"
            saturdayButtonText.text = "Sat"
            sundayButtonText.text = "Sun"
    
            plannerViewModel.allTasks.observe(this, {
                taskList = it
            })
    
            plannerViewModel.allWeekdays.observe(this, {
                weekdayList = it
            })
        }
    
        private fun startWeekdayActivity(day: Weekday) {
            val intent = Intent(this, WeekdayActivity::class.java)
            intent.putExtra("dayId", day.id)
            this.startActivity(intent)
        }
    
        private fun clearDb(taskList: List<Task>) {
            val alertDialog: AlertDialog? = this?.let { outerIt ->
                val builder = AlertDialog.Builder(outerIt)
                builder.apply {
                    setPositiveButton("Clear",
                            DialogInterface.OnClickListener { dialog, id ->
                                if (taskList.count() == 0) {
                                    Toast.makeText(context, "No tasks to clear", Toast.LENGTH_SHORT).show()
                                } else {
                                    plannerViewModel.deleteAllTasks()
                                    Toast.makeText(context, "Tasks cleared", Toast.LENGTH_SHORT).show()
                                }
                            })
                    setNegativeButton("Cancel",
                            DialogInterface.OnClickListener { dialog, id ->
                                // User cancelled the dialog
                            })
                }
                        .setTitle("Clear tasks?")
                        .setMessage("Are you sure you want to clear the weeks tasks?")
    
                builder.create()
            }
    
            alertDialog?.show()
        }
    
        private fun checkDay(dayIn: String, weekdayList: List<Weekday>) {
            weekdayList.forEach {
                if (dayIn == "clear_card" && it.day == "Clear") {
                    clearDb(taskList)
                } else {
                    val dayInAbr = dayIn.substring(0, 3).toLowerCase(Locale.ROOT)
                    val dayOutAbr = it.day.substring(0, 3).toLowerCase(Locale.ROOT)
    
                    if (dayInAbr == dayOutAbr) {
                        startWeekdayActivity(it)
                    }
                }
            }
        }
    
        fun buttonClick(view: View) {
            when (view.id) {
                R.id.clear_card -> checkDay(view.context.resources.getResourceEntryName(R.id.clear_card).toString(), weekdayList)
                R.id.sunday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.sunday_card).toString(), weekdayList)
                R.id.monday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.monday_card).toString(), weekdayList)
                R.id.tuesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.tuesday_card).toString(), weekdayList)
                R.id.wednesday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.wednesday_card).toString(), weekdayList)
                R.id.thursday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.thursday_card).toString(), weekdayList)
                R.id.friday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.friday_card).toString(), weekdayList)
                R.id.saturday_card -> checkDay(view.context.resources.getResourceEntryName(R.id.saturday_card).toString(), weekdayList)
            }
        }
    }