SlideShare a Scribd company logo
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

今さら聞けない!Active Directoryドメインサービス入門
今さら聞けない!Active Directoryドメインサービス入門今さら聞けない!Active Directoryドメインサービス入門
今さら聞けない!Active Directoryドメインサービス入門Trainocate Japan, Ltd.
 
How to Extend Axure's Animation Capability
How to Extend Axure's Animation CapabilityHow to Extend Axure's Animation Capability
How to Extend Axure's Animation CapabilitySvetlin Denkov
 
Rundeck: The missing tool
Rundeck: The missing toolRundeck: The missing tool
Rundeck: The missing toolArtur Martins
 
Introduction to CA Service Virtualization
Introduction to CA Service VirtualizationIntroduction to CA Service Virtualization
Introduction to CA Service VirtualizationCA Technologies
 
Azure Appservice WebAppsでWordPressサイトを構築すると 運用が劇的にラクになる話
Azure Appservice WebAppsでWordPressサイトを構築すると運用が劇的にラクになる話Azure Appservice WebAppsでWordPressサイトを構築すると運用が劇的にラクになる話
Azure Appservice WebAppsでWordPressサイトを構築すると 運用が劇的にラクになる話典子 松本
 
Kubescape single pane of glass
Kubescape   single pane of glassKubescape   single pane of glass
Kubescape single pane of glassLibbySchulze1
 
100.RED HAT SINGLE SIGN-ON
100.RED HAT SINGLE SIGN-ON100.RED HAT SINGLE SIGN-ON
100.RED HAT SINGLE SIGN-ONOpennaru, inc.
 
Qlik Cloud データカタログ機能のご紹介
Qlik Cloud データカタログ機能のご紹介Qlik Cloud データカタログ機能のご紹介
Qlik Cloud データカタログ機能のご紹介QlikPresalesJapan
 
Elastic Observability
Elastic Observability Elastic Observability
Elastic Observability FaithWestdorp
 
深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用
深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用
深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用Amazon Web Services
 
TECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバー
TECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバーTECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバー
TECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバーQlikPresalesJapan
 
The Kubernetes Operator Pattern - ContainerConf Nov 2017
The Kubernetes Operator Pattern - ContainerConf Nov 2017The Kubernetes Operator Pattern - ContainerConf Nov 2017
The Kubernetes Operator Pattern - ContainerConf Nov 2017Jakob Karalus
 
DevOps: Benefits & Future Trends
DevOps: Benefits & Future TrendsDevOps: Benefits & Future Trends
DevOps: Benefits & Future Trends9 series
 
API Test Automation using Karate.pdf
API Test Automation using Karate.pdfAPI Test Automation using Karate.pdf
API Test Automation using Karate.pdfVenessa Serrao
 
マイクロサービスと Red Hat Integration
マイクロサービスと Red Hat Integrationマイクロサービスと Red Hat Integration
マイクロサービスと Red Hat IntegrationKenta Kosugi
 
Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用
Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用
Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用QlikPresalesJapan
 

What's hot (20)

CKA_1st.pptx
CKA_1st.pptxCKA_1st.pptx
CKA_1st.pptx
 
Agile and Secure SDLC
Agile and Secure SDLCAgile and Secure SDLC
Agile and Secure SDLC
 
今さら聞けない!Active Directoryドメインサービス入門
今さら聞けない!Active Directoryドメインサービス入門今さら聞けない!Active Directoryドメインサービス入門
今さら聞けない!Active Directoryドメインサービス入門
 
How to Extend Axure's Animation Capability
How to Extend Axure's Animation CapabilityHow to Extend Axure's Animation Capability
How to Extend Axure's Animation Capability
 
DSOMM
DSOMMDSOMM
DSOMM
 
Rundeck: The missing tool
Rundeck: The missing toolRundeck: The missing tool
Rundeck: The missing tool
 
Introduction to CA Service Virtualization
Introduction to CA Service VirtualizationIntroduction to CA Service Virtualization
Introduction to CA Service Virtualization
 
Azure Appservice WebAppsでWordPressサイトを構築すると 運用が劇的にラクになる話
Azure Appservice WebAppsでWordPressサイトを構築すると運用が劇的にラクになる話Azure Appservice WebAppsでWordPressサイトを構築すると運用が劇的にラクになる話
Azure Appservice WebAppsでWordPressサイトを構築すると 運用が劇的にラクになる話
 
Kubescape single pane of glass
Kubescape   single pane of glassKubescape   single pane of glass
Kubescape single pane of glass
 
100.RED HAT SINGLE SIGN-ON
100.RED HAT SINGLE SIGN-ON100.RED HAT SINGLE SIGN-ON
100.RED HAT SINGLE SIGN-ON
 
Qlik Cloud データカタログ機能のご紹介
Qlik Cloud データカタログ機能のご紹介Qlik Cloud データカタログ機能のご紹介
Qlik Cloud データカタログ機能のご紹介
 
Docker Ecosystem on Azure
Docker Ecosystem on AzureDocker Ecosystem on Azure
Docker Ecosystem on Azure
 
Elastic Observability
Elastic Observability Elastic Observability
Elastic Observability
 
深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用
深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用
深探-IaC-(Infrastructure as Code-基礎設施即程式碼-)-在-AWS-上的應用
 
TECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバー
TECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバーTECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバー
TECHTALK 20200923 Qlik Sense+Qlik NPrinting でセルフサービスBIから定型帳票の配信までをカバー
 
