Android Studio Kotlin Boolean.Companion problem - android

In my code i want to execute holder.task.setChecked() command with item.getStatus()) as a parameter.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
holder.task.text = item.getTask()
holder.task.setChecked(item.getStatus())
}
The problem is that setChecked requires type Boolean, but from getStatus function i get Boolean.Companion in return.
class Model(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var id = Int
private var status = Boolean
private var task = String
fun getStatus(): Boolean.Companion {
return status
}
When i try to change the return type of this function, i get thrown with an error.
Been thinking for a while about how to change the types to be equal, but i cannot seem to find the solution.
To be clear, setChecked function is a default function which im executing on Checkbox object

Your example code is very unusual, and I'm not sure you actually meant to do what you're doing here.
This for example
private var id = Int
doesn't hold an Int. id is not a number. It's actually the companion object for the Int class, which in this case holds the constants MAX_VALUE, MIN_VALUE, SIZE_BYTES and SIZE_BITS. Usually you'd access these directly on the Int class, like Int.MAX_VALUE - when you do that you're actually accessing them on the companion object, which is why assigning the value Int to a variable gives you that companion object.
So the same goes for var status = Boolean - it's Boolean's companion object. And that's why your function has to return Boolean.Companion, because it's returning status, and that's what status is. It's not a boolean value of true or false.
There may be reasons why you'd want to do this - it's very unlikely though, and the way you're trying to use this (wanting a Boolean from getStatus) suggests that this is a mistake, and you're not familiar with the language. I'd really recommend running through the basic intro stuff to get a feel for how you define variables and their types, but this is probably what you wanted:
class Model(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var id: Int = -1
private var status: Boolean = false
private var task: String = ""
fun getStatus(): Boolean {
return status
}
}
I've added default values to each because you have to initialise them to something - if you have an init block where that happens, you can omit the values. If you're assigning them right there, you can omit the types too (e.g. var id = -1) unless you need to be more specific than the value is (e.g. if you want it to be a nullable Int? type).
You could also put these values in the constructor:
class Model(
itemView: View,
private var id: Int,
private var status: Boolean,
private var task: String
)
which would require the caller to provide some initial values. It depends what you want!
If you wanted, you could also replace the getter function by doing this:
var status: Boolean = false
private set
which makes the status property public, but read-only from the outside.
And just FYI, your Model class looks like your data for your RecyclerView's Adapter - it should not be a ViewHolder, those are completely separate things. Just use a basic class for your Model (or even better, a data class) and use that in your data list.
ViewHolders are special objects that get reused to display different data items - and by definition there's usually more data items than there are ViewHolders (that's the whole point of a RecyclerView - it recycles them). You shouldn't be keeping them in a list, or storing individual items' state in them.
Keep your data items in a list, fetch the one at position in onBindViewHolder, then display it in the ViewHolder you're provided with. What you have right now isn't going to work, so you'll need to look at a tutorial for setting one up, e.g. this one from the docs. You need to store references to the Views you're using in the VH, like a TextView and a Checkbox, so you can do things like
holder.taskTextView.text = item.getTask()

The setChecked function requires a Boolean param, but the return type of getStatus is Boolean.Companion. This is because you have defined the status field as of Boolean.Companion type.
To fix this, you may define status as a normal class level field and either assign a default value, or provide a constructor to initialize the value appropriately.
private var status: Boolean = false // or true, depending on the default value that suits here
Then you can change the getStatus function to return a Boolean type instead of Boolean.Companion
fun getStatus(): Boolean {
return status
}
and then use it to call holder.task.setChecked(item.getStatus()).

Related

RxJava - Kotlin

