Read Room database entity from a service android - android

I'm trying to read a value from room database of android in a ListenerService class.
repository.responseCode("status");
repository.responseCode("status").observe(
MainActivity.class,flagValue -> {
if(flagValue!=null){
}
}
);
But it says wrong first type argument at "MainActivity.class" at above code.
Can I read any room db value from a service?

You need to pass LifecycleOwner and Observer implementation to observe method.
You can pass your activity's instance directly as first parameter.
You are passing MainActivity Class's instance.
if you are writing in Activity you can repace with this.
repository.responseCode("status").observe(
this,flagValue -> {
if(flagValue!=null){
}
}
);

Related

How to use Room Database Viewmodel Observer in Android Foreground Service

I have following view model and observer in A Fragment(in onViewCreated) which populates the recyelerview using adapter
commonOwnDBViewModel =
new ViewModelProvider(requireActivity()).get(CommonOwnDBViewModel.class);
Observer<List<FolderEntity>> notesObserver =
FolderEntityList -> {
folderNameAdapter.setListManager(FolderEntityList);
recyclerView.getAdapter().notifyDataSetChanged();
Toast.makeText(mActivity, "" + FolderEntityList.size(), Toast.LENGTH_SHORT).show();
};
commonOwnDBViewModel.getAllFolderEntityList
().observe(requireActivity(), notesObserver);
it gives me a list and works fine in the fragment now I want to use the same list in A service using Viewmodel and Observer how can we do that how do we manage Lifecycle in service.
Instead of using normal service, use LifecycleService.
You can get the lifecycle of the service by calling getLifecycle() method and pass this lifecycle to your observer.
More details here: https://developer.android.com/reference/androidx/lifecycle/LifecycleService

Problems returning LiveData in Activity that observes changes in database using MVVM model