The Kubernetes Operator Pattern - ContainerConf Nov 2017
The Kubernetes Operator Pattern - ContainerConf Nov 2017The Kubernetes Operator Pattern - ContainerConf Nov 2017
The Kubernetes Operator Pattern - ContainerConf Nov 2017
 
DevOps: Benefits & Future Trends
DevOps: Benefits & Future TrendsDevOps: Benefits & Future Trends
DevOps: Benefits & Future Trends
 
API Test Automation using Karate.pdf
API Test Automation using Karate.pdfAPI Test Automation using Karate.pdf
API Test Automation using Karate.pdf
 
マイクロサービスと Red Hat Integration
マイクロサービスと Red Hat Integrationマイクロサービスと Red Hat Integration
マイクロサービスと Red Hat Integration
 
Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用
Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用
Qlik Sense SaaSでソフトウェア開発ライフサイクルを活用
 

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

SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar
 
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTier1 app
 
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with StrimziStrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzisteffenkarlsson2
 
10 Essential Software Testing Tools You Need to Know About.pdf
10 Essential Software Testing Tools You Need to Know About.pdf10 Essential Software Testing Tools You Need to Know About.pdf
10 Essential Software Testing Tools You Need to Know About.pdfkalichargn70th171
 
Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...
Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...
Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...Abortion Clinic
 
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAGAI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAGAlluxio, Inc.
 
Advanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should KnowAdvanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should KnowPeter Caitens
 
Studiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting softwareStudiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting softwareinfo611746
 
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdfImplementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdfVictor Lopez
 
Benefits of Employee Monitoring Software
Benefits of  Employee Monitoring SoftwareBenefits of  Employee Monitoring Software
Benefits of Employee Monitoring SoftwareMera Monitor
 
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
KLARNA -  Language Models and Knowledge Graphs: A Systems ApproachKLARNA -  Language Models and Knowledge Graphs: A Systems Approach
KLARNA - Language Models and Knowledge Graphs: A Systems ApproachNeo4j
 
INGKA DIGITAL: Linked Metadata by Design
INGKA DIGITAL: Linked Metadata by DesignINGKA DIGITAL: Linked Metadata by Design
INGKA DIGITAL: Linked Metadata by DesignNeo4j
 
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...Anthony Dahanne
 
De mooiste recreatieve routes ontdekken met RouteYou en FME
De mooiste recreatieve routes ontdekken met RouteYou en FMEDe mooiste recreatieve routes ontdekken met RouteYou en FME
De mooiste recreatieve routes ontdekken met RouteYou en FMEJelle | Nordend
 
CompTIA Security+ (Study Notes) for cs.pdf
CompTIA Security+ (Study Notes) for cs.pdfCompTIA Security+ (Study Notes) for cs.pdf
CompTIA Security+ (Study Notes) for cs.pdfFurqanuddin10
 
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdfA Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdfkalichargn70th171
 
GraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysisGraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysisNeo4j
 
Into the Box 2024 - Keynote Day 2 Slides.pdf
Into the Box 2024 - Keynote Day 2 Slides.pdfInto the Box 2024 - Keynote Day 2 Slides.pdf
Into the Box 2024 - Keynote Day 2 Slides.pdfOrtus Solutions, Corp
 
Crafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM IntegrationCrafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM IntegrationWave PLM
 
How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?
How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?
How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?XfilesPro
 

Recently uploaded (20)

SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBroker
 
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
 
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with StrimziStrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi
StrimziCon 2024 - Transition to Apache Kafka on Kubernetes with Strimzi
 
10 Essential Software Testing Tools You Need to Know About.pdf
10 Essential Software Testing Tools You Need to Know About.pdf10 Essential Software Testing Tools You Need to Know About.pdf
10 Essential Software Testing Tools You Need to Know About.pdf
 
Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...
Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...
Abortion ^Clinic ^%[+971588192166''] Abortion Pill Al Ain (?@?) Abortion Pill...
 
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAGAI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
 
Advanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should KnowAdvanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should Know
 
Studiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting softwareStudiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting software
 
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdfImplementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
 
Benefits of Employee Monitoring Software
Benefits of  Employee Monitoring SoftwareBenefits of  Employee Monitoring Software
Benefits of Employee Monitoring Software
 
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
KLARNA -  Language Models and Knowledge Graphs: A Systems ApproachKLARNA -  Language Models and Knowledge Graphs: A Systems Approach
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
 
INGKA DIGITAL: Linked Metadata by Design
INGKA DIGITAL: Linked Metadata by DesignINGKA DIGITAL: Linked Metadata by Design
INGKA DIGITAL: Linked Metadata by Design
 
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
 
De mooiste recreatieve routes ontdekken met RouteYou en FME
De mooiste recreatieve routes ontdekken met RouteYou en FMEDe mooiste recreatieve routes ontdekken met RouteYou en FME
De mooiste recreatieve routes ontdekken met RouteYou en FME
 
CompTIA Security+ (Study Notes) for cs.pdf
CompTIA Security+ (Study Notes) for cs.pdfCompTIA Security+ (Study Notes) for cs.pdf
CompTIA Security+ (Study Notes) for cs.pdf
 
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdfA Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
 
GraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysisGraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysis
 
Into the Box 2024 - Keynote Day 2 Slides.pdf
Into the Box 2024 - Keynote Day 2 Slides.pdfInto the Box 2024 - Keynote Day 2 Slides.pdf
Into the Box 2024 - Keynote Day 2 Slides.pdf
 
Crafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM IntegrationCrafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM Integration
 
How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?
How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?
How Does XfilesPro Ensure Security While Sharing Documents in Salesforce?
 

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!