Observe single attribute of a object inside LiveData - kotlin

I've Filter class, with attributes like maxPrice; this is the relative ViewModel:
class FilterViewModel : ViewModel() {
private val mutableLiveFilter = MutableLiveData<Filter>()
val liveFilter: LiveData<Filter> get() = mutableLiveFilter
fun setMaxPriceFilter(maxPrice: Int) { mutableLiveFilter.value?.maxPrice = maxPrice }
...
}
In a Fragment I have:
class FilterDialogFragment : BottomSheetDialogFragment() {
private val filterViewModel: FilterViewModel by activityViewModels()
...
}
Problem:
When I invoke setMaxPriceFilter(...) from the Fragment, filterViewModel is updated also in the other fragments where I use it, but the callback
filterViewModel.liveFilter.observe(this) { collectFilter(it) }
isn't called. Instead, the callback collectFilter(it) is invoked when I change the whole Filter attached to the LiveData and not when I change only one attribute.
How can I observe the change of a single attribute of the Filter inside the LiveData?
Thanks.

Related

How to access the switch element of Mainactivity in a class that inherits NotificationListenerService in Kotlin

In NotificationListener, I want to implement code to perform different functions according to the state value of the switch element in activity_main.xml.
So inside the onNotificationPosted() function
I need to know the state of the switch through the following code.
val volumeSeekBar:Switch = findViewById(R.id.switch_id)
To do this, I wrote the following code, and it turns off as soon as the app is launched.
How to access the switch element of Mainactivity in a class that inherits NotificationListenerService ??
NotificationListener.kt
class NotificationListener(activity:MainActivity) : NotificationListenerService() {
override fun onCreate() {
...
}
override fun onListenerConnected() {
...
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
val volumeSeekBar:Switch = activity.findViewById(R.id.switch_id) <-- doesn't work
}
}

Kotlin on Android: How to use LiveData from a database in a fragment?

