Android - Type mismatch with LiveData variable - android

I have the following in my viewmodel :
// hold the list of comments of a Post
private val _commentsOfPost = MutableLiveData<PagedList<Comment>>()
val commentsOfPost : LiveData<PagedList<Comment>> = _commentsOfPost
fun getCommentsOfPost(postId: Long){
_commentsOfPost.value = commentRepository.getCommentsOfPost(postId) // <--- TYPE MISMATCH
}
So, what happens is that whenever the getCommentsOfPost() is called by the Fragment, it retrieves a PagedList of Comment instances belonging to a Post specified by its ID.
But Android tells me about a Type mismatch ( see the arrow in the codesnippet above):
Required: PagedList<Comment>?
Found: LiveData<PagedList<Comment>>
For the sake of completeness, this is the getCommentsOfPost() interface:
fun getCommentsOfPost(postId: Long) : LiveData<PagedList<Comment>>
How can I change it so that this error disappears ?

You should return to PagedList,
sample:
fun getCommentsOfPost(postId: Long) : PagedList<Comment> {
// your code
// return PagedList<Comment>
}

Related

how to write Android Custom lint rule to forbid calling specific function in all classes that extends specific type?

I want to write a custom lint rule to ban calling function states.accept() in all classes that extends BaseViewModel where states is a BehaviorRelay object.
how can I achieve something like this.
I’ve written the check using visitMethodCall but this only can check the function name and if it’s member of BehaviorRelay,
the missing part is how to check if this function is being called in children’s of BaseViewModel.
below is the part that works: using visitMethodCall but detecting the function in whole code.
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
val evaluator = context.evaluator
if (evaluator.isMemberInClass(method, BEHAVIOR_RELAY)) {
if (method.name == ACCEPT_FUNCTION) {
context.report(
Incident(
issue = ISSUE,
scope = node,
location = context.getNameLocation(node),
message = "View Models implements `BaseViewModel` must not update `states`"
)
)
}
}
}
applicableSuperClasses will filter only the classes that extends passed types, in my case BaseViewModel. this function works with visitClass.
Then using AbstractUastVisitor() to visit all calls in that class and find the specific function by name, also checking if it's a member function in target type.
full working code.
override fun applicableSuperClasses(): List<String>? {
return listOf(BASE_VIEW_MODEL)
}
override fun visitClass(context: JavaContext, declaration: UClass) {
val evaluator = context.evaluator
declaration.accept(object : AbstractUastVisitor() {
override fun visitCallExpression(node: UCallExpression): Boolean {
val isRelayFunction = evaluator.isMemberInClass(
node.resolve(),
BEHAVIOR_RELAY
)
if (node.methodName == ACCEPT_FUNCTION && isRelayFunction) {
context.report(
issue = ISSUE,
scope = node,
location = context.getNameLocation(node),
message = "View Models implements `BaseViewModel` must not update `states`"
)
}
return super.visitCallExpression(node)
}
})
}

One-shot request single result in Room Android

I am practicing my android skills (beginner) by coding a grocery list app. I have two tables in my db, a shopping_item table (The items I want to buy) and a reference_item table (The items I know the category and the unit price). Each time I add a shopping item, there is an refId field referencing to the reference item id corresponding. It is a default value to a default reference item if the shopping item is not referenced yet.
I use a MVVM model. I then have a DAO, a repository, a viewModel and my fragments that display data.
When I add a new shopping item, I want to know if there is a corresponding reference item. I want to do the following Query:
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
It returns the id of the reference item corresponding as an Int or is null if it is not referenced yet. In my repository, I have a function like that:
suspend fun getRefItem(refName : String) = db.getShoppingDao().getRefItem(refName)
For now, I think I am doing alright. No mistake in sight I guess.
The problem begin when I try to implement my viewModel. What should I do? What about my fragment?
I have a addNewItem(name: String, amount: Int) function in my fragment to add the new item. I can find the reference item corresponding with the name provided.
I tried multiple things, using LiveData, suspend functions, mutableLiveData/LiveData, but I am getting lost right now. Every tutorials or examples use LiveData or Query all data from the db. I just want one Integer, one Time, no need of LiveData I think.
here is the complete solution. Hope this is useful for you.
DAO
#Query(value = "SELECT refId FROM reference_items WHERE reference_item_name = :refName")
suspend fun getRefItem(refName : String) : Int
Repository
// Specify return datatype as Int
suspend fun getRefItem(refName : String): Int = db.getShoppingDao().getRefItem(refName)
ViewModel
fun getRefItem(name: String): LiveData<Int> {
val result : MutableLiveData<Int>() <-- setup livedata to return as value
viewModelScope.lanuch {
result.postValue(repository.getRefItem(name))
}
return result <-- return livedata
}
Fragment
fun addNewItem(name: String, amount: Int) {
// setup viewModel observer
viewModel.getRefItem(name).observer { viewLifecycleOwner, { value ->
// GET YOUR INT VALUE HERE
Log.i("VALUE", value)
}
}
}

