How to observe field variable of other class in Mobx flutter? - flutter

I am using the flutter Mobx for state management.
I have a simple class:-
class A {
int x;
A(this.x);
}
How can I observe if x changes inside the class in another Mobx store:-
class MyStore extends _MyStore with _$MyStore {
Subs(A a) : super(a);
}
abstract class _MyStore with Store {
#observable
A a;
_Subs(this.a)
}
I want MyStore to observe the a.x.
Is it possible, if yes how?

I ran in to the same issue the other day using flutter mobx ^1.2.1+3 (dart) and
flutter_mobx ^1.1.0+2.
The first thing that comes to my mind is to annotate the field in question, I.e x with the #observable attribute. But it doesn't seem to be effective outside a store class.
So you have to observe the field using the Observable class.
To make it work your code should look something like this:
class A {
//replace with less verbose "var"
Observable<int> x = Observable(0);
A(this.x);
}
class MyStore extends _MyStore with _$MyStore {
Subs(A a) : super(a);
}
abstract class _MyStore with Store {
A a;
_Subs(this.a)
//Will be calculated whenever a change to a.x is observed.
#computed
int get xSquare => a.x.value * a.x.value;
}
As you can see I removed the observable attribute from a, since it does not need to be observed if you want to react to changes to a.x in your store. You probably noticed that you have to access the value of the observable using .value.
That should conclude how you observe a field of a class external to the store, inside your store.

I am not sure that this would be helpful since it is Javascript/Typescript, but that's what I would do :
class Foo {
#observable name = 'foo'
}
class Bar {
foo: Foo
constructor(instanceOfFoo) {
this.foo = instanceOfFoo
autorun(() => {
// Logs foo name when it changes
console.log(this.foo.name)
})
reaction(
() => this.foo.name,
() => {
// Logs foo name when it changes
console.log(this.foo.name)
}
)
}
#observable
name = 'bar'
#computed
get fooNamePlusBarName {
// recomputes automatically whenever foo or bar name changes
return this.foo.name + this.name
}
}
Basically you pass Foo instance to the Bar constructor (or just use imported singleton if it fits you), then you have 3 options: computed, reaction and autorun

Related

Flutter with Equitable and ObjectBox: ToMany relationship not updating properly

