adding packages

This commit is contained in:
2026-01-15 14:38:46 -08:00
parent ef86e3ab6a
commit 6869cf47e5
5253 changed files with 726695 additions and 34 deletions
@@ -0,0 +1,139 @@
## 1.0.4
- Making Safer
- Adding Null Check
## 1.0.2
- Added automatic generation of `toString` method with `@StoreConfig` annotation
Thanks to [@hawkbee1](https://github.com/hawkbee1)
## 1.0.0 - 1.0.1
- Ready for prime time!
- Fixing version resolution
## 0.4.2
- Upgraded the `build_resolvers` dependency to 1.3.2, which fixes issues with certain
versions of Dart being unable to resolve `dart:ui` types.
## 0.4.1+2
- Going back to original `test_coverage` package
## 0.4.1+1
- README updates
- Switching to [Github Actions](https://github.com/mobxjs/mobx.dart/actions) for all builds and publishing
## 0.4.1
There were a number of bugs with the previous implementation of the `LibraryScopedNameFinder`. This resolves them, as well as ensures that a single code path is followed whether or not the analyzed source code contains named imports, reducing the potential for future bugs.
The following bugs have been corrected when using named imports:
- Missing type arguments on classes
- Missing type arguments on function typedefs
- Missing prefixes from imported typedefs
- Missing prefixes from implicit type argument bounds
## 0.4.0 - 0.4.0+1
- Upgraded our `analyzer` dependency's minimum version to **0.38.5** in order to
workaround a bug where collection types would resolve to dynamic
- Updated `pubspec.yaml` to not include the reference to the `@store` annotation. It has been removed.
## 0.3.13
- Fixes the extraction of generic return-types which have nested generic type arguments, eg: `Future<List<User>>`
- Also fixes the issue reported in #367
## 0.3.12 - 0.3.12+1
- Removed the experimental use of `@store` annotation. It fails for some cases and has now been removed. We will explore
other use cases with this annotation in future PRs.
- Package updates
- Added the Flutter Favorite logo
## 0.3.10+1 - 0.3.11
- Package updates
- Upgraded dependency version for the `analyzer` package
## 0.3.9+1 - 0.3.10
- Alters the analyzer dependency to support a range — from the previously supported version
(0.36.3), up to latest (0.39.0).
- Adds support for library prefixes in all situations (`import 'package:foo' as foo`),
so type names are prefixed in generated part files.
## 0.3.8 - 0.3.9+1
- Fixes a minor issue where types in generated code would appear as dynamic when they shouldn't.
- Added a version constant that matches the `pubspec.yaml`
## 0.3.7
- This is mostly about providing better error reporting on classes that don't meet the necessary constraints.
- A class using the SettingsStore mixin, must be marked abstract. This will be reported if not the case.
- A class using the @store annotation, must be marked private. This will be reported if not the case.
- Bit of refactoring to separate things out a bit.
## 0.3.6
- Fixes the type resolution bug that prevented using types from packages like `dart:ui`
- Fixes the type resolution of other public `Store` classes referenced in the `@store` based generation
Thanks to [@shyndman](https://github.com/shyndman) for the tremendous work on this release.
## 0.3.5
- Added the ability to create `Store` classes using the `@store` annotation. It can be added to a private class, which will result in a public generated class.
## 0.3.4
- Refactored some tests that rely on `source-text` to not be based on hard-coded strings. These have been moved to a separate file for easier maintenance. The outputs resulting from the generator are also in a separate file. This allows scaling to more variations of `source-text` in the future.
- Added checks to ensure `@observable` and `@computed` are used for the correct members of the class. These are reported as errors during the codegen process.
- Upgraded `test_coverage`
- Fixed a bunch of analyzer errors
## 0.3.1 - 0.3.3+1
- Adding a conditional action-wrapper for field setters.
- Increasing test coverage
- Adapting to the API change in `mobx 0.3.3`
- Formatting changes
## 0.3.0 - 0.3.0+1
- Adapting to the API changes in `mobx 0.3.0`
- README.md updates
## 0.2.1+2
- Removing the code in `/example` folder and instead having a simple `README.md` in it.
## 0.2.1+1
- README updates
## 0.2.1
- Upgrading to use the `0.2.1` version of `mobx`, which makes it compatible with the latest `beta`/`dev`/`master` channels
## 0.2.0
- A breaking change has been introduced to the use of the `Store` type. Previously it was meant to be used as an _interface_, which has now changed to a **mixin**.
## 0.0.2 - 0.1.3
- Move all the codegen parts to separate templates
- Documentation updates
- Support for async actions
- CircleCI integration improvements
## 0.0.1 - First Release
- Added support for `@observable`, `@computed` and `@action`
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 MobX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,44 @@
[![pub package](https://img.shields.io/pub/v/settings_manager.svg?label=settings_manager&color=blue)](https://pub.dartlang.org/packages/settings_manager)
[![pub package](https://img.shields.io/pub/v/settings_gen.svg?label=settings_gen&color=blue)](https://pub.dartlang.org/packages/settings_gen)
# settings_gen
Code generator for `settings_manager` built for use with SharedPreferences. This will add Streams and ValueNotifiers for each field.
```
$> cd $YOUR_PROJECT_DIR
$> flutter packages pub run build_runner build
```
### Example
```dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:settings_manager/settings_manager.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'settings.g.dart';
class Settings = SettingsBase with _$Settings;
abstract class SettingsBase with SettingsStore {
@BoolSetting(defaultValue: false)
bool darkMode;
@StringSetting(defaultValue: 'none')
String userId;
@IntSetting(defaultValue: 0)
int counterValue;
@DoubleSetting(defaultValue: 0)
double radialValue;
@StringListSetting(defaultValue: [])
List<String> savedItems;
}
````
@@ -0,0 +1,5 @@
include: ../analysis_options.yaml
analyzer:
exclude:
- test/data/**.dart
@@ -0,0 +1,15 @@
targets:
$default:
builders:
settings_gen:
enabled: true
builders:
settings_gen:
target: ':settings_gen'
import: 'package:settings_gen/builder.dart'
builder_factories: ['storeGenerator']
build_extensions: { '.dart': ['.store.g.part'] }
auto_apply: dependents
build_to: cache
applies_builders: ['source_gen|combining_builder']
@@ -0,0 +1,6 @@
import 'package:build/build.dart';
import 'package:settings_gen/settings_gen.dart';
import 'package:source_gen/source_gen.dart';
Builder storeGenerator(BuilderOptions options) =>
SharedPartBuilder([StoreGenerator()], 'store_generator');
@@ -0,0 +1,5 @@
library settings_gen;
export 'src/settings_gen_base.dart';
const version = '1.0.1';
@@ -0,0 +1,204 @@
abstract class CodegenError {
bool get hasErrors;
String get message;
}
class StoreClassCodegenErrors implements CodegenError {
StoreClassCodegenErrors(this.name) {
_errorCategories = [
nonAbstractStoreMixinDeclarations,
invalidComputedAnnotations,
invalidObservableAnnotations,
invalidActionAnnotations,
staticObservables,
finalObservables,
];
}
final String name;
final NonAbstractStoreMixinDeclarations nonAbstractStoreMixinDeclarations =
NonAbstractStoreMixinDeclarations();
final PropertyErrors finalObservables = FinalObservableFields();
final PropertyErrors staticObservables = StaticObservableFields();
final PropertyErrors invalidObservableAnnotations =
InvalidObservableAnnotations();
final PropertyErrors invalidComputedAnnotations =
InvalidComputedAnnotations();
final PropertyErrors invalidActionAnnotations = InvalidActionAnnotations();
List<CodegenError> _errorCategories;
@override
String get message {
final errors = _errorCategories
.where((category) => category.hasErrors)
.toList(growable: false)
.asMap()
.map((i, category) => MapEntry(i, ' ${i + 1}. ${category.message}'))
.values
.join('\n');
return 'Could not make class "$name" observable. Changes needed:\n$errors';
}
@override
bool get hasErrors => _errorCategories.any((category) => category.hasErrors);
}
final _fieldPluralizer = Pluralize('the field', 'fields');
final _methodPluralizer = Pluralize('the method', 'methods');
final _memberPluralizer = Pluralize('the member', 'members');
abstract class _InvalidStoreDeclarations implements CodegenError {
final NameList _classNames = NameList();
// ignore: avoid_positional_boolean_parameters
bool addIf(bool condition, String className) {
if (condition) {
_classNames.add(className);
}
return condition;
}
@override
bool get hasErrors => _classNames.isNotEmpty;
}
class NonAbstractStoreMixinDeclarations extends _InvalidStoreDeclarations {
@override
String get message =>
'Classes that use the SettingsStore mixin must be marked abstract. Affected classes: $_classNames.';
}
abstract class PropertyErrors implements CodegenError {
final NameList _properties = NameList();
// ignore: avoid_positional_boolean_parameters
bool addIf(bool condition, String propertyName) {
if (condition) {
_properties.add(propertyName);
}
return condition;
}
String get propertyList => _properties.toString();
Pluralize propertyPlural = _fieldPluralizer;
String get property => propertyPlural(_properties.length);
@override
bool get hasErrors => _properties.isNotEmpty;
}
class FinalObservableFields extends PropertyErrors {
@override
String get message => 'Remove final modifier from $property $propertyList.';
}
class StaticObservableFields extends PropertyErrors {
@override
String get message => 'Remove static modifier from $property $propertyList.';
}
class AsyncGeneratorActionMethods extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _methodPluralizer;
@override
String get message =>
'Replace async* modifier with async from $property $propertyList.';
}
class NonAsyncMethods extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _methodPluralizer;
@override
String get message =>
'Return a Future or a Stream from $property $propertyList.';
}
class InvalidComputedAnnotations extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _memberPluralizer;
@override
String get message =>
'Remove @computed annotation for $property $propertyList. They only apply to property-getters.';
}
class InvalidObservableAnnotations extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _memberPluralizer;
@override
String get message =>
'Remove @observable annotation for $property $propertyList. They only apply to fields.';
}
class InvalidActionAnnotations extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _memberPluralizer;
@override
String get message =>
'Remove @action annotation for $property $propertyList. They only apply to methods.';
}
class InvalidStaticMethods extends PropertyErrors {
@override
// ignore: overridden_fields
Pluralize propertyPlural = _methodPluralizer;
@override
String get message => 'Remove static modifier from $property $propertyList.';
}
class NameList {
final List<String> _names = [];
void add(String name) => _names.add(name);
int get length => _names.length;
bool get isNotEmpty => _names.isNotEmpty;
@override
String toString() {
if (_names.length == 1) {
return '"${_names[0]}"';
}
final buf = StringBuffer();
for (var i = 0; i < _names.length; i++) {
final name = _names[i];
buf.write('"$name"');
if (i < _names.length - 2) {
buf.write(', ');
} else if (i == _names.length - 2) {
buf.write(' and ');
}
}
return buf.toString();
}
}
class Pluralize {
Pluralize(this._single, this._multiple);
final String _single;
final String _multiple;
String call(int count) => count == 1 ? _single : _multiple;
}
@@ -0,0 +1,71 @@
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type_system.dart';
import 'package:build/build.dart';
import 'package:settings_gen/src/store_class_visitor.dart';
import 'package:settings_gen/src/template/store_file.dart';
import 'package:settings_gen/src/template/store.dart';
import 'package:settings_gen/src/type_names.dart';
import 'package:source_gen/source_gen.dart';
class StoreGenerator extends Generator {
@override
FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
if (library.allElements.isEmpty) {
return '';
}
final typeSystem = await library.allElements.first.session.typeSystem;
final file = StoreFileTemplate()
..storeSources = _generateCodeForLibrary(library, typeSystem).toSet();
return file.toString();
}
Iterable<String> _generateCodeForLibrary(
LibraryReader library,
TypeSystem typeSystem,
) sync* {
for (final classElement in library.classes) {
if (isMixinStoreClass(classElement)) {
yield* _generateCodeForMixinStore(library, classElement, typeSystem);
}
}
}
Iterable<String> _generateCodeForMixinStore(
LibraryReader library,
ClassElement baseClass,
TypeSystem typeSystem,
) sync* {
final typeNameFinder = LibraryScopedNameFinder(library.element);
final otherClasses = library.classes.where((c) => c != baseClass);
final mixedClass = otherClasses.firstWhere((c) {
if (baseClass.typeParameters.length != c.supertype.typeArguments.length) {
return false;
}
return typeSystem.isSubtypeOf(
c.type, baseClass.type.instantiate(c.supertype.typeArguments));
}, orElse: () => null);
if (mixedClass != null) {
yield _generateCodeFromTemplate(
mixedClass.name, baseClass, MixinStoreTemplate(), typeNameFinder);
}
}
String _generateCodeFromTemplate(
String publicTypeName,
ClassElement userStoreClass,
StoreTemplate template,
LibraryScopedNameFinder typeNameFinder,
) {
final visitor = StoreClassVisitor(
publicTypeName, userStoreClass, template, typeNameFinder);
userStoreClass
..accept(visitor)
..visitChildren(visitor);
return visitor.source;
}
}
@@ -0,0 +1,166 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:build/build.dart';
import 'package:settings_gen/src/template/double_setting.dart';
import 'package:settings_gen/src/template/int_setting.dart';
import 'package:settings_gen/src/template/string_list_setting.dart';
import 'package:settings_manager/settings_manager.dart';
import 'package:settings_manager/src/api/annotations.dart'
show BoolSetting, SettingsConfig;
import 'package:source_gen/source_gen.dart';
import 'errors.dart';
import 'template/bool_setting.dart';
import 'template/store.dart';
import 'template/string_setting.dart';
import 'template/util.dart';
import 'type_names.dart';
class StoreClassVisitor extends SimpleElementVisitor {
StoreClassVisitor(
String publicTypeName,
ClassElement userClass,
StoreTemplate template,
this.typeNameFinder,
) : _errors = StoreClassCodegenErrors(publicTypeName) {
_storeTemplate = template
..typeParams.templates.addAll(userClass.typeParameters
.map((type) => typeParamTemplate(type, typeNameFinder)))
..typeArgs.templates.addAll(userClass.typeParameters.map((t) => t.name))
..parentTypeName = userClass.name
..publicTypeName = publicTypeName;
}
final _boolSettingChecker = const TypeChecker.fromRuntime(BoolSetting);
final _stringSettingChecker = const TypeChecker.fromRuntime(StringSetting);
final _intSettingChecker = const TypeChecker.fromRuntime(IntSetting);
final _doubleSettingChecker = const TypeChecker.fromRuntime(DoubleSetting);
final _stringListSettingChecker =
const TypeChecker.fromRuntime(StringListSetting);
StoreTemplate _storeTemplate;
LibraryScopedNameFinder typeNameFinder;
final StoreClassCodegenErrors _errors;
String get source {
if (_errors.hasErrors) {
log.severe(_errors.message);
return '';
}
return _storeTemplate.toString();
}
@override
void visitClassElement(ClassElement element) {
if (isMixinStoreClass(element)) {
_errors.nonAbstractStoreMixinDeclarations
.addIf(!element.isAbstract, element.name);
}
// if the class is annotated to generate toString() method we add the information to the _storeTemplate
_storeTemplate.generateToString = hasGeneratedToString(element);
}
@override
void visitFieldElement(FieldElement element) {
if (_fieldIsNotValid(element)) {
return;
}
if (_boolSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _boolSettingChecker.firstAnnotationOfExact(element);
final template = BoolSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toBoolValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.boolSettings.add(template);
}
if (_stringSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _stringSettingChecker.firstAnnotationOfExact(element);
final template = StringSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toStringValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.stringSettings.add(template);
}
if (_intSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _intSettingChecker.firstAnnotationOfExact(element);
final template = IntSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toIntValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.intSettings.add(template);
}
if (_doubleSettingChecker.hasAnnotationOfExact(element)) {
final annotation = _doubleSettingChecker.firstAnnotationOfExact(element);
final template = DoubleSettingTemplate()
..defaultValue = annotation.getField('defaultValue').toDoubleValue()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.doubleSettings.add(template);
}
if (_stringListSettingChecker.hasAnnotationOfExact(element)) {
final annotation =
_stringListSettingChecker.firstAnnotationOfExact(element);
final template = StringListSettingTemplate()
..defaultValue = annotation
.getField('defaultValue')
.toListValue()
.map((item) => item.toStringValue())
.toList()
..addStream = annotation.getField('addStream').toBoolValue()
..addValueNotifier =
annotation.getField('addValueNotifier').toBoolValue()
..isPrivate = element.isPrivate
..name = element.name;
_storeTemplate.stringListSettings.add(template);
}
return;
}
bool _fieldIsNotValid(FieldElement element) => _any([
_errors.staticObservables.addIf(element.isStatic, element.name),
_errors.finalObservables.addIf(element.isFinal, element.name)
]);
}
const _storeMixinChecker = TypeChecker.fromRuntime(SettingsStore);
const _toStringAnnotationChecker = TypeChecker.fromRuntime(SettingsConfig);
bool isMixinStoreClass(ClassElement classElement) =>
classElement.mixins.any(_storeMixinChecker.isExactlyType);
// Checks if the class as a toString annotation
bool isStoreConfigAnnotatedStoreClass(ClassElement classElement) =>
_toStringAnnotationChecker.hasAnnotationOfExact(classElement);
bool hasGeneratedToString(ClassElement classElement) {
if (isStoreConfigAnnotatedStoreClass(classElement)) {
final annotation =
_toStringAnnotationChecker.firstAnnotationOfExact(classElement);
return annotation.getField('hasToString').toBoolValue();
}
return true;
}
bool _any(List<bool> list) => list.any(_identity);
T _identity<T>(T value) => value;
@@ -0,0 +1,77 @@
import 'setting_impl.dart';
class BoolSettingTemplate implements SettingsImpl {
bool defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return '${name}Notify($defaultValue);';
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<bool>.broadcast();');
sb.writeln(
'Stream<bool> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<bool>($defaultValue);');
sb.writeln('ValueListenable<bool> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
bool get $name {
return prefs?.getBool('$name') ?? $defaultValue;
}
@override
set $name(bool value) {
${name}Async(value);
}
Future<bool> ${name}Async(bool value) async {
final success = await prefs.setBool('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(bool value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,25 @@
import 'package:settings_gen/src/template/util.dart';
class CommaList<T> {
CommaList(this.templates) : assert(templates != null);
final List<T> templates;
@override
String toString() =>
templates.map((t) => t.toString()).where((s) => s.isNotEmpty).join(', ');
}
class SurroundedCommaList<T> {
SurroundedCommaList(this.prefix, this.suffix, this.templates)
: assert(prefix != null),
assert(suffix != null),
assert(templates != null);
final String prefix;
final String suffix;
final List<T> templates;
@override
String toString() => surroundNonEmpty(prefix, suffix, CommaList(templates));
}
@@ -0,0 +1,78 @@
import 'setting_impl.dart';
class DoubleSettingTemplate implements SettingsImpl {
double defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return '${name}Notify($defaultValue);';
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<double>.broadcast();');
sb.writeln(
'Stream<double> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<double>($defaultValue);');
sb.writeln(
'ValueListenable<double> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
double get $name {
return prefs?.getDouble('$name') ?? $defaultValue;
}
@override
set $name(double value) {
${name}Async(value);
}
Future<bool> ${name}Async(double value) async {
final success = await prefs.setDouble('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(double value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,76 @@
import 'setting_impl.dart';
class IntSettingTemplate implements SettingsImpl {
int defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return '${name}Notify($defaultValue);';
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<int>.broadcast();');
sb.writeln('Stream<int> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<int>($defaultValue);');
sb.writeln('ValueListenable<int> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
int get $name {
return prefs?.getInt('$name') ?? $defaultValue;
}
@override
set $name(int value) {
${name}Async(value);
}
Future<bool> ${name}Async(int value) async {
final success = await prefs.setInt('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(int value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,34 @@
class ParamTemplate {
String name;
String type;
String defaultValue;
bool hasRequiredAnnotation = false;
String get asArgument => name;
NamedArgTemplate get asNamedArgument => NamedArgTemplate()..name = name;
String get metadata => hasRequiredAnnotation ? '@required ' : '';
@override
String toString() => defaultValue == null
? '$metadata$type $name'
: '$type $name = $defaultValue';
}
class TypeParamTemplate {
String name;
String bound;
String get asArgument => name;
@override
String toString() => bound == null ? name : '$name extends $bound';
}
class NamedArgTemplate {
String name;
@override
String toString() => '$name: $name';
}
@@ -0,0 +1,12 @@
class Rows<T> {
final List<T> _templates = [];
void add(T template) => _templates.add(template);
bool get isEmpty => _templates.isEmpty;
List get templates => _templates;
@override
String toString() =>
_templates.map((t) => t.toString()).where((s) => s.isNotEmpty).join('\n');
}
@@ -0,0 +1,5 @@
abstract class SettingsImpl {
String preInit();
String postInit();
String dispose();
}
@@ -0,0 +1,140 @@
import 'bool_setting.dart';
import 'comma_list.dart';
import 'double_setting.dart';
import 'int_setting.dart';
import 'params.dart';
import 'rows.dart';
import 'setting_impl.dart';
import 'string_list_setting.dart';
import 'string_setting.dart';
class MixinStoreTemplate extends StoreTemplate {
String get typeName => '_\$$publicTypeName';
@override
String toString() => '''
mixin $typeName$typeParams on $parentTypeName$typeArgs, SettingsStore {
$storeBody
}''';
}
abstract class StoreTemplate {
final SurroundedCommaList<TypeParamTemplate> typeParams =
SurroundedCommaList('<', '>', []);
final SurroundedCommaList<String> typeArgs =
SurroundedCommaList('<', '>', []);
String publicTypeName;
String parentTypeName;
final Rows<BoolSettingTemplate> boolSettings = Rows();
final Rows<StringSettingTemplate> stringSettings = Rows();
final Rows<IntSettingTemplate> intSettings = Rows();
final Rows<DoubleSettingTemplate> doubleSettings = Rows();
final Rows<StringListSettingTemplate> stringListSettings = Rows();
final List<String> toStringList = [];
bool generateToString = false;
String _actionControllerName;
String get actionControllerName =>
_actionControllerName ??= '_\$${parentTypeName}ActionController';
String get storeBody {
var toStringMethod = '';
final sb = StringBuffer();
sb.writeln('SharedPreferences prefs;');
sb.writeln(
'final _controller = StreamController<$publicTypeName>.broadcast();');
sb.writeln('Stream<$publicTypeName> get stream => _controller.stream;');
sb.writeln();
List<SettingsImpl> _settingsImpl = [];
_settingsImpl.addAll(
boolSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
stringSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
intSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
doubleSettings.templates.whereType<SettingsImpl>().map((t) => t).toList(),
);
_settingsImpl.addAll(
stringListSettings.templates
.whereType<SettingsImpl>()
.map((t) => t)
.toList(),
);
sb.writeln(' Future<bool> init() async {');
for (final setting in _settingsImpl) {
sb.writeln(setting.preInit());
}
sb.writeln(' prefs = await SharedPreferences.getInstance();');
for (final setting in _settingsImpl) {
sb.writeln(setting.postInit());
}
sb.writeln(' return prefs != null;');
sb.writeln(' }');
sb.writeln();
sb.writeln('$boolSettings');
sb.writeln();
sb.writeln('$stringSettings');
sb.writeln();
sb.writeln('$intSettings');
sb.writeln();
sb.writeln('$doubleSettings');
sb.writeln();
sb.writeln('$stringListSettings');
sb.writeln();
sb.writeln(' void dispose() {');
for (final setting in _settingsImpl) {
sb.writeln(setting.dispose());
}
sb.writeln('_controller.close();');
sb.writeln(' }');
sb.writeln();
final baseBody = sb.toString();
if (generateToString) {
final publicBoolSettings = boolSettings.templates
..removeWhere((element) => element.isPrivate);
final publicStringSettings = stringSettings.templates
..removeWhere((element) => element.isPrivate);
final publicIntSettings = intSettings.templates
..removeWhere((element) => element.isPrivate);
final publicDoubleSettings = doubleSettings.templates
..removeWhere((element) => element.isPrivate);
final publicStringListSettings = doubleSettings.templates
..removeWhere((element) => element.isPrivate);
toStringList
..addAll(publicBoolSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicStringSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicIntSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicDoubleSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'))
..addAll(publicStringListSettings.map(
(current) => '${current.name}: \${${current.name}.toString()}'));
toStringMethod = '''
@override
String toString() {
final string = \'${toStringList.join(',')}\';
return '{\$string}';
}
''';
}
return baseBody + toStringMethod;
}
@override
String toString();
}
@@ -0,0 +1,15 @@
const _analyzerIgnores =
'// ignore_for_file: non_constant_identifier_names, unnecessary_lambdas, prefer_expression_function_bodies, lines_longer_than_80_chars, avoid_as, avoid_annotating_with_dynamic';
class StoreFileTemplate {
Iterable<String> storeSources;
@override
String toString() => storeSources.isEmpty
? ''
: '''
$_analyzerIgnores
${storeSources.join('\n\n')}
''';
}
@@ -0,0 +1,77 @@
import 'setting_impl.dart';
class StringListSettingTemplate implements SettingsImpl {
List<String> defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return "${name}Notify($defaultValue);";
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<List<String>>.broadcast();');
sb.writeln(
'Stream<List<String>> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln('final _${name}Notifier = ValueNotifier<List<String>>($defaultValue);');
sb.writeln('ValueListenable<List<String>> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
List<String> get $name {
return prefs?.getStringList('$name') ?? $defaultValue;
}
@override
set $name(List<String> value) {
${name}Async(value);
}
Future<bool> ${name}Async(List<String> value) async {
final success = await prefs.setStringList('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(List<String> value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,77 @@
import 'setting_impl.dart';
class StringSettingTemplate implements SettingsImpl {
String defaultValue;
String name;
bool isPrivate;
bool addStream = true;
bool addValueNotifier = true;
@override
String preInit() {
return "${name}Notify('$defaultValue');";
}
@override
String postInit() {
return '${name}Notify($name);';
}
@override
String dispose() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(' _${name}Controller.close();');
}
return sb.toString();
}
@override
String toString() {
final sb = StringBuffer();
if (addStream) {
sb.writeln(
'final _${name}Controller = StreamController<String>.broadcast();');
sb.writeln(
'Stream<String> get ${name}Stream => _${name}Controller.stream;');
}
if (addValueNotifier) {
sb.writeln("final _${name}Notifier = ValueNotifier<String>('$defaultValue');");
sb.writeln('ValueListenable<String> get ${name}Notifier => _${name}Notifier;');
}
sb.writeln("""
@override
String get $name {
return prefs?.getString('$name') ?? '$defaultValue';
}
@override
set $name(String value) {
${name}Async(value);
}
Future<bool> ${name}Async(String value) async {
final success = await prefs.setString('$name', value);
if (success) {
${name}Notify(value);
}
return success;
}
""");
sb.writeln('void ${name}Notify(String value) {');
if (addStream) {
sb.writeln(' _${name}Controller.add(value);');
}
if (addValueNotifier) {
sb.writeln('_${name}Notifier.value = value;');
}
sb.writeln('_controller.add(this);');
sb.writeln('}');
return sb.toString();
}
}
@@ -0,0 +1,42 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:settings_gen/src/template/params.dart';
import 'package:settings_gen/src/type_names.dart';
import 'package:source_gen/source_gen.dart';
// ignore: avoid_annotating_with_dynamic
String surroundNonEmpty(String prefix, String suffix, dynamic content) {
final contentStr = content.toString();
return contentStr.isEmpty ? '' : '$prefix$contentStr$suffix';
}
const _streamChecker = TypeChecker.fromRuntime(Stream);
class AsyncMethodChecker {
AsyncMethodChecker([TypeChecker checkStream]) {
_checkStream = checkStream ?? _streamChecker;
}
TypeChecker _checkStream;
bool returnsFuture(MethodElement method) =>
method.returnType.isDartAsyncFuture ||
(method.isAsynchronous &&
!method.isGenerator &&
method.returnType.isDynamic);
bool returnsStream(MethodElement method) =>
_checkStream.isAssignableFromType(method.returnType) ||
(method.isAsynchronous &&
method.isGenerator &&
method.returnType.isDynamic);
}
TypeParamTemplate typeParamTemplate(
TypeParameterElement param,
LibraryScopedNameFinder typeNameFinder,
) =>
TypeParamTemplate()
..name = param.name
..bound = param.bound != null
? typeNameFinder.findTypeParameterBoundsTypeName(param)
: null;
@@ -0,0 +1,140 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:settings_gen/src/template/comma_list.dart';
/// Determines the names of types within the context of a library, determining
/// prefixes if applicable.
///
/// For example, if a library has been imported with a name, references to types
/// contained or exported by that library must be prefixed by that name.
///
/// ```dart
/// import 'dart:io' as io;
///
/// io.File someFile;
/// ```
///
/// If we had a reference to `someFile`'s [Element], [findVariableTypeName]
/// would return `"io.File"`.
class LibraryScopedNameFinder {
LibraryScopedNameFinder(this.library);
final LibraryElement library;
Map<Element, String> _namesByElement;
Map<Element, String> get namesByElement {
if (_namesByElement != null) {
return _namesByElement;
}
_namesByElement = {};
// Add all of this library's type-defining elements to the name map
final libraryElements =
library.topLevelElements.whereType<TypeDefiningElement>();
for (final element in libraryElements) {
_namesByElement[element] = element.name;
}
// Reverse each import's export namespace so we can map elements to their
// library-local names. Note that the definedNames include a prefix if there
// is one.
for (final import in library.imports) {
for (final entry in import.namespace.definedNames.entries) {
_namesByElement[entry.value] = entry.key;
}
}
return _namesByElement;
}
String findVariableTypeName(VariableElement variable) =>
_getDartTypeName(variable.type);
String findGetterTypeName(PropertyAccessorElement getter) {
assert(getter.isGetter);
return findReturnTypeName(getter);
}
String findParameterTypeName(ParameterElement parameter) =>
_getDartTypeName(parameter.type);
String findReturnTypeName(FunctionTypedElement executable) =>
_getDartTypeName(executable.returnType);
List<String> findReturnTypeArgumentTypeNames(ExecutableElement executable) {
final returnType = executable.returnType;
return returnType is ParameterizedType
? returnType.typeArguments.map(_getDartTypeName).toList()
: [];
}
String findTypeParameterBoundsTypeName(TypeParameterElement typeParameter) {
assert(typeParameter.bound != null);
return _getDartTypeName(typeParameter.bound);
}
/// Calculates a type name, including its type arguments
///
/// The returned string will include import prefixes on all applicable types.
String _getDartTypeName(DartType type) {
var typeElement = type.element;
if (type is FunctionType) {
// If we're dealing with a typedef, we let it undergo the standard name
// lookup. Otherwise, we special case the function naming.
if (typeElement?.enclosingElement is GenericTypeAliasElement) {
typeElement = typeElement.enclosingElement;
} else {
return _getFunctionTypeName(type);
}
} else if (
// Some types don't have associated elements, like void
typeElement == null ||
// This is a bare type param, like "T"
type is TypeParameterType) {
// TODO(shyndman): This ignored deprecation can be removed when we
// increase the analyzer dependency's lower bound to 0.39.2, and
// migrate to using `DartType.getDisplayString`.
// ignore: deprecated_member_use
return type.displayName;
}
return _getNamedElementTypeName(typeElement, type);
}
String _getFunctionTypeName(FunctionType type) {
final returnTypeName = _getDartTypeName(type.returnType);
final normalParameterTypeNames =
CommaList(type.normalParameterTypes.map(_getDartTypeName).toList());
final optionalParameterTypeNames = SurroundedCommaList(
'[', ']', type.optionalParameterTypes.map(_getDartTypeName).toList());
final namedParameterPairs = type.namedParameterTypes.entries
.map((entry) => '${_getDartTypeName(entry.value)} ${entry.key}')
.toList();
final namedParameterTypeNames =
SurroundedCommaList('{', '}', namedParameterPairs);
final parameterTypeNames = CommaList([
normalParameterTypeNames,
optionalParameterTypeNames,
namedParameterTypeNames,
]);
return '$returnTypeName Function($parameterTypeNames)';
}
String _getNamedElementTypeName(Element typeElement, DartType type) {
// Determine the name of the type, without type arguments.
assert(namesByElement.containsKey(typeElement));
// If the type is parameterized, we recursively name its type arguments
if (type is ParameterizedType && type.typeArguments.isNotEmpty) {
final typeArgNames = SurroundedCommaList(
'<', '>', type.typeArguments.map(_getDartTypeName).toList());
return '${namesByElement[typeElement]}$typeArgNames';
}
return namesByElement[typeElement];
}
}
@@ -0,0 +1,25 @@
name: settings_gen
description: Code generator for MobX that adds support for annotating your code with @observable, @computed, @action and also creating SettingsStore classes.
version: 1.0.4
homepage: https://github.com/rodydavis/settings_manager
environment:
sdk: ">=2.6.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
analyzer: ">=0.38.5 <0.40.0"
build: ^1.1.4
build_resolvers: ^1.3.2
meta: ^1.1.0
path: ^1.6.2
source_gen: ^0.9.4
settings_manager: 1.0.3
dev_dependencies:
build_runner: ^1.7.2
build_test: ^0.10.9
logging: ^0.11.3
mockito: ^4.0.0
test: ^1.9.4
test_coverage: ^0.4.1