Android - LiveData doesn't get updated

In my fragment I observe dbQuestionsList field:
viewModel.dbQuestionsList.observe(viewLifecycleOwner, Observer { list ->
Log.i("a", "dbQuestionsList inside fragment = $list ")
})
In my fragment I have few buttons and depending on which one is pressed I call method on viewModel passing the string which was set as tag to the button.
viewModel.onFilterChanged(button.tag as String)
My ViewMode:
lateinit var dbQuestionsList: LiveData<List<DatabaseQuestion>>
init{
onFilterChanged("")
}
private fun onFilterChanged(filter: String) {
dbQuestionsList = mRepository.getDbQuestionsForCategory(filter)
}
Repository method:
fun getDbQuestionsForCategory(categoryName: String): LiveData<List<DatabaseQuestion>> {
return database.dbQuestionsDao().getDbQuestionsByCategory(categoryName)
}
Dao method:
#Query("SELECT * FROM db_questions_database WHERE category = :categoryId")
fun getDbQuestionsByCategory(categoryId: String): LiveData<List<DatabaseQuestion>>
When I press button, viewModel method is called with argument which should be used to update LiveData by searching through room database, but NOTHING gets updated for no reason. Database is not empty so there is no reason to return null and not trigger observer in main Fragment.
But when I do this in my viewModel:
lateinit var dbQuestionsList: LiveData<List<DatabaseQuestion>>
init{
onFilterChanged("travel")
}
where I hardcode parameter, the room will return list and observer in fragment will be triggered, so it works like that but doesn't work when arguments is passed when button is pressed, Please explain because this thing doesn't make sense. I tried with mutable live data, with using .setValue and .postValue but NOTHING works.
The reason you aren't getting updates is because onFilterChanged() is reassigning dbQuestionsList, not updating it. So the variable you observe initially is never actually modified.
I would probably implement this using a Transformation:
val filter = MutableLiveData<String>().apply { value = "" }
val dbQuestionsList = Transformations.switchMap(filter) {
mRepository.getDbQuestionsForCategory(filter)
}
Then in your fragment just set the filter when your button is clicked:
viewModel.filter.value = button.tag as String
Try this:
dbQuestionsList.value = mRepository.getDbQuestionsForCategory(filter)
or
dbQuestionsList.postValue(mRepository.getDbQuestionsForCategory(filter))

How to use recyclerview-selection by setting the key as a string?

