无法使用观察者模式在 DialogFragment 和 Activity 之间进行通信?
Can't communication between DialogFragment and Activity using Observer pattern?
当你按下按钮打开一个单独的输入window,有一个功能来显示结果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"
}
}
(示例项目 Url : https://github.com/heukhyeon/DialogObserverPattern )
此示例代码适用于正常情况。但是,经过以下程序后吐司并没有漂浮起来。
- 开发者选项 - 不要保持活动启用
- 打开 TestPopup,然后输入您的文本。 (不要按确定按钮)
- 按主页按钮将应用移至后台
- 应用被系统杀掉
- 重新激活应用程序(如点击应用程序列表中的应用程序)
在这种情况下,我输入的文本保留在屏幕上,但当我按下 OK 按钮时没有任何反应。
我当然知道会发生这种情况,因为在 activity 结束时 activity 和对话框之间的观察关系结束了。
大多数代码使用 Activity 中该对话框的回调接口的实现来处理这种情况。
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()
}
}
但我认为这是一种不符合Observer模式的方式,我想尽可能使用Observer模式来实现。
我正在考虑从 FragmentManager 获取 Fragment 并在 onCreate 再次订阅,但我认为有更好的方法。
有人可以帮助我吗?
您对问题的理解是正确的,除了任何配置更改都会出现问题,包括屏幕旋转。您可以在不使用开发人员模式的情况下重现问题。例如试试这个:
- 打开 TestPopup,然后输入您的文本。 (不要按确定按钮)
- 旋转屏幕
- 看到 toast 消息没有弹出。
另请注意,您的 "observer pattern" 实施不是适当的观察者模式。观察者模式有一个 subject 和一个 observer。在您的实现中,activity 同时充当 subject 和 observer。对话框不参与此观察者模式,使用 .setOnDismissListener
只是另一种形式的侦听器模式。
为了在 Fragment(主题)和 Activity(观察者)之间实现观察者模式,Activity 需要按照您的建议使用 FragmentManager 获取 Fragment 的引用.我建议使用视图模型并在视图层和视图模型层之间建立观察者模式。
RxJava 示例:
//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()
}
}
当你按下按钮打开一个单独的输入window,有一个功能来显示结果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"
}
}
(示例项目 Url : https://github.com/heukhyeon/DialogObserverPattern )
此示例代码适用于正常情况。但是,经过以下程序后吐司并没有漂浮起来。
- 开发者选项 - 不要保持活动启用
- 打开 TestPopup,然后输入您的文本。 (不要按确定按钮)
- 按主页按钮将应用移至后台
- 应用被系统杀掉
- 重新激活应用程序(如点击应用程序列表中的应用程序)
在这种情况下,我输入的文本保留在屏幕上,但当我按下 OK 按钮时没有任何反应。
我当然知道会发生这种情况,因为在 activity 结束时 activity 和对话框之间的观察关系结束了。
大多数代码使用 Activity 中该对话框的回调接口的实现来处理这种情况。
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()
}
}
但我认为这是一种不符合Observer模式的方式,我想尽可能使用Observer模式来实现。
我正在考虑从 FragmentManager 获取 Fragment 并在 onCreate 再次订阅,但我认为有更好的方法。
有人可以帮助我吗?
您对问题的理解是正确的,除了任何配置更改都会出现问题,包括屏幕旋转。您可以在不使用开发人员模式的情况下重现问题。例如试试这个:
- 打开 TestPopup,然后输入您的文本。 (不要按确定按钮)
- 旋转屏幕
- 看到 toast 消息没有弹出。
另请注意,您的 "observer pattern" 实施不是适当的观察者模式。观察者模式有一个 subject 和一个 observer。在您的实现中,activity 同时充当 subject 和 observer。对话框不参与此观察者模式,使用 .setOnDismissListener
只是另一种形式的侦听器模式。
为了在 Fragment(主题)和 Activity(观察者)之间实现观察者模式,Activity 需要按照您的建议使用 FragmentManager 获取 Fragment 的引用.我建议使用视图模型并在视图层和视图模型层之间建立观察者模式。
RxJava 示例:
//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()
}
}