I use MVVM and have a list of data elements in a database that is mapped through a DAO and repository to ViewModel functions.
Now, my problem is rather banal; I just want to use the data in fragment variables, but I get a type mismatch.
The MVVM introduces a bit of code, and for completeness of context I'll run through it, but I'll strip it to the essentials:
The data elements are of a data class, "Objects":
#Entity(tableName = "objects")
data class Objects(
#ColumnInfo(name = "object_name")
var objectName: String
) {
#PrimaryKey(autoGenerate = true)
var id: Int? = null
}
In ObjectsDao.kt:
#Dao
interface ObjectsDao {
#Query("SELECT * FROM objects")
fun getObjects(): LiveData<List<Objects>>
}
My database:
#Database(
entities = [Objects::class],
version = 1
)
abstract class ObjectsDatabase: RoomDatabase() {
abstract fun getObjectsDao(): ObjectsDao
companion object {
// create database
}
}
In ObjectsRepository.kt:
class ObjectsRepository (private val db: ObjectsDatabase) {
fun getObjects() = db.getObjectsDao().getObjects()
}
In ObjectsViewModel.kt:
class ObjectsViewModel(private val repository: ObjectsRepository): ViewModel() {
fun getObjects() = repository.getObjects()
}
In ObjectsFragment.kt:
class ObjectsFragment : Fragment(), KodeinAware {
private lateinit var viewModel: ObjectsViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, factory).get(ObjectsViewModel::class.java)
// I use the objects in a recyclerview; rvObjectList
rvObjectList.layoutManager = GridLayoutManager(context, gridColumns)
val adapter = ObjectsAdapter(listOf(), viewModel)
// And I use an observer to keep the recyclerview updated
viewModel.getObjects.observe(viewLifecycleOwner, {
adapter.objects = it
adapter.notifyDataSetChanged()
})
}
}
The adapter:
class ObjectsAdapter(var objects: List<Objects>,
private val viewModel: ObjectsViewModel):
RecyclerView.Adapter<ObjectsAdapter.ObjectsViewHolder>() {
// Just a recyclerview adapter
}
Now, all the above works fine - but my problem is that I don't want to use the observer to populate the recyclerview; in the database I store some objects, but there are more objects that I don't want to store.
So, I try to do this instead (in the ObjectsFragment):
var otherObjects: List<Objects>
// ...
if (condition) {
adapter.objects = viewModel.getObjects()
} else {
adapter.objects = otherObjects
}
adapter.notifyDataSetChanged()
And, finally, my problem; I get type mismatch for the true condition assignment:
Type mismatch: inferred type is LiveData<List> but List was expected
I am unable to get my head around this. Isn't this pretty much what is happening in the observer? I know about backing properties, such as explained here, but I don't know how to do that when my data is not defined in the ViewModel.
We need something to switch data source. We pass switching data source event to viewModel.
mySwitch.setOnCheckedChangeListener { _, isChecked ->
viewModel.switchDataSource(isChecked)
}
In viewModel we handle switching data source
(To use switchMap include implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0")
class ObjectsViewModel(private val repository: ObjectsRepository) : ViewModel() {
// Best practice is to keep your data in viewModel. And it is useful for us in this case too.
private val otherObjects = listOf<Objects>()
private val _loadDataFromDataBase = MutableLiveData<Boolean>()
// In case your repository returns liveData of favorite list
// from dataBase replace MutableLiveData(otherObjects) with repository.getFavorite()
fun getObjects() = _loadDataFromDataBase.switchMap {
if (it) repository.getObjects() else MutableLiveData(otherObjects)
}
fun switchDataSource(fromDataBase: Boolean) {
_loadDataFromDataBase.value = fromDataBase
}
}
In activity/fragment observe getObjects()
viewModel.getObjects.observe(viewLifecycleOwner, {
adapter.objects = it
adapter.notifyDataSetChanged()
})
You can do something like this:
var displayDataFromDatabase = true // Choose whatever default fits your use-case
var databaseList = emptyList<Objects>() // List we get from database
val otherList = // The other list that you want to show
toggleSwitch.setOnCheckedChangeListener { _, isChecked ->
displayDataFromDatabase = isChecked // Or the negation of this
// Update adapter to use databaseList or otherList depending upon "isChecked"
}
viewModel.getObjects.observe(viewLifecycleOwner) { list ->
databaseList = list
if(displayDataFromDatabase)
// Update adapter to use this databaseList
}

How to get value in kotlin?

I remember that in kotlin language there is a option to get value by get() property, but can't find how to write it.
What I mean is: I have a LiveData into my ViewModel and I need that access to post in LiveData has only ViewModel and outside just option to get for subscribe.
How I implemented it for now is
class MyViewModel(ctx: Context) : AndroidViewModel(ctx as Application)
{
private val _showLoadingPB = SingleLiveEvent<Boolean>()
fun showLoadingPB(): SingleLiveEvent<Boolean>
{
return _showLoadingPB
}
...
}
But I remember that there is an option to write it like this
class MyViewModel(ctx: Context) : AndroidViewModel(ctx as Application)
{
private val _showLoadingPB = SingleLiveEvent<Boolean>()
val showLoadingPB: SingleLiveEvent<Boolean>
get() => _showLoadingPB
}
How to make it works?
I remembered how it should be
class MyViewModel(ctx: Context) : AndroidViewModel(ctx as Application)
{
private val _showLoadingPB = SingleLiveEvent<Boolean>()
val showLoadingPB: LiveData<Boolean>
get() = _showLoadingPB
}
This way user can't assign new value to your SingleLiveEvent as well as post new event in LiveData, he can just observe it.

How do I cast custom MutableLiveData to custom LiveData?

suppose there are 2 classes:
class MyLiveData:LiveData<Int>()
class MyMutableLiveData:MutableLiveData<Int>()
Casting from MutableLiveData to LiveData is permitted:
val ld1=MutableLiveData<Int>()
val ld2:LiveData<Int> = ld1 //ok
But you can't cast your own implementations this way:
val mutable=MyMutableLiveData()
val immutable:MyLiveData = mutable //type missmatch
I understand that MutableLiveData extends LiveData thats why they are castable.But I can't have MyMutableLiveData extending MyLiveData as it won't be mutable in this case
Are there any workarounds?
UPD:I guess I need to show motivation of extending LiveData.I'm trying to implement MutableLiveDataCollection which notifies not just value changes via setValue/postValue but also value modification like adding new elements.I'm surprised there is no native solution for this.
Anyway to obseve modify events there have to be additional observe method.And this method have to be inside immutable part aka LiveDataCollection because views will call it.Inheritance is natural solution here IMHO.
The key idea sits in the MutableLiveData class.The only thing this class does - is it changes access modifiers on setValue/postValue methods.I can do the same trick.Therefore the final code will be:
open class LiveDataCollection<K,
L:MutableCollection<K>,
M:Collection<K>>: LiveData<L>() {
private var active=false
private var diffObservers = ArrayList<Observer<M>>()
fun observe(owner: LifecycleOwner, valueObserver: Observer<L>, diffObserver: Observer<M>) {
super.observe(owner,valueObserver)
diffObservers.add(diffObserver)
}
protected open fun addItems(toAdd:M) {
value?.addAll(toAdd)
if (active)
for (observer in diffObservers)
observer.onChanged(toAdd)
}
override fun removeObservers(owner: LifecycleOwner) {
super.removeObservers(owner)
diffObservers= ArrayList()
}
override fun onActive() {
super.onActive()
active=true
}
override fun onInactive() {
super.onInactive()
active=false
}
}
class MutableLiveDataCollection<K,L:MutableCollection<K>,
M:Collection<K>>: LiveDataCollection<K,L,M>() {
public override fun addItems(toAdd:M) {
super.addItems(toAdd)
}
public override fun postValue(value: L) {
super.postValue(value)
}
public override fun setValue(value: L) {
super.setValue(value)
}
}

Kotlin - Remove repetitive method calls for fragments

I am trying to remove duplicating methods by creating one singular method that takes params. I have a few methods that do the exact thing where they create an instance of a class, a fragment manager and then shows the fragment. Just want to know how I can shorten the following into one method and just pass in params.
private fun openAboutDialogue() {
//get a fragment manager
val fm = fragmentManager
val abtDialogue = GetStartedFragment()
abtDialogue.show(fm, "About the App")
}
private fun openNewRouteDialogue() {
val confirmNewDialogue = NewRouteFragment()
val fm = fragmentManager
confirmNewDialogue.show(fm, "NewRoute")
}
private fun openEndRouteDialogue() {
val confirmEndDialogue = TrafficDataFragment()
val fm = fragmentManager
confirmEndDialogue.show(fm, "GetTraffic")
}
If I understand this correcly, you simply create something like the following which takes Fragment as an argument:
private fun openDialogue(fragment: Fragment, text: String) =
fragment.show(fragmentManager, text)
Technically you could do
fun AppCompatActivity.openDialogue(fragment: DialogFragment, tag: String) {
fragment.show(supportFragmentManager, tag)
}
But now you have to call it as
openDialogue(GetStartedFragment(), "About the App")
openDialogue(NewRouteFragment(), "NewRoute")
openDialogue(TrafficDataFragment(), "GetTraffic")
If you want to get fancy and hide Fragment class from the caller you can use an enum for selection which can double as a fragment tag as well:
enum class DialogueType{ GET_STARTED, NEW_ROUTE, TRAFFIC,DATA }
private fun openDialogue(type: DialogueType){
val fragment = when(type) {
GET_STARTED -> GetStartedFragment()
NEW_ROUTE -> NewRouteFragment()
TRAFFIC_DATA -> TrafficDataFragment()
}
fragment.show(fragmentManager, type.name)
}