How to observe LiveData created by Service from UI/MainActivity/Fragments? - android

I have a service that updates data every now and then. I currently use sharedPreferences to store the data and use LocalBroadcast to communicate between the service and UI.
I would like to improve this to use MutableLiveData, something like so.
class MyService : Service() {
private var mMyServiceCreatedData = MutableLiveData<String>()
private var mData = ""
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
....
mData = someData
mMyServiceCreatedData.postValue(mData)
}
}
I understand there's viewModel but in this case it's the service which generates the data and not a viewModel so I don't know how it works for Service. Is it possible to observe data created from a Service in UI/MainActivity/Fragments?

There are many different approaches to this, so I won't go into too much detail; Stack Overflow is not built around that.
LiveData is just a value holder. It is when "observers" come to "observe" your events that you start having to think about Lifecycle.
If you don't like the LocalBroadcast mode, then you could:
Service stores the value in a repository instead of emitting/posting the value.
Repository emits a flow or exposes LiveData (I don't like liveData in repos but that's fine).
ViewModel (and UI/Fragment/Act) will eventually subscribe to this Repository (same instance, I assume you have Dependency Injection or pass your references anyway you want), when it's in scope.
This decouples all the pieces and allows them to run independently of each other.
As a last resort, you could always bind your service to your activity, but that's -I believe- a different use-case than this.

