I have Implemented MVVM architecture and it is working good when app is in foreground but when i minimize the app, repository send the data to view model and view model to activity but the callback is not called until i resume the application.
Method is Repository:
// Method for new order and its process, and listening the connects collection
fun connects(driverId: String, connectsSnapshot: (QuerySnapshot?) -> Unit) {
if (connectsRef!=null){
connectsRef?.remove()
connectsRef=null
}
this.connectsSnapshot = connectsSnapshot
connectsRef = FirebaseFirestore.getInstance().collection("connects").whereEqualTo("driver_id", UserDto.getInstance().id).whereEqualTo("status", "new").orderBy("created", Query.Direction.DESCENDING).limit(1)
.addSnapshotListener { snapshots, e ->
if (e != null) {
connectsSnapshot (null)
return#addSnapshotListener
}
System.out.println("==>ListnerHit")
connectsSnapshot (snapshots)
}
}
this one send data to view model
val mutableLiveDataForConnect = MutableLiveData<QuerySnapshot>()
val mObserverForConnect: Observer<QuerySnapshot> = Observer {
getView().connectListener(it)
}
fun getConnectsData(driverId: String)
{
repository.connects(driverId){
mutableLiveDataForConnect.postValue(it)
System.out.println("==>ViewModel")
}
}
this one also get called but i am not able to receive the data on activity when app is minimized. it works fine when i move the code to acitivty but i need to move the code to repository.
LiveData doesn't get new values without an active Observer, and when you minimise your app the Observer gets paused, because it is lifecycle dependent. Once the app gets back to the foreground, the Observer returns to the active state, the latest data gets posted to it and your activity is updated. This is how LiveData and Observers are supposed to work.
It's not entirely clear to me what you are trying to do. Why is it necessary to update the activity while it's not visible to anyone? It sounds like you are trying to do something in the activity/Observer that's not supposed to be happening there with MVVM.
Related
I am calling a signin method from a fragment using a viewmodel. I have been using a lot of callbacks in other areas but read that using MVVM I should not be communicating between the fragment and the viewmodel in this way. The Android documentation seems to use LiveData as well. Why is it ok to have listeners for components like adapters for recyclerview and not other components which are called from a view model.
The signin component is in the Splash fragment. Should I call it as a component outside the viewmodel and take advantage of the listeners?
I'm running into an error and want to give feedback to the user. Do I:
Take the component out of the viewmodel and call it directly from the fragment
Leave the component in the viewmodel and provide the feedback to the fragment/user by utilizing livedata?
Leave the signin component in the viewmodel and just use a callback/listener
UPDATE
Thank you for the feedback. I will provide more detail. I'm using Java, FYI. I am focused on the first run procedure, which is different from displaying a list or detail data. So, I need to have a lot of things happen to get the app ready for first use, and I need a lot of feedback in case things go wrong. I created a splash screen and have a way to record the steps in the process, so I can tell when something goes wrong, where it goes wrong. The user ends up seeing the last message, but the complete message is saved.
I have been adding a listener to the call and an interface to the component. If you haven't guessed, I'm somewhat of a novice, but this seemed to really be a good pattern, but I have been reading this is not the right way to communicate between the Fragment and the ViewModel.
Example, from the SplashFragment.java:
viewModel.signIn(getActivity(), getAuthHelperSignInCallback());
In the SplashViewModel.java:
public void signIn (Activity activity, AuthHelper.AuthHelperSignInListener listener) {
AuthHelper authHelper = new AuthHelper(activity.getApplication());
authHelper.signIn(activity,listener);
}
In the AuthHelper.java I have an interface:
public interface AuthHelperSignInListener {
void onSuccess(IAuthenticationResult iAuthenticationResult);
void onCancel();
void onError(MsalException e);
}
Using this method I can get information back that I need, so if I'm not supposed to use a callback/listener in the fragment like this, what is the alternative?
You can use channel to send these events to your activity or fragment, and trigger UI operation accordingly. Channel belongs to kotlinx.coroutines.channels.Channel package.
First, create these events in your viewModel class using a sealed class.
sealed class SignInEvent{
data class ShowError(val message: String) : SignInEvent()
data class ShowLoginSuccess(val message: String) : SignInEvent()
}
Define a channel variable inside viewModel.
private val signInEventChannel = Channel<SignInEvent>()
// below flow will be used to collect these events inside activity/fragment
val signInEvent = signInEventChannel.receiveAsFlow()
Now you can send any error or success event from viewModel, using the defined event channel
fun onSignIn() {
try {
//your sign in logic
// on success
signInEventChannel.send(SignInEvent.ShowLoginSuccess("Login successful"))
} catch(e: Exception){
//on getting an error.
signInEventChannel.send(SignInEvent.ShowError("There is an error logging in"))
}
}
Now you can listen to these events and trigger any UI operation accordingly, like showing a toast or a snackbar
In activity
lifecycleScope.launchWhenStarted {
activityViewModel.signInEvent.collect { event ->
when (event) {
//ActivityViewModel is your viewmodel's class name
is ActivityViewModel.SignInEvent.ShowError-> {
Snackbar.make(binding.root, event.message, Snackbar.LENGTH_SHORT)
.show()
}
is ActivityViewModel.SignInEvent.ShowLoginSuccess-> {
Snackbar.make(binding.root, event.message, Snackbar.LENGTH_SHORT)
.show()
}
}
}
In fragment
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
fragmentViewModel.signInEvent.collect { event ->
when (event) {
is FragmentViewModel.SignInEvent.ShowError-> {
Snackbar.make(requireView(), event.message, Snackbar.LENGTH_SHORT)
.show()
}
is FragmentViewModel.SignInEvent.ShowLoginSuccess-> {
Snackbar.make(requireView(), event.message, Snackbar.LENGTH_SHORT)
.show()
}
}
}
I am using one activity, the multiple fragment model, in my application. I have a sharedViewModel with the coroutine Channel to send and receive data from one fragment to another fragment. Also, I have a custom Dialog fragment as a popup across the app and its user action will observe the remaining fragments. My problem here is the event send from the custom dialog is not triggering in the fragment collect part frequently(2/5 Click). I am adding the codebase for your reference.
SharedView Model Part
private val _cornerDataChannel = Channel<CornerData>()
val cornerDataEvent: Flow<CornerData> = _cornerDataChannel.receiveAsFlow()
fun updateCornerDataEvent(data: CornerData) = viewModelScope.launch {
Log.e(TAG, "Event Added")
_cornerDataChannel.send(data)
}
Sending part in the Custom Dialog Fragment
private fun myToolSelectionCallBack(tool: ToolDTO) =
viewModel.updateCornerDataEvent(CornerData.ToolData(tool))
private fun systemStreamSelectionCallBack(stream: StreamDTO) =
viewModel.updateCornerDataEvent(CornerData.StreamData(stream))
Receiving Part inside fragments
private fun observeCornerItemSelectionCallBack() = lifecycleScope.launchWhenStarted {
viewModel.cornerDataEvent.collect { event ->
when (event) {
is StreamData -> streamSelectionCallBack(data= event.data)
is ToolData -> toolSelectionCallBack(data = event.data)
}
}
}
In the receiver part, some user clicks are missing freqently. But it's always getting the update part under the view model.
This is not a problem with clicks, this is because you're using regular channels for multiple observers/receivers but it is supposed to be single receiver with regular channels. You have to use BroadcastChannels for multiple receivers with single sender.
Now BrodcastChannels has been deprecated it is replaced with SharedFlow()
Note: This API is obsolete since 1.5.0. It will be deprecated with warning in 1.6.0 and with error in 1.7.0. It is replaced with SharedFlow.
Check: BroadcastChannels-OfficialDocs
Background
I'm creating some SDK library, and I want to offer some liveData as a returned object for a function, that will allow to monitor data on the DB.
The problem
I don't want to reveal the real objects from the DB and their fields (like the ID), and so I wanted to use a transformation of them.
So, suppose I have this liveData from the DB:
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
What I did to get the liveData to provide outside, is:
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
This works very well.
However, the problem is that the first line (to get dbLiveData) should work on a background thread, as the DB might need to initialize/update, and yet the Transformations.map part is supposed to be on the UI thread (including the mapping itself, sadly).
What I've tried
This lead me to this kind of ugly solution, of having a listener to a live data, to be run on the UI thread:
#UiThread
fun getAsLiveData(someContext: Context,listener: OnLiveDataReadyListener) {
val context = someContext.applicationContext ?: someContext
val handler = Handler(Looper.getMainLooper())
Executors.storageExecutor.execute {
val dbLiveData = Database.getInstance(context).getSomeDao().getAllAsLiveData()
handler.post {
val resultLiveData: LiveData<List<SomeClass>> = Transformations.map(
dbLiveData) { data ->
data.map { SomeClass(it) }
}
listener.onLiveDataReadyListener(resultLiveData)
}
}
}
Note: I use simple threading solution because it's an SDK, so I wanted to avoid importing libraries when possible. Plus it's quite a simple case anyway.
The question
Is there some way to offer the transformed live data on the UI thread even when it's all not prepared yet, without any listener ?
Meaning some kind of "lazy" initialization of the transformed live data. One that only when some observer is active, it will initialize/update the DB and start the real fetching&conversion (both in the background thread, of course).
The Problem
You are an SDK that has no UX/UI, or no context to derive Lifecycle.
You need to offer some data, but in an asynchronous way because it's data you need to fetch from the source.
You also need time to initialize your own internal dependencies.
You don't want to expose your Database objects/internal models to the outside world.
Your Solution
You have your data as LiveData directly from your Source (in this particular, albeit irrelevant case, from Room Database).
What you COULD do
Use Coroutines, it's the preferred documented way these days (and smaller than a beast like RxJava).
Don't offer a List<TransformedData>. Instead have a state:
sealed class SomeClassState {
object NotReady : SomeClassState()
data class DataFetchedSuccessfully(val data: List<TransformedData>): SomeClassState()
// add other states if/as you see fit, e.g.: "Loading" "Error" Etc.
}
Then Expose your LiveData differently:
private val _state: MutableLiveData<SomeClassState> = MutableLiveData(SomeClassState.NotReady) // init with a default value
val observeState(): LiveData<SomeClassState) = _state
Now, whoever is consuming the data, can observe it with their own lifecycle.
Then, you can proceed to have your fetch public method:
Somewhere in your SomeClassRepository (where you have your DB), accept a Dispatcher (or a CoroutineScope):
suspend fun fetchSomeClassThingy(val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default) {
return withContext(defaultDispatcher) {
// Notify you're fetching...
_state.postValue(SomeClassState.Loading)
// get your DB or initialize it (should probably be injected in an already working state, but doesn't matter)
val db = ...
//fetch the data and transform at will
val result = db.dao().doesntmatter().what().you().do()
// Finally, post it.
_state.postValue(SomeClassState.DataFetchedSuccessfully(result))
}
}
What else I would do.
The fact that the data is coming from a Database is or should be absolutely irrelevant.
I would not return LiveData from Room directly (I find that a very bad decision on Google that goes against their own architecture that if anything, gives you the ability to shoot your own feet).
I would look at exposing a flow which allows you to emit values N times.
Last but not least, I do recommend you spend 15 minutes reading the recently (2021) published by Google Coroutines Best Practices, as it will give you an insight you may not have (I certainly didn't do some of those).
Notice I have not involved a single ViewModel, this is all for a lower layer of the architecture onion. By injecting (via param or DI) the Dispatcher, you facilitate testing this (by later in the test using a Testdispatcher), also doesn't make any assumption on the Threading, nor imposes any restriction; it's also a suspend function, so you have that covered there.
Hope this gives you a new perspective. Good luck!
OK I got it as such:
#UiThread
fun getSavedReportsLiveData(someContext: Context): LiveData<List<SomeClass>> {
val context = someContext.applicationContext ?: someContext
val dbLiveData =
LibraryDatabase.getInstance(context).getSomeDao().getAllAsLiveData()
val result = MediatorLiveData<List<SomeClass>>()
result.addSource(dbLiveData) { list ->
Executors.storageExecutor.execute {
result.postValue(list.map { SomeClass(it) })
}
}
return result
}
internal object Executors {
/**used only for things that are related to storage on the device, including DB */
val storageExecutor: ExecutorService = ForkJoinPool(1)
}
The way I've found this solution is actually via a very similar question (here), which I think it's based on the code of Transformations.map() :
#MainThread
public static <X, Y> LiveData<Y> map(
#NonNull LiveData<X> source,
#NonNull final Function<X, Y> mapFunction) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
#Override
public void onChanged(#Nullable X x) {
result.setValue(mapFunction.apply(x));
}
});
return result;
}
Do note though, that if you have migration code (from other DBs) on Room, it might be a problem as this should be on a background thread.
For this I have no idea how to solve, other than trying to do the migrations as soon as possible, or use the callback of "onCreate" (docs here) of the DB somehow, but sadly you won't have a reference to your class though. Instead you will get a reference to SupportSQLiteDatabase, so you might need to do a lot of manual migrations...
Hello I'm trying to use the new architecture components by jetpack.
So how the AsyncTask will be deprecated, how could I do a callback in android to get the result from a background thread. without my app lag
public void btnConfigurarClick(View v) {
btnConfigurar.setEnabled(false);
myViewModel.configurar(); // do in background resulting true or false
// how to get the result of it with a callback to set enable(true)
...
The concept of Callback gets converted to Subscribe/Publish in terms of ViewModels.
From Acvitity/Fragment, you would need to subscribe to a LiveData that exists inside the ViewModel.
The changes would be notified as you are observing.
Ex :
Class SomeActivity : Activity{
fun startObservingDataChange(){
yourViewModel.someLiveData.observe(viewLifecycleOwner) { data ->
// Whenever data changes in view model, you'll be notified here
// Update to UI can be done here
}
}
}
Class SomeViewModel{
// Observe to this live data in the View
val LiveData<String> someLiveData;
// Update happens to live data in view model
}
You can learn more about Architecture Components in this app.
My database query operations can take a long time, so I want to display a ProgressBar while the query is in progress. This is especially a problem when the user changes the sorting options, because it displays the old list for a while until the new list comes in and the RecyclerView is updated. I just don't know where to capture the Loading and Success states for a query like this.
Here's my method for getting the PagedList from the database:
fun getGameList(): LiveData<PagedList<Game>> {
// Builds a SimpleSQLiteQuery to be used with #RawQuery
val query = buildGameListQuery()
val dataSourceFactory: DataSource.Factory<Int, Game> = database.gameDao.getGameList(query)
val data: LiveData<PagedList<Game>> = LivePagedListBuilder(dataSourceFactory, DATABASE_PAGE_SIZE)
.build()
return data
}
And I update my list by observing this:
val games = Transformations.switchMap(gameRepository.sortOptions) {
gameRepository.getGameList()
}
Do I need a custom DataSource and DataSource.Factory? If so, I have no idea where to even begin with that. I believe it would be a PositionalDataSource, but I can't find any examples online for implementing a custom one.
I also tried adapter.registerAdapterDataObserver() on my RecyclerView adapter. This fires various callbacks when the new list data is being displayed, but I can't discern from the callbacks when loading has started and stopped.
I was ultimately able to fix this by observing the games LiveData. However, it wasn't exactly straightforward.
Here's my DatabaseState class:
sealed class DatabaseState {
object Success : DatabaseState()
object LoadingSortChange: DatabaseState()
object Loading: DatabaseState()
}
Capturing the Loading state was easy. Whenever the user updates the sort options, I call a method like this:
fun updateSortOptions(newSortOptions: SortOptions) {
_databaseState.value = DatabaseState.LoadingSortChange
_sortOptions.value = newSortOptions
}
The Success state was the tricky one. Since my sorting options are contained in a separate Fragment from the RecyclerView, the games LiveData observer fires twice upon saving new sort options (once when ListFragment resumes, and then again a bit later once the database query is completed). So I had to account for this like so:
/**
* The observer that triggers this method fires once under normal circumstances, but fires
* twice if the sort options change. When sort options change, the "success" state doesn't occur
* until the second firing. So in this case, DatabaseState transitions from LoadingSortChange to
* Loading, and finally to Success.
*/
fun updateDatabaseState() {
when (databaseState.value) {
Database.LoadingSortChange -> gameRepository.updateDatabaseState(DatabaseState.Loading)
DatabaseState.Loading -> gameRepository.updateDatabaseState(DatabaseState.Success)
}
}
Finally, I needed to make some changes to my BindingAdapter to smooth out some remaining issues:
#BindingAdapter("gameListData", "databaseState")
fun RecyclerView.bindListRecyclerView(gameList: PagedList<Game>?, databaseState: DatabaseState) {
val adapter = adapter as GameGridAdapter
/**
* We need to null out the old list or else the old games will briefly appear on screen
* after the ProgressBar disappears.
*/
adapter.submitList(null)
adapter.submitList(gameList) {
// This Runnable moves the list back to the top when changing sort options
if (databaseState == DatabaseState.Loading) {
scrollToPosition(0)
}
}
}