I have the following classes:
import 'package:equatable/equatable.dart';
import 'package:objectbox/objectbox.dart';
#Entity()
/*
All fields of a class which extends Equatable should be immutable, but ObjectBox
requires the `id` field to be mutable because its value is set after an instance of
the class has been created. Because of this, we ignore the linter rule
"must_be_immutable" on all ObjectBox entities.
*/
// ignore: must_be_immutable
class Foo extends Equatable {
int id;
final String fooProp;
// I don't need a backlink yet, but very likely will in the future
// #Backlink()
// final ToMany<Bar> bars;
Foo(
this.fooProp,
{
this.id=0,
}
);
#override
List<Object> get props => [fooProp];
}
import 'package:equatable/equatable.dart';
import 'package:objectbox/objectbox.dart';
#Entity()
/*
All fields of a class which extends Equatable should be immutable, but ObjectBox
requires the `id` field to be mutable because its value is set after an instance of
the class has been created. Because of this, we ignore the linter rule
"must_be_immutable" on all ObjectBox entities.
*/
// ignore: must_be_immutable
class Bar extends Equatable {
int id;
final String barProp;
final ToMany<Foo> foos;
Bar(
this.barProp,
this.foos,
{
this.id=0,
}
);
#override
List<Object> get props => [barProp, foos];
}
And here is what I'm trying to do:
import 'package:foo_bar/objectbox/objectbox.dart';
// Get previously stored instance of Foo
Foo foo = ObjectBox.fooBox.get(1);
// Print foo.fooProp
print(foo.fooProp); // Prints "asdf"
// Change foo.fooProp to something else
foo.fooProp = 'fdsa';
// Update foo
ObjectBox.fooBox.put(foo);
// Get the same instance of Foo again
foo = ObjectBox.fooBox.get(1);
// Check foo.fooProp to make sure it updated
print(foo.fooProp); // Prints "fdsa", good so far
// Get previously stored instance of Bar which has Foo instance with ID of 1 in its foos
Bar bar = ObjectBox.barBox.get(1);
// Get our foo from bar.foos
foo = bar.foos[0];
// Verify the ID of foo to make sure it is the same object
print(foo.id); // Prints "1", exactly what we expect
// Print foo.fooProp
print(foo.fooProp); // Prints "asdf", not the expected "fdsa"
The documentation has the following to say on the subject:
Note that to-many relations are resolved lazily on first access, and then cached in the source entity inside the ToMany object. So subsequent calls to any method, like size() of the ToMany, do not query the database, even if the relation was changed elsewhere. To get the latest data fetch the source entity again or call reset() on the ToMany.
The reset() method doesn't appear to be available in the Flutter flavor of ObjectBox, and we can see from my example that even fetching both sides of the ToMany relationship did not result in the expected update.
What am I missing here?
Failed Workaround:
I tried to workaround this problem with the following awful bit of code, but even this does not work. ObjectBox just completely ignores the actual bar.foos and whatever was persisted for foos remains there and doesn't get updated.
final List<Bar> oldBars = ObjectBox.barBox.getAll();
List<Bar> newBars = [];
for(Bar oldBar in oldBars) {
if(oldBar.foos.isNotEmpty) {
List<int> oldFooIds = oldBar.foos.map((foo) => foo.id).toList();
List<Foo> newFoos = foos.where((foo) => oldFooIds.contains(foo.id)).toList();
Bar newBar = oldBar.copy(foos: ToMany<Foo>(items: newFoos));
newBars.add(newBar);
}
}
ObjectBox.barBox.putMany(newBars);
This makes me think there is something wrong with the way I have the relationship setup, but there are no errors when the ObjectBox generator runs
CALL flutter pub run build_runner build --delete-conflicting-outputs
Update:
I have this working now, but clean it is not. I had my Bar constructor set up to accept a collection of Foo objects, but passing the instances of Foo in is what was causing the relations to break. If I instead create an instance of Bar, then use bar.foos.add(foo), the result is as expected. For what it is worth, that is how the examples in the docs show interactions with relations happening, I just didn't think it was that literal, because creating new objects with relations in this manner is a hassle. I think some work can be done in the constructor to make things a bit easier still.
A "solution" (and I use that term loosely) to my problem was the following modification to the Bar class. This allows me to initialize instances of Bar with a pre-built list of instances of Foo.
import 'package:equatable/equatable.dart';
import 'package:objectbox/objectbox.dart';
#Entity()
/*
All fields of a class which extends Equatable should be immutable, but ObjectBox
requires the `id` field to be mutable because its value is set after an instance of
the class has been created. Because of this, we ignore the linter rule
"must_be_immutable" on all ObjectBox entities.
*/
// ignore: must_be_immutable
class Bar extends Equatable {
int id;
final String barProp;
final ToMany<Foo> foos = ToMany<Foo>();
Bar(
this.barProp,
{
this.id=0,
foos=const <Foo>[],
}
) {
this.foos.addAll(foos);
}
#override
List<Object> get props => [barProp, foos];
}
This works fine, for creating new objects, but because I want to use Equatable I'm must make all properties which are used to determine equality final. When a class is an #Entity which will persisted to ObjectBox, most of its properties will typically be used to determine equality, so this requirement of Equatable makes it at odds with ObjectBox when it is time to update an object. For instance, if I have an instance of Bar, I can't update barProp; I have to create a new instance of Bar which is initializes barProp to the desired value. If I create a new instance of Bar which had the desired value for barProp, foos, and has the same ID as an already persisted instance of Bar, then I try to persist that new instance, barProp will be updated as expected, but foos will not. All that to say, I first have to take the heavy handed approach of calling ObjectBox.barBox.remove() or ObjectBox.barBox.removeAll() (depending on the application) before persisting the new instance of Bar.
Foo fooA = Foo('this is Foo A');
Foo fooB = Foo('this is Foo B');
List<Foo> firstFooList = <Foo>[fooA, fooB];
ObjectBox.fooBox.putMany(firstFooList);
Foo fooC = Foo('this is Foo C')
Foo fooD = Foo('this is Foo D')
List<Foo> secondFooList = <Foo>[fooC, fooD];
ObjectBox.fooBox.putMany(secondFooList);
Bar barA = Bar('this is bar A', foos=firstFooList)
ObjectBox.barBox.put(barA); // ObjectBox assigns ID 1 to this Bar
Bar barB = Bar('this is bar B', id=barA.id, foos=secondFooList) // Use the `id` from barA which we just persisted, but initialize `foos` with `secondFooList`
// Without this, the next line which persists `barB` would result in the
// instance of Bar which has ID 1 having a `barProp` value of 'this is bar B',
// but a `foos` value equal to `firstFooList`, not the expected `secondFooList`.
ObjectBox.barBox.remove(barB.id);
ObjectBox.barBox.put(barB);
Time will tell if calling remove and removeAll like this are a bigger performance hit than not using Equatable, and for others reading this, that determination will depend on your specific application (i.e. does your app have more UI interactions that benefit from reduced builds because of the inclusion of Equatable, or does your app have more ObjectBox interactions where excessive calls to remove and removeAll cause a performance hit).