I am working on an integration of a bluetooth sdk,
It forces me to have a static arraylist where the sdk module is hosting the read data, this statement is in Java
public ArrayList<ReaderDevice> tagsList = new ArrayList<>();
In my Kotlin activity I have the static reference
lateinit var sharedObjects: SharedObjects
To get this list of my fragments I use
HomeActivity.sharedObjects.tagsList
What I need is "some way" no matter how "dirty" to be able to have a "listener" -"observer" to know from my fragments when a new element is added to take some action x
but only when I do the "onnext" can I get the size, otherwise it doesn't refresh, I guess it's because it "is copied" but it doesn't have the same reference, can I somehow put the static reference to my viewmodel propertys? I'm lost how to see this list
Try to create the observable like this
//fragment
viewmodel.setReadTagsList.onNext(HomeActivity.sharedObjects.tagsList)
//viewmodel
val setReadTagsList = PublishSubject.create<List<ReaderDevice>>()
private val _tags = BehaviorSubject.create<List<ReaderDevice>>()
setReadTagsList
.bind(_tags)
.disposedBy(disposeBag)
setReadTagsList
.withLatestFrom(_tags){_, o1 -> o1}
.map { "${it.size}" }
.bind(_errorMessage)
.disposedBy(disposeBag)
java.util.ArrayList doesn't support listening (on adding/removing elements), you need to use something different instead.
If you have the option of setting HomeActivity.sharedObjects.tagsList, you can use the following approach:
Set it to another list with the capability. You could either use an existing list supporting that or create a new one that forwards all operation to another list or extends ArrayList and intercepts the methods by overriding like that:
class WatchableArrayList<T>(val listener:(Int, T? , T? )->Unit):ArrayList<T>() {
override fun add(elem: T): Boolean {
val index = size
val ret = super.add(elem)
listener.invoke(index, elem, null)
return ret
}
//similar for other methods
}
This creates a class extending ArrayList that takes a high-order function as a constructor parameter (that takes the index added and removed element as parameters).
You can then create an instance of that and set HomeActivity.sharedObjects.tagsList to that instance like that:
HomeActivity.sharedObjects.tagsList = WatchableArrayList((index, added, removed)->{
//handler here
})
However, you might want to use a wrapper pattern instead of inheritance here:
class WatchableListWrapper<T>(val wrapped: MutableList<T>, val listener:(Int, T? , T? )->Unit) {
override fun add(elem: T): Boolean {
val index = size
val ret = wrapped.add(elem)
listener.invoke(index, elem, null)
return ret
}
//similar for other methods
}
HomeActivity.sharedObjects.tagsList = WatchableListWrapper(HomeActivity.sharedObjects.tagsList, (index, added, removed)->{
//handler here
})

DiffUtil not refreshing view in Observer call android kotlin

