Repository: mobxjs/mobx.dart

Issue:
Support late observables
Currently, it appears not to be possible to declare a store observable that uses the `late` keyword. An attempt to set the observable value for the first time causes mobx to read it first throwing `LateInitializationError`.

```Dart
class CounterStore extends _CounterStore with _$CounterStore {}

abstract class _CounterStore with Store {
  @observable
  late int counter;
}

```
The above will generate:
```Dart
mixin _$CounterStore on _CounterStore, Store {
  late final _$counterAtom =
      Atom(name: '_CounterStore.counter', context: context);

  @override
  int get counter {
    _$counterAtom.reportRead();
    return super.counter;
  }

  @override
  set counter(int value) {
    _$counterAtom.reportWrite(value, super.counter, () { // <- reads value before the initialization has had a chance to happen
      super.counter = value;
    });
  }

  @override
  String toString() {
    return '''
counter: ${counter}
    ''';
  }
}
```

Consider generating code in a way, that reads the current value only if the observable is not late or it's already been initialized.



Fix patch:
diff --git a/mobx_codegen/CHANGELOG.md b/mobx_codegen/CHANGELOG.md
index e9ed8735..c2641986 100644
--- a/mobx_codegen/CHANGELOG.md
+++ b/mobx_codegen/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.5.0
+
+- Support `late` observables by [@amondnet](https://github.com/amondnet). fix [#919](https://github.com/mobxjs/mobx.dart/issues/919)
+
 ## 2.4.1
 
 - Adds `useDeepEquality` for creating observables by [@amondnet](https://github.com/amondnet)
diff --git a/mobx_codegen/lib/src/store_class_visitor.dart b/mobx_codegen/lib/src/store_class_visitor.dart
index 17564bf7..83abf574 100644
--- a/mobx_codegen/lib/src/store_class_visitor.dart
+++ b/mobx_codegen/lib/src/store_class_visitor.dart
@@ -102,6 +102,7 @@ class StoreClassVisitor extends SimpleElementVisitor {
       name: element.name,
       isPrivate: element.isPrivate,
       isReadOnly: _isObservableReadOnly(element),
+      isLate: element.isLate,
       equals: _getEquals(element),
     );
 
diff --git a/mobx_codegen/lib/src/template/observable.dart b/mobx_codegen/lib/src/template/observable.dart
index c536c69a..68e8efd8 100644
--- a/mobx_codegen/lib/src/template/observable.dart
+++ b/mobx_codegen/lib/src/template/observable.dart
@@ -11,6 +11,7 @@ class ObservableTemplate {
     required this.name,
     this.isReadOnly = false,
     this.isPrivate = false,
+    this.isLate = false,
     this.equals,
     this.useDeepEquality,
   });
@@ -21,6 +22,7 @@ class ObservableTemplate {
   final String name;
   final bool isPrivate;
   final bool isReadOnly;
+  final bool isLate;
   final ExecutableElement? equals;
   final bool? useDeepEquality;
 
@@ -53,16 +55,34 @@ class ObservableTemplate {
   }''';
   }
 
+  String _buildSetters() {
+    if (isLate) {
+      return '''
+  bool _${name}IsInitialized = false;
+      
   @override
-  String toString() => """
-  late final $atomName = Atom(name: '${storeTemplate.parentTypeName}.$name', context: context);
-
-${_buildGetters()}
+  set $name($type value) {
+    $atomName.reportWrite(value, _${name}IsInitialized ? super.$name : null, () {
+      super.$name = value;
+      _${name}IsInitialized = true;
+    }${equals != null ? ', equals: ${equals!.name}' : ''});
+  }''';
+    }
 
+    return '''
   @override
   set $name($type value) {
     $atomName.reportWrite(value, super.$name, () {
       super.$name = value;
     }${equals != null ? ', equals: ${equals!.name}' : ''}${useDeepEquality != null ? ', useDeepEquality: $useDeepEquality' : ''});
-  }""";
+  }''';
+  }
+
+  @override
+  String toString() => """
+  late final $atomName = Atom(name: '${storeTemplate.parentTypeName}.$name', context: context);
+
+${_buildGetters()}
+
+${_buildSetters()}""";
 }
diff --git a/mobx_codegen/lib/version.dart b/mobx_codegen/lib/version.dart
index d2bed9e4..bc245f3f 100644
--- a/mobx_codegen/lib/version.dart
+++ b/mobx_codegen/lib/version.dart
@@ -1,4 +1,4 @@
 // Generated via set_version.dart. !!!DO NOT MODIFY BY HAND!!!
 
 /// The current version as per `pubspec.yaml`.
-const version = '2.4.1';
+const version = '2.5.0';
diff --git a/mobx_codegen/pubspec.yaml b/mobx_codegen/pubspec.yaml
index b14e3cd8..91509440 100644
--- a/mobx_codegen/pubspec.yaml
+++ b/mobx_codegen/pubspec.yaml
@@ -1,6 +1,6 @@
 name: mobx_codegen
 description: Code generator for MobX that adds support for annotating your code with @observable, @computed, @action and also creating Store classes.
-version: 2.4.1
+version: 2.5.0
 
 homepage: https://github.com/mobxjs/mobx.dart
 issue_tracker: https://github.com/mobxjs/mobx.dart/issues


Test patch:
diff --git a/mobx_codegen/test/data/valid_late_variables_output.dart b/mobx_codegen/test/data/valid_late_variables_output.dart
index 3fdbde0b..9e280d3c 100644
--- a/mobx_codegen/test/data/valid_late_variables_output.dart
+++ b/mobx_codegen/test/data/valid_late_variables_output.dart
@@ -8,10 +8,14 @@ mixin _$TestStore on _TestStore, Store {
     return super.username;
   }
 
+  bool _usernameIsInitialized = false;
+
   @override
   set username(String value) {
-    _$usernameAtom.reportWrite(value, super.username, () {
+    _$usernameAtom
+        .reportWrite(value, _usernameIsInitialized ? super.username : null, () {
       super.username = value;
+      _usernameIsInitialized = true;
     });
   }
 }
diff --git a/mobx_codegen/test/generator_usage_test.dart b/mobx_codegen/test/generator_usage_test.dart
index 306b6be5..3a6bb2dd 100644
--- a/mobx_codegen/test/generator_usage_test.dart
+++ b/mobx_codegen/test/generator_usage_test.dart
@@ -124,6 +124,9 @@ abstract class _TestStore with Store {
     // ignore: only_throw_errors
     throw 'TEST ERROR';
   }
+
+  @observable
+  late String lateField;
 }
 
 void main() {
@@ -279,4 +282,16 @@ void main() {
 
     expect(values, equals(['', 'field1', 'field2']));
   });
+
+  test('setting late fields with action works', () {
+    final store = TestStore('field1', field2: 'field2');
+
+    final fields = <String>[];
+    autorun((_) {
+      fields.add(store.lateField);
+    });
+    store.lateField = 'field';
+
+    expect(fields, equals(['field']));
+  });
 }
diff --git a/mobx_codegen/test/generator_usage_test.g.dart b/mobx_codegen/test/generator_usage_test.g.dart
index 09d94af6..dff31d23 100644
--- a/mobx_codegen/test/generator_usage_test.g.dart
+++ b/mobx_codegen/test/generator_usage_test.g.dart
@@ -178,6 +178,26 @@ mixin _$TestStore on _TestStore, Store {
     });
   }
 
+  late final _$lateFieldAtom =
+      Atom(name: '_TestStore.lateField', context: context);
+
+  @override
+  String get lateField {
+    _$lateFieldAtom.reportRead();
+    return super.lateField;
+  }
+
+  bool _lateFieldIsInitialized = false;
+
+  @override
+  set lateField(String value) {
+    _$lateFieldAtom.reportWrite(
+        value, _lateFieldIsInitialized ? super.lateField : null, () {
+      super.lateField = value;
+      _lateFieldIsInitialized = true;
+    });
+  }
+
   @override
   ObservableFuture<String> future() {
     final _$future = super.future();
@@ -262,6 +282,7 @@ batchItem2: ${batchItem2},
 batchItem3: ${batchItem3},
 batchItem4: ${batchItem4},
 errorField: ${errorField},
+lateField: ${lateField},
 fields: ${fields},
 batchedItems: ${batchedItems}
     ''';
diff --git a/mobx_codegen/test/nested_store.g.dart b/mobx_codegen/test/nested_store.g.dart
index 6599ebde..79d54e3e 100644
--- a/mobx_codegen/test/nested_store.g.dart
+++ b/mobx_codegen/test/nested_store.g.dart
@@ -17,10 +17,13 @@ mixin _$NestedStore on _NestedStore, Store {
     return super.name;
   }
 
+  bool _nameIsInitialized = false;
+
   @override
   set name(String value) {
-    _$nameAtom.reportWrite(value, super.name, () {
+    _$nameAtom.reportWrite(value, _nameIsInitialized ? super.name : null, () {
       super.name = value;
+      _nameIsInitialized = true;
     });
   }
 
diff --git a/mobx_codegen/test/store_with_custom_context.g.dart b/mobx_codegen/test/store_with_custom_context.g.dart
index aaea811b..17666201 100644
--- a/mobx_codegen/test/store_with_custom_context.g.dart
+++ b/mobx_codegen/test/store_with_custom_context.g.dart
@@ -18,10 +18,13 @@ mixin _$CustomContextStore on _CustomContextStore, Store {
     return super.name;
   }
 
+  bool _nameIsInitialized = false;
+
   @override
   set name(String value) {
-    _$nameAtom.reportWrite(value, super.name, () {
+    _$nameAtom.reportWrite(value, _nameIsInitialized ? super.name : null, () {
       super.name = value;
+      _nameIsInitialized = true;
     });
   }