ok so I have an android app and have followed androids room with a view tutorial. I have managed to get it working as expected with my recyclerview to show a history of all games played. I am now working on an achievements style page and want to check for specific scores achieved.
In my DAO file I have the following;
#Query("SELECT COUNT(*) from SavedScores WHERE difficulty = :Difficulty AND questioncount = :QuestionCount AND answeredcorrectly =:QuestionCount")
fun CheckRecordsForTrophy(Difficulty: String,QuestionCount:Int):Flow<Int>
Then in my room repository I have this;
val easy5: Flow<Int> = savedScoresDao.CheckRecordsForTrophy("Easy",5)
In my view model;
val easy5: LiveData<Int> = repository.easy5.asLiveData()
and then in an activity I have the following;
Before the oncreate method:
private val savedScoresViewModel: SavedScoresViewModel by viewModels {
SavedScoresViewModelFactory((application as ScoreApplication).repository)
}
Within the oncreate method:
var easy5var = savedScoresViewModel.easy5
savedScoresViewModel.easy5.observe(this) {
if(easy5var==0){}
}
I am not 100% sure if I should be following all these steps like I did to get all data into my recycler view but I have effectively followed the same steps with exception of adapters etc as I am simply trying to understand if they have met the criteria for a given achievement.
I have a hard coded elements in my repo at the moment for the function i.e CheckRecordsForTrophy("Easy",5) which I will figure how to set from the activity later
The issue I appear to be facing is with:
if(easy5var==0){}
The error I get is Operator '==' cannot be applied to 'LiveData' and 'Int'.
Goal: Check if within my score table is there a record where the score is equal to the number of questions asked, if so I will mark an achievement as complete. I have read that using count* in the query returns the number of records found so I can use that to work out if they should get the achievement or not. In other words, if no records, no achievement.
You are comparing an Int to a LiveData, when you probably wanted to compare the int to the emitted value of the live data.
var easy5var = savedScoresViewModel.easy5
savedScoresViewModel.easy5.observe(this) { newValue -> // this is what you've missed
// if(easy5var==0) {} <-- you've made the wrong equality check here
if (newValue == 0) {} // <-- probably this is what you've meant to do.
}
Related
In room, I have a dao to something like this:
#Dao
interface FacultyDao {
#Query("select * from faculty")
fun getAll(): LiveData<List<Faculty>>
...
}
And inside the repository, I'm simply calling this method and logging it:
class FacultyRepository(application: Application) {
private val facultyDao: FacultyDao
init {
val db: AppDB = AppDB.getInstance(application)
facultyDao = db.facultyDao()
}
fun getAllFaculty(): LiveData<List<Faculty>> {
val v = facultyDao.getAll()
Log.d("muaxx", v.value.toString())
return v
}
...
}
But the thing is it's returning me null, but when I ran that query in inspector it worked. Am I missing something?
LiveData doesn’t immediately have an initial value. Room queries the database and gets the result on a background thread. Then on the next loop of the main thread, the LiveData’s value will be set to this retrieved value. You are logging value too early. The initial value is going to appear some time in the future, after this function has already returned.
Normally you should only be getting a LiveData value through observing it.
Directly checking the value should usually only be done when you are managing a MutableLiveData and are using the previous value to help determine the next value that you are going to post.
Live data gives us real-time data. Therefore, for the first time, you still don't have some in yourself. And it is waiting for the response of the database. If you want to see some of the live data, you must observe it so that after receiving the information, the observer will be called and the information will be logged.
I'm making a chat app and I'm using Firestore for real-time conversations. I have a simple snapshot listener and it's working fine, but I need to add filters according to different message types (e. g. images or audio). To reach this I'm using multiple Firestore queries to filter the content, the problem is that as I have multiple listeners, my RecyclerView gets updated with all of them.
I've tried adding the current listener into a variable, then detaching and adding the new one with the filters, but once I remove the first listener and add them again, they're not triggered anymore.
How can I toggle between different listeners, remove or add the one I need and only listen to it?
This is my ViewModel, getChatMessages() is the default query and getImageMessages is called when the image filter is applied:
//Current listener
private var listenerRegistration: ListenerRegistration? = null
//Get all messages
fun getChatMessages(id: Int) {
//Remove current listener
listenerRegistration?.remove()
val chatId = "chatroom_$id"
val query = db.collection(chatId).orderBy("timestamp", Query.Direction.DESCENDING)
listenerRegistration = query.addSnapshotListener { value, error ->
//Handle errors and update LiveData with messages
}
}
//Get only image messages
fun getImageMessages(id: Int) {
//Remove current listener
listenerRegistration?.remove()
val chatId = "chatroom_$id"
val query = db.collection(chatId).orderBy("timestamp", Query.Direction.DESCENDING)
.whereEqualTo("typeMessage", "image")
listenerRegistration = query.addSnapshotListener { value, error ->
//Handle errors and update LiveData with messages
}
}
I have a simple snapshot listener and it's working fine, but I need to add filters according to different message types (e. g. images or audio).
The following query works fine:
val query = db.collection(chatId).orderBy("timestamp", Query.Direction.DESCENDING)
Because it's a simple query that doesn't require something special to be done. However, the following query:
query = db.collection(chatId).orderBy("timestamp", Query.Direction.DESCENDING)
.whereEqualTo("typeMessage", "image")
Requires an index, since you're ordering the results on a different field than you perform the whereEqualTo() call. To solve this, please my answer in the following post:
Firestore whereEqualTo, orderBy and limit(1) not working
I've got a question about Flowables. I already have a few solutions for this issue, but I would like to double-check if these are the best possible solutions or not.
Context
I have an Interactor that is supposed to bookmark recipes on the DB. It looks like this:
/**
* This Interactor marks a recipe as "bookmarked" on the DB. The Interactor actually switches
* the isBookmarked value of the related recipeId. If it was marked as true, it switches its value
* to false. If it was false, then it switches its value to true.
*/
class BookmarkRecipeInteractorImpl(
private val recipesCacheRepository: RecipesCacheRepository
) : BookmarkRecipeInteractor {
override fun execute(recipeId: Int, callback: BookmarkRecipeInteractor.Callback) {
// Fetches the recipe from DB. The getRecipeById(recipeId) function returns a Flowable.
// Internally, within the RecipesCacheRepository, I'm using room.
recipesCacheRepository.getRecipeById(recipeId).flatMap { originalRecipe ->
// Switches the isBookmarked value
val updatedRecipe = originalRecipe.copy(
isBookmarked = !originalRecipe.isBookmarked
)
// Update the DB
recipesCacheRepository.updateRecipe(updatedRecipe)
// Here's the issue, since I'm updating a DB record and the getRecipeById returns
// a Flowable, as soon as I update the DB, the getRecipeById is going to get triggered
// again, and switch the value again, and again, and again...
}
.subscribe(
{
callback.onSuccessfullyBookmarkedRecipe(it.response)
},
{
callback.onErrorFetchingRecipes()
}
)
}
}
So, if you follow the code, the error is pretty straightforward. I get stuck on a loop, where I constantly change the recipe record.
Possible solutions
1) Have two different functions on my DAO, one called getRecipeByIdFlowable(id) that returns a Flowable, and another called getRecipeByIdSingle(id) that returns a rx.Single. That way I can expose the getRecipeByIdSingle(id) through the Repository and use it instead of the function that returns the Flowable. That way I cut the loop.
Pro: It works.
Con: I don't like having functions like this on my DAO.
2) Save the Disposable on a lateinit property and dispose it as soon as the subscriber triggers the onNext().
Pro: It works.
Con: I don't like having to do something like this, feels hacky.
3) Using ...getRecipeById(recipeId).take(1).flatMap... so it only handles the first emitted object.
Pro: It works, it looks tidy.
Con: I'm not sure if there's a better way to do it.
Question
Ideally, I would like to call some function that just allows me to disable the Flowable behavior and prevent it from emitting more items if the DB changes. So far the solution that I like the most is #3, but I'm not really sure if this is the right way to do it.
Thanks!
Edit 1
I'm just adding a bit more of information about the use case here. I need an Interactor that given a recipeId changes the isBookmarked value on DB to its oposite.
The DB records look like:
data class DbRecipeDto(
#PrimaryKey
val id: Int,
val name: String,
val ingredients: List<String>,
val isBookmarked: Boolean = false
)
I know that maybe there's some other ways in which I could tackle this issue differently. Maybe I could pass the recipeId arg and a bookmark (Boolean) argument and just run the update query.
But this use case it is totally made up, just an example; The thing that I'm trying to figure out how to prevent a Flowable from emitting more items if something changes on the DB.
You should probably call .take(1).singleOrError() on the end of getRecipeById(recipeId).
This will take the first item (or the error) emitted by the Flowable retrieved by calling getRecipeById and wrap it in a Single. In my opinion this correctly matches the semantics of what you want to achieve.
In addition, if I recall correctly, because you will be subscribing on a Single by doing this, your Flowable will not continue to do work after the first item is consumed by the downstream call to singleOrError.
So basically up until now I have been using rxjava2 extensively in the applications, but decided to check out data binding, view models and live data. And Im not sure I've got all of this right, because apart from saving state during rotation of device I do not see any other clear benefits of switching, I could even say that I see downsides of introducing data binding with view model between view and rx java powered requests.
Lets see example of some registration form. It would contain:
2 inputs - name and surname
Field with 3 choices
Button with progress
In the reactive world I would have two observables with name and surname, one observable that would merge 3 choices clicks and map them to the right enum, then I could combine all the data together, communicate directly with my single responsible for sending the data in between I would have state with progress or error and tada Im done.
And here is the thing that I came up with using data binding and view models:
class LiveDataViewModel : ViewModel() {
enum class Choice {
NONE, FIRST, SECOND, THIRD
}
private val _progressVisibilityLiveData = MutableLiveData<Boolean>()
private val _errorLiveData = MutableLiveData<GlobalError>()
val progressVisibilityLiveData: LiveData<Boolean> = _progressVisibilityLiveData.apply { value = false }
val errorLiveData: LiveData<GlobalError> = _errorLiveData
val data = LiveDataData()
val observableData = ObservableField(LiveDataData())
fun actionContinue() {
_progressVisibilityLiveData.postValue(true)
if (observableData.get()?.isValid() == false) _errorLiveData.postValue(GlobalError.AllFieldsRequired)
else sendToApi()
}
private fun sendToApi() {
// TODO there would be still an rx java call to single, when we would handle error in the same way we are doing
// it in actionContinue
}
data class LiveDataData(val firstName: ObservableField<String> = ObservableField(""),
val secondName: ObservableField<String> = ObservableField(""),
val choice: ObservableField<Choice> = ObservableField(Choice.NONE)) {
fun changeChoice(newChoice: Choice) {
choice.set(newChoice)
}
fun isValid(): Boolean = !firstName.get().isNullOrEmpty() && !secondName.get().isNullOrEmpty() && choice.get() != Choice.NONE
fun toRequest(): Request = Request(firstName.get()!!, secondName.get()!!, choice.get()!!)
}
}
So I would change fields of my LiveDataData directly from xml using bindData, also I would change state of my selection box depending on this binding too, progress would have to be done manually and then it would trigger the visibility using data binding. But is it really a good way of handling such cases?
The disadvantages I see are that the whole logic in actionContinue would be manually changing values, the values from ObservableProperties could be null, so we either have to handle nullable values everywhere of we have to use !! and to be honest Im not feeling that this is the right direction.
Maybe any of you guys have thought about something similar and could eventually point me if I made some wrong assumptions or if I shouldn't use for example ObservableProperty at all. Obviously there are tons of articles about data binding and live data etc, but I haven't found any that would satisfy my curiosity. Oh and create MutableLiveData for each property from form is not an option.
RxJava is a completely different concept than DataBinding. It's more of a way of handling concurrency than it is about binding data. I 100% think it's worth learning. The Android community has embraced it with open arms.
Shameless plug: I compiled a list of RxJava resources awhile back - http://gregloesch.com/dev/2014/10/20/resources-for-learning-rxjava-android.html
Lets take the following example:
A many to many mapping exists for PRODUCTS and ORDERS. So a product can be on multiple orders and an order can have multiple products. In Room I have an entity which has both the product id and order id as foreign keys so I can save the relations. It's now very easy to get all the orders for a specific product and also all the products for a specific order.
Now here comes the trouble. As far as I know there is no way to get the order object with all of it's products in 1 query/entity. This can be read in further detail in this post. In most places I can bypass this by just running two queries. The first to get the order I'm interested in, and the second to get the products based on the Id of the order.
Now I want to display the combination of an order with its products in an adapter. For that I need to combine all my orders with their products. I'm clueless on how to solve this with LiveData.
The best solution in my opinion would be to create one query that fetches the OrderWithProducts directly from the database. This post suggests it should be possible, but I've not managed to get this to work. Also the most crucial part in that example is missing: the OrderItem class.
If that solution is not possible there must be some way to get the LiveData OrderWithProducts list with 2 queries and somehow combine them.
EDIT
After the suggestions of #Demigod now I have the following in my ViewModel:
// MediatorLiveData can observe other LiveData objects and react on their emissions.
var liveGroupWithLights = MutableLiveData<List<GroupWithLights>>()
fun createOrdersWithProducts() {
appExecutors.diskIO().execute {
val ordersWithProducts = mutableListOf<OrderWithProducts>()
val orders = orderRepository.getGroupsSync()
for (order in orders) {
val products = productRepository.getProductsSync(order.id)
val orderWithProducts = OrderWithProducts(order, products)
ordersWithProducts.add(orderWithProducts)
}
liveGroupWithLights.postValue(ordersWithProducts)
}
}
The function inside my fragment to submit data to the adapter:
private fun initRecyclerView() {
orderListViewModel.getOrdersWithProducts().observe(this, Observer { result ->
adapter.submitList(result)
})
}
So now I'm able to have a OrderWithProduct object as the item for my adapter. This is great, I can use products for each order in my adapter. Now I'm having trouble to update these items whenever the values in the database changes. Any ideas for this part?
Edit2: the invalidationtracker
db.invalidationTracker.addObserver(object : InvalidationTracker.Observer("orders", "products", "order_product_join") {
override fun onInvalidated(tables: MutableSet<String>) {
createOrdersWithProducts()
}
})
The problem I have now is that the validation tracker gets notified a lot for a single change.
As far as I know, it's not possible currently with a single query.
To solve this, you will need to run several queries here. At first - obtain a list of orders with a single query, and after that obtain a list of products per each order. To achieve this, I can think of several options:
Make your own OrdersWithProductsProvider, which will return this combined entities (Order with List<Porduct>), and it will subscribe for the changes to database to emit new objects using LiveData on every orders or products table change.
You can use a MediatorLiveData to fill the list of Orders with their Products, but I don't think this is a best approach since you will need to run query in a background thread, maybe use of Rx is more convenient here.
Personally, I would use a first option, since probably I want to obtain up-to-date list of orders with their products, which means that the update should trigger on change of three tables (products, orders, products_to_orders), which can be done via Room.InvalidationTracker. Inside that provider I would use Rx (which can work with LiveData via LiveDataReactiveStreams).
Addition on how to achieve that:
How to achieve that isn't really matters, the only thing - run this whole query in the background thread post it to LiveData. You can use Executor, Rx, or a simple Thread. So it will look something like:
private val database : Database // Get the DB
private val executor = Executors.newSingleThreadExecutor()
private val liveData = MutableLiveData<List<OrderWithProducts>>()
fun observeOrdersWithProducts():LiveData<List<OrderWithProducts>> {
return liveData
}
private fun updateOrdersWithProducts() {
executor.post {
val ordersWithProducts = mutableListOf<OrderWithProducts>()
val orders = db.orders()
for (order : orders) {
val products = database.productsForOrder(order)
val orderWithProducts = OrderWithProducts(order, products)
ordersWithProducts.add(orderWithProducts)
}
liveData.post(ordersWithProducts)
}
}
Take it as not complete working code, rather an example of implementation.
Call updateOrdersWithProducts on initialization/first call and every time InvalidationTracker will notify about the db change.