Hi I have this ObservableField in my java code. I want to get the value of it which can be done by calling get method on it.
val email = ObservableField<String>()
This can be done using below approach. I am confused and don't know should I make a getter here to get the value of it ? or there is different standard approach to get the value of ObservableField I am using RxJava too in my app.
fun login(view: View) {
val emailVal = email.get()
}
This is exactly what delegation is about. Delegation of a property in Kotlin means having a class that implements the operator function getValue and optionally setValue, which will be called when accessing or updating the property.
Your delegate could look like this:
class <T> ObservableDelegate
{
val field = ObservableField<T>()
operator fun getValue(self: Any?, prop: KProperty<*>) : T
= field.get()
operator fun setValue(self: Any?, prop: KProperty<*>, value: T)
= field.set(value)
}
You can then use the delegate like this:
val email : String by ObservableDelegate()
fun login(view: View) {
val emailVal = email
}
Read more about delegation of properties here: https://kotlinlang.org/docs/reference/delegated-properties.html
I think it is good enough to use email.get(). If you really want to eliminate the use of .get() in your code, you may use backing field:
val _email = ObservableField<String>()
var email: String
get() = _email.get()
set(value) = _email.set(value)
//use
fun login(view: View) {
val emailVal = email
}
Related
Modifying simple values and data classes using EditText is fairly straight forward, and generally looks like this:
data class Person(var firstName: String, var lastName: Int)
// ...
val (person, setPerson) = remember { mutableStateOf(Person()) }
// common `onChange` function handles both class properties, ensuring maximum code re-use
fun <T> onChange(field: KMutableProperty1<Person, T>, value: T) {
val nextPerson = person.copy()
field.set(nextPerson, value)
setPerson(nextPerson)
}
// text field for first name
TextField(
value = person.firstName,
onChange = { it -> onChange(Person::firstName, it) })
// text field for last name name
TextField(
value = person.lastName,
onChange = { it -> onChange(Person::lastName, it) })
As you can see, the code in this example is highly reusable: thanks to Kotlin's reflection features, we can use a single onChange function to modify every property in this class.
However, a problem arises when the Person class is not instantiated from scratch, but rather pulled from disk via Room. For example, a PersonDao might contain a `findOne() function like so:
#Query("SELECT * FROM peopleTable WHERE id=:personId LIMIT 1")
fun findOne(personId: String): LiveData<Person>
However, you cannot really use this LiveData in a remember {} for many reasons:
While LiveData has a function called observeAsState(), it returns State<T> and not MutableState<T>, meaning that you cannot modify it with the TextFields. As such this does not work:
remember { personFromDb.observeAsState()}
You cannot .copy() the Person that you get from your database because your component will render before the Room query is returned, meaning that you cannot do this, because the Person class instance will be remembered as null:
remember { mutableStateOf(findPersonQueryResult.value) }
Given that, what is the proper way to handle this? Should the component that contains the TextFields be wrapped in another component that handles the Room query, and only displays the form when the query is returned? What would that look like with this case of LiveData<Person>?
I would do it with a copy and an immutable data class
typealias PersonID = Long?
#Entity
data class Person(val firstName: String, val lastName: String) {
#PrimaryKey(autoGenerate = true)
val personID: PersonID = null
}
//VM or sth
object VM {
val liveData: LiveData<Person> = MutableLiveData() // your db call
val personDao: PersonDao? = null // Pretending it exists
}
#Dao
abstract class PersonDao {
abstract fun upsert(person: Person)
}
#Composable
fun test() {
val personState = VM.liveData.observeAsState(Person("", ""))
TextField(
value = personState.value.firstName,
onValueChange = { fName -> VM.personDao?.upsert(personState.value.copy(firstName = fName))}
)
}
Here is my viewmodel:
class MyProfileEditSharedViewModel : ViewModel() {
val question = MutableLiveData<String>()
val answer = MutableLiveData<String>()
fun setQuestion (q: String) {
question.value = q
}
fun setAnswer (a: String) {
answer.value = a
}
}
I set the data using setQuestion and setAnswer like this:
viewModel.setQuestion(currentUserInList.question)
viewModel.setAnswer(currentUserInList.answer)
I try to get question and answer from the ViewModel like this:
val qnaQuestionData = communicationViewModel.question as String
val qnaAnswerData = communicationViewModel.answer as String
Compiler says I cannot cast MutableLiveData to string.
Should I make a separate getter like my setter? I heard that you don't need to use getters and setters in kotlin, is there anyway to edit val question and val answer in my viewmodel without using getters and setters?
Thank you!!
You can't cast it to String because the type of object is MutableLiveData, but you can access the value with .value property
val qnaQuestionData = communicationViewModel.question.value
val qnaAnswerData = communicationViewModel.answer.value
in this case, may facing errors about MutableLiveData initialization.
another way is observing the LiveData for changes:
communicationViewModel.question.observe(this, Observer{ data->
...
})
Or if you have not accessed to any lifecycle owner
communicationViewModel.question.observeForever(Observer{ data->
...
})
but please remember to remove the observer through removeObserver method
for setting the values it's better to use properties directly or binding way
communicationViewModel.question.postValue("some new value")
Or
communicationViewModel.question.value = "some new value"
Suggestion for MutableLiveData properties:
val question: MutableLiveData<String> by lazy { MutableLiveData<String>() }
val answer: MutableLiveData<String> by lazy { MutableLiveData<String>() }
https://developer.android.com/reference/android/arch/lifecycle/LiveData
Create some sort of getter method in your ViewModel
fun getQuestion(): LiveData<String> {
return question //this works because MutableLiveData is a subclass of LiveData
}
Then, you can observe the value in whatever class you care about the value. ie:
communicationsViewModel.getQuestion().observe(this, Observer {
//do something with the value which is 'it'. Maybe qnaQuestionData = it
}
Note if you're trying to observe the value from a fragment or something, you will have to change the parameter this, to viewLifecycleOwner
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.
I have a LiveData property for login form state like this
private val _authFormState = MutableLiveData<AuthFormState>(AuthFormState())
val authFormState: LiveData<AuthFormState>
get() =_authFormState
The AuthFormState data class has child data objects for each field
data class AuthFormState (
var email: FieldState = FieldState(),
var password: FieldState = FieldState()
)
and the FieldState class looks like so
data class FieldState(
var error: Int? = null,
var isValid: Boolean = false
)
When user types in some value into a field the respective FieldState object gets updated and assigned to the parent AuthFormState object
fun validateEmail(text: String) {
_authFormState.value!!.email = //validation result
}
The problem is that the authFormState observer is not notified in this case.
Is it possible to trigger the notification programically?
Maybe you can do:
fun validateEmail(text: String) {
val newO = _authFormState.value!!
newO.email = //validation result
_authFormState.setValue(newO)
}
You have to set the value to itself, like this: _authFormState.value = _authFormState.value to trigger the refresh. You could write an extension method to make this cleaner:
fun <T> MutableLiveData<T>.notifyValueModified() {
value = value
}
For such a simple data class, I would recommend immutability to avoid issues like this altogether (replaces all those vars with vals). Replace validateEmail() with something like this:
fun validateEmail(email: String) = //some modified version of email
When validating fields, you can construct a new data object and set it to the live data.
fun validateFields() = _authFormState.value?.let {
_authFormState.value = AuthFormState(
validateEmail(it.email),
validatePassword(it.password)
)
}
I want to create a Pair which should take a generic type. I mean, I can pass a String to Int. How can I achieve that?
Example with the current behavior:
val liveData = MutableLiveData<Pair<Boolean, Int>>()
Expectation:
val liveData = MutableLiveData<Pair<T, T>>()
try this
class Abc<T, U> {
val liveData = MutableLiveData<Pair<T, U>>()
}
fun <T, U> Abc1(): MutableLiveData<Pair<T, U>> {
return MutableLiveData<Pair<T, U>>()
}
val liveData = Abc<String, Int>()
If you want to pass either String or Int, a sealed class may be the right choice, rather than a generic.
In your case something like:
sealed class StrInt
data class Numeric(val value:Int):StrInt()
data class Alphanum(val value:String):StrInt()
val a:Pair<StrInt, StrInt> = Numeric(10) to Alphanum("qwerty")