Loose coupling in Dart

I was trying to implement loose coupling in one of my Flutter projects. It was not able to find the method.
Have replicated the same in a simple Dart code, how can I fix this, and is there some way to achieve loose coupling in Dart?
abstract class A{}
class B extends A{
void help(){
print("help");
}
}
class C {
A b;
C({required this.b});
void test(){
b.help();
}
}
void main() {
var c = C(b:B());
c.test();
}
Giving error at b.help(), the method does on exist.
Exact error
The method 'help' isn't defined for the type 'A'.
b is known to be of type A, and the A interface does not provide a help method.
I don't know exactly what your definition of "loose coupling" is (it'd be better to describe a specific problem that you're trying to solve), but if you want help to be callable on type A, then you must add it to the A interface.
You alternatively could explicitly downcast b to B with a runtime check:
class C {
A b;
C({required this.b});
void test() {
// Shadow `this.b` with a local variable so that the local
// variable can be automatically type-promoted.
final b = this.b;
if (b is B) {
b.help();
}
}
}
Or if you want duck typing, you could declare (or cast) b as dynamic:
class C {
dynamic b;
C({required this.b});
void test() {
try {
b.help();
} on NoSuchMethodError {}
}
}
although I would consider the last form to be bad style.

How to get properties of subclass in dart?

I have a class A with some properties:
abstract class A {
double doubleA;
String stringA;
...
A({this.doubleA = 0, this.stringA = ""});
}
and a class B with some properties, that extends class A:
class B extends A {
int intB;
String stringB;
B({
this.intB = 0,
this.stringB = "",
double doubleA = 0,
String stringA = "",
}) : super(doubleA: doubleA, stringA: stringA);
}
In my code I want to now check if an instance of A has a value that is of type of subclass B:
A a; // Value can be of different subtypes of A including B
if(a is B) {
// here dart should give me access to the properties of a like:
print(a.stringA);
// but it should also be possible to access the type B properties
// since the value of a can also be of subclass type B:
print(a.stringB);
}
This sounds wrong at first but I know that it can work because of examples in flutter.
Example Listener:
Listener(
onPointerSignal: (event) {
// event is of type PointerSignalEvent which has no property 'scrollDelta'.
// So print(event.scrollData); does not work here.
if (event is PointerScrollEvent) {
// if you check if event is of subtype PointerScrollEvent the property 'scrollDelta'
// that is included in the class PointerScrollEvent becomes available.
print(event.scrollDelta); // works without any problem.
}
},
}
However I have not been able to replicate this with my classes A and B and I don't know why it doesn't work. I have also looked into the implementations of these flutter classes and copied the class structure but I can still only access the properties of A after the check if(a is B) which doesn't correspond to the behavior observed with the flutter classes.
What am I doing wrong? Am I am missing something?
Thanks for reading :D <3
If you declare a variable 'a' as being of type 'A', you will not be able to access the properties of classes inheriting from A (like B).
Let's say A is Animal and B is Baboon. Baboon inherits properties from Animal, so all variables instantiated with the Baboon type with have access to properties from both classes. But variables instantiated with the Animal type will only have access to the Animal properties.
Here are some examples: https://medium.com/jay-tillu/inheritance-in-dart-bd0895883265

