Mobx.dart: Support for read-only observables

Created on 10 Jul 2019  路  12Comments  路  Source: mobxjs/mobx.dart

I'd like to define stores where observables are only mutated by actions and cannot be mutated externally of the store. This currently is not possible because the code generator defines get/set properties for observables. It would be particularly useful to define read-only observable ObservableList properties since you usually never call the setter once it's initialized.

I know one option is to make private observables then provide read-only computed properties for the observables, but a) that seems unnecessarily verbose, and b) it's not really computed, they're just simple accessors.

For comparison, here's a simple example of how it currently works.

# Current approach
class _TestStore with Store {
    @observable
    int value;
}

# Current generated code
mixin _$TestStore on _TestStore, Store {
 final _$valueAtom = Atom(name: '_TestStore.value');

  @override
  int get value {
    _$valueAtom.context.enforceReadPolicy(_$valueAtom);
    _$valueAtom.reportObserved();
    return super.value;
  }

  @override
  set value(int value) {
    _$valueAtom.context.conditionallyRunInAction(() {
      super.value = value;
      _$valueAtom.reportChanged();
    }, _$valueAtom, name: '${_$valueAtom.name}_set');
  }
}

Here's one suggestion of an alternate approach.

# Alternate approach
class _TestStore with Store {
    int _value;

    @observable(setter: '_setValue')
    int get value
        => _value;

    void _setValue(int value)
        => _value = value;
}

# Alternate generated code
mixin _$TestStore on _TestStore, Store {
 final _$valueAtom = Atom(name: '_TestStore.value');

  @override
  int get value {
    _$valueAtom.context.enforceReadPolicy(_$valueAtom);
    _$valueAtom.reportObserved();
    return super.value;
  }

  @override
  void _setValue(int value) {
    # Same code that was originally in the generated setter
    _$valueAtom.context.conditionallyRunInAction(() {
      super.value = value;
      _$valueAtom.reportChanged();
    }, _$valueAtom, name: '${_$valueAtom.name}_set');
  }
}

I think the ObservableList case as just a read-only property with the default @observable annotation should be sufficient, in that case just generate the getter but not the setter.

enhancement

Most helpful comment

It would be cool if @readonly generated the public getter, and you only needed to write

class _TestStore with Store {
    @readonly
    @observable
    int _value = 0;

    @action
    void incrementValue() => _value++;
}

Or even better

class _TestStore with Store {
    @observable(readonly: true)
    int _value = 0;

    @action
    void incrementValue() => _value++;
}

All 12 comments

Are you suggesting something like a @readonly annotation where a setter won't be generated:

@readonly
@observable 
int value;

@pavanpodila I don't think so. If no code at all is generated for a setter then how could value be mutated such that this code would run and trigger a change?

```
_$valueAtom.context.conditionallyRunInAction(() {
super.value = value;
_$valueAtom.reportChanged();
}, _$valueAtom, name: '${_$valueAtom.name}_set');
````

Ok got it! This will be a custom setter then, whose name you are specifying in the annotation

Yes, that's what I was suggesting. There may be a better way to declare a readonly observable that can be mutated within the store, that approach was the one I thought was simplest though.

Can't you do this right now by creating an observable on a private field?

class _TestStore with Store {
    @observable(setter: '_setValue')
    int _value = 0;

    int get value => _value;

    @action
    void incrementValue() => _value++;
}

@shyndman that seems to work for me, you don't need the setter param though (it doesn't exist)

class _TestStore with Store {
    @observable
    int _value = 0;

    int get value => _value;

    @action
    void incrementValue() => _value++;
}

It would be cool if @readonly generated the public getter, and you only needed to write

class _TestStore with Store {
    @readonly
    @observable
    int _value = 0;

    @action
    void incrementValue() => _value++;
}

Or even better

class _TestStore with Store {
    @observable(readonly: true)
    int _value = 0;

    @action
    void incrementValue() => _value++;
}

Adding a @readonly annotation would be great, meaning a public getter and a private setter are generated, so the value is observable and only mutable from inside the store.

If this feature is really desirable, I suggest we upvote the issue. That way we can prioritize on what we pick up next.

@eltonomicon what your thoughts about the ObservableList, I see that this approach is good for simple values, link string or int, but for a complex class, that expose methods that allow changing of itself, how do you think that we could still user ObservableList but only with inside store modifications.

@AlbertoMonteiro you can that complex class be a Store itself. I do this in a bunch of places today.

Sorry @MisterJimson but I think that you didn't understand what I said.

Was this page helpful?
0 / 5 - 0 ratings