SlideShare a Scribd company logo
1 of 140
Download to read offline
Dialogs in MVVM
Uladzislau Yarmolin
What is it all about?
NOT another holy war about the architecture...
MVC MVP MVVM
Stop haunting me!

I can choose architecture
by myself!
...but how we integrated alert dialogs into our project with
MVVM architecture, implemented using Android Jetpack
stack.
Lets remind ourselves few important MVVM concepts.
John Gossman,
2005
Introduction to Model/View/
ViewModel pattern for building
WPF apps

https://epa.ms/1KThHI
MVC
Model
Notifications
Controller
Commands
Updates View state Controller manipulates the Model
View State
View
Controller updates View state
–John Gossman
“The View is almost always defined declaratively,
very often with a tool. By the nature of these tools
and declarative languages some view state that
MVC encodes in its View classes is not easy to
represent.”
MVVM
View Model
View State
Notifications
ViewModel
Data Binding and Commands
Send notifications Send notifications
ViewModel updates the Model
In Android
View State
Notifications
ViewModel
LiveData
SingleLiveEvent
ViewModel
SingleLiveEvent
• SingleLiveEvent is a LiveData which notifies only one observer;
SingleLiveEvent
• SingleLiveEvent is a LiveData which notifies only one observer;

• Sends only new updates after subscription;
SingleLiveEvent
• SingleLiveEvent is a LiveData which notifies only one observer;

• Sends only new updates after subscription;

• Only calls the observer if there's an explicit call to setValue() or call();
SingleLiveEvent
• SingleLiveEvent is a LiveData which notifies only one observer;

• Sends only new updates after subscription;

• Only calls the observer if there's an explicit call to setValue() or call();

• When used with Void generic type you can use singleLiveEvent#call() to
notify observer;
SingleLiveEvent
• Avoids a common problem with events: on configuration change (like
rotation) an update can be emitted if the observer is active;

• Used for events like navigation and Snackbar messages.
–John Gossman
“Model/View/ViewModel also relies on
one more thing: a general mechanism for
data binding.”
Android Databinding
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text=“@string/text“ />
Standard layout.xml
Android Databinding
<layout>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text=“@string/text" />
</layout>
Wrap with layout tag
Android Databinding
<layout>
<data>
<variable
name="viewModel"
type="com.epam.StubViewModel" />
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text=“@string/text” />
</layout>
Declare data
Android Databinding
<layout>
<data>
<variable
name="viewModel"
type="com.epam.StubViewModel" />
</data>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text=“@{viewModel.text}” />
</layout>
Bind to data
Android Databinding
Databinding plugin generates binding class from layout
public class ActivityMainBindingImpl extends ActivityMainBinding {
...
}
Android Databinding
Set content view in Activity
class ActivityMain : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityMainBinding>(
this,
R.layout.activity_main
)
}
}
Android Databinding
Setup binding in Activity
class ActivityMain : AppCompatActivity() {
private val viewModel = StubViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityMainBinding>(
this,
R.layout.activity_main
).also {
it.lifecycleOwner = this
it.viewModel = viewModel
}
}
}
In a nutshell
• View state belongs to ViewModel;
• View is connected with ViewModel via DataBinding.
Showing Dialogs
• How to implement dialog?

• How to control appearance of dialog?