Finally I fixed it.
Create MyApp class which extends Application
Create instance of MyApp
private static MyApp myApp;
public static MyApp getInstance() {
return myApp;
}
Access MyApp from your service/fragment/anywhere you want.
Create instance of your model class in MyApp.
public ViewModel model;
#Override
public void onCreate() {
super.onCreate();
model = new ViewModelProvider(this).get(ViewModel.class);
To access your model
MyApp.getInstance().model.doYourWork();
Using this you can update your model and update ui.

Related

Android + Kotlin + Hilt: Injecting into static methods classes

I'm not very clear about the best way to inject into a static methods helper class (lets say a Custom class).
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways:
Object class.
Class + companion object.
To start, I'm not sure which one is the most recommended one (if there is a best practice regarding this), but my "problem" arises when needing to inject dependencies into a static method class.
Let's go with a simple example:
I have a static methods class called AWUtils (not decided if it should be an object class or a class with companion object yet though, and this will most likely depend on the injection mechanism recommended) with the next method:
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
Here, app is the instance of my AppSettings class which holds all app configuration so, as you see setAmpersand2Yellow needs AppSettings, and of course I would't pass it as a parameter by any means, so it's a AWUtils dependence.
Using AWUtils as a class with companion object for the static methods I cannot inject directly AppSettings into company object as far as I know (at least I cannot do constructor injection, let me know if I'm wrong) and if I inject into companion object parent class (AWUtils) constructor then I don't know how to access those dependences from the companion object itself (the child).
If I use fields injection in AWUtils as a class then it complains than lateinit field has not been initialised and I don't know how to deal with this, because as far as I know lateinit fields are initialised in onCreate, which does not exist in this kind of classes.
One other possibility is to use an object with fields and set the dependencies values from caller in a static way before calling the method, for example:
object AWUtils {
var app: AppSettings? = null
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
#AndroidEntryPoint
class OtherClass
#Inject constructor(private val app: AppSettings) {
fun AnyFunction() {
var mystr = "whatever"
AWUtils.app = app
var yellowStr = AWUtils.setAmpersand2Yellow(myStr)
}
}
In the end, I'm not sure on how to supply dependencies to a static methods class and which form of "static" class should I choose.
Edit 1:
Apart from my ApSettings class, I need a context, like for example in this next isTablet method:
val isTablet: String
get() {
return ((context.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
}
In the end, I need a context and my AppSettings (or any other custom classes) to be injected anyway in a class with static methods.
Edit 2:
I could do (from the activity):
AWUtils.context = this
AWUtils.app = app
var isTablet = AWUtils.isTablet
And it works, but rather to be in the need of assigning a value to two fields (or more) every time I need to call a static method, I would prefer the fields to be injected in any way.
That's what dependency injection is meant for, isn't it?
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
As you clarified in the comments, you want to have your utils class accessible in an easy way across your codebase, so this answer will focus on that and on your original questions.
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways: Object class or Class + companion object.
Kotlin does not have Java-style statics. One reasoning behind it was to encourage more maintainable coding practices. Static methods and static classes are also a nightmare for testing your code.
In Kotlin you would go with an object (but a class + companion object would work in the same way)
object AWUtils {
lateinit var appContext: Context
lateinit var appSettings: AppSettings
fun initialize(
appContext: Context,
appSettings: AppSettings,
// more dependencies go here
) {
this.appContext = appContext
this.appSettings = appSettings
// and initialize them here
}
val isTablet: Boolean
get() = ((appContext.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
appSettings.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
Since this object should be accessible across the whole application it should be initialized as soon as possible, so in Application.onCreate
#HiltAndroidApp
class Application : android.app.Application() {
// you can inject other application-wide dependencies here
// #Inject
// lateinit var someOtherDependency: SomeOtherDependency
override fun onCreate() {
super.onCreate()
// initialize the utils singleton object with dependencies
AWUtils.initialize(applicationContext, AppSettings())
}
Now anywhere in your app code you can use AWUtils and AppSettings
class OtherClass { // no need to inject AppSettings anymore
fun anyFunction() {
val mystr = "whatever"
val yellowStr = AWUtils.setAmpersand2Yellow(myStr)
// This also works
if (AWUtils.isTablet) {
// and this as well
val color = AWUtils.appSettings.drawerFooterColor
}
}
}
There is another way in Kotlin to write helper/util functions, called extension functions.
Your isTablet check might be written as an extension function like this
// This isTablet() can be called on any Configuration instance
// The this. part can also be omitted
fun Configuration.isTablet() = ((this.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
// This isTablet() can be called on any Resources instance
fun Resources.isTablet() = configuration.isTablet()
// This isTablet() can be called on any Context instance
fun Context.isTablet() = resources.isTablet()
With the above extension functions in place the implementation inside AWUtils would be simplified to
val isTablet: Boolean
get() = appContext.isTablet()
Inside (or on a reference of) any class that implements Context, such as Application, Activity, Service etc., you can then simply call isTablet()
class SomeActivity : Activity() {
fun someFunction() {
if (isTablet()) {
// ...
}
}
}
And elsewhere where Context or Resources are available in some way, you can simply call resources.isTablet()
class SomeFragment : Fragment() {
fun someFunction() {
if (resources.isTablet()) {
// ...
}
}
}
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
Yeah, Hilt is focusing on constructor injection and can only do field injection out-of-the-box in very limited cases, afaik only inside Android classes annotated with #AndroidEntryPoint and inside the class extending the Application class when annotated with #HiltAndroidApp.
Docs for #AndroidEntryPoint say
Marks an Android component class to be setup for injection with the standard Hilt Dagger Android components. Currently, this supports activities, fragments, views, services, and broadcast receivers.
If you feel that you need a lot of field injection, because you are working with "static"-like objects in Kotlin, consider using Koin instead of Hilt for your next project.

View models for RecyclerView items

My activity has a Google's ViewModel that fetches some model items. These items are then transformed into adapter items of a RecyclerView. There are also many types of adapter items supported by one RecyclerView.
I would like to have separate view model object for each of these model objects so that I can have more complex logic encapsulated only within that "small" view model.
Currently when I have some asynchronous logic (that needs to be stopped in onCleared()) that is related only to some adapter item I have to somehow route callbacks through main view model so that everything is properly unregistered.
I was considering using ViewModelProvider::get(key, modelClass) but my items are changing over time and I can't find a nice way to "clear" old items.
How are you handling these cases in your projects?
Edit: To add more information about my concern, maybe in different words: I want my "small" ViewModel to live as long as the model item which it represents. It means that:
I must receive onCleared() callback in the same scenarios in which parent of these items receive
I must receive onCleared() callback when item is no longer
Edit: Please try to compare it to a ViewPager with Fragments as items. Every individual model item is represented as a Fragment with its ViewModel. I would like achieve something similar but for RecyclerView.
androidx.lifecycle.ViewModel's are not meant to be used on RecyclerView items by default
Why?
ViewModel is AAC (Android Architecture Component) whose sole purpose is to survive configuration changes of Android Activity/Fragment lifecycle, so that data can be persisted via ViewModel for such case.
This achieved by caching VM instance in storage tied to hosting activity.
That's why it shouldn't be used on RecyclerView (ViewHolder) Items directly as the Item View itself would be part of Activity/Fragment and it (RecyclerView/ViewHolder) doesn't contain any specific API to provide ViewModelStoreOwner (From which ViewModels are basically derived for given Activity/Fragment instance).
Simplistic syntax to get ViewModel is:
ViewModelProvider(this).get(ViewModel::class.java)
& here this would be referred to Activity/Fragment context.
So even if you end up using ViewModel in RecyclerView Items, It would give you same instance due to context might be of Activity/Fragment is the same across the RecyclerView which doesn't make sense to me. So ViewModel is useless for RecyclerView or It doesn't contribute to this case much.
TL;DR
Solution?
You can directly pass in LiveData object that you need to observe from your Activity/Fragment's ViewModel in your RecyclerView.Adapter class. You'll need to provide LifecycleOwner as well for you adapter to start observing that given live data.
So your Adapter class would look something like below:
class RecyclerViewAdapter(private val liveDataToObserve: LiveData<T>, private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<ViewHolder>() {
init {
liveDataToObserve.observe(lifecycleOwner) { t ->
// Notify data set or something...
}
}
}
If this is not the case & you want to have it on ViewHolder class then you can pass your LiveData object during onCreateViewHolder method to your ViewHolder instance along with lifecycleOwner.
Bonus point!
If you're using data-binding on RecyclerView items then you can easily obtain lifecyclerOwner object from your binding class. All you need to do is set it during onCreateViewHolder() something like below:
class RecyclerViewAdapter(private val liveDataToObserve: LiveData<T>, private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder: ViewHolder {
// Some piece of code for binding
binding.lifecycleOwner = this#RecyclerViewAdapter.lifecycleOwner
// Another piece of code and return viewholder
}
}
class ViewHolder(private val someLiveData: LiveData<T>, binding: ViewDataBinding): RecyclerView.ViewHolder(binding.root) {
init {
someLiveData.observe(requireNotNull(binding.lifecycleOwner)) { t->
// set your UI by live data changes here
}
}
}
So yes, you can use wrapper class for your ViewHolder instances to provide you LiveData out of the box but I would discourage it if wrapper class is extending ViewModel class.
As soon as concern about mimicking onCleared() method of ViewModel, you can make a method on your wrapper class that gets called when ViewHolder gets recycled or detaches from window via method onViewRecycled() or onViewDetachedFromWindow() whatever fits best in your case.
Edit for comment of #Mariusz: Concern about using Activity/Fragment as LifecycleOwner is correct. But there would be slightly misunderstanding reading this as POC.
As soon as one is using lifecycleOwner to observe LiveData in given RecyclerViewHolder item, it is okay to do so because LiveData is lifecycle aware component and it handles subscription to lifecycle internally thus safe to use. Even if you can explicitly remove observation if wanted to, using onViewRecycled() or onViewDetachedFromWindow() method.
About async operation inside ViewHolder:
If you're using coroutines then you can use lifecycleScope from lifecycleOwner to call your operation and then provide data back to particular observing LiveData without explicitly handling clear out case (LifecycleScope would take care of it for you).
If not using Coroutines then you can still make your asyc call and provide data back to observing LiveData & not to worry about clearing your async operation during onViewRecycled() or onViewDetachedFromWindow() callbacks. Important thing here is LiveData which respects lifecycle of given LifecycleOwner, not the ongoing async operation.
Don't know if google has nice support for nested ViewModel's, looks like not.
Thankfully, we don't need to stick to androidx.lifecycle.ViewModel to apply MVVM approach where we need. And there is a small example I decided to write:
Fragment, nothing changes:
#Override public void onCreate(#Nullable Bundle savedInstanceState) {
final ItemListAdapter adapter = new ItemListAdapter();
binding.getRoot().setAdapter(adapter);
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
viewModel.getItems().observe(getViewLifecycleOwner(), adapter::submitList);
}
ItemListAdapter, in addition to populate view, it also becomes responsible for notifying item's observers - should they continue to listen, or not. In my example adapter was ListAdapter which extends RecyclerView.Adapter, so it receives list of items. This is unintentionally, just edited some code I already have. It's probably much better to use different base implementation, but it's acceptable for demonstration purposes:
#Override public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
return new Holder(parent);
}
#Override public void onBindViewHolder(Holder holder, int position) {
holder.lifecycle.setCurrentState(Lifecycle.State.RESUMED);
holder.bind(getItem(position));
}
#Override public void onViewRecycled(Holder holder) {
holder.lifecycle.setCurrentState(Lifecycle.State.DESTROYED);
}
// Idk, but these both may be used to pause/resume, while bind/recycle for start/stop.
#Override public void onViewAttachedToWindow(Holder holder) { }
#Override public void onViewDetachedFromWindow(Holder holder) { }
Holder. It implements LifecycleOwner, which allows to unsubscribe automatically, just copied from androidx.activity.ComponentActivity sources so all should be okay :D :
static class Holder extends RecyclerView.Holder implements LifecycleOwner {
/*pkg*/ LifecycleRegistry lifecycle = new LifecycleRegistry(this);
/*pkg*/ Holder(ViewGroup parent) { /* creating holder using parent's context */ }
/*pkg*/ void bind(ItemViewModel viewModel) {
viewModel.getItem().observe(this, binding.text1::setText);
}
#Override public Lifecycle getLifecycle() { return lifecycle; }
}
List view-model, "classique" androidx-ish ViewModel, but very rough, also provide nested view models. Please, pay attention, in this sample all view-models start to operate immediately, in constructor, until parent view-model is commanded to clear! Don't Try This at Home!
public class ItemListViewModel extends ViewModel {
private final MutableLiveData<List<ItemViewModel>> items = new MutableLiveData<>();
public ItemListViewModel() {
final List<String> list = Items.getInstance().getItems();
// create "nested" view-models which start background job immediately
final List<ItemViewModel> itemsViewModels = list.stream()
.map(ItemViewModel::new)
.collect(Collectors.toList());
items.setValue(itemsViewModels);
}
public LiveData<List<ItemViewModel>> getItems() { return items; }
#Override protected void onCleared() {
// need to clean nested view-models, otherwise...
items.getValue().stream().forEach(ItemViewModel::cancel);
}
}
Item's view-model, using a bit of rxJava to simulate some background work and updates. Intentionally I do not implement it as androidx....ViewModel, just to highlight that view-model is not what google names ViewModel but what behaves as view-model. In actual program it most likely will extend, though:
// Wow, we can implement ViewModel without androidx.lifecycle.ViewModel, that's cool!
public class ItemViewModel {
private final MutableLiveData<String> item = new MutableLiveData<>();
private final AtomicReference<Disposable> work = new AtomicReference<>();
public ItemViewModel(String topicInitial) {
item.setValue(topicInitial);
// start updating ViewModel right now :D
DisposableHelper.set(work, Observable
.interval((long) (Math.random() * 5 + 1), TimeUnit.SECONDS)
.map(i -> topicInitial + " " + (int) (Math.random() * 100) )
.subscribe(item::postValue));
}
public LiveData<String> getItem() { return item; }
public void cancel() {
DisposableHelper.dispose(work);
}
}
Few notes, in this sample:
"Parent" ViewModel lives in activity scope, so all its data (nested view models) as well.
In this example all nested vm start to operate immediately. Which is not what we want. We want to modify constructors, onBind, onRecycle and related methods accordingly.
Please, test it on memory leaks.
Although that is true that Android uses ViewModels in Android Architecture Components it does not mean that they are just part of AAC. In fact, ViewModels are one of the components of the MVVM Architecture Pattern, which is not Android only related. So ViewModel's actual purpose is not to preserve data across Android's lifecycle changes. However, because of exposing its data without having a View's reference makes it ideal for the Android specific case in which the View can be recreated without affecting to the component that holds its state (the ViewModel). Nonetheless, it has other benefits such as facilitating the Separation of Concerns among others.
It is also important to mention that your case can not be 100% compared to the ViewPager-Fragments case, as the main difference is that the ViewHolders will be recycled between items. Even if ViewPager's Fragments are destroyed and recreated, they will still represent the same Fragment with that same data. That is why they can safely bind the data provided by their already existing ViewModel. However, in the ViewHolder case, when it is recreated, it can be representing a totally new item, so the data its supposed ViewModel could be providing may be incorrect, referencing the old item.
That being said you could easily make the ViewHolder become a ViewModelStoreOwner:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), ViewModelStoreOwner {
private var viewModelStore: ViewModelStore = ViewModelStore()
override fun getViewModelStore(): ViewModelStore = viewModelStore
}
This can still be useful if the data provided by the ViewModel is the same independently of the ViewHolder's item (shared state between all items). However, if that is not the case, then you would need to invalidate the ViewModelStore by calling viewModelStore.clear() and create a new ViewModel instance probably in ViewHolder's onViewRecycled. You will loose the advantage of keeping the state no matter the view's lifecycle, but can sometimes still be useful as to follow Separation of Concerns.
Finally, regarding to the option of using a LiveData instance to control the state, no matter if it is provided by a ViewHolder's shared or specific ViewModel or it is passed through the Adapter, you will need a LifecycleOwner to observe it. A better approach to using the current Fragment or Activity lifecycle is to just use the specific ViewHolder's actual lifecycle, as they are actually created and destroyed, by making them implement the LifecycleOwner interface. I created a small library which does exactly that.
I followed this wonderfull answer HERE by aeracode with a one exception. Instead of ViewModel I've used Rx BehaviourSubject that work perfectly for me.
In case of coroutines You can use alternatively StateFlow.
clas MyFragment: Fragment(){
private val listSubject = BehaviorSubject.create<List<Items>>()
...
private fun observeData() {
viewModel.listLiveData.observe(viewLifecycleOwner) { list ->
listSubject.onNext(list)
}
}
}
RecyclerView
class MyAdapter(
private val listObservable: BehaviorSubject<List<Items>>
) : RecyclerView.Adapter<MyViewHolder>() {
[...]
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bindToData(getItem(position))
}
override fun onViewRecycled(holder: MyViewHolder) {
holder.onViewRecycled()
}
...
class MyViewHolder(val binding: LayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
private var disposable: Disposable? = null
fun bindToData(item: Item) = with(binding) {
titleTv.text = item.title
disposable = listObservable.subscribe(::setItemList) <- Here You listen
}
fun onViewRecycled() {
disposable?.dispose()
}
}

How to write a use case that retrieves data from Android framework with Context

I am migrating an application to MVVM and clean architecture, and I am missing one part of the puzzle.
The problem domain:
List all applications on device and display them in the Fragment / Activity
A device app is represented by its package name:
data class DeviceApp(val packageName: String)
This is how the device apps are listed:
private fun listAllApplications(context: Context): List<DeviceApp> {
val ans = mutableListOf<DeviceApp>()
val packageManager: PackageManager = context.applicationContext.packageManager
val packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (applicationInfo in packages) {
val packageName = applicationInfo.packageName
ans.add(DeviceApp(packageName))
}
return ans
}
As I understand, calling listAllApplications() should be done in a UseCase inside the 'Domain Layer', which is called by a ViewModel.
However listAllApplications receives a Context, and the Domain Layer should be plain code only.
In clean architecture / MVVM, in which layer should I put listAllApplications(context)?
And more generally, how should the ViewModel interact with Android framework APIs that require Context (location, etc.)?
Domain Layer should be plain code only.
That's correct!, but in my opinion it's partially correct. Now considering your scenario you need context at domain level. You shouldn't have context at domain level but in your need you should either choose other architecture pattern or consider it as exceptional case that you're doing this.
Considering you're using context at domain, you should always use applicationContext in spite of activity context, because earlier persists through out process.
How should the ViewModel interact with android framework APIs that require Context (location, etc.)?
Whenever you need Context at ViewModel either you can provide it from UI as method parameter (I.e. viewModel.getLocation(context)) or else use AndroidViewModel as your parent class for ViewModel (it provides getApplication() public method to access context through out ViewModel).
All I would like to point you out is that make sure you don't accidentally hold any View/Context globally inside ViewModel/Domain Layer, because it can make catastrophe like memory leaking or crashes at worse.
You can solve this problem very cleanly with dependency-injection. If you aren't already using DI, you probably want to be, as it will greatly simplify your clean-architecture endeavours.
Here's how I'd do this with Koin for DI.
First, convert your usecase from a function to a class. This allows for constructor injection:
class ListAllApplications(private val context: Context) {
...
}
You now have a reference to context inside your usecase. Great! We'll deal with actually providing the value of context in a moment.
Now you're thinking... but aren't usecases meant to use reusable functions? What's the guy on about with usecases being classes?
We can leverage the miracle that is operator funs to help us here.
class ListAllApplications(private val context: Context) {
operator fun invoke(): List<DeviceApp> {
val ans = mutableListOf<DeviceApp>()
val packageManager: PackageManager = context.applicationContext.packageManager
val packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (applicationInfo in packages) {
val packageName = applicationInfo.packageName
ans.add(DeviceApp(packageName))
}
return ans
}
}
invoke is a special function which allows an instance of a class to be invoked as if it were a function. It effectively transforms our class into a function with an injectable constructor 🤯
And this allows us to continue to invoke our usecase in the ViewModel with the standard function invocation syntax:
class MyViewModel(private val listAllApplications: ListAllApplications): ViewModel {
init {
val res = listAllApplications()
}
}
Notice that our ListAllApplications usecase is also being injected into the constructor of MyViewModel, meaning that the ViewModel remains entirely unaware of Context.
The final piece of the puzzle is wiring all this injection together with Koin:
object KoinModule {
private val useCases = module {
single { ListAllApplications(androidContext()) }
}
private val viewModels = module {
viewModel { MyViewModel(get()) }
}
}
Don't worry if you've never used Koin before, other DI libraries will let you do similar things. The key is that your ListAllApplications instance is being constructed by Koin, which provides an instance of Context with androidContext(). Your MyViewModel instance is also being constructed by Koin, which provides the instance of ListAllApplications with get().
Finally you inject MyViewModel into the Activity/Fragment which uses it. With Koin that's as simple as:
class MyFragment : Fragment {
private val viewModel: MyViewModel by viewModel()
}
Et Voilà!

Is LiveData cached in ViewModel?

I need some clarification on how LiveData works with Android's Architecture components like Room.
Let's say I use this way of getting live data:
Dao:
#Query("SELECT * FROM check_table")
LiveData<List<DataItem>> getAllItems();
Repository Constructor:
private DataRepository(Application application) {
DataDatabase database = DataDatabase.getInstance(application);
dataDao = database.dataDao();
dataItems = dataDao.getAllData();
}
ViewModel Constructor:
public DataViewModel(#NonNull Application application) {
super(application);
repository = DataRepository.getInstance(application);
dataItems = repository.getDataItems();
}
Getter:
public LiveData<List<DataItem>> getDataItems() {
return dataItems;
}
Is the LiveData in ViewModel being updated everytime even when there are no active listeners?
I'm asking because I want to use the same data in pretty much all my fragments, and I want to know if the data has to be queried every time I add the listener to the data in one of my fragments or the LiveData object is updated in ViewModel and when i switch fragments and add listener to that LiveData in there, it just gets cached LiveData instead of querying for it once again

Sharing Data between ViewModels

I have a complex screen in my project which I'm breaking in more than one fragment. I'm trying to follow the MVVM architecture for these classes, so which fragment has its own ViewModel and Contract class.
The issue is that all the ViewModels needs the same object instance (let's call it Book) to do Room transactions.
Does it have a correct way to share data (or LiveData) between ViewModels? I know the concept of Shared ViewModel but I don't know if I can apply it to this case. I also thought about using MediatorLiveData but didn't get a good approach on it too.
I'm thinking about having a class (let's call BookObservableProvider) with a LiveData<Book> (or Rx Subject<Book>) where each ViewModel injects the same instance and load/update always the same value.
Is it a good approach?
In my personal opinion your approach is not bad for this situation, but if want to try something else, I can advise you RxBus method. Here is a great article about it. With this approach you can simply publish data in activity, which holds fragments, and then listen to this particular event in all your fragments.
Something like :
//Activity
RxBus.publish(RxEvent.EventOnBookProvide(bookObject)
and
//Fragment
RxBus.listen(RxEvent.EventOnBookProvide::class.java).subscribe {
useObject(it)
}
And don't forget to use Disposable and .dispose() it in onDestroy() if using Activity and onDestroyView() if using fragment.
You should share those data between fragments/activities (maybe using Intents for activities) , and than handle those data by the other ViewModel
The answer is as usual, it depends.
If the reason behind your question is Room access, then it is recmended to have a DataRepository class that handles all Database access and you just pass that repository singleton to each AndroidViewModel.
mRepository = ((MainApp) application).getRepository();
In MainApp:
public DataRepository getRepository() {
return DataRepository.getInstance(getDatabase(), mAppExecutors);
}
And the Repository:
public class DataRepository {
private static DataRepository sInstance;
private MediatorLiveData<String> mObservableString;
private DataRepository(final AppDatabase database, final AppExecutors executors) {
mObservableString.addSource(database.myDao().loadString(),
mString -> {
if (database.getDatabaseCreated().getValue() != null) {
mObservableString.postValue(mString);
}
});
}
public static DataRepository getInstance(final AppDatabase database, final AppExecutors executors) {
if (sInstance == null) {
synchronized (DataRepository.class) {
if (sInstance == null) {
sInstance = new DataRepository(database, executors);
}
}
}
return sInstance;
}
// and then your access methods
public LiveData<String> getString() {
return mObservableString;
}
In the repository it is recommended to have a MediatorLivedata if you want to change the reference (source). Otherwise a normal LiveData does the job.
Regarding ViewModels:
In theory each fragment gets it's own Viewmodel. And if you get it by using requireActivity() as reference, you can get each ViewModel everywhere and have it therefore shared.
As an example:
viewModelA = new ViewModelProvider(requireActivity()).get(ViewModelA.class);
viewModelB = new ViewModelProvider(requireActivity()).get(ViewModelB.class);
This you could call in every Fragment and get the same ViewModel instances. If the DataRepository setup seems overkill to you, make one ViewModel with Room access and load it from every Fragment.
I am facing the same issue. But if you don't have different view models for the various fragments or the design does not necessitate using different view models for the various fragments you can just share one fragment between the entire activity( all the other fragment) and they will all share the same data instances.
follow this link for more https://developer.android.com/guide/fragments/communicate
what you need to do is make sure all the fragments initiate the view model(main view model) with the same context.
public class FilterFragment extends Fragment {
private ListViewModel viewModel;
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
// Update the selected filters UI
});
}
}
take note of this
viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
requireActivity() makes sure all fragments call the context of the host activity.
you can't share data with activities this way though since the view model instance is destroyed when the activity is destroyed

Categories

Resources