Transformations.map LiveData - android

I have following code :
val liveData = MutableLiveData<String>()
liveData.value = "Ali"
val res = map(liveData) { post(it) }
textview.text = res.value.toString()
fun post(name: String): String {
return "$name $name"
}
I expect it to print Ali Ali but it prints a null value. What am I missing?

You are missing a null check.
res.value.toString()
Imagine the case when res.value is null you are doing this.
null.toString() which the result is the string "null"
And the other hand, when you use LiveData the right approach is to observe all changes like zsmb13 suggested.
res.observe(this, Observer { name ->
textview.text = name
})

LiveData works asynchronously. The value you've set for it isn't immediately available in the transformed LiveData. Instead of trying to read that value directly, you should observe its changes, then you'll get the latest values:
res.observe(this, Observer { name ->
textview.text = name
})
(This code sample assumes that this is a LifecyleOwner, such as an AppCompatActivity.)

Related

update a MutableStateFlow when another MutableStateFlow emits

val persons = MutableStateFlow<List<Person>>(emptyList())
val names = MutableStateFlow<List<String>>(emptyList())
I want to update names whenever persons emits a new value.
This could be done by observing persons like:
viewModelScope.launch{
persons.collectLatest{personList->
names.emit(personList.map{it.name})
}
}
but I was wondering if there is another way to achieve that, e.g. using flow operators ?
Looks a little nicer
persons.map{ persons ->
names.emit(persons.map{ it.name })
}.launchIn(viewModelScope)
If there is no need for reactive actions, then a function can be used.
val names = {
persons.map{
it.name
}
}
// call
println(names())
If it's a class property, even better
val names: String
get() = persons.map{it.name}

How to avoid default value in MutableStateFlow kotlin

I am using MutableStateFlow in my project. When we initialise the MutableStateFlow object we need to give default value.
val topics = MutableStateFlow<List<String>>(emptyList())
when I collect this value
[null, "Hello", "world"]
I want to pass this list in adapter . So is there a way we can remove the null object before passing in adapter or Is there any better way ?
viewModel.topics.collect { topicsList ->
println(topicsList) // [null, "Hello", "world"]
adapter.submitList(topicsList)
}
If you don't want it to have an enforced initial value, use MutableSharedFlow instead. If you give it replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST, and distinctUntilChanged(), it's basically the same thing as a MutableStateFlow without the enforced value. And if onBufferOverflow is not BufferOverflow.SUSPEND, tryEmit will always succeed so you can use tryEmit() instead of value = .
private val _topics = MutableSharedFlow<List<String>>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val topics: Flow<List<String>> = _topics.distinctUntilChanged()
// emitting to the shared flow:
_topics.tryEmit(newValue)
If you want to ignore initial value of StateFlow, set initial value null or anything you want. Then you can use filter function on flow.
For example initial value is null
launch {
val topicsState = MutableStateFlow<List<String?>?>(null)
topicsState.filterNotNull().map { topics -> topics.filterNotNull() }.onEach { topics ->
println(topics)
}.launchIn(this)
launch {
delay(1000)
topicsState.update { listOf(null, "Hello", "world") }
}
}
Output
[Hello, world]
Since it emits a list of strings you could try to initialise the StateFlow with a null like so
val topics = MutableStateFlow<List<String>?>(null)
And when you collect you can check if the emitted value is null or not
viewModel.topics.collect { topicsList ->
topicsList?.let { safeTopics ->
adapter.submitList(safeTopics)
}
}
If we have given a common generic type sealed class.
Common Sealed Class
sealed class Resource<T>(val data: T? = null, val error: String? = null) {
class Loading<T> : Resource<T>()
class Success<T>(data: T) : Resource<T>(data = data)
class Error<T>(error: String) : Resource<T>(error = error)
}
In that case, we can set the initial value like this.
private val _mutableStateFlow: MutableStateFlow<Resource<List<PackageModel>>?> = MutableStateFlow(null)
PackageModel is Model/Pojo class
I think what you need is this:
val sampleList = listOf(null, "Hello", "world")
val topics = MutableStateFlow<List<String>>(sampleList.filer { it != null })

How To Return Nested Variable?