• How to handle callbacks in ViewModel?
By given different answers on those questions we
produced 3 different solutions
Solution #1
Activity/Fragment
DialogFragment
ViewModel
SingleLiveEvent
EventBus
How to implement dialog?
Implement CommonDialogFragment
class CommonDialogFragment : DialogFragment() {
private val viewModel by viewModel<DialogViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = DataBindingUtil.inflate<ViewDataBinding>(
inflater,
R.layout.dialog_error,
it,
true
)?.apply {
lifecycleOwner = viewLifecycleOwner
setVariable(BR.viewModel, viewModel)
setVariable(BR.uiConfig, uiConfig)
}
}
And navigator for it
class DialogNavigator(private val fragmentManager: FragmentManager) {
}
And navigator for it
class DialogNavigator(private val fragmentManager: FragmentManager) {
private fun FragmentManager.isFragmentNotExist(tag: String) =
findFragmentByTag(tag) == null
}
And navigator for it
class DialogNavigator(private val fragmentManager: FragmentManager) {
fun hideDialog(tag: String) {
val fragment = fragmentManager.findFragmentByTag(tag)
if (fragment != null) {
fragmentManager.beginTransaction().remove(fragment).commit()
}
}
private fun FragmentManager.isFragmentNotExist(tag: String) =
findFragmentByTag(tag) == null
}
And navigator for it
class DialogNavigator(private val fragmentManager: FragmentManager) {
fun showDialog(tag: String, uiConfig: IDialogUiConfig) {
if (fragmentManager.isFragmentNotExist(tag)) {
CommonDialogFragment.newInstance(uiConfig).show(fragmentManager, tag)
}
}
fun hideDialog(tag: String) {
val fragment = fragmentManager.findFragmentByTag(tag)
if (fragment != null) {
fragmentManager.beginTransaction().remove(fragment).commit()
}
}
private fun FragmentManager.isFragmentNotExist(tag: String) =
findFragmentByTag(tag) == null
}
How to control appearance of
dialog?
Via SingleLiveEvent
}
class Solution1ViewModel(
service: FirstTryFailingService,
private val dialogEventBus: EventBus
) : BaseSolutionViewModel(service) {
Via SingleLiveEvent
}
class Solution1ViewModel(
service: FirstTryFailingService,
private val dialogEventBus: EventBus
) : BaseSolutionViewModel(service) {
val dialogControlEvent = SingleLiveEvent<DialogControlEvent>()
Via SingleLiveEvent
}
class Solution1ViewModel(
service: FirstTryFailingService,
private val dialogEventBus: EventBus
) : BaseSolutionViewModel(service) {
val dialogControlEvent = SingleLiveEvent<DialogControlEvent>()
override fun hideErrorDialog() {
dialogControlEvent.value = DialogControlEvent.Hide(DIALOG_TAG)
}
Via SingleLiveEvent
}
class Solution1ViewModel(
service: FirstTryFailingService,
private val dialogEventBus: EventBus
) : BaseSolutionViewModel(service) {
val dialogControlEvent = SingleLiveEvent<DialogControlEvent>()
override fun showErrorDialog() {
dialogControlEvent.value = DialogControlEvent.Show(
tag = DIALOG_TAG,
uiConfig = STANDARD_DIALOG_CONFIG
)
}
override fun hideErrorDialog() {
dialogControlEvent.value = DialogControlEvent.Hide(DIALOG_TAG)
}
Observe it in Activity
}
class Solution1Activity : AppCompatActivity() {
private val dialogNavigator: DialogNavigator by inject { parametersOf(this) }
private val viewModel by viewModel<Solution1ViewModel>()
Observe it in Activity
}
class Solution1Activity : AppCompatActivity() {
private val dialogNavigator: DialogNavigator by inject { parametersOf(this) }
private val viewModel by viewModel<Solution1ViewModel>()
private fun showOrHideDialog(event: DialogControlEvent) {
when (event) {
is Show -> dialogNavigator.showDialog(event.tag, event.uiConfig)
is Hide -> dialogNavigator.hideDialog(event.tag)
}
}
Observe it in Activity
}
class Solution1Activity : AppCompatActivity() {
private val dialogNavigator: DialogNavigator by inject { parametersOf(this) }
private val viewModel by viewModel<Solution1ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// inflate layout and setup binding
observe(viewModel.dialogControlEvent, ::showOrHideDialog)
}
private fun showOrHideDialog(event: DialogControlEvent) {
when (event) {
is Show -> dialogNavigator.showDialog(event.tag, event.uiConfig)
is Hide -> dialogNavigator.hideDialog(event.tag)
}
}
How to handle callbacks in
ViewModel?
Use EventBus: post in CommonDialogFragment
}
class CommonDialogFragment : DialogFragment() {
private val dialogEventBus by inject<EventBus>()
private val viewModel by viewModel<DialogViewModel>()
Use EventBus: post in CommonDialogFragment
}
class CommonDialogFragment : DialogFragment() {
private val dialogEventBus by inject<EventBus>()
private val viewModel by viewModel<DialogViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
observeEmptyEvent(viewModel.positiveButtonClickEvent) {
dialogEventBus.post(PositiveButtonClickEvent(tag!!))
}
}
And observe in ViewModel
}
class Solution1ViewModel(
service: FirstTryFailingService,
private val dialogEventBus: EventBus
) : BaseSolutionViewModel(service) {
And observe in ViewModel
}
class Solution1ViewModel(
service: FirstTryFailingService,
private val dialogEventBus: EventBus
) : BaseSolutionViewModel(service) {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPositiveButtonClick(event: PositiveButtonClickEvent) {
event.doIfTagMatches(DIALOG_TAG, ::onErrorRetry)
}
And observe in ViewModel
}
class Solution1ViewModel(
service: FirstTryFailingService,
private val dialogEventBus: EventBus
) : BaseSolutionViewModel(service) {
init {
dialogEventBus.register(this)
}
override fun onCleared() {
dialogEventBus.unregister(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPositiveButtonClick(event: PositiveButtonClickEvent) {
event.doIfTagMatches(DIALOG_TAG, ::onErrorRetry)
}
Activity/Fragment
Show dialog event Add DialogFragment Add DialogFragment
ViewModel
Assumesthatdialogvisible
Process killed and restored
FragmentManager
DialogFragmentstoredinthemanager
DialogFragment
Visibletotheuser
The "Duplicated State" problem
The “Abstraction" problem
ViewModel must be unaware of view. But in the described solution it is
aware of EventBus, which is an implementation detail of the view.
The "Scope" problem
To the solution to be highly re-usable and easy to integrate into any activity/
fragment we used EventBus for CommonDialogFragment and ViewModel
communication. 

EventBus has to live in the scope width enough for both
CommonDialogFragment and ViewModel i.e. in the singleton scope. Thus
we need to manage lifecycle manually to prevent potential leaks.
The "Expandability" problem
The "Expandability" problem
The "Expandability" problem
The "Expandability" problem
Solution Analysis
• "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the
CommonDialogFragment;
Solution Analysis
• "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the
CommonDialogFragment;

• "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of
CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel;
Solution Analysis
• "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the
CommonDialogFragment;

• "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of
CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel;

• "Scope" problem: to setup communication between dialog and ViewModel we use EventBus
which has to live in singleton scope. We need to manually handle lifecycle;
Solution Analysis
• "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the
CommonDialogFragment;

• "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of
CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel;

• "Scope" problem: to setup communication between dialog and ViewModel we use EventBus
which has to live in singleton scope. We need to manually handle lifecycle;

• "Expandability" problem: switching to embed view for error handling requires a lot of
modification. Why? It's just change of the view!
Solution Analysis
• "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the
CommonDialogFragment;

• "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of
CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel;

• "Scope" problem: to setup communication between dialog and ViewModel we use EventBus
which has to live in singleton scope. We need to manually handle lifecycle;

• "Expandability" problem: switching to embed view for error handling requires a lot of
modification. Why? It's just change of the view!

• "Expandability" problem: view logic is spread across both XML and fragment/activity :(
Solution #2
Activity/Fragment
DialogFragment
ViewModel
LiveData<Boolean>
EventBus
Solution #2
Activity/Fragment
DialogFragment
ViewModel
LiveData<Boolean>
EventBus
Has anyone noticed the change?
Solution #2
Activity/Fragment
DialogFragment
ViewModel
LiveData<Boolean>
EventBus
We now use LiveData
Solution #2
Activity/Fragment
DialogFragment
ViewModel
LiveData<Boolean>
EventBus
But that’s not all…
How to implement dialog?
The same: using CommonDialogFragment
But lets create another wrapper around DialogNavigator
class ErrorView(
private val lifecycleOwner: LifecycleOwner,
private val dialogNavigator: DialogNavigator,
private val dialogEventBus: EventBus
) : DefaultLifecycleObserver {
init {
lifecycleOwner.lifecycle.addObserver(this)
}
}
How to control appearance of
dialog?
Replace SingleLiveEvent with LiveData
}
class Solution2ViewModel(
service: FirstTryFailingService
) : BaseSolutionViewModel(service) {
Replace SingleLiveEvent with LiveData
}
class Solution2ViewModel(
service: FirstTryFailingService
) : BaseSolutionViewModel(service) {
Notice that if has initial state
val errorDialogConfig = STANDARD_DIALOG_CONFIG
Replace SingleLiveEvent with LiveData
}
class Solution2ViewModel(
service: FirstTryFailingService
) : BaseSolutionViewModel(service) {
val isDialogVisible = MutableLiveData<Boolean>(false)
Notice that if has initial state
val errorDialogConfig = STANDARD_DIALOG_CONFIG
Replace SingleLiveEvent with LiveData
}
class Solution2ViewModel(
service: FirstTryFailingService
) : BaseSolutionViewModel(service) {
val isDialogVisible = MutableLiveData<Boolean>(false)
override fun showErrorDialog() {
isDialogVisible.value = true
}
override fun hideErrorDialog() {
isDialogVisible.value = false
}
val errorDialogConfig = STANDARD_DIALOG_CONFIG
Observe it in ErrorView
}
class ErrorView(
private val lifecycleOwner: LifecycleOwner,
private val dialogNavigator: DialogNavigator,
private val dialogEventBus: EventBus
) : DefaultLifecycleObserver {
Observe it in ErrorView
}
class ErrorView(
private val lifecycleOwner: LifecycleOwner,
private val dialogNavigator: DialogNavigator,
private val dialogEventBus: EventBus
) : DefaultLifecycleObserver {
private fun bind(viewModel: Solution2ViewModel) {
with(lifecycleOwner) {
observe(viewModel.isDialogVisible, ::updateErrorDialogState)
}
}
private fun updateErrorDialogState(visible: Boolean) {
// show or hide dialog using dialogNavigator
// DialogUiConfig is being created here
}
Observe it in ErrorView
}
class ErrorView(
private val lifecycleOwner: LifecycleOwner,
private val dialogNavigator: DialogNavigator,
private val dialogEventBus: EventBus
) : DefaultLifecycleObserver {
var viewModel: Solution2ViewModel? = null
set(value) {
field = value
value?.let { bind(it) }
}
private fun bind(viewModel: Solution2ViewModel) {
with(lifecycleOwner) {
observe(viewModel.isDialogVisible, ::updateErrorDialogState)
}
}
private fun updateErrorDialogState(visible: Boolean) {
// show or hide dialog using dialogNavigator
// DialogUiConfig is being created here
}
How to handle callbacks in
ViewModel?
Observe events in ErrorView
}
class ErrorView(
private val lifecycleOwner: LifecycleOwner,
private val dialogNavigator: DialogNavigator,
private val dialogEventBus: EventBus
) : DefaultLifecycleObserver {
Observe events in ErrorView
}
class ErrorView(
private val lifecycleOwner: LifecycleOwner,
private val dialogNavigator: DialogNavigator,
private val dialogEventBus: EventBus
) : DefaultLifecycleObserver {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPositiveButtonClick(event: PositiveButtonClickEvent) {
event.doIfTagMatches(DIALOG_TAG) { viewModel?.onErrorRetry() }
}
Observe events in ErrorView
}
class ErrorView(
private val lifecycleOwner: LifecycleOwner,
private val dialogNavigator: DialogNavigator,
private val dialogEventBus: EventBus
) : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) {
dialogEventBus.register(this)
}
override fun onDestroy(owner: LifecycleOwner) {
dialogEventBus.unregister(this)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPositiveButtonClick(event: PositiveButtonClickEvent) {
event.doIfTagMatches(DIALOG_TAG) { viewModel?.onErrorRetry() }
}
Finally bind ErrorView to ViewModel in Activity
}
class Solution2Activity : AppCompatActivity() {
private val errorView: ErrorView by inject { parametersOf(this) }
private val viewModel by viewModel<Solution2ViewModel>()
Finally bind ErrorView to ViewModel in Activity
}
class Solution2Activity : AppCompatActivity() {
private val errorView: ErrorView by inject { parametersOf(this) }
private val viewModel by viewModel<Solution2ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// inflate layout and setup binding
errorView.viewModel = viewModel
}
Activity/Fragment
Dialog visible state Add DialogFragment Add DialogFragment
Process killed and restored
FragmentManager
DialogFragmentinthemanager
DialogFragment
Visibletotheuser
The "Duplicated State" problem - Solved
ViewModel
LiveDatastores"false"LiveDatastores"true"
NoFragment
Notvisible
Dialog invisible state Remove DialogFragment Remove DialogFragment
The "Abstraction" problem - Solved
To connect the helper ErrorView with ViewModel we use custom databinding
mechanism thus ViewModel is not aware of the view’s implementation
details anymore.
Solution Analysis
• "Duplicated State" problem: view state is stored only in ViewModel;
Solution Analysis
• "Duplicated State" problem: view state is stored only in ViewModel;

• "Abstraction" problem: ViewModel knows nothing about EventBus and connects with
ErrorView via databinding;
Solution Analysis
• "Duplicated State" problem: view state is stored only in ViewModel;

• "Abstraction" problem: ViewModel knows nothing about EventBus and connects with
ErrorView via databinding;

• "Scope" problem: to setup communication between dialog and ErrorView we use EventBus
which has to live in singleton scope. We need to handle lifecycle;
Solution Analysis
• "Duplicated State" problem: view state is stored only in ViewModel;

• "Abstraction" problem: ViewModel knows nothing about EventBus and connects with
ErrorView via databinding;

• "Scope" problem: to setup communication between dialog and ErrorView we use EventBus
which has to live in singleton scope. We need to handle lifecycle;

• "Expandability" problem: switching to embed view requires a lot of modification. Why? It's just
change of the view! :(
Solution Analysis
• "Duplicated State" problem: view state is stored only in ViewModel;

• "Abstraction" problem: ViewModel knows nothing about EventBus and connects with
ErrorView via databinding;

• "Scope" problem: to setup communication between dialog and ErrorView we use EventBus
which has to live in singleton scope. We need to handle lifecycle;

• "Expandability" problem: switching to embed view requires a lot of modification. Why? It's just
change of the view! :(

• "Expandability" problem: view logic is spread across both XML and fragment/activity :(
Solution #3
Activity/Fragment
ViewModel
LiveData<Boolean>
View
AlertDialog
Databinding
How to implement dialog?
As a custom View
}
class DialogShowingView @JvmOverloads constructor(
...
) : View(context, attrs) {
As a custom View
}
class DialogShowingView @JvmOverloads constructor(
...
) : View(context, attrs) {
private var dialog: Dialog
private var binding: ViewDataBinding
init {
binding = ...
dialog = ...
}
As a custom View
}
class DialogShowingView @JvmOverloads constructor(
...
) : View(context, attrs) {
private var dialog: Dialog
private var binding: ViewDataBinding
init {
binding = ...
dialog = ...
}
override fun setVisibility(visibility: Int) {
// Show or hide dialog
}
As a custom View
}
class DialogShowingView @JvmOverloads constructor(
...
) : View(context, attrs) {
var bindingData: Pair<DialogUiConfig?, IDialogViewModel?>? = null
set(value) {
// update binding
}
private var dialog: Dialog
private var binding: ViewDataBinding
init {
binding = ...
dialog = ...
}
override fun setVisibility(visibility: Int) {
// Show or hide dialog
}
And we need some BindingAdapters
@BindingAdapter(value = ["dialogConfig", "dialogViewModel"], requireAll = false)
fun DialogShowingView.bindTextAndActions(
dialogConfig: DialogUiConfig? = null,
dialogViewModel: IDialogViewModel? = null
) {
bindingData = Pair(dialogConfig, dialogViewModel)
}
@BindingAdapter("visibleOrGone")
fun View.visibleOrGone(isVisible: Boolean?) {
if (isVisible != null) {
visibility = if (isVisible) View.VISIBLE else View.GONE
}
}
How to control appearance of
dialog?
Exactly the same VM as in previous solution
class Solution3ViewModel(
service: FirstTryFailingService
) : BaseSolutionViewModel(service) {
val isDialogVisible = MutableLiveData<Boolean>(false)
val errorDialogConfig = STANDARD_DIALOG_CONFIG
override fun showErrorDialog() {
isDialogVisible.value = true
}
override fun hideErrorDialog() {
isDialogVisible.value = false
}
}
Bind View to it instead of observing in code
</layout>
<layout 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">
Bind View to it instead of observing in code
</layout>
<layout 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">
<data>
<variable
name="viewModel"
type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" />
</data>
<by.ve.dialogsbinding.ui.dialog.view.DialogShowingView/>
Bind View to it instead of observing in code
</layout>
<layout 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">
<data>
<variable
name="viewModel"
type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" />
</data>
<by.ve.dialogsbinding.ui.dialog.view.DialogShowingView
app:dialogConfig="@{viewModel.errorDialogConfig}"
app:visibleOrGone=“@{viewModel.isDialogVisible}"/>
How to handle callbacks in
ViewModel?
Adapt callbacks in ViewModel
override fun showErrorDialog() {
isDialogVisible.value = true
}
override fun hideErrorDialog() {
isDialogVisible.value = false
}
}
class Solution3ViewModel(
service: FirstTryFailingService
) : BaseSolutionViewModel(service) {
val isDialogVisible = MutableLiveData<Boolean>(false)
val errorDialogConfig = STANDARD_DIALOG_CONFIG
Adapt callbacks in ViewModel
override fun showErrorDialog() {
isDialogVisible.value = true
}
override fun hideErrorDialog() {
isDialogVisible.value = false
}
}
val errorDialogViewModel = DialogViewModel(
positiveClick = ::onErrorRetry,
negativeClick = ::onErrorCancel
)
class Solution3ViewModel(
service: FirstTryFailingService
) : BaseSolutionViewModel(service) {
val isDialogVisible = MutableLiveData<Boolean>(false)
val errorDialogConfig = STANDARD_DIALOG_CONFIG
Bind View to them
</layout>
<layout 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">
<data>
<variable
name="viewModel"
type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" />
</data>
<by.ve.dialogsbinding.ui.dialog.view.DialogShowingView
app:dialogConfig="@{viewModel.errorDialogConfig}"
app:visibleOrGone=“@{viewModel.isDialogVisible}"/>
Bind View to them
</layout>
<layout 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">
<data>
<variable
name="viewModel"
type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" />
</data>
<by.ve.dialogsbinding.ui.dialog.view.DialogShowingView
app:dialogConfig="@{viewModel.errorDialogConfig}"
app:visibleOrGone=“@{viewModel.isDialogVisible}"
app:dialogViewModel="@{viewModel.errorDialogViewModel}"/>
Setup binding in Activity, just like we did before
Notice that there’s now nothing more than binding setup.

Activity is very thin!
class Solution3Activity : AppCompatActivity() {
private val viewModel by viewModel<Solution3ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// inflate layout and setup binding
}
}
The "Scope" problem - Solved
We don’t use EventBus anymore. Databinding plugin manages lifecycle for
us.
The "Expandability" problem - Solved
R.layout.activity_solution3_embed.xml
R.layout.activity_solution3_dialog.xml
Solution Analysis
• "Duplicated State" problem: view state is stored only in viewmodel. Hooray!
Solution Analysis
• "Duplicated State" problem: view state is stored only in viewmodel. Hooray!

• "Abstraction" problem: ViewModel knows nothing about view and connects with
DialogShowingView via Android databinding. Hooray!
Solution Analysis
• "Duplicated State" problem: view state is stored only in viewmodel. Hooray!

• "Abstraction" problem: ViewModel knows nothing about view and connects with
DialogShowingView via Android databinding. Hooray!

• "Scope" problem: DialogShowingView and ViewModel communicate through the databinding
mechanism. Hooray!
Solution Analysis
• "Duplicated State" problem: view state is stored only in viewmodel. Hooray!

• "Abstraction" problem: ViewModel knows nothing about view and connects with
DialogShowingView via Android databinding. Hooray!

• "Scope" problem: DialogShowingView and ViewModel communicate through the databinding
mechanism. Hooray!

• "Expandability" problem: switching to embed view is just change of the XML. Hooray!
Solution Analysis
• "Duplicated State" problem: view state is stored only in viewmodel. Hooray!

• "Abstraction" problem: ViewModel knows nothing about view and connects with
DialogShowingView via Android databinding. Hooray!

• "Scope" problem: DialogShowingView and ViewModel communicate through the databinding
mechanism. Hooray!

• "Expandability" problem: switching to embed view is just change of the XML. Hooray!

• "Expandability" problem: just like any default view DialogShowingView is being setup in XML.
Hooray!
Issue Fix
If we use DialogShowingView within
Fragment then, upon replacing the Fragment
with another, alert dialog is not being
dismissed.
Hide the dialog in onDetachedFromWindow
method of the DialogShowingView.
Issue Fix
If we use DialogShowingView within
Fragment then, upon replacing the Fragment
with another, alert dialog is not being
dismissed.
Hide the dialog in onDetachedFromWindow
method of the DialogShowingView.
DialogShowingView effects layout hierarchy.
Override onMeasure to always return zero
dimensions.
Issue Fix
If we use DialogShowingView within
Fragment then, upon replacing the Fragment
with another, alert dialog is not being
dismissed.
Hide the dialog in onDetachedFromWindow
method of the DialogShowingView.
DialogShowingView effects layout hierarchy.
Override onMeasure to always return zero
dimensions.
DialogShowingView doesn't work within UI
less fragment.
There's no :(
Issue Fix
Bonus!
Showing Toasts
Everything is a view! Why not handle Toasts the
same way as Dialogs?
class ToastShowingView @JvmOverloads constructor(
) : View(context, attrs, defStyleAttr) {
private lateinit var toast: Toast
private lateinit var binding: ViewDataBinding
var bindingData: ToastViewModel? = null
set(value) {
// update binding
}
init {
binding = ...
toast = ...
}
fun show() {
toast.show()
}
}
ToastShowingView
@BindingAdapter("toastDisplaySignal")
fun ToastShowingView.show(signal: ToastDisplaySignal?) {
signal?.let { show() }
}
@BindingAdapter("toastViewModel")
fun ToastShowingView.bindViewModel(viewModel: ToastViewModel?) {
if (viewModel != null) {
bindingData = viewModel
}
}
And we need some BindingAdapters
@BindingAdapter("toastDisplaySignal")
fun ToastShowingView.show(signal: ToastDisplaySignal?) {
signal?.let { show() }
}
@BindingAdapter("toastViewModel")
fun ToastShowingView.bindViewModel(viewModel: ToastViewModel?) {
if (viewModel != null) {
bindingData = viewModel
}
}
And we need some BindingAdapters
Wait a sec…
@BindingAdapter("toastDisplaySignal")
fun ToastShowingView.show(signal: ToastDisplaySignal?) {
signal?.let { show() }
}
@BindingAdapter("toastViewModel")
fun ToastShowingView.bindViewModel(viewModel: ToastViewModel?) {
if (viewModel != null) {
bindingData = viewModel
}
}
And we need some BindingAdapters
What is ToastDisplaySignal?
How we use SingleLiveEvent?
showToastEvent.call()
What does it do?
class SingleLiveEvent<T> : MutableLiveData<T>() {
@MainThread
fun call() {
value = null
}
}
The “Default Value” problem
• SingleLiveEvent actually holds state;
The “Default Value” problem
• SingleLiveEvent actually holds state;

• When we use call() we set it to null;
The “Default Value” problem
• SingleLiveEvent actually holds state;

• When we use call() we set it to null;

• ViewDataBinding#executeBindings method is being called
for the first time earlier, then we set binding data to it;
The “Default Value” problem
• SingleLiveEvent actually holds state;

• When we use call() we set it to null;

• ViewDataBinding#executeBindings method is being called for the
first time earlier, then we set binding data to it;

• And thus it passes default value to the binding adapters. This
value is null;
The “Default Value” problem
So to distinguish use some object!
The “State” problem
Fragment 1
Toast
Fragment 2
Open
Press "back"
The “State” problem
• As we already know SingleLiveEvent actually holds state;
The “State” problem
• As we already know SingleLiveEvent actually holds state;

• ViewDataBinding#executeBindings is being executed each time we get
back to the first Fragment;
The “State” problem
• As we already know SingleLiveEvent actually holds state;

• ViewDataBinding#executeBindings is being executed each time we get
back to the first Fragment;

• It gets value of SingleLiveEvent via getter;
The “State” problem
• As we already know SingleLiveEvent actually holds state;

• ViewDataBinding#executeBindings is being executed each time we get back to
the first Fragment;

• It gets value of SingleLiveEvent via getter;

• That value is not null anymore as we’ve already shown the Toast;
The “State” problem
Make getter of SingleLiveEvent#value return stored
value just once per value set
TL;DR
• In MVVM architecture dialogs, toasts, popups, etc. can be setup in
layout.xml and managed as any other Android View;
TL;DR
• In MVVM architecture dialogs, toasts, popups, etc. can be setup in
layout.xml and managed as any other Android View;

• Helper views, which display them, must have zero dimensions;
TL;DR
• In MVVM architecture dialogs, toasts, popups, etc. can be setup in
layout.xml and managed as any other Android View;

• Helper views, which display them, must have zero dimensions;

• To use SingleLiveEvent with databinding you need to "fix" it first!
Demo
https://epa.ms/1LaSPM

More Related Content

What's hot

Performance Testing With Jmeter
Performance Testing With JmeterPerformance Testing With Jmeter
Performance Testing With JmeterAdam Goucher
 
Sistemas Distribuídos - Aula 01
Sistemas Distribuídos - Aula 01Sistemas Distribuídos - Aula 01
Sistemas Distribuídos - Aula 01Arthur Emanuel
 
Automation Tools Overview
Automation Tools OverviewAutomation Tools Overview
Automation Tools OverviewMurageppa-QA
 
Agile QA and Testing process
Agile QA and Testing processAgile QA and Testing process
Agile QA and Testing processGloria Stoilova
 
Functional Testing Tutorial | Edureka
Functional Testing Tutorial | EdurekaFunctional Testing Tutorial | Edureka
Functional Testing Tutorial | EdurekaEdureka!
 

What's hot (10)

Qualidade de Software
Qualidade de SoftwareQualidade de Software
Qualidade de Software
 
Performance Testing With Jmeter
Performance Testing With JmeterPerformance Testing With Jmeter
Performance Testing With Jmeter
 
Sistemas Distribuídos - Aula 01
Sistemas Distribuídos - Aula 01Sistemas Distribuídos - Aula 01
Sistemas Distribuídos - Aula 01
 
QA Process Overview
QA Process OverviewQA Process Overview
QA Process Overview
 
Test automation proposal
Test automation proposalTest automation proposal
Test automation proposal
 
ISO/IEC 15504
ISO/IEC 15504 ISO/IEC 15504
ISO/IEC 15504
 
Automation Tools Overview
Automation Tools OverviewAutomation Tools Overview
Automation Tools Overview
 
Agile QA and Testing process
Agile QA and Testing processAgile QA and Testing process
Agile QA and Testing process
 
Teste de software
Teste de softwareTeste de software
Teste de software
 
Functional Testing Tutorial | Edureka
Functional Testing Tutorial | EdurekaFunctional Testing Tutorial | Edureka
Functional Testing Tutorial | Edureka
 

Similar to Dialogs in Android MVVM (14.11.2019)

MVVM & Data Binding Library
MVVM & Data Binding Library MVVM & Data Binding Library
MVVM & Data Binding Library 10Clouds
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Mahmoud Hamed Mahmoud
 
Will your code blend? : Toronto Code Camp 2010 : Barry Gervin
Will your code blend? : Toronto Code Camp 2010 : Barry GervinWill your code blend? : Toronto Code Camp 2010 : Barry Gervin
Will your code blend? : Toronto Code Camp 2010 : Barry GervinBarry Gervin
 
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)NHN FORWARD
 
Knockoutjs databinding
Knockoutjs databindingKnockoutjs databinding
Knockoutjs databindingBoulos Dib
 
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.UA Mobile
 
Présentation et bonnes pratiques du pattern MVVM - MIC Belgique
Présentation et bonnes pratiques du pattern MVVM - MIC BelgiquePrésentation et bonnes pratiques du pattern MVVM - MIC Belgique
Présentation et bonnes pratiques du pattern MVVM - MIC BelgiqueDenis Voituron
 
Design Patterns in ZK: Java MVVM as Model-View-Binder
Design Patterns in ZK: Java MVVM as Model-View-BinderDesign Patterns in ZK: Java MVVM as Model-View-Binder
Design Patterns in ZK: Java MVVM as Model-View-BinderSimon Massey
 
Effective Android Data Binding
Effective Android Data BindingEffective Android Data Binding
Effective Android Data BindingEric Maxwell
 
Asp.net mvc training
Asp.net mvc trainingAsp.net mvc training
Asp.net mvc trainingicubesystem
 
Voo doodriver training
Voo doodriver trainingVoo doodriver training
Voo doodriver trainingSanjeev Sinha
 
MVVM Design Pattern NDC2009
MVVM Design Pattern NDC2009MVVM Design Pattern NDC2009
MVVM Design Pattern NDC2009Jonas Follesø
 
Data binding 入門淺談
Data binding 入門淺談Data binding 入門淺談
Data binding 入門淺談awonwon
 
Web Components for Java Developers
Web Components for Java DevelopersWeb Components for Java Developers
Web Components for Java DevelopersJoonas Lehtinen
 

Similar to Dialogs in Android MVVM (14.11.2019) (20)

MVVM & Data Binding Library
MVVM & Data Binding Library MVVM & Data Binding Library
MVVM & Data Binding Library
 
MVVM Lights
MVVM LightsMVVM Lights
MVVM Lights
 
Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development Windows Store app using XAML and C#: Enterprise Product Development
Windows Store app using XAML and C#: Enterprise Product Development
 
Will your code blend? : Toronto Code Camp 2010 : Barry Gervin
Will your code blend? : Toronto Code Camp 2010 : Barry GervinWill your code blend? : Toronto Code Camp 2010 : Barry Gervin
Will your code blend? : Toronto Code Camp 2010 : Barry Gervin
 
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
 
Asp.NET MVC
Asp.NET MVCAsp.NET MVC
Asp.NET MVC
 
Knockoutjs databinding
Knockoutjs databindingKnockoutjs databinding
Knockoutjs databinding
 
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
MVVM with Databinding and Google's new ViewModel. UA Mobile 2017.
 
Fundaments of Knockout js
Fundaments of Knockout jsFundaments of Knockout js
Fundaments of Knockout js
 
Présentation et bonnes pratiques du pattern MVVM - MIC Belgique
Présentation et bonnes pratiques du pattern MVVM - MIC BelgiquePrésentation et bonnes pratiques du pattern MVVM - MIC Belgique
Présentation et bonnes pratiques du pattern MVVM - MIC Belgique
 
Knockout.js
Knockout.jsKnockout.js
Knockout.js
 
Design Patterns in ZK: Java MVVM as Model-View-Binder
Design Patterns in ZK: Java MVVM as Model-View-BinderDesign Patterns in ZK: Java MVVM as Model-View-Binder
Design Patterns in ZK: Java MVVM as Model-View-Binder
 
Effective Android Data Binding
Effective Android Data BindingEffective Android Data Binding
Effective Android Data Binding
 
Valentine with AngularJS
Valentine with AngularJSValentine with AngularJS
Valentine with AngularJS
 
Asp.net mvc training
Asp.net mvc trainingAsp.net mvc training
Asp.net mvc training
 
Voo doodriver training
Voo doodriver trainingVoo doodriver training
Voo doodriver training
 
MVVM Design Pattern NDC2009
MVVM Design Pattern NDC2009MVVM Design Pattern NDC2009
MVVM Design Pattern NDC2009
 
Knockoutjs
KnockoutjsKnockoutjs
Knockoutjs
 
Data binding 入門淺談
Data binding 入門淺談Data binding 入門淺談
Data binding 入門淺談
 
Web Components for Java Developers
Web Components for Java DevelopersWeb Components for Java Developers
Web Components for Java Developers
 

Recently uploaded

SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxComplianceQuest1
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfkalichargn70th171
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about usDynamic Netsoft
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxbodapatigopi8531
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️anilsa9823
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 

Recently uploaded (20)

SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
 
Exploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the ProcessExploring iOS App Development: Simplifying the Process
Exploring iOS App Development: Simplifying the Process
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
DNT_Corporate presentation know about us
DNT_Corporate presentation know about usDNT_Corporate presentation know about us
DNT_Corporate presentation know about us
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 

Dialogs in Android MVVM (14.11.2019)

  • 2. What is it all about?
  • 3. NOT another holy war about the architecture... MVC MVP MVVM Stop haunting me!
 I can choose architecture by myself!
  • 4. ...but how we integrated alert dialogs into our project with MVVM architecture, implemented using Android Jetpack stack.
  • 5. Lets remind ourselves few important MVVM concepts.
  • 6. John Gossman, 2005 Introduction to Model/View/ ViewModel pattern for building WPF apps https://epa.ms/1KThHI
  • 7. MVC Model Notifications Controller Commands Updates View state Controller manipulates the Model View State View Controller updates View state
  • 8. –John Gossman “The View is almost always defined declaratively, very often with a tool. By the nature of these tools and declarative languages some view state that MVC encodes in its View classes is not easy to represent.”
  • 9. MVVM View Model View State Notifications ViewModel Data Binding and Commands Send notifications Send notifications ViewModel updates the Model
  • 11. SingleLiveEvent • SingleLiveEvent is a LiveData which notifies only one observer;
  • 12. SingleLiveEvent • SingleLiveEvent is a LiveData which notifies only one observer; • Sends only new updates after subscription;
  • 13. SingleLiveEvent • SingleLiveEvent is a LiveData which notifies only one observer; • Sends only new updates after subscription; • Only calls the observer if there's an explicit call to setValue() or call();
  • 14. SingleLiveEvent • SingleLiveEvent is a LiveData which notifies only one observer; • Sends only new updates after subscription; • Only calls the observer if there's an explicit call to setValue() or call(); • When used with Void generic type you can use singleLiveEvent#call() to notify observer;
  • 15. SingleLiveEvent • Avoids a common problem with events: on configuration change (like rotation) an update can be emitted if the observer is active; • Used for events like navigation and Snackbar messages.
  • 16. –John Gossman “Model/View/ViewModel also relies on one more thing: a general mechanism for data binding.”
  • 21. Android Databinding Databinding plugin generates binding class from layout public class ActivityMainBindingImpl extends ActivityMainBinding { ... }
  • 22. Android Databinding Set content view in Activity class ActivityMain : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main ) } }
  • 23. Android Databinding Setup binding in Activity class ActivityMain : AppCompatActivity() { private val viewModel = StubViewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) DataBindingUtil.setContentView<ActivityMainBinding>( this, R.layout.activity_main ).also { it.lifecycleOwner = this it.viewModel = viewModel } } }
  • 24. In a nutshell • View state belongs to ViewModel; • View is connected with ViewModel via DataBinding.
  • 26. • How to implement dialog? • How to control appearance of dialog? • How to handle callbacks in ViewModel?
  • 27. By given different answers on those questions we produced 3 different solutions
  • 29. How to implement dialog?
  • 30. Implement CommonDialogFragment class CommonDialogFragment : DialogFragment() { private val viewModel by viewModel<DialogViewModel>() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ) = DataBindingUtil.inflate<ViewDataBinding>( inflater, R.layout.dialog_error, it, true )?.apply { lifecycleOwner = viewLifecycleOwner setVariable(BR.viewModel, viewModel) setVariable(BR.uiConfig, uiConfig) } }
  • 31. And navigator for it class DialogNavigator(private val fragmentManager: FragmentManager) { }
  • 32. And navigator for it class DialogNavigator(private val fragmentManager: FragmentManager) { private fun FragmentManager.isFragmentNotExist(tag: String) = findFragmentByTag(tag) == null }
  • 33. And navigator for it class DialogNavigator(private val fragmentManager: FragmentManager) { fun hideDialog(tag: String) { val fragment = fragmentManager.findFragmentByTag(tag) if (fragment != null) { fragmentManager.beginTransaction().remove(fragment).commit() } } private fun FragmentManager.isFragmentNotExist(tag: String) = findFragmentByTag(tag) == null }
  • 34. And navigator for it class DialogNavigator(private val fragmentManager: FragmentManager) { fun showDialog(tag: String, uiConfig: IDialogUiConfig) { if (fragmentManager.isFragmentNotExist(tag)) { CommonDialogFragment.newInstance(uiConfig).show(fragmentManager, tag) } } fun hideDialog(tag: String) { val fragment = fragmentManager.findFragmentByTag(tag) if (fragment != null) { fragmentManager.beginTransaction().remove(fragment).commit() } } private fun FragmentManager.isFragmentNotExist(tag: String) = findFragmentByTag(tag) == null }
  • 35. How to control appearance of dialog?
  • 36. Via SingleLiveEvent } class Solution1ViewModel( service: FirstTryFailingService, private val dialogEventBus: EventBus ) : BaseSolutionViewModel(service) {
  • 37. Via SingleLiveEvent } class Solution1ViewModel( service: FirstTryFailingService, private val dialogEventBus: EventBus ) : BaseSolutionViewModel(service) { val dialogControlEvent = SingleLiveEvent<DialogControlEvent>()
  • 38. Via SingleLiveEvent } class Solution1ViewModel( service: FirstTryFailingService, private val dialogEventBus: EventBus ) : BaseSolutionViewModel(service) { val dialogControlEvent = SingleLiveEvent<DialogControlEvent>() override fun hideErrorDialog() { dialogControlEvent.value = DialogControlEvent.Hide(DIALOG_TAG) }
  • 39. Via SingleLiveEvent } class Solution1ViewModel( service: FirstTryFailingService, private val dialogEventBus: EventBus ) : BaseSolutionViewModel(service) { val dialogControlEvent = SingleLiveEvent<DialogControlEvent>() override fun showErrorDialog() { dialogControlEvent.value = DialogControlEvent.Show( tag = DIALOG_TAG, uiConfig = STANDARD_DIALOG_CONFIG ) } override fun hideErrorDialog() { dialogControlEvent.value = DialogControlEvent.Hide(DIALOG_TAG) }
  • 40. Observe it in Activity } class Solution1Activity : AppCompatActivity() { private val dialogNavigator: DialogNavigator by inject { parametersOf(this) } private val viewModel by viewModel<Solution1ViewModel>()
  • 41. Observe it in Activity } class Solution1Activity : AppCompatActivity() { private val dialogNavigator: DialogNavigator by inject { parametersOf(this) } private val viewModel by viewModel<Solution1ViewModel>() private fun showOrHideDialog(event: DialogControlEvent) { when (event) { is Show -> dialogNavigator.showDialog(event.tag, event.uiConfig) is Hide -> dialogNavigator.hideDialog(event.tag) } }
  • 42. Observe it in Activity } class Solution1Activity : AppCompatActivity() { private val dialogNavigator: DialogNavigator by inject { parametersOf(this) } private val viewModel by viewModel<Solution1ViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // inflate layout and setup binding observe(viewModel.dialogControlEvent, ::showOrHideDialog) } private fun showOrHideDialog(event: DialogControlEvent) { when (event) { is Show -> dialogNavigator.showDialog(event.tag, event.uiConfig) is Hide -> dialogNavigator.hideDialog(event.tag) } }
  • 43. How to handle callbacks in ViewModel?
  • 44. Use EventBus: post in CommonDialogFragment } class CommonDialogFragment : DialogFragment() { private val dialogEventBus by inject<EventBus>() private val viewModel by viewModel<DialogViewModel>()
  • 45. Use EventBus: post in CommonDialogFragment } class CommonDialogFragment : DialogFragment() { private val dialogEventBus by inject<EventBus>() private val viewModel by viewModel<DialogViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) observeEmptyEvent(viewModel.positiveButtonClickEvent) { dialogEventBus.post(PositiveButtonClickEvent(tag!!)) } }
  • 46. And observe in ViewModel } class Solution1ViewModel( service: FirstTryFailingService, private val dialogEventBus: EventBus ) : BaseSolutionViewModel(service) {
  • 47. And observe in ViewModel } class Solution1ViewModel( service: FirstTryFailingService, private val dialogEventBus: EventBus ) : BaseSolutionViewModel(service) { @Subscribe(threadMode = ThreadMode.MAIN) fun onPositiveButtonClick(event: PositiveButtonClickEvent) { event.doIfTagMatches(DIALOG_TAG, ::onErrorRetry) }
  • 48. And observe in ViewModel } class Solution1ViewModel( service: FirstTryFailingService, private val dialogEventBus: EventBus ) : BaseSolutionViewModel(service) { init { dialogEventBus.register(this) } override fun onCleared() { dialogEventBus.unregister(this) } @Subscribe(threadMode = ThreadMode.MAIN) fun onPositiveButtonClick(event: PositiveButtonClickEvent) { event.doIfTagMatches(DIALOG_TAG, ::onErrorRetry) }
  • 49. Activity/Fragment Show dialog event Add DialogFragment Add DialogFragment ViewModel Assumesthatdialogvisible Process killed and restored FragmentManager DialogFragmentstoredinthemanager DialogFragment Visibletotheuser The "Duplicated State" problem
  • 50. The “Abstraction" problem ViewModel must be unaware of view. But in the described solution it is aware of EventBus, which is an implementation detail of the view.
  • 51. The "Scope" problem To the solution to be highly re-usable and easy to integrate into any activity/ fragment we used EventBus for CommonDialogFragment and ViewModel communication. EventBus has to live in the scope width enough for both CommonDialogFragment and ViewModel i.e. in the singleton scope. Thus we need to manage lifecycle manually to prevent potential leaks.
  • 56. Solution Analysis • "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the CommonDialogFragment;
  • 57. Solution Analysis • "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the CommonDialogFragment; • "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel;
  • 58. Solution Analysis • "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the CommonDialogFragment; • "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel; • "Scope" problem: to setup communication between dialog and ViewModel we use EventBus which has to live in singleton scope. We need to manually handle lifecycle;
  • 59. Solution Analysis • "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the CommonDialogFragment; • "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel; • "Scope" problem: to setup communication between dialog and ViewModel we use EventBus which has to live in singleton scope. We need to manually handle lifecycle; • "Expandability" problem: switching to embed view for error handling requires a lot of modification. Why? It's just change of the view!
  • 60. Solution Analysis • "Duplicated State" problem: both ViewModel and FragmentManager control appearance of the CommonDialogFragment; • "Abstraction" problem: ViewModel knows about EventBus which is an implementation detail of CommongDialogFragment. But CommonDialogFragment is just a view for ViewModel; • "Scope" problem: to setup communication between dialog and ViewModel we use EventBus which has to live in singleton scope. We need to manually handle lifecycle; • "Expandability" problem: switching to embed view for error handling requires a lot of modification. Why? It's just change of the view! • "Expandability" problem: view logic is spread across both XML and fragment/activity :(
  • 65. How to implement dialog?
  • 66. The same: using CommonDialogFragment But lets create another wrapper around DialogNavigator class ErrorView( private val lifecycleOwner: LifecycleOwner, private val dialogNavigator: DialogNavigator, private val dialogEventBus: EventBus ) : DefaultLifecycleObserver { init { lifecycleOwner.lifecycle.addObserver(this) } }
  • 67. How to control appearance of dialog?
  • 68. Replace SingleLiveEvent with LiveData } class Solution2ViewModel( service: FirstTryFailingService ) : BaseSolutionViewModel(service) {
  • 69. Replace SingleLiveEvent with LiveData } class Solution2ViewModel( service: FirstTryFailingService ) : BaseSolutionViewModel(service) { Notice that if has initial state val errorDialogConfig = STANDARD_DIALOG_CONFIG
  • 70. Replace SingleLiveEvent with LiveData } class Solution2ViewModel( service: FirstTryFailingService ) : BaseSolutionViewModel(service) { val isDialogVisible = MutableLiveData<Boolean>(false) Notice that if has initial state val errorDialogConfig = STANDARD_DIALOG_CONFIG
  • 71. Replace SingleLiveEvent with LiveData } class Solution2ViewModel( service: FirstTryFailingService ) : BaseSolutionViewModel(service) { val isDialogVisible = MutableLiveData<Boolean>(false) override fun showErrorDialog() { isDialogVisible.value = true } override fun hideErrorDialog() { isDialogVisible.value = false } val errorDialogConfig = STANDARD_DIALOG_CONFIG
  • 72. Observe it in ErrorView } class ErrorView( private val lifecycleOwner: LifecycleOwner, private val dialogNavigator: DialogNavigator, private val dialogEventBus: EventBus ) : DefaultLifecycleObserver {
  • 73. Observe it in ErrorView } class ErrorView( private val lifecycleOwner: LifecycleOwner, private val dialogNavigator: DialogNavigator, private val dialogEventBus: EventBus ) : DefaultLifecycleObserver { private fun bind(viewModel: Solution2ViewModel) { with(lifecycleOwner) { observe(viewModel.isDialogVisible, ::updateErrorDialogState) } } private fun updateErrorDialogState(visible: Boolean) { // show or hide dialog using dialogNavigator // DialogUiConfig is being created here }
  • 74. Observe it in ErrorView } class ErrorView( private val lifecycleOwner: LifecycleOwner, private val dialogNavigator: DialogNavigator, private val dialogEventBus: EventBus ) : DefaultLifecycleObserver { var viewModel: Solution2ViewModel? = null set(value) { field = value value?.let { bind(it) } } private fun bind(viewModel: Solution2ViewModel) { with(lifecycleOwner) { observe(viewModel.isDialogVisible, ::updateErrorDialogState) } } private fun updateErrorDialogState(visible: Boolean) { // show or hide dialog using dialogNavigator // DialogUiConfig is being created here }
  • 75. How to handle callbacks in ViewModel?
  • 76. Observe events in ErrorView } class ErrorView( private val lifecycleOwner: LifecycleOwner, private val dialogNavigator: DialogNavigator, private val dialogEventBus: EventBus ) : DefaultLifecycleObserver {
  • 77. Observe events in ErrorView } class ErrorView( private val lifecycleOwner: LifecycleOwner, private val dialogNavigator: DialogNavigator, private val dialogEventBus: EventBus ) : DefaultLifecycleObserver { @Subscribe(threadMode = ThreadMode.MAIN) fun onPositiveButtonClick(event: PositiveButtonClickEvent) { event.doIfTagMatches(DIALOG_TAG) { viewModel?.onErrorRetry() } }
  • 78. Observe events in ErrorView } class ErrorView( private val lifecycleOwner: LifecycleOwner, private val dialogNavigator: DialogNavigator, private val dialogEventBus: EventBus ) : DefaultLifecycleObserver { override fun onCreate(owner: LifecycleOwner) { dialogEventBus.register(this) } override fun onDestroy(owner: LifecycleOwner) { dialogEventBus.unregister(this) } @Subscribe(threadMode = ThreadMode.MAIN) fun onPositiveButtonClick(event: PositiveButtonClickEvent) { event.doIfTagMatches(DIALOG_TAG) { viewModel?.onErrorRetry() } }
  • 79. Finally bind ErrorView to ViewModel in Activity } class Solution2Activity : AppCompatActivity() { private val errorView: ErrorView by inject { parametersOf(this) } private val viewModel by viewModel<Solution2ViewModel>()
  • 80. Finally bind ErrorView to ViewModel in Activity } class Solution2Activity : AppCompatActivity() { private val errorView: ErrorView by inject { parametersOf(this) } private val viewModel by viewModel<Solution2ViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // inflate layout and setup binding errorView.viewModel = viewModel }
  • 81. Activity/Fragment Dialog visible state Add DialogFragment Add DialogFragment Process killed and restored FragmentManager DialogFragmentinthemanager DialogFragment Visibletotheuser The "Duplicated State" problem - Solved ViewModel LiveDatastores"false"LiveDatastores"true" NoFragment Notvisible Dialog invisible state Remove DialogFragment Remove DialogFragment
  • 82. The "Abstraction" problem - Solved To connect the helper ErrorView with ViewModel we use custom databinding mechanism thus ViewModel is not aware of the view’s implementation details anymore.
  • 83. Solution Analysis • "Duplicated State" problem: view state is stored only in ViewModel;
  • 84. Solution Analysis • "Duplicated State" problem: view state is stored only in ViewModel; • "Abstraction" problem: ViewModel knows nothing about EventBus and connects with ErrorView via databinding;
  • 85. Solution Analysis • "Duplicated State" problem: view state is stored only in ViewModel; • "Abstraction" problem: ViewModel knows nothing about EventBus and connects with ErrorView via databinding; • "Scope" problem: to setup communication between dialog and ErrorView we use EventBus which has to live in singleton scope. We need to handle lifecycle;
  • 86. Solution Analysis • "Duplicated State" problem: view state is stored only in ViewModel; • "Abstraction" problem: ViewModel knows nothing about EventBus and connects with ErrorView via databinding; • "Scope" problem: to setup communication between dialog and ErrorView we use EventBus which has to live in singleton scope. We need to handle lifecycle; • "Expandability" problem: switching to embed view requires a lot of modification. Why? It's just change of the view! :(
  • 87. Solution Analysis • "Duplicated State" problem: view state is stored only in ViewModel; • "Abstraction" problem: ViewModel knows nothing about EventBus and connects with ErrorView via databinding; • "Scope" problem: to setup communication between dialog and ErrorView we use EventBus which has to live in singleton scope. We need to handle lifecycle; • "Expandability" problem: switching to embed view requires a lot of modification. Why? It's just change of the view! :( • "Expandability" problem: view logic is spread across both XML and fragment/activity :(
  • 89. How to implement dialog?
  • 90. As a custom View } class DialogShowingView @JvmOverloads constructor( ... ) : View(context, attrs) {
  • 91. As a custom View } class DialogShowingView @JvmOverloads constructor( ... ) : View(context, attrs) { private var dialog: Dialog private var binding: ViewDataBinding init { binding = ... dialog = ... }
  • 92. As a custom View } class DialogShowingView @JvmOverloads constructor( ... ) : View(context, attrs) { private var dialog: Dialog private var binding: ViewDataBinding init { binding = ... dialog = ... } override fun setVisibility(visibility: Int) { // Show or hide dialog }
  • 93. As a custom View } class DialogShowingView @JvmOverloads constructor( ... ) : View(context, attrs) { var bindingData: Pair<DialogUiConfig?, IDialogViewModel?>? = null set(value) { // update binding } private var dialog: Dialog private var binding: ViewDataBinding init { binding = ... dialog = ... } override fun setVisibility(visibility: Int) { // Show or hide dialog }
  • 94. And we need some BindingAdapters @BindingAdapter(value = ["dialogConfig", "dialogViewModel"], requireAll = false) fun DialogShowingView.bindTextAndActions( dialogConfig: DialogUiConfig? = null, dialogViewModel: IDialogViewModel? = null ) { bindingData = Pair(dialogConfig, dialogViewModel) } @BindingAdapter("visibleOrGone") fun View.visibleOrGone(isVisible: Boolean?) { if (isVisible != null) { visibility = if (isVisible) View.VISIBLE else View.GONE } }
  • 95. How to control appearance of dialog?
  • 96. Exactly the same VM as in previous solution class Solution3ViewModel( service: FirstTryFailingService ) : BaseSolutionViewModel(service) { val isDialogVisible = MutableLiveData<Boolean>(false) val errorDialogConfig = STANDARD_DIALOG_CONFIG override fun showErrorDialog() { isDialogVisible.value = true } override fun hideErrorDialog() { isDialogVisible.value = false } }
  • 97. Bind View to it instead of observing in code </layout> <layout 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">
  • 98. Bind View to it instead of observing in code </layout> <layout 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"> <data> <variable name="viewModel" type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" /> </data> <by.ve.dialogsbinding.ui.dialog.view.DialogShowingView/>
  • 99. Bind View to it instead of observing in code </layout> <layout 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"> <data> <variable name="viewModel" type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" /> </data> <by.ve.dialogsbinding.ui.dialog.view.DialogShowingView app:dialogConfig="@{viewModel.errorDialogConfig}" app:visibleOrGone=“@{viewModel.isDialogVisible}"/>
  • 100. How to handle callbacks in ViewModel?
  • 101. Adapt callbacks in ViewModel override fun showErrorDialog() { isDialogVisible.value = true } override fun hideErrorDialog() { isDialogVisible.value = false } } class Solution3ViewModel( service: FirstTryFailingService ) : BaseSolutionViewModel(service) { val isDialogVisible = MutableLiveData<Boolean>(false) val errorDialogConfig = STANDARD_DIALOG_CONFIG
  • 102. Adapt callbacks in ViewModel override fun showErrorDialog() { isDialogVisible.value = true } override fun hideErrorDialog() { isDialogVisible.value = false } } val errorDialogViewModel = DialogViewModel( positiveClick = ::onErrorRetry, negativeClick = ::onErrorCancel ) class Solution3ViewModel( service: FirstTryFailingService ) : BaseSolutionViewModel(service) { val isDialogVisible = MutableLiveData<Boolean>(false) val errorDialogConfig = STANDARD_DIALOG_CONFIG
  • 103. Bind View to them </layout> <layout 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"> <data> <variable name="viewModel" type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" /> </data> <by.ve.dialogsbinding.ui.dialog.view.DialogShowingView app:dialogConfig="@{viewModel.errorDialogConfig}" app:visibleOrGone=“@{viewModel.isDialogVisible}"/>
  • 104. Bind View to them </layout> <layout 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"> <data> <variable name="viewModel" type="by.ve.dialogsbinding.ui.demo.dialog.solution3.Solution3ViewModel" /> </data> <by.ve.dialogsbinding.ui.dialog.view.DialogShowingView app:dialogConfig="@{viewModel.errorDialogConfig}" app:visibleOrGone=“@{viewModel.isDialogVisible}" app:dialogViewModel="@{viewModel.errorDialogViewModel}"/>
  • 105. Setup binding in Activity, just like we did before Notice that there’s now nothing more than binding setup. Activity is very thin! class Solution3Activity : AppCompatActivity() { private val viewModel by viewModel<Solution3ViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // inflate layout and setup binding } }
  • 106. The "Scope" problem - Solved We don’t use EventBus anymore. Databinding plugin manages lifecycle for us.
  • 107. The "Expandability" problem - Solved R.layout.activity_solution3_embed.xml R.layout.activity_solution3_dialog.xml
  • 108. Solution Analysis • "Duplicated State" problem: view state is stored only in viewmodel. Hooray!
  • 109. Solution Analysis • "Duplicated State" problem: view state is stored only in viewmodel. Hooray! • "Abstraction" problem: ViewModel knows nothing about view and connects with DialogShowingView via Android databinding. Hooray!
  • 110. Solution Analysis • "Duplicated State" problem: view state is stored only in viewmodel. Hooray! • "Abstraction" problem: ViewModel knows nothing about view and connects with DialogShowingView via Android databinding. Hooray! • "Scope" problem: DialogShowingView and ViewModel communicate through the databinding mechanism. Hooray!
  • 111. Solution Analysis • "Duplicated State" problem: view state is stored only in viewmodel. Hooray! • "Abstraction" problem: ViewModel knows nothing about view and connects with DialogShowingView via Android databinding. Hooray! • "Scope" problem: DialogShowingView and ViewModel communicate through the databinding mechanism. Hooray! • "Expandability" problem: switching to embed view is just change of the XML. Hooray!
  • 112. Solution Analysis • "Duplicated State" problem: view state is stored only in viewmodel. Hooray! • "Abstraction" problem: ViewModel knows nothing about view and connects with DialogShowingView via Android databinding. Hooray! • "Scope" problem: DialogShowingView and ViewModel communicate through the databinding mechanism. Hooray! • "Expandability" problem: switching to embed view is just change of the XML. Hooray! • "Expandability" problem: just like any default view DialogShowingView is being setup in XML. Hooray!
  • 113.
  • 115. If we use DialogShowingView within Fragment then, upon replacing the Fragment with another, alert dialog is not being dismissed. Hide the dialog in onDetachedFromWindow method of the DialogShowingView. Issue Fix
  • 116. If we use DialogShowingView within Fragment then, upon replacing the Fragment with another, alert dialog is not being dismissed. Hide the dialog in onDetachedFromWindow method of the DialogShowingView. DialogShowingView effects layout hierarchy. Override onMeasure to always return zero dimensions. Issue Fix
  • 117. If we use DialogShowingView within Fragment then, upon replacing the Fragment with another, alert dialog is not being dismissed. Hide the dialog in onDetachedFromWindow method of the DialogShowingView. DialogShowingView effects layout hierarchy. Override onMeasure to always return zero dimensions. DialogShowingView doesn't work within UI less fragment. There's no :( Issue Fix
  • 119. Everything is a view! Why not handle Toasts the same way as Dialogs?
  • 120. class ToastShowingView @JvmOverloads constructor( ) : View(context, attrs, defStyleAttr) { private lateinit var toast: Toast private lateinit var binding: ViewDataBinding var bindingData: ToastViewModel? = null set(value) { // update binding } init { binding = ... toast = ... } fun show() { toast.show() } } ToastShowingView
  • 121. @BindingAdapter("toastDisplaySignal") fun ToastShowingView.show(signal: ToastDisplaySignal?) { signal?.let { show() } } @BindingAdapter("toastViewModel") fun ToastShowingView.bindViewModel(viewModel: ToastViewModel?) { if (viewModel != null) { bindingData = viewModel } } And we need some BindingAdapters
  • 122. @BindingAdapter("toastDisplaySignal") fun ToastShowingView.show(signal: ToastDisplaySignal?) { signal?.let { show() } } @BindingAdapter("toastViewModel") fun ToastShowingView.bindViewModel(viewModel: ToastViewModel?) { if (viewModel != null) { bindingData = viewModel } } And we need some BindingAdapters Wait a sec…
  • 123. @BindingAdapter("toastDisplaySignal") fun ToastShowingView.show(signal: ToastDisplaySignal?) { signal?.let { show() } } @BindingAdapter("toastViewModel") fun ToastShowingView.bindViewModel(viewModel: ToastViewModel?) { if (viewModel != null) { bindingData = viewModel } } And we need some BindingAdapters What is ToastDisplaySignal?
  • 124. How we use SingleLiveEvent? showToastEvent.call()
  • 125. What does it do? class SingleLiveEvent<T> : MutableLiveData<T>() { @MainThread fun call() { value = null } }
  • 126. The “Default Value” problem • SingleLiveEvent actually holds state;
  • 127. The “Default Value” problem • SingleLiveEvent actually holds state; • When we use call() we set it to null;
  • 128. The “Default Value” problem • SingleLiveEvent actually holds state; • When we use call() we set it to null; • ViewDataBinding#executeBindings method is being called for the first time earlier, then we set binding data to it;
  • 129. The “Default Value” problem • SingleLiveEvent actually holds state; • When we use call() we set it to null; • ViewDataBinding#executeBindings method is being called for the first time earlier, then we set binding data to it; • And thus it passes default value to the binding adapters. This value is null;
  • 130. The “Default Value” problem So to distinguish use some object!
  • 131. The “State” problem Fragment 1 Toast Fragment 2 Open Press "back"
  • 132. The “State” problem • As we already know SingleLiveEvent actually holds state;
  • 133. The “State” problem • As we already know SingleLiveEvent actually holds state; • ViewDataBinding#executeBindings is being executed each time we get back to the first Fragment;
  • 134. The “State” problem • As we already know SingleLiveEvent actually holds state; • ViewDataBinding#executeBindings is being executed each time we get back to the first Fragment; • It gets value of SingleLiveEvent via getter;
  • 135. The “State” problem • As we already know SingleLiveEvent actually holds state; • ViewDataBinding#executeBindings is being executed each time we get back to the first Fragment; • It gets value of SingleLiveEvent via getter; • That value is not null anymore as we’ve already shown the Toast;
  • 136. The “State” problem Make getter of SingleLiveEvent#value return stored value just once per value set
  • 137. TL;DR • In MVVM architecture dialogs, toasts, popups, etc. can be setup in layout.xml and managed as any other Android View;
  • 138. TL;DR • In MVVM architecture dialogs, toasts, popups, etc. can be setup in layout.xml and managed as any other Android View; • Helper views, which display them, must have zero dimensions;
  • 139. TL;DR • In MVVM architecture dialogs, toasts, popups, etc. can be setup in layout.xml and managed as any other Android View; • Helper views, which display them, must have zero dimensions; • To use SingleLiveEvent with databinding you need to "fix" it first!