Home > front end >  Observer is called multiple times when DialogFragment is opened with screen rotation changes
Observer is called multiple times when DialogFragment is opened with screen rotation changes

Time:11-17

I have an activity that implements an observer inside the OnCreate for a MutableLiveData variable that is in a ViewModel.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
    subscribeDialogFragmentManualInput()
}

private fun subscribeDialogFragmentManualInput() {
    this.sharedViewModel.inputBarcode.observe(this) { inputValue ->
        postInput(inputValue)
    }
}

My activity is always in landscape mode (default in Manifest) and when a button is pressed a DialogFragment is opened and it changes the rotation to Portrait, when it is closed the activity returns to Landscape mode.

private fun showInvoiceInputDialog() {
    val inputDialog = InvoiceInputDialogFragment
    val transaction = supportFragmentManager.beginTransaction()
    inputDialog.show(transaction, InvoiceInputDialogFragment.TAG)
}
class InvoiceInputDialogFragment : DialogFragment() {

    lateinit var binding: DialogFragmentInvoiceInputBinding
    private val sharedViewModel by sharedViewModel<ManualInvoiceInputViewModel>()
    private var invoiceInput: String = ""
...


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        setStyle(
            STYLE_NORMAL,
            R.style.FullScreenDialog
        )

        binding = DataBindingUtil.inflate(
            inflater,
            R.layout.dialog_fragment_invoice_input,
            container,
            false
        )

        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        ...

        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

        setViews()
    }

    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)

        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
        if(invoiceInput.isNotEmpty()) {
            sharedViewModel.sendInputInvoice(invoiceInput)
        }
    }

    private fun setViews() {
        binding.btConfirmInput.setOnClickListener {
            invoiceInput = binding.scannerManualInput.text
            dismiss()
        }
    }
}

This flow makes the observer triggered multiple times making my app having and undesirable comportment because the livedata result open a dialog and it is displayed multiple times.

I want to call postInput method only one time after DialogFragment is closed.

Thanks!

CodePudding user response:

LiveData is by default setup in near circular logic. This raises some problems when the view updates LiveData the LiveData will then want to update the view again, creating an infinite loop. Although you are doing it indirectly by provoking the Android Lifecycle to re-create your Activity and when its re-created it attaches another observer thus re-emitting the old value.

You have 2 options:

Save the fact that the dialog was already called in the savedInstanceState Bundle

overide fun onSaveInstanceState(savedInstanceState : Bundle?) {
// Save the user's current game state
 savedInstanceState.putBoolean("dialogCausedRecreate", dialogWasCreated);
 super.onSaveInstanceState(savedInstanceState); 
} 

//in onCreate
this.sharedViewModel.inputBarcode.observe(this) { inputValue ->
    if(!savedInstanceState.getBoolean("dialogCausedRecreate",false)){
         postInput(inputValue)
    }
}

OR

Create a new wrapper class that can keeps track if its been emitted. Here is an example LiveData prevent receive the last value when start observing

The accepted answer will have you wrap your barcode inside an Event class and that will allow you to tell if it already been emitted previously.

  • Related