Hey I am using diff util with ListAdapter. The updating of list works but I can only see those new values by scrolling the list, I need to view the updates even without recycling the view (when scrolling) just like notifyItemChanged(). I tried everything inside this answer ListAdapter not updating item in RecyclerView only working for me is notifyItemChanged or setting adapter again. I am adding some code. Please someone know how to fix this problem?
Data and Enum class
data class GroupKey(
val type: Type,
val abc: Abc? = null,
val closeAt: String? = null
)
data class Group(
val key: GroupKey,
val value: MutableList<Item?> = ArrayDeque()
)
enum class Type{
ONE,
TWO
}
data class Abc(
val qq: String? = null,
val bb: String? = null,
val rr: RType? = null,
val id: String? = null
)
data class RType(
val id: String? = null,
val name: String? = null
)
data class Item(
val text: String? = null,
var abc: Abc? = null,
val rr: rType? = null,
val id: String? = null
)
viewmodel.kt
var list: MutableLiveData<MutableList<Group>?> = MutableLiveData(ArrayDeque())
fun populateList(){
// logic to call api
list.postValue(data)
}
fun addItemTop(){
// logic to add item on top
list.postValue(data)
}
inside view model I am filling data by api call inside viewmodel function and return value to list. Also another function which item is inserting at top of list so that's why is used ArrayDeque
Now I am adding nested reyclerview diff util callback.
FirstAdapter.kt
class FirstAdapter :
ListAdapter<Group, RecyclerView.ViewHolder>(comp) {
companion object {
private val comp = object : DiffUtil.ItemCallback<Group>() {
override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Group, newItem: Group): Boolean {
return ((oldItem.value == newItem.value) && (oldItem.key == newItem.key))
}
}
}
......... more function of adapter
}
FirstViewHolder
val adapter = SecondAdapter()
binding.recyclerView.adapter = adapter
adapter.submitList(item.value)
SecondAdapter.kt
class SecondAdapter : ListAdapter<Item, OutgoingMessagesViewHolder>(comp) {
companion object {
private val comp = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return ((oldItem.rr == newItem.rr) &&
(oldItem.text == oldItem.text) && (oldItem.abc == newItem.abc))
}
}
}
..... more function
}
Activity.kt
viewModel.list.observe(this, { value ->
submitList(value)
})
private fun submitList(list: MutableList<Group>?) {
adapter?.submitList(list)
// adapter?.notifyDataSetChanged()
}
I am 100% sure that my list is updating and my observer is calling when my new list is added. I debug that through debug view. But problem is I can only see those new values by scrolling the list, I need to view the updates even without recycling the view (when scrolling) just like notifyItemChanged()
UPDATE
viewmodel.kt
class viewModel : BaseViewModel(){
var list: MutableLiveData<MutableList<Group>?> = MutableLiveData()
//... more variables...
fun fetchData(context: Context) {
viewModelScope.launch {
val response = retroitApiCall()
response.handleResult(
onSuccess = { response ->
list.postValue(GroupData(response?.items, context))
},
onError = { error ->
Log.e("error" ,"$error")
}
)
}
}
}
internal fun GroupData(items: List<CItem>?, context: Context): MutableList<Group> {
val result: MutableList<Group> = MutableList()
items?.iterator()?.forEach { item ->
// adding item in list by add function and then return list.
return result
}
private fun addItemOnTop(text: String) {
list.value?.let { oldlist ->
// logic to add items on top of oldlist variable
if(top != null){
oldlist.add(0,item)
}else{
val firstGroup = oldlist[0]
firstGroup.value.add(item)
}
list.postValue(oldlist)
}
}
}
I am using sealed class something like this but not this one Example. And Something similar to these when call api Retrofit Example. Both link I am giving you example. What I am using in my viewmodel.
I don't know what's going on, but I can tell you two things that caught my attention.
First Adapter:
override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean {
return oldItem == newItem
}
You're not comparing if the items are the same, you're comparing the items and their contents are the same. Don't you have an Id like you did in your second adapter?
I'd probably check oldItem.key == newItem.key.
Submitting the List
As indicated in the answer you linked, submitList has a very strange logic where it compares if the reference of the actual list is the same, and if it is, it does nothing.
In your question, you didn't show where the list comes from (it's observed through what appears to be liveData or RXJava), but the souce of where the list is constructed is not visible.
In other words:
// P S E U D O C O D E
val item1 = ...
val item2 = ...
val list1 = mutableListOf(item1, item2)
adapter.submitList(list1) // works fine
item1.xxx = ""
adapter.submitList(list1) // doesn't work well.
WHY?
Unfortunately, submitList's source code shows us that if the reference to the list is the same, the diff is not calculated. This is really not on the adapter, but rather on AsyncListDiffer, used by ListAdapter internally. It is this differ's responsibility to trigger the calculation(s). But if the list references are the same, it doesn't, and it silently ignores it.
My suspicion is that you're not creating a new list. This rather undocumented and silent behavior hurts more than it helps, because more often than not, developers aren't expecting to duplicate a list supplied to an object whose purpose and promise is to offer the ability to "magically" (and more importantly, automatically) calculate its differences between the previous.
I understand why they did it, but I would have at the very least emitted a log WARNING, indicating you're supplying the same list. Or, if you want to avoid polluting the already polluted logCat, then at least be much more explicit about it in its official documentation.
The only hint is this simple phrase:
you can use submitList(List) when new lists are available.
The key here being the word new lists. So not the same list with new items, but simply a new List reference (regardless of whether the items are the same or not).
What should you try?
I'd start by modifying your submitList method:
private fun submitList(list: MutableList<Group>?) {
adapter?.submitList(list.toMutableList())
}
For Java users out there:
adapter.submitList(new ArrayList(oldList));
The change is to create a copy of the list you receive: list.ToMutableList(). This way the AsyncListDiffer's check for list equality will return false and the code will continue.
UPDATE / DEBUG
Unfortunately, I don't know what is going on with your code; I assure you that ListAdapter works, as I use it myself on a daily basis; If you think you've found a case where there are problems with it, I suggest you create a small prototype and publish it on github or similar so we can reproduce it.
I would start by using debug/breakpoints in key areas:
ViewModel; write down the reference fromthe list you "return".
DiffUtil methods, is diffUtil being called?
Your submitList() method, is the list reference the same as the one you had in your ViewModel?
etc.
You need to dig a bit deeper until you find out who is not doing what.
On Deep vs Shallow copy and Java and whatever...
Please keep in mind, ListAdapter (through AsyncDiff) checks if the reference to the list is the same. In other words, if you have a list val x = mutableListOf(...) and you give this to the adapter, it will work the 1st time.
If you then modify the list...
val x = mutableListOf(...)
adapter.submitList(x)
x.clear()
adapter.submitList(x)
This will NOT WORK correctly, because to the eyes of the Adapter both lists are the same (they actually are the same list).
The fact that the list is mutable is irrelevant. (I still frown upon the mutable list; why does submitList accept a mutable list if you cannot mutate it and submit it again, escapes my knowledge but I would not have approved that Pull Request like so) It would have avoided most problems if they only took a non-mutable list, therefore implying you must supply a new list every time if you mutate it. Anyway...
as I was saying, duplicating a list is simple, in either Kotlin or Java there are multiple variations:
val newListWithSameContents = list1.toList()
List newListWithSameContents = ArrayList(list1);
now if list1 has an item...
list1.add("hello")
When you copy list1 into newList... The reference to "Hello" (the string) is the same. If String were mutable (it's not, but assume it is), and you modified that string somehow... you would be modifying both strings at the same time or rather, the same string, referenced in both lists.
data class Thing(var id: Int)
val thing = Thing(1)
val list1: MutableList<Thing> = mutableListOf(thing)
val list2: MutableList<Thing> = list1.toMutableList()
println(list1)
println(list2)
// This prints
[Thing(id=1)]
[Thing(id=1)]
Now modify the thing...
thing.id = 2
println(list1)
println(list2)
As expected, both lists, pointing to the same object:
[Thing(id=2)]
[Thing(id=2)]
This was a shallow copy because the items were not copied. They still point to the same thing in memory.
ListAdapter/DiffUtil do not care if the objects are the same in that regard (depending how you implemented your diffutil that is); but they certainly care if the lists are the same. As in the above example.
I hope this clarifies what is needed for ListAdapter to dispatch updates. If it fails to do so, then check if you're effectively doing the right thing.

How to Initialize an Object and access its values from different methods - Kotlin

I am kind of new to Android. I can't figure this out. I want to create an object that is accessible from two different functions. Here is the object:
class Person(var firstName: String="", var lastName: String="", var order: List<Orders> )
class Order(var orderId: String="", var orderTitle: String="")
Then in an activity:
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var order: Order()
var person: Person(order) //I am sure I am not doing this right
}
fun Function1(){
person.order[1].orderTitle = "New Order" //to update order title
}
fun Function2(){
// to read new order title
var newOrderTitle = person.order[1].orderTitle
}
}
You created your Person instance as a local variable inside the onCreate() function, so it is only accessible inside the onCreate() function. To make it accessible from your other functions, it needs to be a property member of the class (defined outside any functions). You also need to use the = symbol to set the initial value. The : symbol is for declaring what type the property or variable is, and is optional in most cases.
By the way, in Kotlin, the convention is to always start function names with a lower-case letter, so it is easy to distinguish them from constructors. (This differs from languages like C#, where the new keyword makes constructor calls obvious.)
class MainActivity : AppCompatActivity(){
var order = Order()
var person = Person(order)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun function1(){
person.order[1].orderTitle = "New Order" //to update order title
}
fun function2(){
// to read new order title
var newOrderTitle = person.order[1].orderTitle
}
}
As well as what #TenFour04 says about making the variables visible to the functions, there's a couple of problems with how you're creating your Person object.
First, you're using default values for everything so you don't need to pass in a value for every parameter, right? That's how you can call Order() without providing any other data. But if you are passing in data, like with your Person(order) call, you need to tell it which parameter you're passing by using a named argument:
Person(order = order)
using the same name for the variable you're passing in and the name of the argument maybe makes it look more confusing, but you're specifically saying "the argument called order, here's a value for it".
You can pass in arguments without names, but you have to provide them in the order they're declared - so the 1st argument (a String), or the 1st and 2nd, or the 1st, 2nd and 3rd. Since you want to jump straight to the 3rd argument, you need to explicitly name it.
Second issue is your 3rd argument's type isn't Order, it's a List of orders. You can't just pass in one - so you need to wrap it in a list:
Person(order = listOf(order))
that's all you need to do!
The third problem is you've actually written the type as List<Orders> (sorry about the formatting). The type is Order, so we say List<Order> because it's a list holding objects of the Order type. You can use plurals in your variable names though (like val listOfOrders: List<Order>)

LiveData "pass-by-reference" initial value

I have a ViewModel class that looks like this:
class EditUserViewModel(
private val initUser: User,
) : ViewModel() {
private val _user = MutableLiveData(initUser)
val user: LiveData<User>
get() = _user
fun hasUserChanged() = initUser != _user.value
}
User can update some properties of the User data class instance through the UI.
To check if there are any changes when navigating from the fragment I use hasUserChanged method.
The problem is that is always false. I checked and it seems that the initialUser changes every time I change the _user MutableLiveData.
Why is that? Is the initial value of MutableLiveData passed by reference? I always thought that Kotlin is a "pass-by-value" type of language.
Update:
The problem seems to disappear when copying initUser before putting it inside the MutableLiveData.
private val _user = MutableLiveData(initUser.copy())
But it still doesn't make sense to me why I have to do that.
Kotlin is like java and they are pass-by-value. If you implement the equals function in User class, or make it as data class (which implements the equals function implicitly), it makes you sure that the content of the user objects is checked by != operator.
Update
If you are changing the value of LiveData directly, for example like this:
_user.value.name = "some name"
it means that you are changing the name property of the initUser, because _user.value exactly refers to the object that the initUser does. Consequently, the != operator always returns false, because we have one object with two references to it.
Now, when you are doing so:
private val _user = MutableLiveData(initUser.copy())
you are creating a deep copy of initUser (let's call it X) which is a new object in memory with the same property values of initUser.
Thus, by changing its properties like: _user.value.name = "some name", in fact, you are making this change on X, not initUser. It leads to preserving the initial values in initUser, meaning do not changing them, and solving the issue.

Set value of a field without calling set method - Kotlin

I've been using Kotlin to develop some apps in Android and what i want to do currently is to set a field value inside the defining class without calling the setter method.
Here is the code inside my class:
var projectList: List<Project>? = null
set(value) {
saveProjects(value as ArrayList<Project>)
field = value
}
//GO to the database and retrieve list of projects
fun loadProjects(callback: Project.OnProjectsLoadedListener) {
database.projectDao().getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ success ->
callback.onProjectsLoaded(success)
//Here i don't want to save the projects, because i've loaded them from the database
this.projectList = success
},
{ error -> GenericErrorHandler.handleError(error,callback.retrieveContext())}
)
}
Does anybody know a way to do this without calling the set(value) method?
You can only gain access to the field directly from the setter. Inside a setter, the field can be accessed through the invisible field variable.
There are perhaps some other ways around your requirements though. Here are 2 examples. You wouldn't have to follow them exactly, but could instead also combine them to make whatever solution you want.
You could use another shell property to act as the setter for your actual property:
class Example1 {
var fieldProperty: Int = 0
var setterPropertyForField: Int
get() = fieldProperty
set(value) {
fieldProperty = value
}
}
You could use setters as you actually would in Java with a JVM field and a set method. The #JvmField is probably not necessary.
class Example2 {
#JvmField var fieldProperty: Int = 0
fun setField(value: Int) {
fieldProperty = value
}
}
You could probably access the field and change it through reflection, but I don't recommend that approach. That would likely only lead to problems.

Categories

Resources