Using the android library androidx.recyclerview.selection, I have tried to implement multiple selection in RecyclerView following the tutorials here and here.
But, I want my key to be a String, instead of Long, but I am facing two errors as follows:
tracker = SelectionTracker.Builder<String>(
"selection_id",
recyclerView,
StableIdKeyProvider(recyclerView), // this line shows error
MyItemDetailsLookup(recyclerView),
StorageStrategy.createStringStorage() // this line shows error
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
I want some details about how is ItemKeyProvider implemented for a String, and secondly,
StorageStrategy.createStringStorage() // this shows error
StorageStrategy.createLongStorage() // this doesn't show error
why is that happening, when everywhere I have replaced the generic type from Long to String?
According to the docs, StorageStrategy is used for storing keys in saved state,
/* for Long keys */ StorageStrategy.createLongStorage()
/* for String keys */ StorageStrategy.createStringStorage()
/* for Parcelable keys */ StorageStrategy.createParcelableStorage(Class)
Also, according to the docs, StableIdKeyProvider provides for keys of type Long. That is why, your StorageStrategy is showing error because it is expecting Long keys.
To provide String keys, you have to create your own ItemKeyProvider class. For more details on ItemKeyProvider, you can refer the docs here.
This is how you can implement ItemKeyProvider class for String keys:
class MyItemKeyProvider(private val rvAdapter: MyAdapter): ItemKeyProvider<String>(SCOPE_CACHED) {
override fun getKey(position: Int): String = rvAdapter.getItem(position).myKey
override fun getPosition(key: String): Int = rvAdapter.getPosition(key)
}
and in MyAdapter:
class MyAdapter(private val myList: ArrayList<MyModel>): RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
// functions used in MyItemKeyProvider
fun getItem(position: Int) = myList[position]
fun getPosition(key: String) = myList.indexOfFirst { it.myKey == key }
// other functions
}
where MyModel is something like this:
data class MyModel (
val myKey: String,
// other data
)
Now, you can simply build your SelectionTracker like this:
myTracker = SelectionTracker.Builder(
"my_selection_id",
recyclerView,
MyItemKeyProvider(rvAdapter),
MyItemDetailsLookup(recyclerView),
StorageStrategy.createStringStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
Note that you should not write the following code in your Adapter if you are not using StableIdKeyProvider:
init { setHasStableIds(true) }
otherwise it will show this error:
Attempt to invoke virtual method 'boolean androidx.recyclerview.widget.RecyclerView$ViewHolder.shouldIgnore()' on a null object reference
This tutorial shows how to implement recyclerview-selection with Long keys, also showing how to implement your own ItemKeyProvider class for Long keys.
To implement recyclerview-selection with Parcelable keys, I have found a sample code here.

Kotlin generics anomaly

Ok, so I have three interface types.
Movement<T : Animal>
Animal with subinterfaces Cat, Dog, Horse
AnimalMovement
Movement Interface
interface Movement<T : Animal> {
fun moveAnimal(type:T)
}
Animal Interfaces
interface Animal {
fun takeSteps()
fun jump()
fun hide()
}
interface Cat : Animal
interface Dog : Animal
AnimalMovement
interface CatMovement : Movement<Cat>
I then implement the CatMovement interface
class CatMovementImpl : CatMovement {
override fun moveAnimal(type: Cat) {
TODO("not implemented")
}
}
Problem
fun TestGenerics() {
var catMovement : Movement<Cat> = CatMovementImpl() // this works
var catMovement : Movement<Animal> = CatMovementImpl() // this doesn't?
}
I am sure in Java both lines would have worked fine. However in Kotlin the second line fails to execute. Why would that be? An animal is the base type for cat, so this should have worked right?
I am not an expert of Kotlin, but that seems perfectly normal:
When declared like this:
var animalMovement : Movement<Animal>
You can write code:
animalMovement.moveAnimal(dog)
But if assigning it like this:
var animalMovement : Movement<Animal> = CatMovementImpl()
is allowed, it means that your CatMovementImpl should be able to move a dog ?
What moskito said in the comments is correct.
I am pretty sure that doesn't work in Java either. A Movement<Cat> is
NOT a subtype of Movement<Animal>, the same way a List is NOT
a subtype of List<Object>. You might want to read this.
But in Kotlin this is possible using type variance.
fun TestGenerics() {
var catMovement1: Movement<Cat> = CatMovementImpl()
var catMovement2: Movement<out Animal> = CatMovementImpl() // works
}
You basically tell the compiler "accept all implementations of Movement<Animal> or implementations Movement<S> for which S has Animal as upper bound".
But, then a problem arises. You cannot invoke
val cat: Cat = /* ... */
catMovement2.moveAnimal(cat) // error
giving you the error
Out-projected type Movement<out Animal> prohibits the use of [...].
because T can only be used as producer (out position) and not as consumer (in position) like this (function made up to demonstrate the point):
val c: Cat = catMovement2.getAnimal() // works
This problem becomes clear right away when you use out at the Movement declaration like this:
interface Movement<out T : Animal> {
fun moveAnimal(type: T) // error
}
It depends on your use case but maybe you should just let Kotlin infer the type, which would be CatMovementImpl.
var catMovement = CatMovementImpl()
Credit goes to EpicPandaForce for already suggesting using out in the comments.

Categories

Resources