So I have an ArrayList<MovieDetailEntity> which contains a data class I made called MovieDetailEntity. In the data class, I have a movie ID which is unique to every movie. I'm trying to retrieve the rest of the properties in the object from linear search.
So currently I have these functions:
private fun getMovieDetails():ArrayList<MovieDetailEntity>{
return DetailDataDummy.getMovieDetail()
}
fun getDetailsById(movieId: String):MovieDetailEntity{
val details = getMovieDetails()
var detailEntity: MovieDetailEntity
for (item in details){
if (movieId == item.movieId){
detailEntity = item
break
}
}
return detailEntity
}
I'm trying to get the whole object returned in fun getDetailsById(movieId: String) when the ID is matched. How can I achieve this?
Kotlin's standard library provides a lot of functions for doing standard tasks like this without needing to write for loops. Here you can do
fun getDetailsById(movieId: String): MovieDetailEntity {
return getMovieDetails().first {
it.movieId == movieId
}
}
Note that this will crash if no such item can be found. To not crash but allow a nullable return use firstOrNull.
Related
Trying to sort the custom components on defined order, and other subjects should be after that desired order: Like if there is any other subject except the defined List ie "GK" then it should be on last position etc.
However I am getting Null Pointer Exception due to subject is not defined in the requireList if scheduleCommandList have the subject which is not in requiredList. How can I overcome this?
Desired Order List is Below:
private val requiredList: HashMap<String, Int> = hashMapOf(
"Maths" to 0,
"Physics" to 1,
"Science" to 2,
)
Sorting function to sort the List:
private fun sortCommandList(scheduleCommandList: ArrayList<BaseComponent>): ArrayList<BaseComponent> {
val comparator = Comparator { o1: BaseComponent, o2: BaseComponent ->
return#Comparator requiredList[o1.name]!! - requiredList[o2.name]!!
}
val copy = arrayListOf<BaseComponent>().apply { addAll(scheduleCommandList) }
copy.sortWith(comparator)
return copy
}
It seems you understand the problem correctly. If an item is not present in requiredList then you still try to compare their required positions and this causes NullPointerException. Remember that you should use !! only in cases when you are sure there can't be a null. In this case null is possible and we have to handle it somehow. The easiest is to replace it with Int.MAX_VALUE which places the item at the end. Also, this code can be really much simpler:
private fun sortCommandList(scheduleCommandList: List<BaseComponent>): List<BaseComponent> {
return scheduleCommandList.sortedBy { requiredList[it.name] ?: Int.MAX_VALUE }
}
It can be even better to create this utility as extension function:
private fun List<BaseComponent>.mySort(): List<BaseComponent> {
return sortedBy { requiredList[it.name] ?: Int.MAX_VALUE }
}
Then we can simplify the name of the function, because it is implicit that it is used to sort BaseComponent objects.
I have a ViewModel that has a MutableLiveData of an arraylist of class Course
private var coursesList: MutableLiveData<ArrayList<Course>> = MutableLiveData()
This coursesList is filled with data got from an API (by Retrofit): coursesList.postValue(response.body())
Now, a user can search for a course by its name. The function that I have for searching is that I iterate through the elements of the coursesList and check if its name is equal to what a user typed. It returns an arrayList with the courses that start with the name typed (this list is later sent to a fragment which passes it to an adapter to be shown in a recyclerview):
fun getCoursesList(): MutableLiveData<ArrayList<Course>> {
return coursesList
}
fun searchCourses(searchString: String): ArrayList<Course> {
val resultsList: ArrayList<Course> = ArrayList()
if (getCoursesList().value == null) return resultsList
if (getCoursesList().value!!.size > 0) {
for (course in getCoursesList().value!!.iterator()) {
if (course.name.toLowerCase(Locale.ROOT).startsWith(searchString)) {
resultsList.add(course)
}
}
}
resultsList.sortBy { it.price }
return resultsList
}
This function works and all but my instructor asked me to use LiveData for searching without giving any additional hints on how to do that.
So my question is how to use LiveData for searching? I tried to search for answers, I saw that some used LiveDataTransformations.switchMap but they were all using RoomDAOs and I couldn't adapt it to the code that I have.
Any help would be appreciated very much. Thanks in advance.
Maybe that can help you a little bit,
class YourViewModel(
private val courcesRepository: CourcesRepository
) : ViewModel() {
// Private access - mutableLiveData!
private val _coursesList = MutableLiveData<ArrayList<Course>>()
// Public access - immutableLiveData
val coursesList: LiveData<ArrayList<Course>>
get() = _coursesList
init {
// mutableLiveData initialize, automatic is immutable also initialize
_coursesList.postValue(getCourses())
}
// Here you get your data from repository
private fun getCourses(): ArrayList<Course> {
return courcesRepository.getCources()
}
// Search function
fun searchCourses(searchString: String) {
// you hold your data into this methode
val list: ArrayList<Course> = getCources()
if (searchString.isEmpty()) {
// here you reset the data if search string is empty
_coursesList.postValue(list)
} else {
// here you can search the list and post the new one to your LiveData
val filterList = list.filter {
it.name.toLowerCase(Locale.ROOT).startsWith(searchString)
}
filterList.sortedBy { it.price }
_coursesList.postValue(filterList)
}
}
}
The first tip is you should use LiveData like below, that is also recommended from google's jet pack team. The reason is so you can encapsulate the LivaData.
The second tip is you should use kotlin's idiomatic way to filter a list. Your code is readable and faster.
At least is a good idea to make a repository class to separate the concerns in your app.
And some useful links for you:
https://developer.android.com/jetpack/guide
https://developer.android.com/topic/libraries/architecture/livedata
I hope that's helpful for you
Ii is hard to guess the desired outcome, but a possible solution is to use live data for searched string also. And then combine them with coursesList live data into live data for searched courses, like this for example.
val searchStringLiveData: MutableLiveData<String> = MutableLiveData()
val coursesListLiveData: MutableLiveData<ArrayList<Course>> = MutableLiveData()
val searchedCourses: MediatorLiveData<ArrayList<Course>> = MediatorLiveData()
init {
searchedCourses.addSource(searchStringLiveData) {
searchedCourses.value = combineLiveData(searchStringLiveData, coursesListLiveData)
}
searchedCourses.addSource(coursesListLiveData) {
searchedCourses.value = combineLiveData(searchStringLiveData, coursesListLiveData)
}
}
fun combineLiveData(searchStringLiveData: LiveData<String>, coursesListLiveData: LiveData<ArrayList<Course>> ): ArrayList<Course> {
// your logic here to filter courses
return ArrayList()
}
I haven't run the code so I am not 100% sure that it works, but the idea is that every time either of the two live data changes value, searched string or courses, the combine function is executed and the result is set as value of the searchedCourses mediator live data. Also I omitted the logic of the filtering for simplicity.
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)
}
}
}
I want to update Recyclerview in realtime when a document is added or removed from firestore. I am using this logic in Kotlin:
for (doc in docs!!.documentChanges) {
val classElement: FireClassModel=doc.document.toObject(FireClassModel::class.java)
if (doc.type == DocumentChange.Type.ADDED) {
adapterList.add(classElement)
} else if(doc.type == DocumentChange.Type.REMOVED){
adapterList.remove(classElement)
}
adapter.notifyDataSetChanged()
}
Its working fine when document is added but it does not work when data is removed. It has no error but it doesn't update in real-time. It only updates when I restart the Application.
Updated
FireClassModel:
class FireClassModel {
var classID: String = ""
var className: String = ""
}
I tried this classesList.contains(classElement) and it returns false. It means I am unable to compare objects in my ArrayList.
Finally I have solved the issue. The issue was, I am not getting the right object.
I just replaced adapterList.remove(classElement) with following:
for (cls in adapterList) {
if (cls.classID == doc.document.id) {
adapterList.remove(cls)
}
}
Thanks to #Markus
Your code looks fine, but it seems that the element that you are trying to remove from the list cannot be found there.
adapterList.remove(classElement)
When removing an element from a list using remove(o: Object): Boolean the first matching element from the list will be removed. A matching element is an element where the equals method returns true.
listElement == elementToRemove // Kotlin
listElement.equals(elementToRemove); // Java
By default (that means if you do not override equals) objects will only be equal, if they share the same location in memory. In your example the element in the list sits at a different location than the element that you create from Firestore in your document change listener.
The solution depends on your FireClassModel. Looking at multiple FireClassModel objects, how would you decide which two of them are equal? Maybe they'll have the same id? Then override the equals method (and per contract also hashCode) and compare the fields that make two objects identical. For an id, the solution could look like that (generated by Android Studio):
class FireClassModel(val id: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FireClassModel
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id
}
}
After that comparing two FireClassModel objects with the same ID will return true. Without overriding the equals method that would not be the case (unless you comparing an object to itself).
More about equals can be found on Stackoverflow and in the Java documentation.
Context :
Some content (Sheet, Category, etc.) are protected by groups. (Content class has 2 RealmLists of groups, the first "groupsOneOf" an the second "groupsAllOf")
Users can be in groups (User has one RealmList "groups")
Some content is available only for user which is at least in one of the content's group.
Some content is available only for user which is in all of content's group.
For "at least in one of the content's group", I found 'in' operator in documentation :
This allows you to test if objects match any value in an array of values.
But for the second, no way to perform this kind of query. I searched solution in other posts but some people seems to say that's not supported right now. In iOS, it seems to be available with NSPredicate but not in java.
Here is my code :
fun <E: RealmObject> RealmQuery<E>.groupsAllowed(): RealmQuery<E>{
val userGroupsIds = realm.queryUserConnected()?.findFirst()?.groups?.toGroupIds()
if(userGroupsIds == null || userGroupsIds.isEmpty()){
isEmpty("groupsAllOf")
isEmpty("groupsOneOf")
}else{
beginGroup()
beginGroup()
isEmpty("groupsOneOf")
or()
`in`("groupsOneOf.id", userGroupsIds.toTypedArray())
endGroup()
//beginGroup()
//isEmpty("groupsAllOf")
//or()
//TODO
//endGroup()
endGroup()
}
return this
}
Currently I'm performing a post filter when I get the RealmResults for groupsAllOf :
fun <E: RealmObject> E.groupsAllowed(userGroupIds: List<String>): E?{
val groupsAllOf: RealmList<Group>? = when(this){
is Sheet -> groupsAllOf
is Category -> groupsAllOf
else -> null
}
if(groupsAllOf == null || groupsAllOf.isEmpty()){
return this
}else{
return if(userGroupIds.containsAll(groupsAllOf.toGroupIds())) this else null
}
}
fun <E: RealmObject> RealmResults<E>.groupsAllowed(): List<E>{
val userGroupIds = realm.queryUserConnected().findFirst()?.groups?.toGroupIds()?: emptyList()
val listAllowed = arrayListOf<E>()
this.forEach { it.groupsAllowed(userGroupIds)?.let { contentAllowed -> listAllowed.add(contentAllowed) } }
return listAllowed
}
fun RealmList<Group>.toGroupIds(): List<String> = arrayListOf<String>().apply { this#toGroupIds.forEach { group -> this.add(group.id) } }
But this is annoying because I have to no forget to call this function when I get the RealmResults :/
Some help would be greatly appreciated.