I have an array of feedback channels because (outside of question scope) in my ViewModel.
Now, I don't want to expose my MutableLiveData to outside my Viewmodel.
So, i make a private list of LiveData objects, but compiler complains of "Useless Cast"
private val _feedbackChannels = Array(10) { MutableLiveData<FeedbackEvent>() }
val feedbackChannels
get() = _feedbackChannels.map{
#Suppress("USELESS_CAST") // it is not useless as it no longer exposes the mutableLiveData
it as LiveData<*>
}
Why do I get USELESS_CAST warning?
Compiler doesn't realize you're doing it only to force implication of property type.
Just specify type explicitly and you'll be able to drop the cast entirely. You won't even have to use map, a simple toList() will do:
private val _feedbackChannels = Array(10) { MutableLiveData<FeedbackEvent>() }
val feedbackChannels : List<LiveData<FeedbackEvent>>
get() = _feedbackChannels.toList()
Clearly the compiler doesn't understand the point of the cast. In order to do this in a more explicit way and remove the costly map function, you can just upcast it like this:
private val _feedbackChannels = Array(10) { MutableLiveData<FeedbackEvent>() }
val feedbackChannels: Array<out LiveData<FeedbackEvent>>
get() = _feedbackChannels
Edit
If you wanted to expose a List specifically (avoid exposing a mutable array) then you should probably just create one in the first place:
private val _feedbackChannels = List(10) { MutableLiveData<FeedbackEvent>() }
val feedbackChannels: List<out LiveData<FeedbackEvent>>
get() = _feedbackChannels
Related
I have view model and I use live data in encapsulation, which one is recommended to use and why?
private val _licenseStatusFromWebService = MutableLiveData<String?>()
val licenseStatusFromWebService: LiveData<String?> = _licenseStatusFromWebService
private val _licenseStatusFromWebService = MutableLiveData<String?>()
val licenseStatusFromWebService: LiveData<String?>
get() = _licenseStatusFromWebService
It Does not matter which way you use it as long as the MutableLiveData you are referring to is a val and not a var, but if you are going to modify or reassign the MutableLiveData to something else the getter approach get() = will return the latest instance and equals approach = will return the initial instance.
Also, Kotlin internally builds a getter for every property you have so if you are choosing the equals approach = for the sole purpose of reducing code on production, it will amount to nothing.
I think using an object directly is recommended way in ViewModel
private val _licenseStatusFromWebService = MutableLiveData<String?>()
val licenseStatusFromWebService: LiveData<String?> = _licenseStatusFromWebService
because, I am using this approach in some of my projects
It`s just to encapsulate mutable LiveData from immutable. As in UI you should use already prepared data from ViewModel to avoid modifying it from the UI directly.
private val _licenseStatusFromWebService = MutableLiveData<String?>()
val licenseStatusFromWebService: LiveData<String?> = _licenseStatusFromWebService
I have the below working code which uses a dropdown to update the satusFilterFlow to allow for the filtering of characters through the getCharacterList call. The getCharacterList call uses the jetpack paging and returns Flow<PagerData<Character>>.
private val statusFilterFlow = MutableStateFlow<StatusFilter>(NoStatusFilter)
// private val searchFilterFlow = MutableStateFlow<SearchFilter>(NoSearchFilter)
val listData: LiveData<PagingData<Character>> =
statusFilterFlow.flatMapLatest{ statusFilter ->
characterRepository.getCharacterList(null, statusFilter.status)
.cachedIn(viewModelScope)
.flowOn(Dispatchers.IO)
}.asLiveData()
Given the above working solution, what is the correct flow extension to allow for me to add multiple StateFlows as I build out additional filters (e.g. SearchFilter).
I have tried combineTransorm as follows:
private val statusFilterFlow = MutableStateFlow<StatusFilter>(NoStatusFilter)
private val searchFilterFlow = MutableStateFlow<SearchFilter>(NoSearchFilter)
val listData: LiveData<PagingData<Character>> =
statusFilterFlow.combineTransform(searchFilterFlow) { statusFilter, searchFilter ->
characterRepository.getCharacterList(searchFilter.search, statusFilter.status)
.flowOn(Dispatchers.IO)
.cachedIn(viewModelScope)
}.asLiveData()
However, this gives me a "Not enough information to infer type variable R" error.
The usual way to understand and/or fix those errors is to specify types explicitly in the function call:
statusFilterFlow.combineTransform<StatusFilter, SearchFilter, PagingData<Character>>(searchFilterFlow) { ... }
This is orthogonal to the problem at hand, but I'd also suggest using the top-level combineTransform overload that takes all source flows as argument (instead of having the first one as receiver), so there is a better symmetry. Since I believe there is no reason one of the filters is more special than the other.
All in all, this gives:
val listData: LiveData<PagingData<Character>> =
combineTransform<StatusFilter, SearchFilter, PagingData<Character>>(statusFilterFlow, searchFilterFlow) { statusFilter, searchFilter ->
characterRepository.getCharacterList(searchFilter.search, statusFilter.status)
.flowOn(Dispatchers.IO)
.cachedIn(viewModelScope)
}.asLiveData()
For anymore else, this is too complex or doesn't work out for you ... Use Combine then flatMap latest on the top of that.
private val _selectionLocation: MutableStateFlow<Location?> = MutableStateFlow(null)
val searchKeyword: MutableStateFlow<String> = MutableStateFlow("")
val unassignedJobs: LiveData<List<Job>> =
combine(_selectionLocation, searchKeyword) { location: Location?, keyword: String ->
Log.e("HomeViewModel", "$location -- $keyword")
location to keyword
}.flatMapLatest { pair ->
_repo.getJob(Status.UNASSIGNED, pair.first).map {
Log.e("HomeViewModel", "size ${it.size}")
it.filter { it.desc.contains(pair.second) }
}
}.flowOn(Dispatchers.IO).asLiveData(Dispatchers.Main)
I was following one of the UDACITYs Android Tutorial on LiveData/Room/Persistence and Repository Architecture.
After gluing the codes all together, I came across (what I believe, a very common issue) Type Mismatch exception.
On the course example, a VideosRepository was created with a member videos which is a LiveData:
class VideosRepository(private val database: VideosDatabase) {
/**
* A playlist of videos that can be shown on the screen.
*/
val videos: LiveData<List<Video>> =
Transformations.map(database.videoDao.getVideos()) {
it.asDomainModel()
}
and in the Model, I have a introduce a MutableLiveData of _video
val playlist = videosRepository.videos //works fine
// added by me
private val _video = MutableLiveData<List<Video>>()
val video: LiveData<List<Video>> = _video
When I tried to access the LiveData, this is where I am getting the Type mismatch.
fun sample(){
_video.value = videosRepository.videos //does not work and throws a Type mismatch.
//Required: List<Video> Found: LiveData<List<Video>>
}
And if I try to just stuff all LiveData in the ViewModel (meaning, only the ViewModel will have the LiveData object declarations) and converting all LiveData to just plain List and a function such as
fun getVideos(): List<Video>{
return database.videoDao.getVideo()
}
I would then get Cannot access database on the main thread since it may potentially lock the UI for a long period of time. which I understand clearly. So if that is the case, then LiveData is the only way to do it.
But how can I get away from the Type mismatch.
PS. I understand concepts of OOP as well as Java, but never had the in-depth hands-on experience, so please bear with me.
Input of _video.value is a List<Video> but you assigned videosRepository.videos that is a LiveData<List<Video>>
You have to get List<Video> from LiveData :
_video.value = videosRepository.videos.value
videosRepository.videos's data type is LiveData<List<Video>> but _video.value's data type is List<Video>, so you can't assign like that.
Try:
val video: LiveData<List<Video>> = videosRepository.videos
Then in the view, observe the livedata to do what you want with the data, an example in Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
viewModel.video.observe(viewLifecycleOwner, Observer {
val data: List<Video> = it
// Do something with the data such as showing it...
})
}
If you really want to have a MutableLiveData in case of modifying it later, use MediatorLiveData:
private val _video = MediatorLiveData<List<Video>>().apply {
addSource(videosRepository.videos) {
value = it
}
}
val video: LiveData<List<Video>> = _video
I understand the need for creating getter and setter points for LiveData in the ViewModel, but I'm looking to understand how the get() syntax works in Android.
ie:
val isRealtime: LiveData<Boolean>
get() = _isRealtime
private val _isRealtime = MutableLiveData<Boolean>()
get() is not related to Android.
val isRealtime: LiveData<Boolean>
get() = _isRealtime
Here, get() is overriding the automatically-generated Kotlin getter function for the isRealtime property. So, instead of returning its own value, it returns the value of _isRealtime.
Personally, I recommend simpler syntax:
private val _isRealtime = MutableLiveData<Boolean>()
val isRealtime: LiveData<Boolean> = _isRealtime
The objective of either of these is to keep the mutability private, so consumers of this class do not accidentally update the MutableLiveData themselves.
In Kotlin we have multiple ways of exposing live data from ViewModel to the view.
class MyViewModel: ViewModel() {
// Solution 1 - make MutableLiveData public
// This approach works, but this is a bad idea because
// view can modify the LiveData values
val liveDataA1 = MutableLiveData<State>()
// Solution 2 - let's make LiveData public (expose it instead of MutableLiveData)
// Now from view perspective this solution looks fine, bu we have a problem,
// because we need MutableLiveData within ViewModel to put/post new values to
// the stream (we can't post values to LiveData).
val liveDataA2 = MutableLiveData<State>() as LiveData<State>
// Let's capture our requirements:
// 1. We need to expose (immutable) LiveData to the view,
// so it cannot edit the data itself.
// 2. We need to access MutableLiveData from ViewModel to put/post new values.
// Now, let's consider few appropriate solutions
// Solution 3
// Let's name mutable live data using underscore prefix
private val _liveData3 = MutableLiveData<State>()
val liveData3 = _liveData3 as LiveData<State>
// Solution 4
// We can also perform casting by specifying type for a variable
// (we can do it because MutableLiveData extends LiveData)
private val _liveData4 = MutableLiveData<State>()
val liveData4: LiveData<State> = _liveData4
// Solution 5
// Starting from Kotlin 1.4-M.2 we can delegate call to another property
private val _liveData5 = MutableLiveData<State>()
val liveData5 by this::_liveData5
// Solution 6
// These above solutions work quite well, but we could do even better by
// defining custom asLiveData extension function.
private val _liveData6 = MutableLiveData<State>()
val liveData6 = _liveData6.asLiveData()
fun <T> MutableLiveData<T>.asLiveData() = this as LiveData<T>
// Amount of code is similar, but notice that this approach works much better
// with code completion.
// Solution 7 (IMO Best)
// We can also use alternative naming convention - use "mutableLiveData"
// as variable for mutable live data instead of using underscore prefix
private val mutableLiveData7 = MutableLiveData<State>()
val liveData7 = mutableLiveData7.asLiveData()
// BTW
// We could also expose getLiveData8() method, but liveData is a state not an action.
// Solution 9
// This does not create backing field for the property
// (more optimised but still Solution 7 is easier to use)
private val _liveData9 = MutableLiveData<State>()
val liveData9 get() = _liveData9 as LiveData<State>
}
I wrote a util function for this logic:
import android.arch.lifecycle.LiveData
import android.arch.lifecycle.MutableLiveData
import kotlin.reflect.KProperty
fun <T> immutable(data: MutableLiveData<T>): Immutable<T> {
return Immutable(data)
}
class Immutable<T>(private val data: MutableLiveData<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): LiveData<T> {
return data
}
}
Then you can use in any of your ViewModel as:
private val _counter: MutableLiveData<Int> = MutableLiveData()
val counter: LiveData<Int> by immutable(_counter)
or in short:
private val _counter = MutableLiveData<Int>()
val counter by immutable(_counter)
While learning ViewModels in Android, a problem has arisen that feels like Kotlin was meant to solve. In the code below, we can see that MutableLiveData values are being use to edit values and indicators. However, we do not want these mutable values to be exposed to anything else, specifically members of an Android lifecycle. We DO want Android Lifecycle members to have access to read values but not set them. Therefore, the 3 exposed functions, displayed below, are of the LiveData<> immutable type.
Is there an easier or more concise way to expose read only values that can be edited internally? This seems like what Kotlin was made to avoid: boilerplate verbosity.
class HomeListViewModel: ViewModel(){
//Private mutable data
private val repositories = MutableLiveData<List<Repo>>()
private val repoLoadError = MutableLiveData<Boolean>()
private val loading = MutableLiveData<Boolean>()
//Exposed uneditable LIveData
fun getRepositories():LiveData<List<Repo>> = repositories
fun getLoadError(): LiveData<Boolean> = repoLoadError
fun getLoadingStatuses(): LiveData<Boolean> = loading
init{...//Do some stuff to MutableLiveData<>
}
}
A non-Android scenario that might be similar is:
class ImmutableAccessExample{
private val theThingToBeEditedInternally = mutableListOf<String>()
fun theThingToBeAccessedPublicly(): List<String> = theThingToBeEditedInternally
init {
theThingToBeEditedInternally.add(0, "something")
}
}
I don't know if it is possible to avoid the verbosity. But, I've seen that before and it is usually declared as a property.
private val _repositories = MutableLiveData<List<Repo>>()
val repositories : LiveData<List<Repo>>
get() = _repositories
This is the convention, see the doc here in Names for backing properties
If a class has two properties which are conceptually the same but one is part of a public API and another is an implementation detail, use an underscore as the prefix for the name of the private property:
Following the idea of this post:
class HomeListViewModel: ViewModel(){
val repositories: LiveData<List<Repo>> = MutableLiveData()
init {
repositories as MutableLiveData
...//Do some stuff to repositories
}
}
I haven't found any elegant solution to this problem however this is how I handle it.
private val selectedPositionLiveData = MutableLiveData<Int>()
fun getSelectedPosition() = selectedPositionLiveData as LiveData<Int>
The View observes via the public getter method and there's no need to define a second member in the ViewModel. I probably favour this approach due to my Java background with explicit getters but this seems to me to be as clean and concise as any of the other workarounds.
val doesn't have a setter since it's readonly but if you want a var you can do this
var repositories = MutableLiveData<List<String>>()
private set
var repoLoadError = MutableLiveData<Boolean>()
private set
var loading = MutableLiveData<Boolean>()
private set
This will give you a private setter and a public getter