I'm trying to retrieve a single Client item from my Room database. Every client is displayed in a list, and each client has an edit button on them. When the button is pressed, I would like to retrieve that client from the database by their id. Their details will then be displayed on an edit screen.
My problem arises in actually getting the client from the database. So far I have tried 2 approaches:
Coroutines based approach
I have tried to retrieve the item using coroutine based functions with Room. This approach does "work" to an extent, but in its current form the coroutine ends up retrieving the newly searched for client ***after*** the edit screen has been displayed. This makes it so that when you edit a client, you end up editing the one you tried to edit previously.
I have tried to counteract this by using .join(), using viewModelScope.async rather than launch and then attempting to use .await, and a few other ideas, but none of them have worked.
ClientDao.kt
#Dao
interface ClientDao {
#Query("SELECT * FROM tblClient WHERE id = :id")
suspend fun getClientToEdit(id: Int): List<Client>
}
ClientRepository.kt
class ClientRepository(private val clientDao: ClientDao) {
val clientSearchResults = MutableLiveData<List<Client>>()
suspend fun getClientToEdit(id: Int) {
clientSearchResults.value = clientDao.getClientToEdit(id)
}
}
ClientViewModel.kt
class ClientViewModel(application: Application): ViewModel() {
private val repository: ClientRepository
val clientSearchResults: MutableLiveData<List<Client>>
init {
val clientDB = ManagementDatabase.getDatabase(application)
val clientDao = clientDB.clientDao()
repository = ClientRepository(clientDao)
clientSearchResults = repository.clientSearchResults
}
fun getClientToEdit(clientId: Int) = viewModelScope.launch {
repository.getClientToEdit(clientId)
}
}
ManagementApp.kt
ClientScreen(
onEditClient = { id ->
clientViewModel.getClientToEdit(id)
val editClientList: List<Client>? = clientViewModel.clientSearchResults.value
//This looks awful but it works
// It just gets the client details of the selected client
if (editClientList != null) {
if (editClientList.firstOrNull() != null) {
selectedClient = editClientList[0]
If I could just find a way to make it so that clientViewModel.getClientToEdit(id) fully executed before running the rest of the code in ManagementApp.kt, it would work. The problem is I'm not sure how.
Flow based approach:
I didn't really think this approach would work, but it was worth a shot. I have tried to retrieve the item using a flow list, in the same way I have been retrieving the whole list.
ClientDao.kt
#Dao
interface ClientDao {
#Query("SELECT * FROM tblClient WHERE id = :id")
fun getClientToEdit(id: Int): Flow<List<Client>>
}
ClientRepository.kt
class ClientRepository(private val clientDao: ClientDao) {
fun getClientSearchResults(id: Int): Flow<List<Client>> =
clientDao.getClientToEdit(id)
}
ClientViewModel.kt
class ClientViewModel(application: Application): ViewModel() {
private val repository: ClientRepository
init {
val clientDB = ManagementDatabase.getDatabase(application)
val clientDao = clientDB.clientDao()
repository = ClientRepository(clientDao)
}
fun getClientToEdit(clientId: Int): LiveData<List<Client>> {
return repository.getClientSearchResults(id = clientId).asLiveData()
}
}
ManagementApp.kt
ClientScreen(
onEditClient = { id ->
val editClientList by clientViewModel.getClientToEdit(id).observeAsState(listOf())
//This looks awful but it works
// It just gets the client details of the selected client
if (editClientList != null) {
if (editClientList.firstOrNull() != null) {
selectedClient = editClientList[0]
The problem with this approach is that .observeAsState gives me the '#Composable invocations can only happen from the context of a #Composable function' error (Although the snippet of code above is actually within a #Composable function).
If anyone could provide some much needed help I would greatly appreciate it. I'm new to Android and have struggled with Room quite a bit, so my apologies if the code isn't really up to scratch. Thank you.
When the button is pressed, I would like to retrieve that client from the database by their id. Their details will then be displayed on an edit screen.
If by "edit screen" you mean proper screen with ViewModel to which you navigate using for example androidx.navigation, better approach would be to just pass the id to that new screen and do the loading in its ViewModel.
If I could just find a way to make it so that clientViewModel.getClientToEdit(id) fully executed before running the rest of the code in ManagementApp.kt
You can do that by making getClientToEdit suspend fun and then doing something like this:
val scope = rememberCoroutineScope()
ClientScreen(
onEditClient = { id ->
scope.launch {
clientViewModel.getClientToEdit(id)
// now getClientToEdit was executed
}
}
)
I would also suggest returning Client directly from the getClientToEdit, using LiveData for that is not necessary
Although the snippet of code above is actually within a #Composable function
It's not, you are trying to call it from onClick callback and onClick is not marked with #Composable, so you cannot call composable functions from there.
To sum it up:
If the result of your action is navigation to another screen, you can do one of these:
Pass just the id to that other screen and do the loading there, as I suggested.
Launch coroutine inside onEditClient callback, load the client and navigate from there as shown above.
Load the client in ViewModel, update some state there and navigate based on that state, something like:
// ViewModel
val actions = MutableSharedFlow<Action>()
fun editClient(id: Int) = viewModelScope.launch {
val client = repository.getClientToEdit(clientId)
actions.emit(NavigateToEditScreen(client))
}
// Screen
val action by clientViewModel.actions.collectAsState()
LaunchedEffect(action) {
if (action is NavigateToEditScreen) {
// do the navigation using action.client
}
}
ClientScreen(
onEditClient = { id ->
clientViewModel.editClient(id)
}
)
Related
In my application I want update data with SharedFlow and my application architecture is MVI .
I write below code, but just update one of data!
I have 2 spinners and this spinners data fill in viewmodel.
ViewModel code :
class MyViewModel #Inject constructor(private val repository: DetailRepository) : ViewModel() {
private val _state = MutableStateFlow<MyState>(MyState.Idle)
val state: StateFlow<MyState> get() = _state
fun handleIntent(intent: MyIntent) {
when (intent) {
is MyIntent.CategoriesList -> fetchingCategoriesList()
is MyIntent.PriorityList -> fetchingPrioritiesList()
}
}
private fun fetchingCategoriesList() {
val data = mutableListOf(Car, Animal, Color, Food)
_state.value = DetailState.CategoriesData(data)
}
private fun fetchingPrioritiesList() {
val data = mutableListOf(Low, Normal, High)
_state.value = DetailState.PriorityData(data)
}
}
With below codes I filled spinners in fragment :
lifecycleScope.launch {
//Send
viewModel.handleIntent(MyIntent.CategoriesList)
viewModel.handleIntent(MyIntent.PriorityList)
//Get
viewModel.state.collect { state ->
when (state) {
is DetailState.Idle -> {}
is DetailState.CategoriesData -> {
categoriesList.addAll(state.categoriesData)
categorySpinner.setupListWithAdapter(state.categoriesData) { itItem ->
category = itItem
}
Log.e("DetailLog","1")
}
is DetailState.PriorityData -> {
prioritiesList.addAll(state.prioritiesData)
prioritySpinner.setupListWithAdapter(state.prioritiesData) { itItem ->
priority = itItem
}
Log.e("DetailLog","2")
}
}
When run application not show me number 1 in logcat, just show number 2.
Not call this line : is DetailState.CategoriesData
But when comment this line viewModel.handleIntent(MyIntent.PriorityList) show me number 1 in logcat!
Why when use this code viewModel.handleIntent(MyIntent.CategoriesList) viewModel.handleIntent(MyIntent.PriorityList) not show number 1 and 2 in logcat ?
The problem is that a StateFlow is conflated, meaning if you rapidly change its value faster than collectors can collect it, old values are dropped without ever being collected. Therefore, StateFlow is not suited for an event-like system like this. After all, it’s in the name that it is for states rather than events.
It’s hard to suggest an alternative because your current code looks like you shouldn’t be using Flows at all. You could simply call a function that synchronously returns data that you use synchronously. I don’t know if your current code is a stepping stone towards something more complicated that really would be suitable for flows.
My task is to get whole Article with provided title from RecyclerView.
When I click on specific Article i get title from it.
Room database:
#Query("SELECT * FROM article_table WHERE title = :title")
fun getArticleDetails(title: String): Flow<ArticleLocal>
Repository:
fun getArticleDetails(title: String): Flow<ArticleLocal> {
return articleDao.getArticleDetails(title)
}
ViewModel:
val articleDetail = MutableStateFlow<ArticleLocal>(ArticleLocal("","","","",""))
fun getArticle(title: String) {
viewModelScope.launch {
articleRepository.getArticleDetails(title).collect {
articleDetail.emit(it)
}
}
}
MainActivity:
lifecycleScope.launch {
viewModel.getArticle(title)
viewModel.articleDetail.collect {
Log.d(TAG, "onCreate: $it")
}
}
Problem with this code is that articleDetail on first touch gives me empty ArticleLocal e.g. title = "" I defined in ViewModel, later I get good result.
EDIT: With MyActivity .collet I get whole object but cannot access propert like it.title
Use a SharedFlow so it doesn't have to publish a default result. The flow won't emit anything until it receives its first value. Use replay = 1 to get similar behavior as StateFlow as far as new subscribers getting the most recent value immediately.
You also need to consider that if the title changes, it should not keep publishing values with the old title. Currently, you have it collecting from more and more flows each time the title changes.
If you use another MutableSharedFlow just for the title, you can get it to automatically cancel unnecessary collection of those old title flows. It also allows you to get the benefit of SharingStarted.WhileSubscribed to avoid unnecessary collection from the repository when there are no subscribers.
In ViewModel:
private val articleTitle = MutableSharedFlow<String>(bufferOverflow = BufferOverflow.DROP_OLDEST)
val articleDetail = articleTitle.flatMapLatest { articleRepository.getArticleDetails(it) }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(5000), replay = 1)
fun getArticle(title: String) {
articleTitle.tryEmit(title)
}
You can get rid of additional flow to emit data and use the flow returned from the repository directly.
ViewModel:
fun getArticle(title: String): Flow<ArticleLocal> {
return articleRepository.getArticleDetails(title)
}
MainActivity:
lifecycleScope.launch {
viewModel.getArticle(title).collect {
Log.d(TAG, "onCreate: $it")
}
}
Right now, my method of updating my jetpack compose UI on database update is like this:
My Room database holds Player instances (or whatever they're called). This is my PlayerDao:
#Dao
interface PlayerDao {
#Query("SELECT * FROM player")
fun getAll(): Flow<List<Player>>
#Insert
fun insert(player: Player)
#Insert
fun insertAll(vararg players: Player)
#Delete
fun delete(player: Player)
#Query("DELETE FROM player WHERE uid = :uid")
fun delete(uid: Int)
#Query("UPDATE player SET name=:newName where uid=:uid")
fun editName(uid: Int, newName: String)
}
And this is my Player Entity:
#Entity
data class Player(
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
#ColumnInfo(name = "name") val name: String,
)
Lastly, this is my ViewModel:
class MainViewModel(application: Application) : AndroidViewModel(application) {
private val db = AppDatabase.getDatabase(application)
val playerNames = mutableStateListOf<MutableState<String>>()
val playerIds = mutableStateListOf<MutableState<Int>>()
init {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().getAll().collect {
playerNames.clear()
playerIds.clear()
it.forEach { player ->
playerNames.add(mutableStateOf(player.name))
playerIds.add(mutableStateOf(player.uid))
}
}
}
}
fun addPlayer(name: String) {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().insert(Player(name = name))
}
}
fun editPlayer(uid: Int, newName: String) {
CoroutineScope(Dispatchers.IO).launch {
db.playerDao().editName(uid, newName)
}
}
}
As you can see, in my ViewHolder init block, I 'attach' a 'collector' (sorry for my lack of proper terminology) and basically whenever the database emits a new List<Player> from the Flow, I re-populate this playerNames list with new MutableStates of Strings and the playerIds list with MutableStates of Ints. I do this because then Jetpack Compose gets notified immediately when something changes. Is this really the only good way to go? What I'm trying to achieve is that whenever a change in the player table occurs, the list of players in the UI of the app gets updated immediately. And also, I would like to access the data about the players without always making new requests to the database. I would like to have a list of Players at my disposal at all times that I know is updated as soon as the database gets updated. How is this achieved in Android app production?
you can instead use live data. for eg -
val playerNames:Livedata<ListOf<Player>> = db.playerDao.getAll().asliveData
then you can set an observer like -
viewModel.playerNames.observe(this.viewLifecycleOwner){
//do stuff when value changes. the 'it' will be the changed list.
}
and if you have to have seperate lists, you could add a dao method for that and have two observers too. That might be way more efficient than having a single function and then seperating them into two different lists.
First of all, place a LiveData inside your data layer (usually ViewModel) like this
val playerNamesLiveData: LiveData<List<Player>>
get() = playerNamesMutableLiveData
private val playerNamesMutableLiveData = MutableLiveData<List<Player>>
So, now you can put your list of players to an observable place by using playerNamesLiveData.postValue(...).
The next step is to create an observer in your UI layer(fragment). The observer determines whether the information is posted to LiveData object and reacts the way you describe it.
private fun observeData() {
viewModel.playerNamesLiveData.observe(
viewLifecycleOwner,
{ // action you want your UI to perform }
)
}
And the last step is to call the observeData function before the actual data posting happens. I prefer doing this inside onViewCreated() callback.
I am learning android development and I decided to build a weather app using api that comes from service named open water map. Unfortunately I’ve got the following problem:
In order to get the weather data for wanted city, I first need to perform request to get the geographical coordinates. So what I need to do is to create one request, wait until it is finished, and after that do another request with data that has been received from the first one.
This is how my view model for location looks like:
class LocationViewModel constructor(private val repository: WeatherRepository): ViewModel() {
val location = MutableLiveData<List<GeocodingModel>>()
private val API_KEY = „xxxxxxxxxxxxxxxxxxxxxxxxx”
fun refresh() {
CoroutineScope(Dispatchers.IO).launch {
// call fetch location here in coroutine
}
}
private suspend fun fetchLocation(): Response<GeocodingModel> {
return repository.getCoordinates(
"Szczecin",
API_KEY
)
}
}
And this is how my view model for weather looks like”
class WeatherSharedViewModel constructor(private val repository: WeatherRepository): ViewModel() {
private val API_KEY = „xxxxxxxxxxxxxxxxxxxxxxxxx”
val weather = MutableLiveData<List<SharedWeatherModel>>()
val weatherLoadError = MutableLiveData<Boolean>()
val loading = MutableLiveData<Boolean>()
fun refresh(lat: String, lon: String) {
loading.value = true
CoroutineScope(Dispatchers.IO).launch {
// call fetchWeather here in coroutine
}
loading.value = false
}
private suspend fun fetchWeather(lat: String, lon: String): Response<SharedWeatherModel> {
return repository.getWeather(
lat,
lon,
"minutely,hourly,alerts",
"metric",
API_KEY
)
}
}
I am using both view models in a fragment in such way:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val weatherService = WeatherApi.getInstance()
val repository = WeatherRepository(weatherService)
locationViewModel = ViewModelProvider(requireActivity(), ViewModelFactory(repository)).get(LocationViewModel::class.java)
weatherViewModel = ViewModelProvider(requireActivity(), ViewModelFactory(repository)).get(WeatherSharedViewModel::class.java)
locationViewModel.refresh()
Log.d(TAG, "lat: ${locationViewModel.location.value?.get(0)?.get(0)?.lat.toString()}, lon: ${locationViewModel.location.value?.get(0)?.get(0)?.lon.toString()}")
weatherViewModel.refresh(
locationViewModel.location.value?.get(0)?.get(0)?.lat.toString(),
locationViewModel.location.value?.get(0)?.get(0)?.lon.toString()
)
val weatherList = view?.findViewById<RecyclerView>(R.id.currentWeatherList)
weatherList?.apply {
layoutManager = LinearLayoutManager(context)
adapter = currentWeatherAdapter
}
val cityList = view?.findViewById<RecyclerView>(R.id.currentCityList)
cityList?.apply {
layoutManager = LinearLayoutManager(context)
adapter = currentLocationAdapter
}
observerLocationViewModel()
observeWeatherViewModel()
}
So on a startup both models are refreshed, which means that requests are made. I was trying to somehow synchronize those calls but my last attempt ended that data passed to the refresh method of weather view model was null. So problem is that both coroutine are launched one after another, first one is not waiting for second.
The main question: is there any synchronisation mechanism in coroutines? That I can launch one coroutine and wait with launching second one as long as first is not finished?
You are violating the "Single Responsibilty Principle" you need to learn how to write CLEAN code. that is why you are running into such problems. A member of stackoverflow has explained it in depth: single responsibility
A few tips:
Your general design is somewhat convoluted because you are trying to update LiveData with coroutines, but one LiveData's exposed data is something determined by the other LiveData. This is theoretically OK if you need to be able to access the city even after you already have the weather for that city, but since you've split this behavior between two ViewModels, you end up having to manage that interaction externally with your Fragment, which is very messy. You cannot control it from a single coroutine unless you use the fragment's lifecycle scope, but then the fetch tasks restart if the screen rotates before they're done. So I would use a single ViewModel for this.
In a ViewModel, you should use viewModelScope for your coroutines instead of creating an ad hoc CoroutineScope that you never cancel. viewModelScope will automatically cancel your coroutines when the ViewModel goes out of scope.
Coroutines make it extremely easy to sequentially do background work. You just need to call suspend functions in sequence within a single coroutine. But to do that, once again, you really need a single ViewModel.
It's convoluted to have separate LiveDatas for the loading and error states. If you use a sealed class wrapper, it will be much simpler for the Fragment to treat the three possible states (loading, error, have data).
Putting this together gives the following. I don't really know what your repo is doing and how you convert Response<GeocodingModel> to List<GeocodingModel> (or why), so I am just using a placeholder function for that. Same for the weather.
sealed class WeatherState {
object Loading: WeatherState()
object Error: WetaherState()
data class LoadedData(val data: List<SharedWeatherModel>)
}
class WeatherViewModel constructor(private val repository: WeatherRepository): ViewModel() {
val location = MutableLiveData<List<GeocodingModel>>()
private val API_KEY = „xxxxxxxxxxxxxxxxxxxxxxxxx”
val weather = MutableLiveData<LoadedData>().apply {
value = WeatherState.Loading
}
fun refreshLocation() = viewModelScope.launch {
weather.value = WeatherState.Loading
val locationResponse = fetchLocation() //Response<GeocodingModel>
val locationList = unwrapLocation(location) //List<GeocodingModel>
location.value = locationList
val latitude = locationList.get(0).get(0).lat.toString()
val longitude = locationList.get(0).get(0).lon.toString()
try {
val weatherResponse = fetchWeather(latitude, longitude) //Response<SharedWeatherModel>
val weatherList = unwrapWeather(weatherResponse) //List<SharedWeatherModel>
weather.value = WeatherState.LoadedData(weatherList)
} catch (e: Exception) {
weather.value = WeatherState.Error
}
}
private suspend fun fetchLocation(): Response<GeocodingModel> {
return repository.getCoordinates(
"Szczecin",
API_KEY
)
}
private suspend fun fetchWeather(lat: String, lon: String): Response<SharedWeatherModel> {
return repository.getWeather(
lat,
lon,
"minutely,hourly,alerts",
"metric",
API_KEY
)
}
}
And in your Fragment you can observe either LiveData. The weather live data will always have one of the three states, so you have only one place where you can use a when statement to handle the three possible ways your UI should look.
Without referring to your actual code only to the question itself:
By default code inside coroutines is sequential.
scope.launch(Dispatcher.IO) {
val coordinates = repository.getCoordinates(place)
val forecast = repository.getForecast(coordinates)
}
Both getCoordinates(place) and getForecast(coordinates) are suspend functions since they're making network requests and waiting for the result.
getForecast(coordinates) won't execute until getCoordinates(place) is done and returned the coordinates.
I'm pretty new in the world of MVI pattern. So I'm trying to understand how fit together all the pieces.
I have an app that I structured using MVI pattern (or at least it was what I was meant to do). I have my fragment (I used navigation component but at the moment focus just on one fragment), which is supported by its own ViewModel. Then I have a repository class where all viewmodels retrieve data. Repository has 2 source of data, a web API and a local DB used as cache of data, I used Room for DB management.
I tried different approaches to the problem. At the moment I have done in this way:
In the DAO I used this instruction to retrieve data from the DB:
#Query("SELECT * FROM Users WHERE idTool=:idTool AND nickname LIKE '%' || :query || '%'")
fun users(idTool: Int, query: String) : Flow<List<User>>
Then in my repository I simple get this query to forward to ViewModels:
fun usersFlow(idTool: Int, query: String) = userDao.users(idTool, query)
In the ViewModel I created two MutableLiveData, coordinated by a MediatorLiveData:
val nicknameQuery = MutableStateFlow("")
private val nicknameQueryFlow = nicknameQuery.flatMapLatest {
repository.usersFlow(idToolQuery.value, it)
}
val idToolQuery = MutableStateFlow(DEFAULT_TOOL_ID)
private val idToolQueryFlow = idToolQuery.flatMapLatest {
repository.usersFlow(it, nicknameQuery.value)
}
val users = MediatorLiveData<List<User>>()
init {
users.addSource(nicknameQueryFlow.asLiveData()) {
users.value = it
}
users.addSource(idToolQueryFlow.asLiveData()) {
users.value = it
}
fetchUsers()
}
In this way, from my fragment, I can simply update nicknameQuery or idToolQuery to have an updated list in my RecyclerView. My first doubt is that in this way the fetch of data from my DB is done 2 times, one time for each mutable, but I'd like to retrieve data just one on the app opening (maybe the solution fro this is just check in the nicknameQuery that current query is different from the passed one, in this way since at the beginning current query is empty and it pass an empty query, it is bypassed).
In the Init method of ViewModel, I also call fetchUsers():
private fun fetchUsers() {
viewModelScope.launch {
repository.fetchUsers(DEFAULT_TOOL_ID).collect {
_dataState.value = it
}
}
}
This method checks into the database if there are already cached users with this specific idTool, if not it fetches them from the web and it stores retrieved data into the DB. This is the method inside my repository class:
suspend fun fetchUsers(
idTool: Int,
forceRefetch: Boolean = false
): Flow<DataState<List<User>>> = flow {
try {
var cachedUser = userDao.users(idTool, "").first()
val users: List<User>
if(cachedUser.isEmpty() || forceRefetch) {
Log.d(TAG, "Retrieve users: from web")
emit(DataState.Loading)
withContext(Dispatchers.IO) {
appJustOpen = false
val networkUsers =
api.getUsers(
idTool,
"Bearer ${sessionClient.tokens.accessToken.toString()}"
)
users = entityMapper.mapFromEntitiesList(networkUsers)
userDao.insertList(users)
}
} else {
users = cachedUser
}
emit(DataState.Success(users))
} catch (ex: Exception) {
emit(DataState.Error(ex))
}
}
This method checks if I have already users inside the DB with this specific idTool, if not it fetches them from API. It uses a DataState to update the UI, based on the result of the call. During the fetch of data, it emits a Loading state, this shows a progress bar in my fragment. If data is correctly fetched it emits a Success, and the fragment hides the progress bar to shows the recycler view. This is done in the following way. In my ViewModel I have this mutable state
private val _dataState = MutableLiveData<DataState<List<User>>>()
val dataState: LiveData<DataState<List<User>>> get() = _dataState
As you saw above, my fetch method is
private fun fetchUsers() {
viewModelScope.launch {
repository.fetchUsers(DEFAULT_TOOL_ID).collect {
_dataState.value = it
}
}
}
And finally in my fragment I have:
userListViewModel.dataState.observe(viewLifecycleOwner, { dataState ->
when (dataState) {
is DataState.Success -> {
showUserList()
}
is DataState.Error -> {
Log.e("TEST", dataState.exception.toString())
hideLoader()
Toast.makeText(activity, "Error retrieving data: ${dataState.exception}", Toast.LENGTH_LONG).show()
}
is DataState.Loading -> {
showLoader()
}
else -> {
// Do Nothing in any other case
}
}
})
At this moment Success state takes a list of users, but this list is there from a previous approach, at the moment it is useless since after data is fetched list is inserted into the DB, and I have a Flow to the DB which takes care to update the UI. In this way when I change idTool, when I change query, when I remove a user, the view is always notified
Is this approach correct?
Before this, I used another approach. I returned not a flow from my DB but just a List. Then my fetchUsers always returned a DataState<List>, it checked in the DB and if didn't found anything it fetched data from the web and returned that list. This approach caused me some problems, since every time I changed idTool or query, I always had to call fetchUsers method. Even if a user was removed from database, views didn't get notified since I didn't have a direct flow with the DB.