H.Kim
H.Kim

Reputation: 595

Can't communication between DialogFragment and Activity using Observer pattern?

When you press the button to open a separate input window, there is a function to display the results toast.

class MainActivity : AppCompatActivity() {

val disposable = CompositeDisposable()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    button.setOnClickListener {
        val f = TestPopup()
        usingRxJava(f)
        //usingLiveData(f)
    }
}

private fun usingRxJava(f: TestPopup) {
    val subject = SingleSubject.create<String>()
    f.show(supportFragmentManager, "TAG")
    button.post {
        f.dialog.setOnDismissListener {
            val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
            subject.onSuccess(str)
        }
    }
    subject.subscribe({
        Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
    }, {

    }).addTo(disposable)
}

private fun usingLiveData(f: TestPopup) {
    val liveData = MutableLiveData<String>()
    f.show(supportFragmentManager, "TAG")
    button.post {
        f.dialog.setOnDismissListener {
            val str = f.arguments?.getString(TestPopup.TEST_KEY) ?: ""
            liveData.postValue(str)
        }
    }
    liveData.observe(this, Observer {
        Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
    })
}

override fun onDestroy() {
    disposable.dispose()
    super.onDestroy()
}
}

DialogFragment

class TestPopup : DialogFragment() {

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.dialog_test, container, false)
}

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    button_test.setOnClickListener {
        val arg = Bundle()
        arg.putString(TEST_KEY, edit_test.text.toString())
        arguments = arg
        dismiss()
    }
}

companion object {
    const val TEST_KEY = "KEY"
}
}

(Sample Project Url : https://github.com/heukhyeon/DialogObserverPattern )

This sample code works in normal cases. However, the toast does not float after the following procedure.

  1. Developer Option - Dont'keep activities enable
  2. Open TestPopup, and enter your text. (Do not press OK button)
  3. Press the home button to move the app to the background
  4. The app is killed by the system.
  5. Reactivate the app (Like clicking on an app in the apps list)

In this case, the text I entered remains on the screen, but nothing happens when I press the OK button.

Of course I know this happens because at the end of the activity the observe relationship between the activity and the Dialog is over.

Most of the code uses the implementation of the callback interface for that Dialog in the Activity to handle that case.

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    button_test.setOnClickListener {
        val input = edit_test.text.toString()
        (activity as MyListener).inputComplete(input)
        dismiss()
    }
}

class MainActivity : AppCompatActivity(), TestPopup.MyListener {

override fun inputComplete(input: String) {
    Toast.makeText(this, "Accept : $input", Toast.LENGTH_SHORT).show()
}
}

But I think it's a way that doesn't match the Observer pattern, and I want to implement it using the Observer pattern as much as possible.

I'm thinking of getting a Fragment from the FragmentManager and subscribing again at onCreate, but I think there's a better way.

Can someone help me?

Upvotes: 0

Views: 472

Answers (1)

Sanlok Lee
Sanlok Lee

Reputation: 3494

Your understanding of the problem is correct, except that the problem happens with any configuration changes, including screen rotation. You can reproduce issue without using the developer mode. Try this for example:

  1. Open TestPopup, and enter your text. (Do not press OK button)
  2. Rotate screen
  3. See toast message not popping up.

Also note that your "observer pattern" implementation is not a proper observer pattern. Observer pattern has a subject and an observer. In your implementation, the activity is acting as both the subject and the observer. The dialog is not taking any part in this observer pattern, and using .setOnDismissListener is just another form of a listener pattern.

In order to implement observer pattern between the Fragment(the subject) and the Activity(the observer), the Activity needs to get the reference of the Fragment using the FragmentManager as you suggested. I suggest to use view model and establish observer pattern between view layer and view model layer instead.

RxJava example:

//MainViewModel.kt
class MainViewModel: ViewModel() {

    val dialogText = PublishProcessor.create<String>()

    fun postNewDialogText(text: String) {
        dialogText.onNext(text)
    }
}
// Activity
val disposable = CompositeDisposable()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)

    viewModel.dialogText.subscribe {
        Toast.makeText(this, "Accept : $it", Toast.LENGTH_SHORT).show()
    }.addTo(disposable)

    button.setOnClickListener {
        TestPopup().show(supportFragmentManager, "TAG")
        // usingRxJava(f)
        // usingLiveData(f)
    }
}

override fun onDestroy() {
    disposable.dispose()
    super.onDestroy()
}
// Dialog Fragment
override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    // Important!! use activity when getting the viewmodel.
    val viewModel = ViewModelProviders.of(requireActivity()).get(MainViewModel::class.java)

    button_test.setOnClickListener {
        viewModel.postNewDialogText(edit_test.text.toString())
        dismiss()
    }
}

Upvotes: 1

Related Questions