implementation "com.vladmarkovic.briefactions:briefactions:$briefActionsVersion"
Benefits
Why use brief-actions library pattern:
- Prevent short-term actions reoccurring (i.e. on screen rotation)
- Easy, readable and intuitive way to call display and navigation actions
- Code traceability
- Default
observe
function allows only one observer to subscribe.
On Android, in combination with LiveData, ViewModel is more than what it might be on some other platforms. It also represents an architectural component for easy state management across view lifecycle events. When an Activity or Fragment gets recreated, subscribing to observe LiveData gives us the latest value, thus orientation changes are easy to manage. But sometimes this is not desirable. Some state delivered by certain action might be only relevant at the time when the action happened. For instance, navigating to another screen, or displaying a non-persistent message. In these cases we would not want another screen to open again or another message to show again when we rotate the device (value is preserved in LiveData).
BriefAction type represents these actions and in combination with LiveAction, helper observers, ViewModel and the rest of the pattern, enables consuming of such actions only once.
The beauty of this pattern is in its easy use and the way it nicely and intuitively reads for initiating display or navigation actions from a ViewModel simply by calling:.
display(ShowMessage(R.string.hello))
ornavigateTo(SettingsScreen)
or from Activity / Fragment
viewModel.display(ShowMessage(R.string.hello))
orviewModel.navigateTo(SettingsScreen)
Another benefit is improving code traceability, which is often a disadvantage of MVVM comparing to MVP.
Using command+tap on the defined action, we can trace where it is used or handled.
Also, by default observe
function allows only one observer to subscribe for observing display or navigation actions. This ensures prevention of opening the same screen or showing of same message multiple times. If you want you can override this behaviour by passing true
as the last function param.
Use
- Extend the library ViewModel when you create your view model.
- Create your BriefActions (inside VM for scoping or outside).
- Call them with
display
ornavigateTo
functions defined in the ViewModel.
You can call them directly from the view model, or from Activity / Fragment. - Observe them inside Activity / Fragment.
Important Note:
DisplayAction and NavigationAction are sent using LiveAction which by default allows for only one observer to subscribe with the defaultobserve
function. If you want to enable multiple observers, you will need to explicitly passtrue
for the lastadd
param.
So:viewModel.displayActions.observe(lifecycleOwner: LifecycleOwner, observer: Observer<in A>)
is equivalent tosetObserver
viewModel.displayActions.observe(lifecycleOwner: LifecycleOwner, observer: Observer<in A>, add = true)
is equivalent toaddObserver
(same forviewModel.navigationActions
)
Just be aware that:- if you
add
several observers and thenset
one after them, it will remove all the previously added ones. - if you
set
an observer and thenadd
one after, there will be two subscribed.
Example
class MyViewModel(private val auth: SomeAuth) : ViewModel() {
//...
fun login(userId: String, password: String) {
if (userId.isNotBlank() && password.isNotBlank()) {
signIn(userId, password)
} else {
display(ShowMessage("Please fill out all the fields."))
}
}
private fun signIn(userId: String, password: String) {
// helper extension function for launching a coroutine
launchHandleOnMain {
suspended = {
success = auth.signIn(userId, password)
if (success) navigateTo(DashboardScreen(userId))
else navigateTo(SignUpScreen)
},
onError = { error ->
display(error.message)
}
}
}
sealed class MyNavigationAction : NavigationAction {
object SignUpScreen : MyNavigationAction()
data class DashboardScreen(val userId: String) : MyNavigationAction()
}
}
data class ShowMessage(val message: String) : DisplayAction()
class MyActivity : AppCompatActivity() {
private val viewModel by viewModel<MyViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
viewModel.display(ShowMessage("Hello!"))
viewModel.observe(this, DisplayActionObserver { displayAction ->
when (val data = displayAction) {
is ShowMessage -> Toast.makeText(this, data.message, Toast.LENGTH_SHORT).show()
}
})
viewModel.observe(this, NavigationActionObserver { navigationAction ->
when (navigationAction) {
is MyNavigationAction -> handleMyNavigationAction(navigationAction)
}
})
}
private fun handleMyNavigationAction(navigateTo: MyNavigationAction) =
when (val data = navigateTo) {
is SignUpScreen -> startActivity(SignUpActivity.newIntent())
is DashboardScreen -> startActivity(DashboardActivity.newIntent(data.userId))
}
}
License
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.