Coder Social home page Coder Social logo

Code generator design about mobx.dart HOT 67 CLOSED

mobxjs avatar mobxjs commented on May 22, 2024 3
Code generator design

from mobx.dart.

Comments (67)

rrousselGit avatar rrousselGit commented on May 22, 2024 2

This issue is likely outdated. It says that dartpad doesn't support them, but it actually does.

And only the mixin keyword is new. The class Foo = Bar with Mixin has been there for ages.

If an issue arises (which I doubt), we can generate the old mixin syntax instead:

abstract class _$User extends UserBase {}

from mobx.dart.

amrnt avatar amrnt commented on May 22, 2024 2

I have tried this example:

import 'package:json_annotation/json_annotation.dart';
import 'package:mobx/mobx.dart';

part 'user.g.dart';

@JsonSerializable()
class User = UserBase with _$User;

abstract class UserBase implements Store {
  UserBase(this.id);

  final String id;

  @observable
  String firstName = '';

  @observable
  String lastName = '';

  @computed
  String get fullName => '$firstName $lastName';

  @action
  void updateNames({String firstName, String lastName}) {
    if (firstName != null) this.firstName = firstName;
    if (lastName != null) this.lastName = lastName;
  }
}

One issue with the short hand : class User = UserBase with _$User; that we can't add these to the class:

   factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
   Map<String, dynamic> toJson() => _$UserToJson(this);

Also, i can confirm that the @computed getters was ignored by json_serializable: https://gist.github.com/amrnt/48802c3de2654511017905d800fd027f

from mobx.dart.

katis avatar katis commented on May 22, 2024 2

Basically, if the codegen generates a mixin that overrides stuff of the base class the declaration must be

mixin _$User on User {}

Then if the class would use the mixin

class User with _$User {}

This would be circular relationship which won't work.

We could instead reverse the codegen so that the user creates a mixin, and we generate a class for it. This would remove the need for a the mixin application, but then the user couldn't add a constructor. To me that would be a bigger problem because mixin initialization is pretty limited.

I think we should change the codegen a bit that it would search for all uses of the mixin, instead of just the short class Foo = FooBase with _$Foo; syntax. This way you could add your json_serializable stuff to the class:

@JsonSerializable()
class User extends UserBase with _$User {
   User(String id) : super(id); 

   factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
   Map<String, dynamic> toJson() => _$UserToJson(this);
}

from mobx.dart.

katis avatar katis commented on May 22, 2024 2

@amrnt It was simple to relax the rules of code generation, see #53

from mobx.dart.

copydog avatar copydog commented on May 22, 2024 1

@pavanpodila The @store is marked on store class which used by code generator, @inject is used on widget, it's different.

from mobx.dart.

copydog avatar copydog commented on May 22, 2024 1

@katis We can get more clear code for loop and control flow. And allow us to create one template file for each annotation.

here is the file structure that I used, very clean and maintainable
image

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024 1

I don't think it's even possible to begin with. The dart way of your function-based example would be this:

Something selector(Map anotherThing) {}

@Inject(selector)
class Foo {}

Closures are not compile time constant.

And we'd need to create a factory for the widget to be able to inject the field; which is not very ideal.

from mobx.dart.

katis avatar katis commented on May 22, 2024 1

I did think about that, but making a separate getter and setter seems too much trouble for a simple field.
I also don't understand why we're trying to force the use of Observable internally since Atom solves pretty much all the problems Observable causes in the first place 😅
If we run into issues with Atom while implementing spying/tracing, we can create a new class for the use case, or do some other refactorings, but for now Atom does everything we want.

from mobx.dart.

katis avatar katis commented on May 22, 2024 1

I think that the idea about the @store is a good, here's my suggestions for it:

Instead of the code generator searching for a class with an annotation, it searches for abstract classes implementing a Store interface. We can add spying/tracing methods to it later, but for now it could just be an empty marker interface.

