Suppose I have defined a library app with some classes:
library app;
class User {
// some members
}
class Question {}
class Answer {}
Is it possible to get the mirrors of class specified by name?
var className = specifyClassName(); // may be "User", "Question", etc
ClassMirror cm = getClassMirror(className);
Here is possible example of your requirements.
library app;
import 'dart:mirrors';
import 'dart:async';
void main() {
var names = [
'dart.async.Future',
'dart.mirrors.ClassMirror',
'app.User',
'app.Question',
'Answer',
'app.Stackoverflow'
];
for(var name in names) {
var clazz = getClassMirrorByName(name);
print('$name: $clazz');
}
}
ClassMirror getClassMirrorByName(String className) {
if(className == null) {
return null;
}
var index = className.lastIndexOf('.');
var libname = '';
var name = className;
if(index > 0) {
libname = className.substring(0, index);
name = className.substring(index + 1);
}
LibraryMirror lib;
if(libname.isEmpty) {
lib = currentMirrorSystem().isolate.rootLibrary;
} else {
var libs = currentMirrorSystem().findLibrary(new Symbol(libname)).toList();
if(libs.length == 1) {
lib = libs.first;
}
}
if(lib == null) {
return null;
}
return lib.classes[new Symbol(name)];
}
class User {
// some members
}
class Question {}
class Answer {}
Output:
dart.async.Future: ClassMirror on 'Future'
dart.mirrors.ClassMirror: ClassMirror on 'ClassMirror'
app.User: ClassMirror on 'User'
app.Question: ClassMirror on 'Question'
Answer: ClassMirror on 'Answer'
app.Stackoverflow: null
Related
I would like to pass some initial information into a singleton in dart.
Unfortunately, the information I like to access is null (see dartpad output below)
It seems like I get a new instance of my object and not the singleton but I can not wrap my head around it. Any idea?
ElmCommandProvider.fromMetaData
ElmCommandProvider._internal()
ElmCommandProvider._init
ElmCommandProvider()
null
This is the code which can be pasted in DartPad
class Command {
Command(this.i);
final int i;
}
class MetaData {
MetaData(this.i);
final int i;
}
class ElmCommandProvider {
List<Command> commandsList;
bool _lock = false;
static Map<String, MetaData> _metaDataPool;
factory ElmCommandProvider.fromMetaData(Map<String, MetaData> metaDataPool) {
print('ElmCommandProvider.fromMetaData');
assert(!_singleton._lock, "it's a singleton that can't re-defined");
ElmCommandProvider._metaDataPool = metaDataPool;
_singleton._lock = true;
ElmCommandProvider._init();
return _singleton;
}
factory ElmCommandProvider() {
print('ElmCommandProvider()');
return _singleton;
}
static final ElmCommandProvider _singleton =
new ElmCommandProvider._internal();
ElmCommandProvider._internal() {
print('ElmCommandProvider._internal()');
}
ElmCommandProvider._init() {
print('ElmCommandProvider._init');
commandsList =
_metaDataPool.values.map((bloc) => Command(bloc.i)).toList();
}
}
void main() {
ElmCommandProvider.fromMetaData({'1': MetaData(1), '2': MetaData(2)});
print( ElmCommandProvider().commandsList);
}
_init() should not be a constructor. Or at least there is no need for it to be one and it's confusing you. It should be changed to a static method or a private instance method.
When you do commandsList= in ElmCommandProvider._init(), commandsList is referring to the commandsList instance variable in the new ElmCommandProvider object you're creating with the constructor. You likely actually mean to modify the singleton's commandsList so you should have been doing singleton.commandsList = instead of just commandsList =.
Example working code with static method:
class Command {
Command(this.i);
final int i;
}
class MetaData {
MetaData(this.i);
final int i;
}
class ElmCommandProvider {
List<Command> commandsList;
bool _lock = false;
static Map<String, MetaData> _metaDataPool;
factory ElmCommandProvider.fromMetaData(Map<String, MetaData> metaDataPool) {
print('ElmCommandProvider.fromMetaData');
assert(!_singleton._lock, "it's a singleton that can't re-defined");
ElmCommandProvider._metaDataPool = metaDataPool;
_singleton._lock = true;
_init();
return _singleton;
}
factory ElmCommandProvider() {
print('ElmCommandProvider()');
return _singleton;
}
static final ElmCommandProvider _singleton =
new ElmCommandProvider._internal();
ElmCommandProvider._internal() {
print('ElmCommandProvider._internal()');
}
static _init() {
print('ElmCommandProvider._init');
_singleton.commandsList =
_metaDataPool.values.map((bloc) => Command(bloc.i)).toList();
}
}
void main() {
ElmCommandProvider.fromMetaData({'1': MetaData(1), '2': MetaData(2)});
print( ElmCommandProvider().commandsList);
}
Example working code with private instance method:
class Command {
Command(this.i);
final int i;
}
class MetaData {
MetaData(this.i);
final int i;
}
class ElmCommandProvider {
List<Command> commandsList;
bool _lock = false;
static Map<String, MetaData> _metaDataPool;
factory ElmCommandProvider.fromMetaData(Map<String, MetaData> metaDataPool) {
print('ElmCommandProvider.fromMetaData');
assert(!_singleton._lock, "it's a singleton that can't re-defined");
ElmCommandProvider._metaDataPool = metaDataPool;
_singleton._lock = true;
_singleton._init();
return _singleton;
}
factory ElmCommandProvider() {
print('ElmCommandProvider()');
return _singleton;
}
static final ElmCommandProvider _singleton =
new ElmCommandProvider._internal();
ElmCommandProvider._internal() {
print('ElmCommandProvider._internal()');
}
void _init() {
print('ElmCommandProvider._init');
commandsList =
_metaDataPool.values.map((bloc) => Command(bloc.i)).toList();
}
}
void main() {
ElmCommandProvider.fromMetaData({'1': MetaData(1), '2': MetaData(2)});
print( ElmCommandProvider().commandsList);
}
I have weird issue when trying to create a custom autocomplete editor.
Basicly what I've done is I've pulled the built-in AutocompleteEditor class and refactored it to plain ES6, and renamed the class to ProductSelectEditor. No modifications to the code logic.
When I try to use it, I'm getting error "Cannot read property 'onCommit' of undefined" when handleChange() is called:
handleChange() {
this.props.onCommit(); // props undefined
}
Now if i replace the editor with the real built-in AutocompleteEditor, it works just fine. I can't see any straight reason, why my custom version does not work, when only alterations I'm doing are refactoring the code away from TypeScript, renaming the class, and eventually exporting the class out as default?
Any clues on what I'm not understanding here?
Below is the whole refactored code
import React from 'react'
import ReactDOM from 'react-dom'
import ReactAutocomplete from 'ron-react-autocomplete';
import PropTypes from 'prop-types';
import '../css/ron-react-autocomplete.css'
const { shapes: { ExcelColumn } } = require('react-data-grid')
let optionPropType = PropTypes.shape({
id: PropTypes.required,
title: PropTypes.string
});
export default class ProductSelectEditor extends React.Component {
static propTypes = {
onCommit: PropTypes.func,
options: PropTypes.arrayOf(optionPropType),
label: PropTypes.any,
value: PropTypes.any,
height: PropTypes.number,
valueParams: PropTypes.arrayOf(PropTypes.string),
column: PropTypes.shape(ExcelColumn),
resultIdentifier: PropTypes.string,
search: PropTypes.string,
onKeyDown: PropTypes.func,
onFocus: PropTypes.func,
editorDisplayValue: PropTypes.func
};
static defaultProps = {
resultIdentifier: 'id'
};
handleChange() {
this.props.onCommit();
}
getValue() {
let value;
let updated = {};
if (this.hasResults() && this.isFocusedOnSuggestion()) {
value = this.getLabel(this.autoComplete.state.focusedValue);
if (this.props.valueParams) {
value = this.constuctValueFromParams(this.autoComplete.state.focusedValue, this.props.valueParams);
}
} else {
value = this.autoComplete.state.searchTerm;
}
updated[this.props.column.key] = value;
return updated;
}
getEditorDisplayValue() {
let displayValue = {title: ''};
let { column, value, editorDisplayValue } = this.props;
if (editorDisplayValue && typeof editorDisplayValue === 'function') {
displayValue.title = editorDisplayValue(column, value);
} else {
displayValue.title = value;
}
return displayValue;
}
getInputNode() {
return ReactDOM.findDOMNode(this).getElementsByTagName('input')[0];
}
getLabel(item) {
let label = this.props.label != null ? this.props.label : 'title';
if (typeof label === 'function') {
return label(item);
} else if (typeof label === 'string') {
return item[label];
}
}
hasResults() {
return this.autoComplete.state.results.length > 0;
}
isFocusedOnSuggestion() {
let autoComplete = this.autoComplete;
return autoComplete.state.focusedValue != null;
}
constuctValueFromParams(obj, props) {
if (!props) {
return '';
}
let ret = [];
for (let i = 0, ii = props.length; i < ii; i++) {
ret.push(obj[props[i]]);
}
return ret.join('|');
}
render() {
let label = this.props.label != null ? this.props.label : 'title';
return (<div height={this.props.height} onKeyDown={this.props.onKeyDown}>
<ReactAutocomplete search={this.props.search} ref={(node) => this.autoComplete = node} label={label} onChange={this.handleChange} onFocus={this.props.onFocus} resultIdentifier={this.props.resultIdentifier} options={this.props.options} value={this.getEditorDisplayValue()} />
</div>);
}
}
Alright, after few hours of poking and mangling found the reason for the props to be undefined. Apparently after stripping out the Typescripts, I needed to re-bind 'this' in order to get the correct context:
<ReactAutocomplete ... onChange={this.handleChange.bind(this)} ... />
Suppose i had this class:
abstract class DynamicallyAccessible{
operator [](String key){
throw 'DynamicallyAcessible [] operator is not implemented';
}
operator []=(String key,Object value){
throw 'DynamicallyAcessible []= operator is not implemented';
}
call(String methodName,List<Object> params){
throw 'DynamicallyAcessible call method is not implemented';
}
}
and this class which extends the above:
class Model extends DynamicallyAccessible{
String _a;
String get a => _a;
set a(v)=> _a=v;
String _b;
String get b => _b;
set b(v)=> _b=v;
int c;
dummyMethod(int a,int b){
return 6+a+b;
}
//this is to be generated by the transformer
//calls a getter or returns a value field by name
operator [](String key){
switch (key){
case 'a':return a;
case 'b':return b;
case 'c':return c;
default:throw 'no getter or field called $key';
}
}
//calls a setter or sets a field by name
operator []=(String key,Object value){
switch (key){
case 'a':a=value;return;
case 'b':b=value;return;
case 'c':c=value;return;
default:throw 'no setter or field called $key';
}
}
//calls a method by name
call(String key,List<Object> params){
switch(key){
case 'dummyMethod':return dummyMethod(params[0],params[1]);
default : throw 'no method called $key';
}
}
}
i implement the methods manually which is a waste of time, my question is does a transformer that does something similar exist or i have to write one from scratch, if not any suggestions to make this better?
the reason is to avoid using mirrors with dart2js
Here is an example of a transformer that use the analyzer package to find the classes that extends DynamicallyAccessible and inject the code for the operator[].
import 'package:barback/barback.dart';
import 'package:analyzer/analyzer.dart';
class DynamicallyAccessibleTransformer extends Transformer {
DynamicallyAccessibleTransformer.asPlugin();
get allowedExtensions => '.dart';
apply(Transform transform) async {
var content = await transform.readInputAsString(transform.primaryInput.id);
var newContent = _transformDartFile(content);
transform.addOutput(new Asset.fromString(transform.primaryInput.id, newContent));
}
String _transformDartFile(String content) {
CompilationUnit unit = parseCompilationUnit(content);
String newContent = content;
for (ClassDeclaration declaration in unit.declarations.reversed.where((d) => d is ClassDeclaration)) {
if (_isDynamicallyAccessible(declaration)) {
String sourceToInject = _createSourceToInject(declaration);
String before = content.substring(0, declaration.endToken.offset);
String after = content.substring(declaration.endToken.offset);
newContent = before + "\n$sourceToInject\n" + after;
}
}
return newContent;
}
/// TODO: this is a fragile approach as we only check for the class name
/// and not the library from where it comes. We probably should resolve
/// the Element and check the library.
_isDynamicallyAccessible(ClassDeclaration declaration) {
ExtendsClause extendsClause = declaration.extendsClause;
if (extendsClause != null) {
return extendsClause.superclass.name.name == 'DynamicallyAccessible';
}
return false;
}
_createSourceToInject(ClassDeclaration declaration) {
//TODO: do the same things for setters, methods and variable declaration.
String getterCases = declaration.members
.where((member) => member is MethodDeclaration && member.isGetter)
.map((MethodDeclaration getter) => getter.name.name)
.map((String name) => " case '$name': return $name;")
.join('\n');
return '''
operator [](String key) {
switch (key) {
$getterCases
default:throw 'no getter called \$key';
}
}
''';
}
}
Here is my version, also supports positional and named arguments for calling methods haven't tested deeply but it works fine
//TODO:support multiple inheritance where the sub class gets access to the super class members
class DynamicTransformer extends Transformer {
DynamicTransformer.asPlugin();
String get allowedExtensions => ".dart";
Future apply(Transform transform) async{
var logger = new BuildLogger(transform);
var content = await transform.primaryInput.readAsString();
var sourceFile = new SourceFile(content);
var transaction = _transformCompilationUnit(content,sourceFile,logger);
var id = transform.primaryInput.id;
if(transaction.hasEdits){
var printer = transaction.commit();
var url = id.path.startsWith('lib/')
? 'package:${id.package}/${id.path.substring(4)}' : id.path;
printer.build(url);
transform.addOutput(new Asset.fromString(id,printer.text));
}
return logger.writeOutput();
}
Future<bool> isPrimary(id) {
return new Future.value(id.extension == '.dart');
}
TextEditTransaction _transformCompilationUnit(
String inputCode, SourceFile sourceFile, BuildLogger logger) {
var unit = parseCompilationUnit(inputCode, suppressErrors: true);
var editor = new TextEditTransaction(inputCode, sourceFile);
unit.declarations
.where((dec) => dec is ClassDeclaration &&
isSubClassOrHasMixinOf(dec, 'DynamicAccess'))
.forEach((classDec) {
_transformClass(classDec,editor,sourceFile,logger);
});
return editor;
}
void _transformClass(ClassDeclaration cls, TextEditTransaction editor,
SourceFile file, BuildLogger logger) {
//TODO:throw another exception for private fields
var readWriteFieldNames = getClassDeclarationFieldDeclarationNames(cls,public:true);
var readOnlyFieldNames = getClassDeclarationFieldDeclarationNames(cls,public:true,readOnly:true);
var getters = getClassDeclarationMethodDeclarationNames(cls,getter:true).union(readWriteFieldNames).union(readOnlyFieldNames);
var setters = getClassDeclarationMethodDeclarationNames(cls,setter:true).union(readWriteFieldNames);
var methods = getClassDeclarationMethodDeclarations(cls,normal:true);
var methodsString = _buildSquareBracketsOperatorGet(getters);
methodsString+=_buildSquareBracketsOperatorAssignment(setters,readOnlyFieldNames);
methodsString+=_buildCallMethod(methods);
editor.edit(cls.endToken.offset,cls.endToken.offset,'$methodsString');
}
//build [] operator method
String _buildSquareBracketsOperatorGet(Set<String> getters) {
var sb = new StringBuffer();
sb.writeln('operator [](String key){');
sb.writeln('switch (key){');
getters.forEach((getter) {
sb.writeln("case '$getter': return this.$getter;");
});
sb.writeln("default:throw 'no getter or field called \$key';");
sb.writeln('}}');
return sb.toString();
}
String _buildSquareBracketsOperatorAssignment(Set<String> setters,Set<String> readOnlyFields) {
var sb = new StringBuffer();
sb.writeln('operator []=(String key,Object value){');
sb.writeln('switch (key){');
setters.forEach((setter) {
sb.writeln("case '$setter':this.$setter=value;return;");
});
readOnlyFields.forEach((readOnlyField) {
sb.writeln("case '$readOnlyField':throw 'field \$key is read only';return;");
});
sb.writeln("default:throw 'no setter or field called \$key';");
sb.writeln('}}');
return sb.toString();
}
String _buildCallMethod(Set<MethodDeclaration> methods){
var sb = new StringBuffer();
sb.writeln('call(String key,{List<Object> required:null,List<Object> positional:null,Map<String,Object> named:null}){');
sb.writeln('switch (key){');
methods.forEach((md){
sb.writeln("case '${md.name.name}': return this.${buildMethodCallString(md)}");
});
sb.writeln("default:throw 'no setter or field called \$key';");
sb.writeln('}}');
return sb.toString();
}
}
some utility methods:
isSubClassOrHasMixinOf(ClassDeclaration classDec, String superClassOrMixinName) {
if(classDec.withClause!=null){
print(classDec.withClause.mixinTypes);
}
if ((classDec.extendsClause != null &&
classDec.extendsClause.superclass.name.name == superClassOrMixinName) ||
(classDec.withClause != null &&
classDec.withClause.mixinTypes.any((type)=>type.name.name==superClassOrMixinName))) {
return true;
}
return false;
}
Set<String> getClassDeclarationFieldDeclarationNames(ClassDeclaration cd,
{public: true, private: false, readOnly: false}) {
var fieldNames = new Set<String>();
cd.members.forEach((m) {
if (m is FieldDeclaration) {
var fd = m as FieldDeclaration;
if ((fd.fields.isFinal || fd.fields.isConst) && readOnly || (!readOnly && !fd.fields.isFinal && !fd.fields.isConst)) {
fd.fields.variables.forEach((variable) {
var fName = variable.name.name;
if ((Identifier.isPrivateName(fName) && private) || (!Identifier.isPrivateName(fName) && public)) {
fieldNames.add(fName);
}
});
}
}
});
return fieldNames;
}
Set<FieldDeclaration> getClassDeclarationFieldDeclarations(ClassDeclaration cd,
{public: true, private: false, readOnly: false}) {
var fieldsDeclarations = new Set<FieldDeclaration>();
cd.members.forEach((m) {
if (m is FieldDeclaration) {
var fd = m as FieldDeclaration;
if ((fd.fields.isFinal || fd.fields.isConst) && readOnly || (!readOnly && !fd.fields.isFinal && !fd.fields.isConst)) {
fd.fields.variables.forEach((variable) {
if ((Identifier.isPrivateName(variable.name.name) && private) || (!Identifier.isPrivateName(variable.name.name) && public)) {
fieldsDeclarations.add(fd);
}
});
}
}
});
return fieldsDeclarations;
}
String buildMethodCallString(MethodDeclaration md){
var sb = new StringBuffer();
sb.write('${md.name.name}(');
var requiredParams = getMethodFormalParameterDeclarations(md,required:true);
if(requiredParams.length>0){
for(int i=0;i<requiredParams.length;i++){
sb.write('required[$i],');
};
}
var positionalParams = getMethodFormalParameterDeclarations(md,positional:true);
if(positionalParams.length>0){
for(int i=0;i<positionalParams.length;i++){
sb.write('(positional==null || positional[$i]==null)?${positionalParams[i].childEntities.last}:positional[$i],');
};
}
var namedParams = getMethodFormalParameterDeclarations(md,named:true);
if(namedParams.length > 0){
for(int i=0;i<namedParams.length;i++){
sb.write("${namedParams[i].identifier.name}:(named==null || !named.containsKey('${namedParams[i].identifier}'))?${namedParams[i].childEntities.last}:named['${namedParams[i].identifier}'],");
};
}
sb.write(');');
return sb.toString().replaceAll(r',)',')');
}
Set<MethodDeclaration> getClassDeclarationMethodDeclarations(
ClassDeclaration cd, {public: true, private: false, getter: false,
setter: false, operator: false, normal: false}) {
var methodDeclarations = new Set<MethodDeclaration>();
cd.members.forEach((d) {
if (d is MethodDeclaration) {
var md = d as MethodDeclaration;
var mName = md.name.name;
if ((Identifier.isPrivateName(mName) && private) ||
(!Identifier.isPrivateName(mName) && public)) {
if (md.isSetter && setter) {
methodDeclarations.add(md);
} else if (md.isGetter && getter) {
methodDeclarations.add(md);
} else if (md.isOperator && operator) {
//to do warn if [] []= are already overloaded and terminate
return;
} else if (normal && !md.isOperator && !md.isGetter && !md.isSetter) {
methodDeclarations.add(md);
}
}
}
});
return methodDeclarations;
}
Set<String> getClassDeclarationMethodDeclarationNames(
ClassDeclaration cd, {public: true, private: false, getter: false,
setter: false, operator: false, normal: false}) {
var methodDeclarationNames = new Set<String>();
cd.members.forEach((d) {
if (d is MethodDeclaration) {
var md = d as MethodDeclaration;
var mName = md.name.name;
if ((Identifier.isPrivateName(mName) && private) ||
(!Identifier.isPrivateName(mName) && public)) {
if (md.isSetter && setter) {
methodDeclarationNames.add(mName);
} else if (md.isGetter && getter) {
methodDeclarationNames.add(mName);
} else if (md.isOperator && operator) {
//to do warn if [] []= are already overloaded and terminate
return;
} else if (normal && !md.isOperator && !md.isGetter && !md.isSetter) {
methodDeclarationNames.add(mName);
}
}
}
});
return methodDeclarationNames;
}
List<FormalParameter> getMethodFormalParameterDeclarations(MethodDeclaration md,
{required: false, positional: false, named: false}) {
var formalParameters = new List<FormalParameter>();
md.parameters.parameters.forEach((fp) {
if ((fp.kind == ParameterKind.REQUIRED && required) ||
(fp.kind == ParameterKind.POSITIONAL && positional) ||
(fp.kind == ParameterKind.NAMED && named)) {
formalParameters.add(fp);
}
});
return formalParameters;
}
example:
class Model extends Object with DynamicAccess{
String _a;
String get a => _a;
set a(v)=> _a=v;
String _b;
String get b => _b;
set b(v)=> _b=v;
final int c=9;
String _z = 'xx';
dummyMethod(int a,int b){
return 6+a+b;
}
dummyNoParams(){
return 0;
}
dummyMethodWithPositionalParams(int a,String d,[int x=4,y=6]){
return '${a+x+y} $d';
}
dummyMethodWithNamedParams(int x,int y,{int f:4,String g:'no'}){
return '${x+y+f} $g';
}
}
gives:
class Model extends Object with DynamicAccess {
String _a;
String get a => _a;
set a(v) => _a = v;
String _b;
String get b => _b;
set b(v) => _b = v;
final int c = 9;
String _z = 'xx';
dummyMethod(int a, int b) {
return 6 + a + b;
}
dummyNoParams() {
return 0;
}
dummyMethodWithPositionalParams(int a, String d, [int x = 4, y = 6]) {
return '${a+x+y} $d';
}
dummyMethodWithNamedParams(int x, int y, {int f: 4, String g: 'no'}) {
return '${x+y+f} $g';
}
operator [](String key) {
switch (key) {
case 'a':
return this.a;
case 'b':
return this.b;
case 'c':
return this.c;
default:
throw 'no getter or field called $key';
}
}
operator []=(String key, Object value) {
switch (key) {
case 'a':
this.a = value;
return;
case 'b':
this.b = value;
return;
case 'c':
throw 'field $key is read only';
return;
default:
throw 'no setter or field called $key';
}
}
call(String key, {List<Object> required: null, List<Object> positional: null,
Map<String, Object> named: null}) {
switch (key) {
case 'dummyMethod':
return this.dummyMethod(required[0], required[1]);
case 'dummyNoParams':
return this.dummyNoParams();
case 'dummyMethodWithPositionalParams':
return this.dummyMethodWithPositionalParams(required[0], required[1],
(positional == null || positional[0] == null) ? 4 : positional[0],
(positional == null || positional[1] == null) ? 6 : positional[1]);
case 'dummyMethodWithNamedParams':
return this.dummyMethodWithNamedParams(required[0], required[1],
f: (named == null || !named.containsKey('f')) ? 4 : named['f'],
g: (named == null || !named.containsKey('g')) ? 'no' : named['g']);
default:
throw 'no setter or field called $key';
}
}
}
I'm using SharedObjects in my game to store the progress of the player in the game (level scores, unlocked levels, etc.).
Everything is working fine, but the problem is when I submitted an update of the game (with the same certificates and the same names of the .swf and .ipa files) when the game is updated the old SharedObject is deleted and this is very big problem for the game and for me.
Both versions of the game are made with Flash CS 6 and Air SDK 3.5.
Any idea why the SharedObject is deleted, how can I prevent that?
I'm assuming that the reason why SharedObject becomes overwritten is because it's bundled with the .ipa during conversion.
I understand that will not help your current situation with salvaging your SharedObject but you could try using flash.filesystem to read/write your data to a preference file instead of employing SharedObject in the future.
Below is my Archive class that I use with my own work, but I've never developed for iOS before so i'm not certain that it will function as it does on other deployment targets, although I believe it should.
Use Case:
//Imports
import com.mattie.data.Archive;
import com.mattie.events.ArchiveEvent;
//Constants
private static const PREF_CANVAS_VOLUME:String = "prefCanvasVolume";
private static const DEFAULT_VOLUME:Number = 0.5;
//Initialize Archive
private function initArchive():void
{
archive = new Archive();
archive.addEventListener(ArchiveEvent.LOAD, init);
archive.load();
}
//Initialize
private function init(event:ArchiveEvent):void
{
archive.removeEventListener(ArchiveEvent.LOAD, init);
canvasVolume = archive.read(PREF_CANVAS_VOLUME, DEFAULT_VOLUME);
}
//Application Exiting Event Handler
private function applicationExitingEventHandler(event:Event):void
{
stage.nativeWindow.removeEventListener(Event.CLOSING, applicationExitingEventHandler);
archive.write(PREF_CANVAS_VOLUME, canvas.volume);
archive.addEventListener(ArchiveEvent.SAVE, archiveSavedEventHandler);
archive.save();
}
//Archive Saved Event Handler
private function archiveSavedEventHandler(event:ArchiveEvent):void
{
archive.removeEventListener(ArchiveEvent.SAVE, archiveSavedEventHandler);
NativeApplication.nativeApplication.exit();
}
Archive Class
package com.mattie.data
{
//Imports
import com.mattie.events.ArchiveEvent;
import flash.data.EncryptedLocalStore;
import flash.desktop.NativeApplication;
import flash.events.EventDispatcher;
import flash.filesystem.File;
import flash.filesystem.FileMode;
import flash.filesystem.FileStream;
import flash.net.registerClassAlias;
import flash.utils.ByteArray;
//Class
public final class Archive extends EventDispatcher
{
//Properties
private static var singleton:Archive;
//Variables
private var file:File;
private var data:Object;
//Constructor
public function Archive()
{
if (singleton)
{
throw new Error("Archive is a singleton that is only accessible via the \"archive\" public property.");
}
file = File.applicationStorageDirectory.resolvePath(NativeApplication.nativeApplication.applicationID + "Archive");
data = new Object();
registerClassAlias("Item", Item);
}
//Load
public function load():void
{
if (file.exists)
{
var fileStream:FileStream = new FileStream();
fileStream.open(file, FileMode.READ);
data = fileStream.readObject();
fileStream.close();
}
dispatchEvent(new ArchiveEvent(ArchiveEvent.LOAD));
}
//Read
public function read(key:String, defaultValue:* = null):*
{
var value:* = defaultValue;
if (data[key] != undefined)
{
var item:Item = Item(data[key]);
if (item.encrypted)
{
var bytes:ByteArray = EncryptedLocalStore.getItem(key);
if (bytes == null)
{
return value;
}
switch (item.value)
{
case "Boolean": value = bytes.readBoolean(); break;
case "int": value = bytes.readInt(); break;
case "uint": value = bytes.readUnsignedInt(); break;
case "Number": value = bytes.readDouble(); break;
case "ByteArray": bytes.readBytes(value = new ByteArray()); break;
default: value = bytes.readUTFBytes(bytes.length);
}
}
else
{
value = item.value;
}
}
return value;
}
//Write
public function write(key:String, value:*, encrypted:Boolean = false, autoSave:Boolean = false):void
{
var oldValue:* = read(key);
if (oldValue != value)
{
var item:Item = new Item();
item.encrypted = encrypted;
if (encrypted)
{
var constructorString:String = String(value.constructor);
constructorString = constructorString.substring(constructorString.lastIndexOf(" ") + 1, constructorString.length - 1);
item.value = constructorString;
var bytes:ByteArray = new ByteArray();
switch (value.constructor)
{
case Boolean: bytes.writeBoolean(value); break;
case int: bytes.writeInt(value); break;
case uint: bytes.writeUnsignedInt(value); break;
case Number: bytes.writeDouble(value); break;
case ByteArray: bytes.writeBytes(value); break;
default: bytes.writeUTFBytes(value);
}
EncryptedLocalStore.setItem(key, bytes);
}
else
{
item.value = value;
}
data[key] = item;
dispatchEvent(new ArchiveEvent(ArchiveEvent.WRITE, key, oldValue, value));
if (autoSave)
{
save();
}
}
}
//Remove
public function remove(key:String, autoSave:Boolean = false):void
{
if (data[key] != undefined)
{
var oldValue:* = read(key);
if (Item(data[key]).encrypted)
{
EncryptedLocalStore.removeItem(key);
}
delete data[key];
dispatchEvent(new ArchiveEvent(ArchiveEvent.DELETE, key, oldValue));
if (autoSave)
{
save();
}
}
}
//Contains
public function contains(key:String):Boolean
{
return (data[key] != undefined);
}
//Save
public function save():void
{
var fileStream:FileStream = new FileStream();
fileStream.open(file, FileMode.WRITE);
fileStream.writeObject(data);
fileStream.close();
dispatchEvent(new ArchiveEvent(ArchiveEvent.SAVE));
}
//Get Singleton
public static function get archive():Archive
{
if (!singleton)
{
singleton = new Archive();
}
return singleton;
}
}
}
//Item
class Item
{
//Variables
public var value:*;
public var encrypted:Boolean = false;
}
Archive Event Class
package com.mattie.events
{
//Imports
import flash.events.Event;
//Class
public class ArchiveEvent extends Event
{
//Constants
public static const LOAD:String = "load";
public static const WRITE:String = "write";
public static const DELETE:String = "delete";
public static const SAVE:String = "save";
//Properties
public var key:String;
public var oldValue:*;
public var newValue:*;
//Constructor
public function ArchiveEvent(type:String, key:String = null, oldValue:* = null, newValue:* = null)
{
super(type);
this.key = key;
this.oldValue = oldValue;
this.newValue = newValue;
}
//Clone
public override function clone():Event
{
return new ArchiveEvent(type, key, oldValue, newValue);
}
//To String
public override function toString():String
{
return formatToString("ArchiveEvent", "type", "key", "oldValue", "newValue");
}
}
}
How can I set another Class as a Class constructor argument?
class MyClass {
String message = 'myClass';
void print() {
print(this.message);
}
}
class TestClass {
var myClass;
TestClass(???) {
this.myClass = ???(); // ???
}
void debug() {
print('DEBUG: ');
this.myClass.print();
}
}
main() {
MyClass myClass;
testClass(myClass);
}
You can't (at least not now). Your best option is to pass a builder function into the constructor.
class MyClass {
var clazzBuilder;
MyClass(this.clazzBuilder);
doStuff() {
var instance = clazzBuilder();
:
}
}
and then use it like this
var myTest = new MyClass(() => new Test());
var myProd = new MyClass(() => new Prod());
Note that the Dart team is planning to add both class concept (.type) and reflection to the language.
What do you want actually do? Often people try to apply paradigms from some languages in some other languages which isn't the best way to do some things.
Since print is an instance method and not a static method, you don't need to pass a class itself to TestClass, just an instance of it. Your program can be fixed like so:
class MyClass {
String message = 'myClass';
void print() {
print(this.message);
}
}
class TestClass {
var myClass;
TestClass(MyClass myClass) {
this.myClass = myClass;
}
void debug() {
print('DEBUG: ');
this.myClass.print();
}
}
main() {
MyClass myClass = new MyClass();
testClass(myClass);
}
Or, more idiomatically:
class MyClass {
String message = 'myClass';
void print() => print(message);
}
class TestClass {
var myClass;
TestClass(this.myClass);
void debug() {
print('DEBUG: ');
myClass.print();
}
}
main() {
testClass(new MyClass());
}