In my application I return LiveData from Room database (SQLite) in repository, and observe the data on my application Activity.
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
The method looks like this in repository:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId); //this part finishes after reuturn
});
return tourWithAllGeoPoints; //this part returns
}
mIsFirstTime checks if the Activity (or Fragment) is loading first time or not (if Bundle is null or not).
databaseWriteExecutor.execute() is a ThreadPool executing the code in own thread.
toursDAO.getTourWithAllGeoPoints(tourId) is where I ask and get data from Room database. It returns a LiveData object.
In Activity code I do observe the LiveData:
activeTourViewModel.getTourWithAllGeoPoints(tourId, mIsFirstTime).observe(this, geoPointsPlanned -> {
//Some code here changing UI, other variables, etc.
}
But the problem is that the method returns 'tourWithAllGeoPoints' before the execute() part is finished. So this means it returns an empty LiveData. Or the LiveData we observe on MainActivity is not the same LiveData we get from toursDAO.
And so in Activity it observes the empty LiveData.
My attempted solutions are:
1) I can run the query in main thread like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
tourWithAllGeoPoints = toursDAO.getTourWithAllGeoPoints(tourId);
return tourWithAllGeoPoints;
}
But then it gives warning message about not to run queries to Room database on main thread as it may take long time.
2) Or I can make the toursDAO.getTourWithAllGeoPoints(tourId) return a TourWithAllGeoPoints object rather than a LiveData, and put it into a LiveDataobject, like this:
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
TourWithAllGeoPoints twagp = toursDAO.getTourWithAllGeoPoints(tourId);
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
So that it observes the changes in LiveData. But then I can't observe the changes made in database, since it just returns a List. This means I have to run the same method every time I make a change in the database.
3) Or I can put a LiveData inside a LiveData, also like this:
public LiveData<LiveData<TourWithAllGeoPoints>> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
if (!mIsFirstTime) {
return tourWithAllGeoPoints;
}
MyTourAssistentDatabase.databaseWriteExecutor.execute(()-> {
LiveData<TourWithAllGeoPoints> twagp = toursDAO.getTourWithAllGeoPoints(tourId); //returns LiveData
tourWithAllGeoPoints.postValue(twagp)
});
return tourWithAllGeoPoints;
}
But I don't know if putting LiveData inside a LiveData is a good idea or not.
Or the are other solutions. But how can I solve this problem?
The problem is: having LiveData in Activity that observes changes in database using MVVM model, and runs some code when data is changed (as this is how observe works).
For the specific problem you described (i.e. returning the first TourWithAllGeoPoints and nothing else), it seems LiveData isn't the most appropriate data type you can use here. LiveData is meant to be used when, as the name says, the underlying data is live and it could change anytime, and you need to observe the data everytime it changes. If all you need is one value, it's better not to use LiveData at all. Just make your DAO method getTourWithAllGeoPoints return TourWithAllGeoPoints (without LiveData) and call it from a background thread. Take a look at this link for some ways to do that. It's much easier to use Kotlin coroutines in this case, but you'd need to be using Kotlin for that (which I recommend :) ).
But if the problem you described is generic (not exactly just for returning one value once), you can use a MediatorLiveData to observe a LiveData and post something different (or not) every time it emits a new value. Take a look at this code:
private MediatorLiveData<TourWithAllGeoPoints> mediator;
public YourRepositoryConstructor() {
mediator = new MediatorLiveData<>();
mediator.addSource(toursDAO.getTourWithAllGeoPoints(tourId), data -> {
if (mediator.getValue() != null) {
mediator.setValue(data);
}
});
return mediator;
}
public LiveData<TourWithAllGeoPoints> getTourWithAllGeoPoints(long tourId, boolean mIsFirstTime) {
return mediator;
}
A MediatorLiveData observes one (or many) other LiveData objects and emits a new value according to the changes of the other LiveData objects. It may emit a different data type (i.e. it doesn't have to be the same type of the underlying LiveData objects) or even not emit anything at all. It's all according to your MediatorLiveData code. In this case specifically, every time the result of getTourWithAllGeoPoints emits something new, you MediatorLiveData will react to that and only emit a new value in itself if it's the first time. It can do that by checking if it's value is null, it doesn't need the variable mIsFirstTime (unless null is a valid value for you in that case).
The MediatorLiveData is a more generic approach suitable for the type of scenario you described, but it may be too much effort if you only need one result for that query, which you might solve by not using LiveData at all.

Best approach to waiting on pending live data using architecture components

I'm working on an application that fetches data from a graphql server via apollo-android.
I do a single fetch on my aws rds database. I do this fetch right at the onCreate() of my CalendarFragment.
The thing is, at onViewCreated(), I want to set my textview to one of the fields that is fetched, first and last name. So, I run my getBarberFullName method which returns the String value of mBarberFullName. I'm trying to follow the UI controller displays while the view model handles all the logic approach. getBarberFullName resides within my ViewModel.
public String getBarberFullName() {
if (appointmentsAreNull()) return mBarberFullName.getValue();
AppointmentModel am = mMasterAppointments.getValue().get(0);
String fullName = am.bFirstName;
fullName = fullName.concat(" " + am.bLastName);
// Get the logged in barber's full name and set it as mBarberFullName.
mBarberFullName.setValue(fullName);
return mBarberFullName.getValue();
}
where mMasterAppointments is a MutableLiveData<List<AppointmentModel>>. In my onViewCreated() callback, I run
String barberName = mBarberViewModel.getBarberFullName();
mTxtv_barberName.setText(barberName);
However, mMasterAppointments is always null so it just returns the default value of mBarberFullName which is a String.
However, if I were to run the following code, in the same onViewCreated(), I get the desired result where the textview is updated with the desired barber's full name.
mBarberViewModel.getAllAppointments().observe(getViewLifecycleOwner(), am -> {
if (am.isEmpty()) {
Log.d(TAG, "No barber.");
return;
}
String barberGreeting;
barberGreeting = am.get(0).bFirstName;
barberGreeting = barberGreeting.concat(" " + am.get(0).bLastName);
mTxtv_barberName.setText(barberGreeting);
});
getAllAppointments returns an observer to mMasterAppointments located in my ViewModel.
Although getAllAppointments and getBarberFullName are called within onViewCreated(), one is able to access the pending values of mMasterAppointments while the other is not. Why?
I don't want to do the logic in my Fragments onViewCreated callback, so how can I wait on the pending mMasterApointmentData in my ViewModel's getBarberFullName()? Are there tools within LiveData and ViewModel that would aid me in this situation?
Use LiveData's Transformations class
when you need to perform calculations, display only a subset of the
data, or change the rendition of the data.
First add a new String LiveData for BarberFullName in the viewmdoel, and give it the value of transforming (mapping) the source LiveData mMasterAppointments into the desired String:
val fullBarberName: LiveData<String> = Transformations.map(mMasterAppointments) { am ->
" ${am[0].bFirstName} ${am.get(0).bLastName}"
}
Now you can observe this String LiveData in your fragment, the way you in did your second snippet.
Note that the code I provided is in Kotlin, I use it nowadays. I hope you get it.

Android MVVM: databinding value is not set from MediatorLiveData in particular situation

When setting a value to MediatorLiveData that reacts to a source added in the constructor of a viewModel or activity onCreate observer in the ViewModel , like this for example:
showingMethodLiveData.addSource(stateChangeLiveData) {
when (it) {
ConfigurationState.CURRENT -> showingMethodLiveData.value = commMethod[it]
ConfigurationState.PENDING -> showingMethodLiveData.value = commMethod[it]
}
}
The value isn't set to the observing view, although the set method is called.
I can work around this by either adding the source in onStart (which creates other problems of registering observer more than once), or using postValue instead of setValue.
The debug of setValue method leads me to following code, where there is an interesting comment that tells the story, the method returns without setting the value to the binded view.
in androidx.databinding package of lifecycle dependency:
class ViewDataBinding:
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver) {
// We're in LiveData registration, which always results in a field change
// that we can ignore. The value will be read immediately after anyway, so
// there is no need to be dirty.
return;
}
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}
The value is not set afterwards either, but only when the mediatorlivedata is invoked again by change in it's source.
Why this situation occurs?
Thank you for the help
PS
I think it may be an android library bug
The use of Mediatorlivedata is to compare two values and then provide a result.
If you want to change the value of a variable, you can simply use MutableLiveData and to assign a new value, write variableName.value = newValue
Should be even easier to achieve like this:
val showingMethodLiveData = Transformations.map(stateChangeLiveData) { commMethod[it] }