abstract class User implements Store {
}

If the class implements Store and has an @observable annotation, all the methods/fields get transformed:

@observable
abstract class User implements Store {
}

from mobx.dart.

katis avatar katis commented on May 22, 2024 1

@copydog @rrousselGit I discovered that generating a mixin allows user defined constructors and inheritance of stores. Implemented in #51, here's a sample:

import 'package:mobx/mobx.dart';

part 'generator_example.g.dart';

class User = UserBase with _$User;

abstract class UserBase implements Store {
  UserBase(this.id);

  final int id;

  @observable
  String firstName = 'Jane';

  @observable
  String lastName = 'Doe';

  @computed
  String get fullName => '$firstName $lastName';

  @action
  void updateNames({String firstName, String lastName}) {
    if (firstName != null) this.firstName = firstName;
    if (lastName != null) this.lastName = lastName;
  }
}

class Admin = AdminBase with _$Admin;

abstract class AdminBase extends User implements Store {
  AdminBase(int id) : super(id);

  @observable
  String userName = 'admin';
}

Generates

mixin _$User on UserBase, Store {
  Computed<String> _$fullNameComputed;

  @override
  String get fullName =>
      (_$fullNameComputed ??= Computed<String>(() => super.fullName)).value;

  final _$firstNameAtom = Atom(name: 'UserBase.firstName');

  @override
  String get firstName {
    _$firstNameAtom.reportObserved();
    return super.firstName;
  }

  @override
  set firstName(String value) {
    super.firstName = value;
    _$firstNameAtom.reportChanged();
  }

  final _$lastNameAtom = Atom(name: 'UserBase.lastName');

  @override
  String get lastName {
    _$lastNameAtom.reportObserved();
    return super.lastName;
  }

  @override
  set lastName(String value) {
    super.lastName = value;
    _$lastNameAtom.reportChanged();
  }

  final _$UserBaseActionController = ActionController(name: 'UserBase');

  @override
  void updateNames({String firstName, String lastName}) {
    final _$prevDerivation = _$UserBaseActionController.startAction();
    try {
      return super.updateNames(firstName: firstName, lastName: lastName);
    } finally {
      _$UserBaseActionController.endAction(_$prevDerivation);
    }
  }
}

mixin _$Admin on AdminBase, Store {
  final _$userNameAtom = Atom(name: 'AdminBase.userName');

  @override
  String get userName {
    _$userNameAtom.reportObserved();
    return super.userName;
  }

  @override
  set userName(String value) {
    super.userName = value;
    _$userNameAtom.reportChanged();
  }
}

Pre 2.1-stable (my Flutter installation uses Dart SDK 2.1-dev...) has a bug that requires an empty constructor for one of the on Foo, Bar constraints of the mixin, luckily Store fulfills that.

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024 1

This raises an interesting question, is this syntax compatible with json_serializable/built_value?

Since the class doesn't define any constructor, on paper we should be able to use both this generator and a serializer on the same class right?

That could be very useful if we wanted to save a model in the local storage

from mobx.dart.

katis avatar katis commented on May 22, 2024 1

@amrnt