I don't know how to RETURN variable from the following function.
Here is the code...
downloadData.setOnClickListener {
val handler = Handler(Looper.getMainLooper())
handler.post {
val fetchData =
FetchData("http://localhost/xampp/CRM/PHP/show_contacts_db.php")
if (fetchData.startFetch()) {
if (fetchData.onComplete()) {
val result = fetchData.data.toString()
Log.i("FetchData", result)
val companyName = result.substringAfter("Name: ").substringBefore(";")
showContactName.text = "${companyName}"
val companyNumber = result.substringAfter("Number: ").substringBefore(";")
showContactNumber.text = "${companyNumber}"
}
}
}
}
companyName and companyNumber needed to be returned so I can use it in other places.
When I Try to use Return companyNumber I have a message that "return" is not allowed here.
Generally with lambdas, you don't explicitly return a value - the lambda returns the value of the last expression. Using your code as an example (it won't actually work but we'll get to that!):
handler.post {
...
companyNumber
}
which is the same as how things like map calls take a transformation function
listOf(1, 2, 3).map { it * 2 }
that's just doubling each number, but the result is being implicitly returned and stored in the resulting list, right? And it lets you chain lambdas together, since each one evaluates to a value (which might be Unit if it "doesn't return a result")
If you want, you can explicitly use what's called a qualified return:
handler.post {
...
return#post companyNumber
}
where you're naming the function call you're returning to.
Kotlin docs: returning a value from a lambda expression
Also if you want to return two values, you can't do that - so you'll have to bundle them up in a single object. You could just return a Pair, or create a data class that's more readable:
return#post Pair(companyName, companyNumber)
//or
data class CompanyDeets(val name: String, val number: String)
...
return#post CompanyDeets(companyName, companyNumber)
But aside from how you do it in general, why do you want to return anything here? Handler#post takes a Runnable which returns nothing (void in Java), and View.OnClickListener#onClick doesn't return anything either.
Neither of them would do anything with a value you returned - and if you explicitly return a value, that means your lambda's signature doesn't match (right now it's implicitly returning Unit to match what's expected by the caller), and you'll get an error
What you probably want to do instead, is create a function inside your class (Activity or whatever) that uses your data, something like fun doSomethingWith(companyName: String, companyNumber: String) and call that inside your lambda. That's way you're executing code in reaction to a click
just declare var Company Name in global, or create a function with that params
var companyName: String? = null
handler.post {
...
companyName = result.substringAfter("Name: ").substringBefore(";")
}
OR
handler.post {
...
save(result.substringAfter("Name: ").substringBefore(";"))
}
fun save(companyName: String){ ... }

How to mutate an incoming string in an observableField

I have the following field binded to an editText.
val lastName = ObservableField(MutableLiveData<String>())
I want to mutate the entered string so that the first letter will be automatically set in uppercase.
So if you type
williams -> Williams
I thought I could solve this by doing this as follows
lastName.getObservable()
.subscribe { input ->
val lastname = input.decapitalize()
lastName.getField().postValue(lastname.capitalize())
}
I noticed that doing it this way, will throw me in an eternal loop because of the postvalue triggering the subscribe each time. How can I mutate the incoming string through RxJava without having to do it in the way I have it now?
You can do this at the source by overriding set. I don't see the reason for multi-layered observability, so I flattened it here.
val lastName = object: ObservableField<String>() {
override fun set(value: String) {
super.set(value.capitalize())
}
}
If there is some reason you need the layering, you could instead override the setValue method of the MutableLiveData.
val lastName = ObservableField(object: MutableLiveData<String>() {
override fun setValue(value: String) {
super.setValue(value.capitalize())
}
})
But this multi-layering looks convoluted to me. I don't see how you can reliably subscribe to the underlying data if the LiveData instance can be overwritten.

How can I perform Transformations on MutableLiveData?

The docs show how you can perform Transformations on a LiveData object? How can I perform a transformation like map() and switchMap() on a MutableLiveData object instead?
MutableLiveData is just a subclass of LiveData. Any API that accepts a LiveData will also accept a MutableLiveData, and it will still behave the way you expect.
Exactly the same way:
fun viewModelFun() = Transformations.map(mutableLiveData) {
//do somethinf with it
}
Perhaps your problem is you dont know how does yor mutable live data fit on this.
In the recent update mutable live data can start with a default value
private val form = MutableLiveData(Form.emptyForm())
That should trigger the transformation as soon as an observer is attached, because it will have a value to dispatch.
Of maybe you need to trigger it once the observer is attached
fun viewModelFun(selection: String) = liveData {
mutableLiveData.value = selection.toUpperCase
val source = Transformations.map(mutableLiveData) {
//do somethinf with it
}
emitSource(source)
}
And if you want the switch map is usually like this:
private val name = MutableLiveData<String>()
fun observeNames() = Transformations.switchMap(name) {
dbLiveData.search(name) //a list with the names
}
fun queryName(likeName: String) {
name.value = likeName
}
And in the view you would set a listener to the edit text of the search
searchEt.doAfterTextChange {...
viewModel.queryName(text)
}

Categories

Resources