How to mock observers easily in Objectbox in junit tests

Is there a simple way to mock observers in Objectbox? More specifically, I want my observer to be called when data is changed. Example:
private DataSubscription listen() {
return addressBoxStore.query()
.equal(Address_.address, address)
.build()
.subscribe()
.on(AndroidScheduler.mainThread())
.onError(this::handleError)
.observer(observeAddress());
}
private DataObserver<List<Address>> observeAddress() {
return addresses -> {
// should only be one address
if (!addresses.isEmpty()) {
// Run some code here
}
};
}
Given the above observer is registered, I want to add a junit test to make sure that my custom piece of code is called by Objectbox when data is changed. Is there a way to trigger events to occur in junit unit tests with some custom data so that I can verify some custom behaviour?
One approach is to mock the builder objects (example below), but that becomes ugly. Is there a better solution?
private void givenAddress(Answer answers) {
when(subscriptionBuilder.observer(any(DataObserver.class))).then(answers);
when(query.subscribe()).thenReturn(subscriptionBuilder);
when(queryBuilder.build()).thenReturn(query);
when(subscriptionBuilder.on(any())).thenReturn(subscriptionBuilder);
when(subscriptionBuilder.onError(any())).thenReturn(subscriptionBuilder);
when(queryBuilder.equal(any(Property.class), eq(ADDRESS))).thenReturn(queryBuilder);
when(addressBox.query()).thenReturn(queryBuilder);
underTest = new ClassToTest(addressBox);
}
This means that as soon as the observers are added, the data in the Answer is passed directly to it, but this solution is hard to read.

Categories

Resources