I have the following observable class sample:
class AppState implements IAppState {
isSignedIn: boolean;
private constructor(
isSignedIn: boolean,
) {
this.isSignedIn = isSignedIn;
makeAutoObservable(this);
}
get itIsSafeToFetch(): boolean {
return this.isSignedIn === true;
}
}
The thing is that I have getter itIsSafeToFetch(), which does not change any observable values. But when I run the code I get warning:
[MobX] Since strict-mode is enabled, changing (observed) observable values
without using an action is not allowed.
Tried to modify: AppState#45.isSignedIn
If I comment out that getter - warning disappear. Why could this happen?
Related
I have this class:
class Mother{
#observable
name: string
child: Child = new Child(this)
constructor(){
makeObservable(this)
}
}
class Child{
constructor(mother: Mother){
const disposer = reaction(() => [mother.name], ()=>{})
console.log(getDependencyTree(disposer));
}
}
The output shows that the disposer has no dependency on mother.name. What am I missing?
The problem is the order of initialization: Child object is initialized before makeObservable(this) is called, thus mother.name is not yet an observable when running in reaction. By moving the intialization of child object after calling makeObservable(this), I solved the problem.
The following code is from the project.
The function of tasksRepository.refreshTasks() is to insert data from remote server to local DB, it's a time consuming operation.
In class TasksViewModel, asksRepository.refreshTasks() is wrapped with viewModelScope.launch{}, it means launch and careless.
1: How can I guarantee tasksRepository.observeTasks().distinctUntilChanged().switchMap { filterTasks(it) } to return the latest result?
2: I don't know how distinctUntilChanged() work, will it keep listening to return the latest result in whole Lifecycle ?
3: What's happened if I use tasksRepository.observeTasks().switchMap { filterTasks(it) } instead of tasksRepository.observeTasks().distinctUntilChanged().switchMap { filterTasks(it) }
Code
class TasksViewModel(..) : ViewModel() {
private val _items: LiveData<List<Task>> = _forceUpdate.switchMap { forceUpdate ->
if (forceUpdate) {
_dataLoading.value = true
viewModelScope.launch {
tasksRepository.refreshTasks()
_dataLoading.value = false
}
}
tasksRepository.observeTasks().distinctUntilChanged().switchMap { filterTasks(it) }
}
...
}
class DefaultTasksRepository(...) : TasksRepository {
override suspend fun refreshTask(taskId: String) {
updateTaskFromRemoteDataSource(taskId)
}
private suspend fun updateTasksFromRemoteDataSource() {
val remoteTasks = tasksRemoteDataSource.getTasks()
if (remoteTasks is Success) {
tasksLocalDataSource.deleteAllTasks()
remoteTasks.data.forEach { task ->
tasksLocalDataSource.saveTask(task)
}
} else if (remoteTasks is Result.Error) {
throw remoteTasks.exception
}
}
override fun observeTasks(): LiveData<Result<List<Task>>> {
return tasksLocalDataSource.observeTasks()
}
}
switchMap - The returned LiveData delegates to the most recent LiveData created by calling switchMapFunction with the most recent value set to source, without changing the reference. Doc
Yes, it'll keep listening to return the latest result in whole Lifecycle. distinctUntilChanged creates a new LiveData object that does not emit a value until the source LiveData value has been changed. The value is considered changed if equals() yields false.
Yes you can use that too but it'll keep emitting the values even the values are the same as the last emitted value.
e.g. first emitted value is ["aman","bansal"] and the second is the same ["aman","bansal"] which you don't want to emit since the values are same. So you use distinctUntilChanged to make sure it won't emit the same value until changed.
I hope this helped.
In java you can do the follwing:
public class Foo {
private String bar = "text";
public void method() {
// direct access (no logic)
System.out.println(this.bar);
}
// only if you access the object from the outside
// you are forced to use the getter with some logic in it
public String getBar() {
System.out.println(this.bar);
return this.bar;
}
}
But if you define a getter or a setter with logic in Kotlin you are forced to always execute this logic when accessing the field:
class Foo {
var bar: String = "text"
get() {
println(field)
return field
}
private set
fun method() {
// this also executes the getter
// Is it possible to skip the getter
// and directly access the field?
println(this.bar)
}
}
Is there a better way to access the field without executing the getter or setter logic than creating your own fun getBar() in Kotlin?
There is no possible way to skip a getter or a setter, they are intended to block the direct access of a property.
What you can do is make a multi-reference to same value (fake-referencing):
private var _bar: String = "text"
var bar
get() {
// some operations intercepting the getter
return _bar
}
// direct access
_bar
// intercepted access public field
bar
In Kotlin the backing fields (in your case the private variable) are not exposed by design. There are a few exceptions explained here: https://kotlinlang.org/docs/reference/properties.html#backing-fields
All access to val and var happens through implicit getters and setters. A val resolves to a property with a getter() while var resolves to a property with a getter and a setter: https://kotlinlang.org/docs/reference/properties.html#properties-and-fields
I am working on a library where any change in argument refreshes the view. In refresh() function, I am setting some arguments' values to default values.
var viewAlpha= 255
set(value) {
field = value
refresh()
}
fun refresh() {
viewAlpha = 255
invalidate()
}
This is causing StackOverflowError due to obvious reasons.
Caused by: java.lang.StackOverflowError: stack size 8MB
Is it possible to access variables in kotlin without invoking its setter when we are accessing it in the same class. Similar to what we do in java.
One way would be to provide you good ol' backing field to get out of setter-refres cycle:
private var _viewAlpha = 255
var viewAlpha
get() {
return _viewAlpha
}
set(value) {
_viewAlpha = value
refresh()
}
fun refresh() {
_viewAlpha = 255
invalidate()
}
If you want to simplify your logic for multiple fields you can abstract this implementation into separate class and use callback call with direct setter that will work without refresh invocation. Like this:
class Field(val onSetCb: (Field) -> Unit) {
private var viewAlpha = 255
fun get() {
return viewAlpha
}
fun set(value: Int) {
setDirect(value)
onSetCb(this)
}
fun setDirect(value: Int) {
viewAlpha = value;
}
}
// Elsewhere...
fun refresh(field: Field) {
field.setDirect(255)
invalidate()
}
val f = Field(::refresh)
f.set(255)
Let's assume I have a global state class that contains a TimeFrame class which has some useful properties
export class StateContainer {
public timeframe: TimeFrame;
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
}
}
export class TimeFrame {
public readonly startTime: Date;
public readonly endTime: Date;
}
Then, I need to consume this state elsewhere, so I do so via DI and then use the bindingEngine.propertyObserver to get changes on the timeframe object as one would do.
However, I would like be able to do something similar to the following, if it's possible:
#autoinject
export class Display {
#observable
timeFrame: TimeFrame = this.state.timeframe;
constructor(private state: StateContainer) {
}
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) {
...
// everytime this.state.timeFrame is changed via setTimeFrame()
// this change handler should fire, and so should any
// bindings from this component for this.timeFrame
}
}
However, when I do the previous, I only get timeFrameChanged(...) notifications on the inital creation, not whenever I call setTimeFrame(). Am I doing something wrong or is this not possible?
Yes, you are doing something wrong, I think you are misunderstanding how the observable decorator and the corresponding timeFrameChanged method are meant to be used.
Let's take it bit by bit. Take a look at these lines:
#observable
timeFrame: TimeFrame = this.state.timeframe;
timeFrameChanged(newValue: TimeFrame, oldValue: TimeFrame) { ... }
The observable decorator tells Aurelia that whenever the property to which it is applied changes, execute the corresponding method. Unless otherwise configured, the corresponding method is nameOfInvolvedProperty + "Changed" (in this case, as you are correctly doing, timeFrameChanged).
However, you are never actually changing the value of that property! If you actually changed that property, it would work:
<button click.delegate="changeProp()">
Change prop
</button>
and the VM:
changeProp() {
this.timeFrame = null;
this.timeFrame = this.state.timeframe;
}
Now you'd see that the handler correctly fires.
But in your code, this property is only ever assigned once, here:
timeFrame: TimeFrame = this.state.timeframe;
Remember, this is just dereferencing. In other words, this code tells the app to take the value of this.state.timeframe, store the momentary value of it in that variable and forget about this.state.timeframe. So it does not get updated whenever this.state.timeframe is updated.
As described in the documentation, you can configure the observable decorator to some extent, however, to my knowledge, there is no way to configure it in such a way that set it up to observe nested properties (or maybe it's just me not knowing how to do that).
Even if it was possible, I think a cleaner way to deal with such situations would be to use an event aggregator. This enables you to employ a subscription mechanism - whenever the value you are interested in changes, you publish an event and whichever component is interested in that change can subscribe to it and update itself accordingly.
A simple implementation:
import { Router, RouterConfiguration } from 'aurelia-router';
import { autoinject } from 'aurelia-framework';
import { observable } from 'aurelia-binding';
import { StateContainer } from './state-container';
import { EventAggregator, Subscription } from 'aurelia-event-aggregator';
#autoinject
export class App {
subscription: Subscription = null;
constructor(private state: StateContainer, private eventAggregator: EventAggregator) {
}
activate() {
this.subscription = this.eventAggregator.subscribe('state:timeframe', (e) => {
console.log('Timeframe changed!', e);
});
}
deactivate() {
// Make sure to dispose of the subscription to avoid memory leaks.
this.subscription.dispose();
}
change() {
this.state.setTimeframe(new Date(), new Date());
}
}
And StateContainer:
import { TimeFrame } from "./time-frame";
import { autoinject } from "aurelia-framework";
import { EventAggregator } from "aurelia-event-aggregator";
#autoinject
export class StateContainer {
public timeframe: TimeFrame = null;
constructor(private eventAggregator: EventAggregator) {
}
public setTimeframe(startTime: Date, endTime: Date) {
// treat this.timeframe as immutable
this.timeframe = { startTime: startTime, endTime: endTime };
this.eventAggregator.publish('state:timeframe', this.timeframe);
}
}
Hope that helps.