instantiate object with *new* keyword and use property inside class with *this* keyword

i'm coming from mainly JS/TS world (NestJS/Angular) and recently i start to building Flutter apps..
i have 2 main questions
there is any difference when instantiate object with or without new keyword?
i saw examples in flutter when people use new Row(children: [Text('Foo'), Text('Bar'),],) instead of just Row(...)
if there is a difference which one is better to use?
inside of my Dart classes in flutter app, i can both use this.property and property again there is any difference and if so which one is better and why?
example:
class Person {
final String name;
final int age;
Person(this.name, this.age);
getNameAge() => '${this.name} is ${this.age}';
getNameAge2() => '$name is $age';
}
both looks the same to me
void main() {
final p = Person('dan', 22);
final p2 = new Person('ben', 20);
print(p.getNameAge()); // dan is 22
print(p2.getNameAge2()); // ben is 20
}
The new keyword is optional in Dart and I think the general consensus is, today, to not use it.
The use of this is useful if you have multiple variables with the same name but in different scope. E.g. (this is just an example. You would not make a setA method in Dart but use properties):
class A {
int a;
A(this.a);
void setA(int a) {
this.a = a;
}
}
Here we use this to distinguish between the argument a and the class variable a. But if you don't have variables with the same name (but in different scope), the use of this is optional. In some projects, you still use this to make it more clear that you are referring to a class variable even if it is not needed.

what happens in react when setState with object instance of a class

I have this fiddle
let m = new Mine();
this.setState(m, () => {
console.log('1:', m instanceof Mine, m.x, m.meth);
// => 1: true 123 function meth() {}
console.log('2:', this.state instanceof Mine, this.state.x, this.state.meth);
// => 2: false 123 undefined
});
As you can see I create an instance of the Mine class and then set state in a react component with that instance.
I would expect this.state to contain exactly that instance but while the instance properties that are set in the constructor are available I can't access any of the class methods on that instance.
The test in the fiddle shows that this.state is not an instance of the class Mine.
Does anybody understand what is going on or is this unintended behavior?
After more investigation I found out the reason why that happens.
The function _processPendingState from react uses Object.assign to set the new state, so since the target object is a new object (different than what is passed to setState) the new state loses the quality of being an instance of the "Mine" class.
And because Object.assign only copies own enumerable properties from the sources to the target the new state also won't have the class methods.
If in the fiddle we replace the line...
let m = new Mine();
with...
let m = {x: 123};
Object.defineProperty(m, 'meth', {
enumerable: false,
get() { return function() {}; }
});
we still don't have the "meth" property on the resulting state. Even if "m" owns the "meth" property it is not enumerable.
The best solution is to surface the method as an arrow function:
class Blah {
constructor() {
// no definition here for surfacedMethod!
}
surfacedMethod = () => {
// do something here
}
}
Then you can set instances of this class in setState and use their methods as if they were attributes set on the instance.
// other component innards
this.setState(state => ({blah: new Blah()}))
// later
this.state.blah.surfacedMethod(); // this will now work
In such case use replaceState, it should work.