I been struggling a lot thinking about where to place Android Services in the new Android recommended Architecture. I came up with many possible solutions, but I cannot make up my mind about which one is the best approach.
I did a lot of research, and I couldn't find any useful guideline nor tutorial. The only hint I found about where to place the Service in my app architecture is this one, from @JoseAlcerreca Medium post
Ideally, ViewModels shouldn’t know anything about Android. This improves testability, leak safety and modularity. A general rule of thumb is to make sure there are no android.* imports in your ViewModels (with exceptions like android.arch.*). The same applies to presenters.
According to that, I should place my Android Services on the top of my Architecture Components hierarchy, at the same level as my Activities and Fragments. That's because Android Services are part of the Android framework, so ViewModels shouldn't know about them.
Now, I will explain briefly my scenario, but only to make the panorama clearer, not because I want an answer for this specific scenario.
Here are the 3 different architectures I can think of:
LiveData inside AndroidService
UPDATE: This is the approach I personally went with at the time because it worked well and allowed me to get it done relatively fast. However, I suggest following the updated answer by Jeel Vankhede for what seems to be a more "idiomatic" implementation.
Shared ViewModel between fragment and AndroidService
Service ViewModel
I am pretty sure I should place them on top of the architecture and treat them just like an Activity/Fragment, because BoundServices are part of the Android Framework, they are managed by the Android OS and they are bound to other Activities and Fragments. In that case, I don't know what's the best way to interact with LiveData, ViewModels and Activities/Fragments.
Some might think that they should be considered as a DataSource (since in my case it's getting data from a scale using Bluetooth), but I don't think this is a good idea, because of all what I've said in the previous paragraph and specially because of what it says here:
Avoid designating your app's entry points—such as activities, services, and broadcast receivers—as sources of data. Instead, they should only coordinate with other components to retrieve the subset of data that is relevant to that entry point. Each app component is rather short-lived, depending on the user's interaction with their device and the overall current health of the system.
So, finally, my question is:
Where should we place our Android (Bound) Services and what is their relation with the other architectural components? Is any of these alternatives a good approach?
Why should you initialize and store LiveData in your ViewModel instead of a UI Controller? Both the ViewModel and LiveData are lifecycle aware. To ensure that the LiveData isn't destroyed when the UI Controller is destroyed. To hide or separate implementation details making your app more flexible.
Attach the Observer object to the LiveData object using the observe() method. The observe() method takes a LifecycleOwner object. This subscribes the Observer object to the LiveData object so that it is notified of changes. You usually attach the Observer object in a UI controller, such as an activity or fragment.
AndroidViewModel inherits ViewModel, so it has all the same functionality. The only added functionality for AndroidViewModel is that it is context aware: when initializing AndroidViewModel you have to pass the context as a parameter. This can be used if you want to show toasts for example.
The LiveData class is also part of the Android Architecture Components and is a data holder class that can be observed. How to download source code from GitHub and open it in Android studio. How to create and run a basic Android app in Kotlin, using activities and fragments. How the activity and fragment life cycles work.
The Fragment observes the LiveData about the state of the connection and adapts the UI accordingly (for example, enable a button if the state is connected). The Fragment observes the LiveData of the new weight measurements. If a new weight measurement comes from the BluetoothDevice, the Fragment then tells its own ViewModel to save the new data.
It leaves us in condition that from now on, You can have a ViewModel to LifecycleService doing operations for itself & if you're following proper app architecture and having repository pattern leaves you to single source of truth!
A general rule of thumb is to make sure there are no android.* imports in your ViewModels (with exceptions like android.arch.*). The same applies to presenters. According to that, I should place my Android Services on the top of my Architecture Components hierarchy, at the same level as my Activities and Fragments.
After getting suggestion from @Ibrahim Disouki (Thank you for that) I dig deeper and found out something interesting! Here's background.
O.P. seeks for solution "Where Service component of Android Framework stands considering Android Architecture Components". So, here's out the box(SDK) solution.
It stands at the same level as Activity/Fragment
. How? If you're extending Service class though rather than that, start extending LifecycleService. Reason behind that is simple that previously we had to rely on Activity/Fragment lifecycle in order to receive updates/do some contextual operations on Service. But now it's not the case.
LifecycleService
now has it's own lifecycle registry/maintainer called ServiceLifecycleDispatcher
which takes care of lifecycle of service which also makes it LifecycleOwner
.
It leaves us in condition that from now on, You can have a ViewModel
to LifecycleService
doing operations for itself & if you're following proper app architecture and having repository pattern leaves you to single source of truth!
In context of O.P. LifecycleService now can have ability to maintain it's ViewModel
to do business logic related to repository layer, and later on another lifecycle aware component like, Activity/Fragment can also consume/reuse same ViewModel
to have their specific operations to it.
Please note that by doing so, you're in state of having two different LifecycleOwner
s (Activity & LifecycleServie) which means you can't share view models between LifecycleService
& other lifecycle aware components. If you don't like approach then be good with old callback kind of approach having callbacks back to Activity/Fragment from service when data is ready to serve etc.
(I suggest not to read through)
In my opinion, Service should be on same level as Activity/Fragment, because it's Framework component & not MVVM. but because of that Service doesn't implements LifecycleOwner and it's Android Framework Component, it shouldn't be treated as data source because it can be entry point to application.
So, dilemma here is that sometimes (In your case), Service acts as data source which provides data from some long running task to UI.
So what it should be in Android Architecture Component? I think you can treat it as LifecycleObserver. because, no matter what you do in background, you'll need to consider about lifecycle of the LifecycleOwner.
Why? because, we usually do bind it to LifecycleOwner (Activity/Fragments) & to do long running tasks off the UI. So, it can be treated like LifecycleObserver. In such a way we made our Service as "Lifecycle aware component" !
How you can implement it?
Take your service class and implement LifecycleObserver interface to it.
When you bind your service to Activity/Fragment
, during your service connection of your service class, add your service to your activity as LifecycleObserver by calling method getLifecycle().addObserver(service class obj)
Now, Take an interface in service class to provide callback from service to your UI and every time your data changes, check that if your service has at least on Lifecycle event create or resume to provide callback with.
In such a way, we won't require LiveData
to update to from service and even no ViewModel
(Why do we need it for service? We don't need configuration changes to survive on service lifecycle. And main task to VM is that to consist data between lifecycles).
Side Note: If you think you're having long running background operations, then consider using WorkManager
. After using this library, you'll feel like Services should be marked as deprecated by now! (Just a random thought)
One way to avoid direct contact with an Android service while still being able to use it is through an interface object. This is part of the "I" for Interface Segregation in the acronym, SOLID. Here is a small example:
public interface MyFriendlyInterface { public boolean cleanMethodToAchieveBusinessFunctionality(); public boolean anotherCleanMethod(); } public class MyInterfaceObject implements MyFriendlyInterface { public boolean cleanMethodToAchieveBusinessFunctionality() { BluetoothObject obj = android.Bluetooth.nastySubroutine(); android.Bluetooth.nastySubroutineTwo(obj); } public boolean anotherCleanMethod() { android.Bluetooth.anotherMethodYourPresentersAndViewModelsShouldntSee(); } } public class MyViewModel { private MyFriendlyInterface _myInterfaceObject; public MyViewModel() { _myInterfaceObject = new MyInterfaceObject(); _myInterfaceObject.cleanMethodToAchieveBusinessFunctionality(); } }
Given the above paradigm, you are free to place your services in a package that's outside your packages that contain POJO code. There is no "right" location to put your services -- but there are definitely WRONG places to put them (e.g. where your POJO code goes).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With