Dart: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
K (→List) |
|||
(40 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | |||
Zeile 5: | Zeile 5: | ||
* https://codingwithjoe.com/dart-fundamentals-async-await | * https://codingwithjoe.com/dart-fundamentals-async-await | ||
* https://dartpad.dev | * https://dartpad.dev | ||
* [[ArgParser Dart]] | |||
= Statements = | = Statements = | ||
< | <syntaxhighlight lang=dart>for (var ix in list) { print(ix); } | ||
for (var entry in list.entries) { print(entry.key + entry.value); } | |||
for (int ix = 0; ix < 10; ix++) {} | for (int ix = 0; ix < 10; ix++) {} | ||
while (doIt()){ } | while (doIt()){ } | ||
Zeile 19: | Zeile 21: | ||
} | } | ||
assert(str.isEmpty(), "String muss leer sein"); // nur im Debug aktiv. | assert(str.isEmpty(), "String muss leer sein"); // nur im Debug aktiv. | ||
</ | </syntaxhighlight> | ||
== Exception == | == Exception == | ||
< | <syntaxhighlight lang=dart> | ||
try { | try { | ||
breedMoreLlamas(); | breedMoreLlamas(); | ||
Zeile 39: | Zeile 41: | ||
MyException(this.message); | MyException(this.message); | ||
} | } | ||
</ | </syntaxhighlight> | ||
= Class = | = Class = | ||
< | <syntaxhighlight lang=dart>abstract class BaseLogger { | ||
int _errors = 0; | int _errors = 0; | ||
int _level; | int _level; | ||
static BaseLogger _lastInstance; | |||
bool get isSilent => _level == 0; // getter | bool get isSilent => _level == 0; // getter | ||
set isSilent(bool value) => _level = value ? 0 : 1; // setter | set isSilent(bool value) => _level = value ? 0 : 1; // setter | ||
Zeile 52: | Zeile 55: | ||
void log(string message); | void log(string message); | ||
void error(string message){ _errors++; log('+++ ' + message); } | void error(string message){ _errors++; log('+++ ' + message); } | ||
static BaseLogger lastInstance() { return _lastInstance; } | |||
} | } | ||
class Logger extends BaseLogger { | class Logger extends BaseLogger { | ||
String _filename; | String _filename; | ||
Logger(this._filename) : super(1); | Logger(this._filename) : super(1); | ||
Logger.silentLogger(this._filename) : super.silent(0); // named constructor | /// weiterer Constructor: | ||
Logger.silentLogger(this._filename): super.silent(0); // named constructor | |||
/// Referenz auf vorigen Construktor: | |||
Logger.fromConfig(String filename): this(Config(filename).getString('logFile')); | |||
} | } | ||
... | ... | ||
var logger = Logger('std.log'), logger2 = Logger.silentLogger('silent.log'); | var logger = Logger('std.log'), logger2 = Logger.silentLogger('silent.log'); | ||
</ | const logger2 = BaseLogger.lastInstance(); | ||
var isSilent = logger.isSilent; | |||
</syntaxhighlight> | |||
== Named constructors == | |||
<syntaxhighlight lang=dart> | |||
class Point { | |||
final double x; | |||
final double y; | |||
Point(this.x, this.y); | |||
// Named constructor | |||
Point.origin() | |||
: x = xOrigin, | |||
y = yOrigin; | |||
} | |||
</syntaxhighlight> | |||
=== Mixins === | |||
Mixins sind Klassen, die ihre Eigenschaften an andere Klassen ausleihen, aber nicht in die Klassenhierarchie | |||
eingebunden sind. | |||
In anderen Sprachen wird das als Trait bezeichnet. | |||
<syntaxhighlight lang=dart> | |||
abstract class ErrorHandler { | |||
final lastError = ''; | |||
// This class is intended to be used as a mixin, and should not be extended directly. | |||
factory ErrorHandler._() => null; | |||
void setError(String msg) => lastError = msg; | |||
void print() => print('+++ $lastError'); | |||
} | |||
class DoIt with ErrorHandler{ | |||
... | |||
bool validate(String s){ | |||
if (s.isEmpty()){ | |||
setError('input is empty'); | |||
} | |||
} | |||
... | |||
} | |||
// Mehrere Mixins: | |||
class A extends B with Errorhandler, MyMixin { | |||
} | |||
</syntaxhighlight> | |||
=== Enum === | |||
<syntaxhighlight lang=dart>enum DataType { bool, int, string, customType } // Schlüsselwörter erlaubt! | |||
DataType.values.forEach((v) => print('value: $v, index: ${v.index}')); | |||
</syntaxhighlight> | |||
== Interface == | == Interface == | ||
* Jede Klasse kann Interface sein. Dann muss jede Methode überschrieben werden | * Jede Klasse kann Interface sein. Dann muss jede Methode überschrieben werden | ||
< | <syntaxhighlight lang=dart> | ||
class D implements A, B, C { | class D implements A, B, C { | ||
@override | @override | ||
Zeile 70: | Zeile 126: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
== Generator == | == Generator == | ||
< | <syntaxhighlight lang=dart>Iterable<int> naturalsTo(int n) sync* { | ||
int k = 0; | int k = 0; | ||
while (k < n) yield k++; | while (k < n) yield k++; | ||
} | } | ||
</ | </syntaxhighlight> | ||
* ... und mit Rekursion: | * ... und mit Rekursion: | ||
< | <syntaxhighlight lang=dart>Iterable<int> naturalsDownFrom(int n) sync* { | ||
if (n > 0) { | if (n > 0) { | ||
yield n; | yield n; | ||
Zeile 85: | Zeile 141: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
= Typen = | |||
* Casting: | |||
<syntaxhighlight lang=dart> | |||
final x = y as String; | |||
</syntaxhighlight> | |||
== typedef == | |||
Definiert eine Methodensignatur oder einen Typ-Alias: | |||
<syntaxhighlight lang=dart> | |||
typedef IntList = List<int>; | |||
typedef Compare<T> = int Function(T a, T b); | |||
# Alte Schreibweise: | |||
typedef bool MyValidator(String input); | |||
bool validate(MyValidator validator){ | |||
if (validator(input)) doIt(); | |||
} | |||
</syntaxhighlight> | |||
= Map = | == Map == | ||
< | <syntaxhighlight lang=dart>final map = <String, int>{ 'John': 1, 'Eve': 2 }; | ||
final knowsEve = map.containsKey('Eve') && map.containsValue(2); | final knowsEve = map.containsKey('Eve') && map.containsValue(2); | ||
map.remove('John'); | map.remove('John'); | ||
Zeile 95: | Zeile 168: | ||
map.forEach((k, v) { print('{ key: $k, value: $v }'); }); | map.forEach((k, v) { print('{ key: $k, value: $v }'); }); | ||
map.entries.forEach((e) { print('{ key: ${e.key}, value: ${e.value} }'); }); | map.entries.forEach((e) { print('{ key: ${e.key}, value: ${e.value} }'); }); | ||
</ | </syntaxhighlight> | ||
= List = | == List == | ||
* Indizes: start: inklusiv end: exklusiv | * Indizes: start: inklusiv end: exklusiv | ||
< | <syntaxhighlight lang=dart>final names = <String>['adam', 'bob', 'charly', 'eve']; | ||
names.add('fred'); | final names2 = [...names, 'judy']; | ||
names.add('fred'); names.insert(3, 'chris'); | |||
ix = names.indexOf('bob'); ix2 = names.indexWhere((item) => item.startsWith('b'), start); | ix = names.indexOf('bob'); ix2 = names.indexWhere((item) => item.startsWith('b'), start); | ||
names.remove('bob'); names.removeAt(2); | names.remove('bob'); names.removeAt(2); | ||
Zeile 112: | Zeile 186: | ||
names.followedBy(name2); // liefert names und die Iterables name2 | names.followedBy(name2); // liefert names und die Iterables name2 | ||
names.any((item) => item.length < 3); // irgend ein Element mit der Bedingung | names.any((item) => item.length < 3); // irgend ein Element mit der Bedingung | ||
names.every((item) => item.length < 3); // alle Elemente | allLowerThan3 = names.every((item) => item.length < 3); // alle Elemente entsprechen der Bedingung | ||
subList = names.where((item) => item[0] > 'k'); | |||
firstNameWithLength3 = names.firstWhere((item) => item.length==3, orElse: () => '<None>'); | |||
final summary = names.fold('names:', (prevValue, item) => prevValue += ' ' + item); | |||
// fold(), reduce(), shuffle(), removeWhere(), | // fold(), reduce(), shuffle(), removeWhere(), | ||
// foreach(), join(), contains() | // foreach(), join(), contains() | ||
</ | </syntaxhighlight> | ||
== DateTime == | |||
<syntaxhighlight lang=dart> | |||
// Für DateFormat | |||
import 'package:intl/intl.dart'; | |||
var now = new DateTime.now(); | |||
var moonLanding = DateTime.parse("1969-07-20 20:18:04Z"); | |||
var sixtyDaysFromNow = now.add(new Duration(days: 60)); | |||
final diffDays = sixtyDaysFromNow.difference(moonLanding).inDays; | |||
var dateUtc = DateTime.utc(1944, 6, 6); | |||
var local = dateUtc.toLocal(); | |||
var isBefore = now.before(sixtyDaysFromNow); | |||
var linuxTime = now.millisecondsSinceEpoch ~/ 1000; | |||
var date2 = DateTime.fromMillisecondsSinceEpoch(linuxTime * 1000); | |||
test (date2.weekday == DateTime.monday /* 1 */ || date2.weekday == DateTime.sunday /* 7 */); | |||
var formatter = DateFormat('yyyy.MM.dd HH:mm:ss dayOfWeek: E'); | |||
String formatted = formatter.format(now); | |||
</syntaxhighlight> | |||
== Set == | |||
<syntaxhighlight lang="dart"> | |||
var foundKeys = <String>{}; | |||
if (! foundKeys.contains('x')){ | |||
foundKeys.add('x'); | |||
} | |||
// Iterable unique machen: | |||
words.split(' ').toSet().toList(); | |||
</syntaxhighlight> | |||
= | == RegExpr == | ||
< | <syntaxhighlight lang=dart>final regExpr = RegExp(r'(\w+)\s*=\s*(\d+)'); | ||
final matcher = regExpr.firstMatch('abc = 123'); | |||
if (matcher != null){ | if (matcher != null){ | ||
vars[matcher.group(1)] = int.parse(matcher.group(2)); | vars[matcher?.group(1)] = int.parse(matcher?.group(2) ?? 0); | ||
} | } | ||
</ | </syntaxhighlight> | ||
= String = | == String == | ||
== Konversion == | <syntaxhighlight lang=dart> | ||
< | const limit = 3; | ||
</ | var interpreted = "Limit: $limit Time: ${time()}"; | ||
const x = "abc" + interpreted; | |||
</syntaxhighlight> | |||
=== Konversion === | |||
<syntaxhighlight lang=dart>final count = int.parse("123"); | |||
</syntaxhighlight> | |||
== Formatierung == | === Formatierung === | ||
< | <syntaxhighlight lang=dart>import 'package:sprintf/sprintf.dart'; | ||
sprintf("%02d %s", [1, "Hi"]); | sprintf("%02d %s", [1, "Hi"]); | ||
print("${new DateTime.now().toString()}: $message\n"); | print("${new DateTime.now().toString()}: $message\n"); | ||
</ | </syntaxhighlight> | ||
== Bytes == | |||
<syntaxhighlight lang=dart> | |||
import 'dart:convert'; | |||
String foo = 'Hello world'; | |||
List<int> bytes = utf8.encode(foo); | |||
String foo2 = utf8.decode(bytes); | |||
</syntaxhighlight> | |||
= Json = | |||
<syntaxhighlight lang=dart>class Photo { | |||
final int id; | |||
final String title; | |||
Photo({this.id, this.title}); | |||
factory Photo.fromJson(Map<String, dynamic> json) { | |||
return Photo( | |||
id: json['id'] as int, | |||
title: json['title'] as String | |||
); | |||
} | |||
final parse(){ | |||
final jsonData = '{ "name" : "Dane", "alias" : "FilledStacks" }'; | |||
final parsedJson = json.decode(jsonData); | |||
} | |||
} | |||
</syntaxhighlight> | |||
= Besonderheiten = | = Besonderheiten = | ||
* entweder optionale Positionsparameter oder optionale Namensparameter, nicht beide. | * entweder optionale Positionsparameter oder optionale Namensparameter, nicht beide. | ||
< | <syntaxhighlight lang=dart>String substr(String str, int pos, [int length, bool uppercase]){ ... } | ||
String substr2(String str, int pos, {int length, bool uppercase, Logger logger}){ ... } | |||
x = substr('Hi world', 3, 2); | x = substr('Hi world', 3, 2); | ||
y = substr2('Hi world', 3, length:2, logger:logger); | y = substr2('Hi world', 3, length:2, logger:logger); | ||
// ..-Operator: Mehrfachzugriff auf voriges Objekt: | |||
var person = Person()..name='Joe'..id=25; | |||
var p = Point(10, -12)..setColor(green)..setWeight(1.22); | var p = Point(10, -12)..setColor(green)..setWeight(1.22); | ||
x ??= 5; // Zuweisung nur, wenn x==null | |||
x ~/ 5 // Ganzzahlige Division | |||
logger?.log() // Aufruf von log nur, wenn logger!=null | |||
</syntaxhighlight> | |||
== Reflection, Runtime-Info == | == Reflection, Runtime-Info == | ||
< | <syntaxhighlight lang=dart>if (a.runtimeType == int || a.runtimeType == String){...} | ||
if (a is String || or a is Map || a is! List){...} | |||
</syntaxhighlight> | |||
== UnitTest == | |||
<syntaxhighlight lang=dart>import 'package:test/test.dart'; | |||
void main() { | |||
group('Validators', () { | |||
test('checkEmail', () { | |||
expect(checkEmail('joe@example.com'), isNull); | |||
expect( | |||
checkEmail('joe@example@com'), | |||
equals( | |||
'Not an email address: joe@example@com Example: joe@example.com')); | |||
}); | |||
</syntaxhighlight> | |||
[https://dartdoc.takyam.com/articles/dart-unit-tests/#matchers Matcher]: | |||
<pre> | |||
isTrue | |||
equals(string) | |||
equalsIgnoringCase(string) | |||
startsWith(prefix) | |||
stringContainsInOrder(List<String> substrings) | |||
matches(regexp) | |||
isList | |||
isMapgreaterThan(v) | |||
greaterThanOrEqualTo(v) | |||
lessThan(v) | |||
</pre> | </pre> | ||
== Async-Pattern == | |||
<syntaxhighlight lang=dart> | |||
Future<bool> hasSubDirs(String path) async { | |||
var rc = false; | |||
final subdir = Directory(path); | |||
await for (var entry in subdir.list()){ | |||
if (await Directory.isDirectory(entry.path)){ | |||
rc = true; | |||
break; | |||
} | |||
} | |||
return rc; | |||
} | |||
</syntaxhighlight> |
Aktuelle Version vom 29. Januar 2023, 13:18 Uhr
Links[Bearbeiten]
- DartAsynchron
- https://codingwithjoe.com/dart-fundamentals-async-await
- https://dartpad.dev
- ArgParser Dart
Statements[Bearbeiten]
for (var ix in list) { print(ix); }
for (var entry in list.entries) { print(entry.key + entry.value); }
for (int ix = 0; ix < 10; ix++) {}
while (doIt()){ }
do { } while(test());
switch(x) {
case '0':
case 'A': y=3; break;
case 'B': b=true; continue on_label_c; // Implementierung von "fall through"
on_label_c: case 'C': y=5; break;
default: break;
}
assert(str.isEmpty(), "String muss leer sein"); // nur im Debug aktiv.
Exception[Bearbeiten]
try {
breedMoreLlamas();
} on OutOfLlamasException {
buyMoreLlamas();
} on Exception catch (e) {
print('Unknown exception: $e'); // Anything else that is an exception
} catch (e, stacktrace) {
print('Something really unknown: $e $stacktrace'); // No specified type, handles all
rethrow; // throw the same exception again
} finally {
cleanUp();
}
throw ArgumentError('not an int');
class MyException implements Exception{
String message;
MyException(this.message);
}
Class[Bearbeiten]
abstract class BaseLogger {
int _errors = 0;
int _level;
static BaseLogger _lastInstance;
bool get isSilent => _level == 0; // getter
set isSilent(bool value) => _level = value ? 0 : 1; // setter
BaseLogger(this._level); // constructor
BaseLogger.silent() : this(0); // named constructor, "redirected constructor"
// abstract function:
void log(string message);
void error(string message){ _errors++; log('+++ ' + message); }
static BaseLogger lastInstance() { return _lastInstance; }
}
class Logger extends BaseLogger {
String _filename;
Logger(this._filename) : super(1);
/// weiterer Constructor:
Logger.silentLogger(this._filename): super.silent(0); // named constructor
/// Referenz auf vorigen Construktor:
Logger.fromConfig(String filename): this(Config(filename).getString('logFile'));
}
...
var logger = Logger('std.log'), logger2 = Logger.silentLogger('silent.log');
const logger2 = BaseLogger.lastInstance();
var isSilent = logger.isSilent;
Named constructors[Bearbeiten]
class Point {
final double x;
final double y;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
Mixins[Bearbeiten]
Mixins sind Klassen, die ihre Eigenschaften an andere Klassen ausleihen, aber nicht in die Klassenhierarchie eingebunden sind.
In anderen Sprachen wird das als Trait bezeichnet.
abstract class ErrorHandler {
final lastError = '';
// This class is intended to be used as a mixin, and should not be extended directly.
factory ErrorHandler._() => null;
void setError(String msg) => lastError = msg;
void print() => print('+++ $lastError');
}
class DoIt with ErrorHandler{
...
bool validate(String s){
if (s.isEmpty()){
setError('input is empty');
}
}
...
}
// Mehrere Mixins:
class A extends B with Errorhandler, MyMixin {
}
Enum[Bearbeiten]
enum DataType { bool, int, string, customType } // Schlüsselwörter erlaubt!
DataType.values.forEach((v) => print('value: $v, index: ${v.index}'));
Interface[Bearbeiten]
- Jede Klasse kann Interface sein. Dann muss jede Methode überschrieben werden
class D implements A, B, C {
@override
void doIt(){
// ...
}
}
Generator[Bearbeiten]
Iterable<int> naturalsTo(int n) sync* {
int k = 0;
while (k < n) yield k++;
}
- ... und mit Rekursion:
Iterable<int> naturalsDownFrom(int n) sync* {
if (n > 0) {
yield n;
yield* naturalsDownFrom(n - 1);
}
}
Typen[Bearbeiten]
- Casting:
final x = y as String;
typedef[Bearbeiten]
Definiert eine Methodensignatur oder einen Typ-Alias:
typedef IntList = List<int>;
typedef Compare<T> = int Function(T a, T b);
# Alte Schreibweise:
typedef bool MyValidator(String input);
bool validate(MyValidator validator){
if (validator(input)) doIt();
}
Map[Bearbeiten]
final map = <String, int>{ 'John': 1, 'Eve': 2 };
final knowsEve = map.containsKey('Eve') && map.containsValue(2);
map.remove('John');
map.removeWhere((k, v) => k.startsWith('J'));
final combinedMap1 = {...map1, ...map2};
map.forEach((k, v) { print('{ key: $k, value: $v }'); });
map.entries.forEach((e) { print('{ key: ${e.key}, value: ${e.value} }'); });
List[Bearbeiten]
- Indizes: start: inklusiv end: exklusiv
final names = <String>['adam', 'bob', 'charly', 'eve'];
final names2 = [...names, 'judy'];
names.add('fred'); names.insert(3, 'chris');
ix = names.indexOf('bob'); ix2 = names.indexWhere((item) => item.startsWith('b'), start);
names.remove('bob'); names.removeAt(2);
names2 = names.getRange(2, 3);
name3 = names.sublist(ixStart, ixEnd);
print(names.first + ' ' + names.last);
names.firstWhere( (item) => item.length == 3);
names.replaceRange(1,3, ['john', 'judith']);
final first2Elements = names.take(2);
final allButFirst2Elements = names.skip(2);
names.followedBy(name2); // liefert names und die Iterables name2
names.any((item) => item.length < 3); // irgend ein Element mit der Bedingung
allLowerThan3 = names.every((item) => item.length < 3); // alle Elemente entsprechen der Bedingung
subList = names.where((item) => item[0] > 'k');
firstNameWithLength3 = names.firstWhere((item) => item.length==3, orElse: () => '<None>');
final summary = names.fold('names:', (prevValue, item) => prevValue += ' ' + item);
// fold(), reduce(), shuffle(), removeWhere(),
// foreach(), join(), contains()
DateTime[Bearbeiten]
// Für DateFormat
import 'package:intl/intl.dart';
var now = new DateTime.now();
var moonLanding = DateTime.parse("1969-07-20 20:18:04Z");
var sixtyDaysFromNow = now.add(new Duration(days: 60));
final diffDays = sixtyDaysFromNow.difference(moonLanding).inDays;
var dateUtc = DateTime.utc(1944, 6, 6);
var local = dateUtc.toLocal();
var isBefore = now.before(sixtyDaysFromNow);
var linuxTime = now.millisecondsSinceEpoch ~/ 1000;
var date2 = DateTime.fromMillisecondsSinceEpoch(linuxTime * 1000);
test (date2.weekday == DateTime.monday /* 1 */ || date2.weekday == DateTime.sunday /* 7 */);
var formatter = DateFormat('yyyy.MM.dd HH:mm:ss dayOfWeek: E');
String formatted = formatter.format(now);
Set[Bearbeiten]
var foundKeys = <String>{};
if (! foundKeys.contains('x')){
foundKeys.add('x');
}
// Iterable unique machen:
words.split(' ').toSet().toList();
RegExpr[Bearbeiten]
final regExpr = RegExp(r'(\w+)\s*=\s*(\d+)');
final matcher = regExpr.firstMatch('abc = 123');
if (matcher != null){
vars[matcher?.group(1)] = int.parse(matcher?.group(2) ?? 0);
}
String[Bearbeiten]
const limit = 3;
var interpreted = "Limit: $limit Time: ${time()}";
const x = "abc" + interpreted;
Konversion[Bearbeiten]
final count = int.parse("123");
Formatierung[Bearbeiten]
import 'package:sprintf/sprintf.dart';
sprintf("%02d %s", [1, "Hi"]);
print("${new DateTime.now().toString()}: $message\n");
Bytes[Bearbeiten]
import 'dart:convert';
String foo = 'Hello world';
List<int> bytes = utf8.encode(foo);
String foo2 = utf8.decode(bytes);
Json[Bearbeiten]
class Photo {
final int id;
final String title;
Photo({this.id, this.title});
factory Photo.fromJson(Map<String, dynamic> json) {
return Photo(
id: json['id'] as int,
title: json['title'] as String
);
}
final parse(){
final jsonData = '{ "name" : "Dane", "alias" : "FilledStacks" }';
final parsedJson = json.decode(jsonData);
}
}
Besonderheiten[Bearbeiten]
- entweder optionale Positionsparameter oder optionale Namensparameter, nicht beide.
String substr(String str, int pos, [int length, bool uppercase]){ ... }
String substr2(String str, int pos, {int length, bool uppercase, Logger logger}){ ... }
x = substr('Hi world', 3, 2);
y = substr2('Hi world', 3, length:2, logger:logger);
// ..-Operator: Mehrfachzugriff auf voriges Objekt:
var person = Person()..name='Joe'..id=25;
var p = Point(10, -12)..setColor(green)..setWeight(1.22);
x ??= 5; // Zuweisung nur, wenn x==null
x ~/ 5 // Ganzzahlige Division
logger?.log() // Aufruf von log nur, wenn logger!=null
Reflection, Runtime-Info[Bearbeiten]
if (a.runtimeType == int || a.runtimeType == String){...}
if (a is String || or a is Map || a is! List){...}
UnitTest[Bearbeiten]
import 'package:test/test.dart';
void main() {
group('Validators', () {
test('checkEmail', () {
expect(checkEmail('joe@example.com'), isNull);
expect(
checkEmail('joe@example@com'),
equals(
'Not an email address: joe@example@com Example: joe@example.com'));
});
isTrue equals(string) equalsIgnoringCase(string) startsWith(prefix) stringContainsInOrder(List<String> substrings) matches(regexp) isList isMapgreaterThan(v) greaterThanOrEqualTo(v) lessThan(v)
Async-Pattern[Bearbeiten]
Future<bool> hasSubDirs(String path) async {
var rc = false;
final subdir = Directory(path);
await for (var entry in subdir.list()){
if (await Directory.isDirectory(entry.path)){
rc = true;
break;
}
}
return rc;
}