Using immutable values for plain data with mutable mobx stores should work just fine (and is probably the way I'll be going with my own apps). Trying to mix the codegen of both in the same class is a recipe for disaster though.

from mobx.dart.

amrnt avatar amrnt commented on May 22, 2024 1

For now it can be done creating a temp class and then inherent it...

@JsonSerializable()
class User extends _TempUser {
  User(String id) : super(id);

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

class _TempUser = UserBase with _$_TempUser;

abstract class UserBase implements Store {
  UserBase(this.id);

  final String id;

  @observable
  String firstName = '';

  @observable
  String lastName = '';

  @computed
  String get fullName => '$firstName $lastName';

  @action
  void updateNames({String firstName, String lastName}) {
    if (firstName != null) this.firstName = firstName;
    if (lastName != null) this.lastName = lastName;
  }
}

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024 1

I guess the limitation with a mixin is that the user can't add a constructor at all? I wouldn't be happy with that.

Apparently spying/tracing requires more detailed events about the things happening with observables than you can get with autorun ( https://mobx.js.org/refguide/observe.html ) and the observe method only tracks the single observable, @pavanpodila might have more information. If we want to get access to the internal values, adding accessor methods to Store is probably the cleanest way.

Conceptually spying and tracing work similar to the onBecomeObserved and onBecomeUnobserved hooks. Observables will report reads and writes to the "spy", which are then relayed back to the outside world. Here the spy is implicitly created and used when someone attaches a handler to a spy() method (not yet implemented)

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024 1

The code that was using Counter won't be updated automatically

Indeed.

I took that option for functional_widget because this heavily simplify the syntax to create a widget and I'm fine with making an analyzer plug-in once possible.

But I wouldn't recommend it for mobx

from mobx.dart.

katis avatar katis commented on May 22, 2024

I've requested that the #46 branch would be merged, it has the package structure and configs already, so should work as a starting point.

from mobx.dart.

katis avatar katis commented on May 22, 2024

I experimented with generating actions, and I think that the easiest way to support generics, optional parameters and named parameters is to skip the Action wrapper as it currently exists, and use the start-finally-stop pattern we use with the observable hook.

class User {
  @action
  void updateNames({String firstName, String lastName}) {
    if (firstName != null) this.firstName = firstName;
    if (lastName != null) this.lastName = firstName;
  }
}

Generates:

class _$User {
  final _$UserActionController = ActionController(name: 'User');

  @override
  void updateNames({String firstName, String lastName}) {
    final _$prevDerivation = _$UserActionController.startAction();
    try {
      return super.updateNames(firstName: firstName, lastName: lastName);
    } finally {
      _$UserActionController.endAction(_$prevDerivation);
    }
  }
}

from mobx.dart.

copydog avatar copydog commented on May 22, 2024

@katis I agree with your idea, we should merge the branch (@pavanpodila ).

Few my opinion

  • This is my way to deal with ObservableList
@observable
List<String> favoriteColorList = [
  'red',
  'blue',
  'yellow',
];
/// Generated
ObservableList<String> _favoriteColorList;

@override
set favoriteColorList(List<String> favoriteColorList) {
    _favoriteColorList
      ..clear()
      ..addAll(favoriteColorList);
}

List<String> get favoriteColorList => _favoriteColorList;

/// constructor code is not posted
/// initialize value and nulled user-defined value will be done in constructor
  • I prefer Observable to Atom, because using Atom MIGHT lose the intercept and value change checking ability (I am not very sure, correct me if I am wrong). And all the work that you described is already done in mobx_generator branch, I will start to migrate these code once your pull request is merged.

  • I suggest we use mustache4dart library. So we can maintain the template easily (you can find in mobx_generator branch)

Few things that need to discuss:

  1. What is the class decorator name, store or observable ? I prefer store, It's more clear for user.
  2. How to solve action conflict?

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

@katis Firstly, thanks for an educative discourse on the options you took. I have personally learnt a few interesting things around the use of factory constructors.

Actions

As regards action, I think we should forgo that name in favor of the Action() constructor (note capital A). We can then reserve the word action for the annotation.

Inject annotation

For the store annotation, I think we should adopt what MobX has done instead: inject. With inject we can supply multiple types of store injections. It could be:

  • name based: in which case we look up from a map of stores given to the Provider (we have not implemented yet. But this will be an InheritedWidget that passes down all stores)
@inject('auth')
class SomeWidget extends StatelessWidget { }
  • function based: in which case we will pass a collection of stores as input argument, which can then be used to return an object containing the stores we need.
@inject((Map<String, Object> storeMap) => storeMap('auth'))
class SomeWidget extends StatelessWidget { }

Not sure if we can overload the inject annotation. If we can't we should go for a function-based approach.

Some other thoughts

I feel we don't have to adopt all the API conventions from MobX as some may not be valid for Dart. For example, the lack of overloading and dynamic dispatch of a function with variable types of arguments. This is why we are stuck in a world of action0, action1, action2 type of placeholder functions for argument arities.

We should be prudent in picking what makes sense for Dart and stay idiomatic for the language. Curious to hear what you guys think.

from mobx.dart.

katis avatar katis commented on May 22, 2024

The @store annotation in the example code is just a marker that the code generator can use to find classes that need Mobx code generation.

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

Got it. I think we have some creative license here to improve the the Developer eXperience (DX) of using mobx.dart.

from mobx.dart.

katis avatar katis commented on May 22, 2024

@copydog Not familiar with mustache4dart, is there some advantage over using pure Dart string templates?

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

Agree with @copydog. Mustache will also allow us to iterate faster with the generated code. The role of the generator would be to just prepare the context for the mustache template, which is kind of fixed for the most part. How we use that context to generate the final code can keep changing in the template.

from mobx.dart.

katis avatar katis commented on May 22, 2024

Sure, mustache is fine by me. My code uses Dart template strings currently, but the code is ugly in a quick&dirty kind of way, so needs refactoring anyways

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Inject annotation

Is there really a need for this? Isn't an InheritedWidget enough?

from mobx.dart.

katis avatar katis commented on May 22, 2024

Yeah, I doubt it's possible in Dart in the way it works with JS. InheritedWidget or making a little hook that does the same seems enough to me.

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Also, would this make sense to offer a simplified @store that consider all variables as @observable, all getters as @computed and methods as @action?

Example:

Instead of:

@store
class Foo {
  factory Foo() = _$Foo;

  @observable
  String foo;
  @computed
  int get length => _foo.length;

  @action
  void someAction() {}
}

we have a simplified:

@store
class Foo {
  factory Foo() = _$Foo;

  String foo;
   int get length => _foo.length;

  void someAction() {}
}

This is much easier to write, which would make users less tempted to make one HUGE store instead of multiple smaller ones.

And it could be used as a replacement for the lack of mirroring to make a useObservable hook for complex objects.

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Finally (sorry for the spam), an alternative to the issue about @observable @katis explained is abstract classes:

@store
abstract class Foo {
  Foo._();
  factory Foo() = _$Foo;

  int get computed => observable

  int get observable;
  set observable(int value);

  void action() {}
}

This is now fine for the generated class to use Observable internally:

class _$Foo extends Foo {
  _$Foo(): super._();

  final _$observable = Observable<String>(null, name: 'User.firstName');

  @override
  String get observable => _$observable.value;

  @override
  set observable(String value) => _$observable.value = value;
}

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

I like the reuse of @observable at the class level to automatically mark all fields as observable, methods as actions and also consider computed properties. We can also pass arguments into @observable to control the automatic observability!

Actually the arguments to @observable reminds me of the decorate() API of MobX JS.

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

it searches for abstract classes implementing a Store interface.

What would Store interface define?


I think we still have an issue with actions:

class Foo {
  Foo._();
  factory Foo() = _$Foo;  
}

We have to use this trick just so that _$Foo can extend Foo.

This looks pretty bad. And if the user wants to pass custom values to the constructor or define final variables, things get even worse.

from mobx.dart.

katis avatar katis commented on May 22, 2024

Currently Store doesn't implement anything, but in the future it might provide an interface for listening the internal values. It would always be automatically implemented by the generator.

Maybe something like:

abstract class Store {
  void observe(String field, listener);
}

The constructor issue is I think unsolvable. built_value has the same issue, its code generation requires this boilerplate:

abstract class Person implements Built<Person, PersonBuilder> {
  Person._();
  factory Person([updates(PersonBuilder b)]) = _$Person;
}

built_value creates a builder so you can initialize it more easily, and that might be the way we have to go if we want ergonomic constructors.

Edit: I think the builder wouldn't help much, built_value needs it because it creates immutable classes.

Darts cascade notation makes leaving the constructor empty bearable:

User()
  ..firstName = 'John'
  ..lastName = 'Johnson';

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Currently Store doesn't implement anything, but in the future it might provide an interface for listening the internal values. It would always be automatically implemented by the generator.

Shouldn't this be done by an autorun/reaction instead?
I'm not sure this is a good example, especially in a typed language.

I'm asking because if we never really have a use-case for this, then the interface just adds some noise. A @store is much easier to write

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Another possibility is mixins using the shorthand syntax:

class Merged = SomeClass with SomeMixin;

We could define a mixin that would be the equivalent of your proposed Store interface:

mixin Store<T> {}

Then the user write:

class Test = _$Test with Store<_Test>;

mixin _Test {
  int foo;
  int get bar => 42;

  void action() {
    print('hello world');
  }
}

And we generate:

abstract class _$Test with _Test {
  int get foo {
    // Subscribe to the value
    return super.foo;
  }
  set foo(int value) {
    // Notify a value changed
    super.foo = value;
  }

  int get bar {
    // Subscribe to the computed value
    return super.bar;
  }
}

The Store mixin is quite useless as of now, but it's required since Dart doesn't have a typedef yet (and we can't do class Foo = Bar;).

But at the same time this covers your suggestion of a Store interface for future features.

from mobx.dart.

katis avatar katis commented on May 22, 2024

I guess the limitation with a mixin is that the user can't add a constructor at all? I wouldn't be happy with that.

Apparently spying/tracing requires more detailed events about the things happening with observables than you can get with autorun ( https://mobx.js.org/refguide/observe.html ) and the observe method only tracks the single observable, @pavanpodila might have more information. If we want to get access to the internal values, adding accessor methods to Store is probably the cleanest way.

from mobx.dart.

katis avatar katis commented on May 22, 2024

I played around with mixins and you could enable user defined constructors like this:

import 'package:mobx/mobx.dart';

class User = UserBase with Store, _$User;

class UserBase {
  final String id;

  UserBase(this.id);

  @observable
  String firstName = 'Jane';
}

mixin _$User on UserBase {
  final _$firstNameAtom = Atom();

  @override
  String get firstName {
    _$firstNameAtom.reportObserved();
    return super.firstName;
  }

  @override
  set firstName(String value) {
    super.firstName = value;
    _$firstNameAtom.reportChanged();
  }
}

But mixins can't have constructors, and final field = Computed(() => super.foo) is not valid field initialization because the lambda accesses super...

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Ah interesting, I didn't think of swapping the class/mixin.

But mixins can't have constructors, and final field = Computed(() => super.foo) is not valid field initialization because the lambda accesses super...

Would a lazy initialization works?

T _field;
T get field {
  _field ??= T();
  return _field;
}

from mobx.dart.

katis avatar katis commented on May 22, 2024

I suppose it would. I'll take a look at the mixin approach, there might be issues with codegen since super mixins are a new feature that is not fully supported on all platforms (?) dart-lang/language#12

from mobx.dart.

copydog avatar copydog commented on May 22, 2024

@rrousselGit The mixin method is working, I tried it in the mobx_generator branch. The only one downside is the mixin can NOT have a constructor, which means the original class and it's super-class can NOT have any constructor.

from mobx.dart.

copydog avatar copydog commented on May 22, 2024

I am using mobx_generator branch in my project now. If you guys have time, please take few minutes to check out these examples.

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Actually with this usage of mixins, we don't need the class Foo = Bar with Mixin, do we?

We can just write

class Bar implements Store with _$Bar {

}

from mobx.dart.

katis avatar katis commented on May 22, 2024

Actually with this usage of mixins, we don't need the class Foo = Bar with Mixin, do we?

Unfortunately we need super access in the mixin body, so the mixin<->class relationship would be circular, which is forbidden by the compiler.

This raises an interesting question, is this syntax compatible with json_serializable/built_value?

Serialization support would indeed be great, needs testing

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

For serialization we have to ensure the computed properties are not included. Not sure if it can be done automatically.

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

For serialization we have to ensure the computed properties are not included. Not sure if it can be done automatically.

Automatically, I don't think so. But at the very least json_serializable allows ignoring a field:

@JsonKey(ignore: true)
String get platformValue => platform?.description;

I haven't checked built_value, but its supports is harder anyway since its model is immutable.

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

If our builder can check that there is a dependency on json_serializable, can we automatically add the @JsonKey(ignore: true) on the computed ?

from mobx.dart.

katis avatar katis commented on May 22, 2024

#51 is now up for review
Edit: now merged

from mobx.dart.

amrnt avatar amrnt commented on May 22, 2024

Since you have just mentioned built_value, may I know how mobx will play with immutable objects?

from mobx.dart.

katis avatar katis commented on May 22, 2024

I guess you could make them free functions instead? :/

from mobx.dart.

amrnt avatar amrnt commented on May 22, 2024

Fair enough. I'm still trying to understand your comment regarding this

Unfortunately we need super access in the mixin body, so the mixin<->class relationship would be circular, which is forbidden by the compiler.

Why do we really need a this circular relationship? Wouldn't be better if its implemented in another way so we can skip this "forbidden" relationship? I'm just loudthinking here 😄

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Why do we need to decorate it this way:

@JsonSerializable()
class User = UserBase with _$User;

?

What about this instead:

class User = UserBase with _$User;

@JsonSerializable()
class UserBase {

}

from mobx.dart.

katis avatar katis commented on May 22, 2024

Because then the serialization creates a UserBase instead of User (and UserBase has to be abstract, so it can't be instantiated).

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Because then the serialization creates a UserBase instead of User

Interesting, because the IDE doesn't see any error until we run the code.

It seems doable though:

void main() {
  final f = User.fromJSON();
  
  print(f.foo);
}

class User = UserBase with Foo;

mixin Foo on UserBase {}

class UserBase {
  final String foo;
  UserBase(this.foo);
  
  UserBase.fromJSON(): this('foo');
}

We should probably talk about this use-case on https://github.com/dart-lang/language
There is currently an ongoing debate on extensions, partial classes, and anything similar. These would heavily simplify our issue.

from mobx.dart.

katis avatar katis commented on May 22, 2024

Maybe I should make a small tweak for the declaration syntax:

class User = UserBase implements Store with _$User;

class UserBase { }

or go back to the annotation

@observable
class User = UserBase with _$User;

class UserBase {}

This way the base class doesn't need to be abstract or contain anything related to Mobx (besides annotations) and it would conceptually be similar to JS:

const user = observable(userBase)

const userBase = {}

We also need a convention for the Base class, for documentation and such. Should it be FooBase, _Foo, PlainFoo or something else?

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

@katis the approach you suggested is a bit cleaner now. How about the names like below?

@observable
class User = _User with _UserMixin;

class _User {}

from mobx.dart.

katis avatar katis commented on May 22, 2024

Tried to implement the discussed syntax, but there's a bug in Dart that prevents it from working: dart-lang/sdk#35011

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

I was going through a bunch of other packages that do code generation and really like the approach @rrousselGit took for functional_widget.

How functional_widget does it

Essentially he introduces a capitalized Class identifier for a lowercase-named function identifier.

Thus...

@widget
Widget foo(BuildContext context, int value) {
  return Text('$value');
}

generates...

class Foo extends StatelessWidget {
  final int value;

  const Foo(this.value, {Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return foo(context, value);
  }
}

and you use it as...

runApp(
    Foo(42)
);

Proposal for a Store implemented class

What if we take the implementation that @katis did and mix some of the ideas from @rrousselGit 's functional_widget?

As a user, you write an abstract class [XXX]Base implementing the Store interface. For example...

abstract class CounterBase implements Store {
  @observable 
  int value = 0;
  
  @action
  void increment() {
    value++;
  }
}

and the generator generates...

class Counter extends CounterBase {
  // Rest of the implementation as is
}

So, we introduce the class identifier Counter automatically by virtue of including the part file. Essentially CounterBaseCounter. I think this will eliminate that one line of boilerplate we have to write currently where we create a class with the mixin.

class Counter = CounterBase with _$Counter;

Do you guys see any potential issues with this approach??

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

Just passing there:

While this works fine, this has other issues that I'm working on:
Refactoring

Renaming CounterBase should rename Counter too. And existing tools to not offer such refactoring yet.

Once we have a support for that in analyzer-plugin then it's fine. But in the mean time, this will cause issues.

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

Good point! Refactoring will be a pain point. But if you rename CounterBase, the build_runner (running in the background, hopefully) will generate the new class identifier too!

Won't that take care of the problem?

[Update]: I see the problem now. The code that was using Counter won't be updated automatically 😞. Hence we need to explicitly create the class identifier as we have now. Renaming that will automatically rename all usages via the IDE.

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

On a separate note, circling back to what @katis asked earlier, can we adopt the convention like so:

class Counter = _CounterBase with _CounterMixin;

abstract class _CounterBase implements Store {}
  • Use _XXXBase as the name of the abstract class that implements Store. Since it would never be required outside, we can safely make it private.
  • Use _XXXMixin as the name of the mixin. This is similar to other mixins in the SDK like ListMixin, MapMixin, etc. The _$XXX convention felt a bit cryptic (even perl-like 😄).

from mobx.dart.

katis avatar katis commented on May 22, 2024

The _$Foo convention is used by other code generation libraries for names that are created by the generator, less chance of name conflicts with user code

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

Got it. Let's keep it then.

from mobx.dart.

katis avatar katis commented on May 22, 2024

One of the benefits of having the user define the class that implements the mixin is that they can add more code to the wrapped class, I wanted to use the InheritedWidget Store.of(context) pattern, so with the current code generation I could do this:

class MyStore extends _MyStore with _$MyStore {
  static MyStore of(BuildContext context) {
    final InjectMyStore widget =
        context.inheritFromWidgetOfExactType(InjectMyStore);
    return widget.myStore;
  }
}

abstract class _MyStore implements Store {
  ...
}

from mobx.dart.

rrousselGit avatar rrousselGit commented on May 22, 2024

How does renaming fields work in that case BTW?

Since the generator users will use the wrapper class instead of the private base model, it may be possible that renaming fields breaks.

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

The generated fields and methods retain the same name via @overrides, so we are not introducing any new identifiers into the code.
Refactoring should work fine. I've tried that already :-) and the IDE takes care of updating the *.g.dart files automatically. In fact, you don't even have to run the build_runner!

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

One of the benefits of having the user define the class that implements the mixin is that they can add more code to the wrapped class, I wanted to use the InheritedWidget Store.of(context) pattern, so with the current code generation I could do this:

class MyStore extends _MyStore with _$MyStore {
  static MyStore of(BuildContext context) {
    final InjectMyStore widget =
        context.inheritFromWidgetOfExactType(InjectMyStore);
    return widget.myStore;
  }
}

abstract class _MyStore implements Store {
  ...
}

If we inject this method into the generated code, you would add a dependency on Flutter. I think there are cases where you will want to keep the Stores as pure data models. Perhaps, have a second interface (InheritedStore or something), which is a hint to the generator to generate the static of() method?

from mobx.dart.

katis avatar katis commented on May 22, 2024

of is just user written code, nothing to do with codegen

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

Ah...now I see it. Thanks for clarifying

from mobx.dart.

pavanpodila avatar pavanpodila commented on May 22, 2024

Closing this as the generators are in place and working

from mobx.dart.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.