In my class Foo I have a boolen field. When the field changes I will react on this change in my class Bar
How can I implement this with RxJava in Android?
I guess you have to create class Foo with subject inside
class Foo {
var booleanField: Boolean = false
set(value) {
field = value
observable.onNext(value)
}
val observable = BehaviorSubject.createDefault(booleanField)
}
Then you have to observe that subject inside Boo class
class Boo(val observable: BehaviorSubject<Boolean>) {
var booleanField: Boolean = false
var disposable: Disposable? = null
fun startObserve() {
disposable?.dispose()
disposable = observable.subscribe {
booleanField = it
}
}
fun finishObserve() {
disposable?.dispose()
disposable = null
}
}
If you have to run it just create Foo and pass it's subject to Boo:
val foo = Foo()
val boo = Boo(foo.observable)
boo.startObserve()
foo.booleanField = true //boo is changed to true too
now if you change foo.booleanField, boo.booleanField will change too.
If you need, you can run startObserve in constructor to start it immidiatly after creating instance.
May be you have to create BehaviorSubject somewhere else and just pass to both classes using DI.
And do not forget to unsubscribe after work is done.
Related
I'm using MVVM architecture to check if a user is signed-in anonymous. In my repository class I have this this field:
val isUserAnonymous = FirebaseAuth.getInstance().currentUser?.isAnonymous
In the ViewModel class I have:
val isUserAnonymous = repository.isUserAnonymous
And in my activity I use something like this:
R.id.sign_out_button -> {
if (mainViewModel.isUserAnonymous!!) {
Log.d("TAG", "isUserAnonymous: " + isUserAnonymous) //False
}
}
Right after I successfully sign-in and I press the sing-out button, the isUserAnonymous is evaluated to false. However, if I use:
R.id.sign_out_button -> {
if (FirebaseAuth.getInstance().currentUser!!.isAnonymous) {
Log.d("TAG", "isUserAnonymous: " + isUserAnonymous) //True
}
}
isAnonymous is evaluated to true. Why? How can I get the value of true, when I use the code in my repository class?
You should change isUserAnonymous implementation to this:
val isUserAnonymous: Boolean?
get() = repository.isUserAnonymous // in view model
val isUserAnonymous: Boolean?
get() = FirebaseAuth.getInstance().currentUser?.isAnonymous // in repository
When you declare get() method for your variables instead of backing field value returned you are able to return value that is calculated at the moment of invocation of this variable.
It would be similar to writing two methods instead of variables:
// in view model
fun isUserAnonymous(): Boolean? {
return repository.isUserAnonymous
}
// in repository
fun isUserAnonymous(): Boolean? {
return FirebaseAuth.getInstance().currentUser?.isAnonymous
}
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'm using a pattern that I've used a few times before to instantiate a ViewModel object. In this case, the data is saved as a string in SharedPreferences. I just need to read that string, parse it to the correct object, and assign that object as the value to my view model.
But when I do the assignment, I create an infinite loop.
class UserDataViewModel(private val prefs: SharedPreferences): ViewModel() {
val userData: MutableLiveData<UserData> by lazy {
MutableLiveData<UserData>().also {
val userDataString = prefs.getString(Authenticator.USER_DATA, "")
val ud = Gson().fromJson(userDataString, UserData::class.java)
userData.value = ud // infinite loop is here
}
}
fun getUserData(): LiveData<UserData> {
return userData
}
}
This is in onCreateView() of the fragment that keeps the reference to the ViewModel:
userDataViewModel = activity?.run {
ViewModelProviders
.of(this, UserDataViewModelFactory(prefs))
.get(UserDataViewModel::class.java)
} ?: throw Exception("Invalid Activity")
userDataViewModel
.getUserData()
.observe(this, Observer {
binding.userData = userDataViewModel.userData.value
})
FWIW, in the fragment, I have break points on both getUserData() and on binding.userData.... The last break point that gets hit is on getUserData().
I don't see where the loop is created. Thanks for any help.
The userData field is only initialized once the by lazy {} block returns. You're accessing the userData field from within the by lazy {} block and that's what is creating the loop - the inner access sees that it hasn't finishing initializing, so it runs the block again..and again and again.
Instead, you can access the MutableLiveData you're modifying in the also block by using it instead of userData, breaking the cycle:
val userData: MutableLiveData<UserData> by lazy {
MutableLiveData<UserData>().also {
val userDataString = prefs.getString(Authenticator.USER_DATA, "")
val ud = Gson().fromJson(userDataString, UserData::class.java)
it.value = ud
}
}
I wanted to know how I define a static variable in Kotlin that can be used in other classes that do not final. Because the data is changing.
Example Java:
public static Boolean ActivityIsRuning = false;
There are three ways to achieve this:
1) Top-level / global declaration
Declare a variable outside of any class or function and it will be accessible from anywhere:
var activityIsRunning = false
2) object (an out of the box singleton)
object StaticData {
var activityIsRunning = false
}
Accessable like this:
StaticData.activityIsRunning
3) Class with companion object (as Todd already suggested)
class Config {
companion object {
var activityIsRunning = false
}
}
Accessable like this:
Config.activityIsRunning
You have to create companion object for this.
Your code in Kotlin would look something like this:
class Foo {
companion object {
lateinit var instance: Foo
}
init {
instance = this
}
}
You can define static variables in the companion object of any class. If you make it a var instead of a val, it can change values:
class MyClass {
companion object {
var activityIsRunning: Boolean = false
}
}
Adding to #Todd and #Willi Mentzel, if you like to group those settings under a common area, you can use a top-level plain object.
object GlobalSettings{
var isHomeActivityRunning = false
var isDrinkingCocoCola = true
}
and this can be accessed anywhere in the code (inside an Activity, Service, or anywhere) like this:
if( GlobalSettings.isDrinkingCocoCola ){
// do something
} else {
GlobalSettings.isDrinkingCocoCola = false
}
I want to verify that my MediatorLiveData has an actual value set:
val entries = MediatorLiveData<List<Entry>>()
get() = when {
entries.value == null -> throw IllegalStateException("wah")
else -> field
}
I get no compiler errors, but when running the code I get an StackOverflow, because the getter is called over and over again in the (entries.value == null) path.
1. How to throw an exception in a custom getter and stop the app from running
UPDATE WITH FINAL SOLUTION:
Thanks to #zapl and #kcoppock for your answers. Both of them helped me to get to my final solution:
private lateinit var _entries: LiveData<List<Entry>>
val entries = MediatorLiveData<List<Entry>>()
get() = when {
!::_entries.isInitialized -> throw IllegalStateException("EntryListViewModel was not initialized. Please call init() first.")
else -> field
}
fun init() {
_entries = getEntries(false)
entries.addSource(_entries) { entries.value = it.orEmpty() }
}
fun refreshEntries() {
entries.removeSource(_entries)
_entries = getEntries(true)
entries.addSource(_entries) { entries.value = it.orEmpty() }
}
I also learned from another source about .isInitialized for lateinit vars which can be used for exactly what I needed. Also the graceful fallback to an empty list was good idea.
What I would do is keep the LiveData private and surface a separate accessor for the checked property:
private val _entries = MediatorLiveData<List<Entry>>()
val entries: List<Entry>
get() = _entries.value!!
Better yet, unless you explicitly want to crash in this case, you could just return an empty list instead:
private val _entries = MediatorLiveData<List<Entry>>()
val entries: List<Entry>
get() = _entries.value.orEmpty()
That said, the point of LiveData is to use an observer, so you only get an update when a value is posted.
EDIT: If your goal is to force an initial value, you could create a subclass that enforces this:
class NonNullMediatorLiveData<T>(initialValue: T) : MediatorLiveData<T>() {
init { value = initialValue }
override fun getValue(): T = super.getValue()!!
override fun setValue(value: T) {
// assert since the parent is defined in Java
super.setValue(value!!)
}
}