From 56a31ac31112c3acec3080b5625a33aee2670d67 Mon Sep 17 00:00:00 2001 From: Ali Ghahremani Date: Tue, 28 Mar 2023 13:55:29 +0330 Subject: [PATCH] feature: depends on async example: for depends on async format --- example/integration_test/01.dart | 5 +- example/integration_test/02.dart | 5 +- example/integration_test/03.dart | 9 +- .../integration_test/entry_point_test.dart | 3 +- example/lib/02_object_apply_sample/main.dart | 1 - .../object_apply_sample.dart | 1 + .../depends_on_sample.dart | 83 +++++++++++++++++ .../main.dart | 2 +- .../depends_on_async_sample.dart | 90 +++++++++++++++++++ example/lib/03_2_depends_on_async/main.dart | 18 ++++ .../depends_on_sample.dart | 83 ----------------- example/test/widget_test.dart | 3 +- lib/src/fs/save_and_load.dart | 15 ++-- lib/src/telescope.dart | 28 ++++-- lib/src/telescope_list.dart | 8 +- test/save_and_load_test.dart | 31 +++---- test/telescope_test.dart | 3 +- test/type_check_test.dart | 84 +++++++++-------- 18 files changed, 295 insertions(+), 177 deletions(-) create mode 100644 example/lib/03_1_depend_observable_sample/depends_on_sample.dart rename example/lib/{03_depend_observable_sample => 03_1_depend_observable_sample}/main.dart (84%) create mode 100644 example/lib/03_2_depends_on_async/depends_on_async_sample.dart create mode 100644 example/lib/03_2_depends_on_async/main.dart delete mode 100644 example/lib/03_depend_observable_sample/depends_on_sample.dart diff --git a/example/integration_test/01.dart b/example/integration_test/01.dart index 49bd7d5..108bdff 100644 --- a/example/integration_test/01.dart +++ b/example/integration_test/01.dart @@ -2,8 +2,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:app/01_simple_text_sample/main.dart' as example01; -void test(){ - +void test() { testWidgets('simple text', (tester) async { example01.main(); await tester.pumpAndSettle(); @@ -23,4 +22,4 @@ void test(){ expect(find.text('aa'), findsNWidgets(2)); expect(find.text('2'), findsOneWidget); }); -} \ No newline at end of file +} diff --git a/example/integration_test/02.dart b/example/integration_test/02.dart index 1e51930..02cd1f6 100644 --- a/example/integration_test/02.dart +++ b/example/integration_test/02.dart @@ -2,8 +2,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:app/02_object_apply_sample/main.dart' as example02; -void test(){ - +void test() { testWidgets('object apply', (tester) async { example02.main(); await tester.pumpAndSettle(); @@ -15,4 +14,4 @@ void test(){ expect(find.text('Ali is 25 years old.'), findsOneWidget); }); -} \ No newline at end of file +} diff --git a/example/integration_test/03.dart b/example/integration_test/03.dart index 80f77de..1a49875 100644 --- a/example/integration_test/03.dart +++ b/example/integration_test/03.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:app/03_depend_observable_sample/main.dart' as example03; - -void test(){ +import 'package:app/03_1_depend_observable_sample/main.dart' as example03; +void test() { testWidgets('bmi', (tester) async { example03.main(); await tester.pumpAndSettle(); @@ -20,9 +19,9 @@ void test(){ var height = tester.allWidgets.whereType().last.value; var result = tester.allWidgets.whereType().last.data!; - var bmi = weight/ ((height/ 100) * (height / 100)); + var bmi = weight / ((height / 100) * (height / 100)); expect(result.contains("$bmi"), true); expect(initResult != result, true); }); -} \ No newline at end of file +} diff --git a/example/integration_test/entry_point_test.dart b/example/integration_test/entry_point_test.dart index 043f779..ae69e0f 100644 --- a/example/integration_test/entry_point_test.dart +++ b/example/integration_test/entry_point_test.dart @@ -10,5 +10,4 @@ void main() { example01.test(); example02.test(); example03.test(); - -} \ No newline at end of file +} diff --git a/example/lib/02_object_apply_sample/main.dart b/example/lib/02_object_apply_sample/main.dart index 0870492..856680e 100644 --- a/example/lib/02_object_apply_sample/main.dart +++ b/example/lib/02_object_apply_sample/main.dart @@ -1,7 +1,6 @@ import 'package:app/02_object_apply_sample/object_apply_sample.dart'; import 'package:flutter/material.dart'; - void main() { runApp(const MyApp()); } diff --git a/example/lib/02_object_apply_sample/object_apply_sample.dart b/example/lib/02_object_apply_sample/object_apply_sample.dart index 2ff723f..4a80651 100644 --- a/example/lib/02_object_apply_sample/object_apply_sample.dart +++ b/example/lib/02_object_apply_sample/object_apply_sample.dart @@ -35,6 +35,7 @@ class ObjectApplySampleLayoutState extends State { ))); } } + class Human { String name; int age; diff --git a/example/lib/03_1_depend_observable_sample/depends_on_sample.dart b/example/lib/03_1_depend_observable_sample/depends_on_sample.dart new file mode 100644 index 0000000..9223972 --- /dev/null +++ b/example/lib/03_1_depend_observable_sample/depends_on_sample.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:telescope/telescope.dart'; + +class DependObservableSampleLayout extends StatefulWidget { + const DependObservableSampleLayout({Key? key}) : super(key: key); + + @override + State createState() => + DependObservableSampleLayoutState(); +} + +class DependObservableSampleLayoutState + extends State { + // observables + late Telescope height; + late Telescope weight; + late Telescope bmi; + late Telescope showingText; + + @override + void initState() { + super.initState(); + + height = Telescope(186); + weight = Telescope(72); + + bmi = Telescope.dependsOn([height, weight], () { + return weight.value / ((height.value / 100) * (height.value / 100)); + }); + + showingText = Telescope.dependsOn([bmi], () { + return "weight is ${weight.value} and height is ${height.value} so bmi will be ${bmi.value.toString().substring(0, 5)}"; + }); + } + + @override + Widget build(BuildContext context) { + var style = const TextStyle(fontSize: 40); + return Material( + type: MaterialType.transparency, + child: SafeArea( + child: Container( + color: Colors.white, + child: Column( + children: [ + const SizedBox(height: 20), + Text( + "Weight(kg):", + style: style, + ), + Slider( + value: weight.watch(this).toDouble(), + min: 1, + max: 200, + label: weight.watch(this).round().toString(), + onChanged: (double value) { + weight.value = value.toInt(); + }), + Text( + "Height(cm):", + style: style, + ), + Slider( + value: height.watch(this).toDouble(), + min: 1, + max: 200, + label: height.watch(this).round().toString(), + onChanged: (double value) { + height.value = value.toInt(); + }), + Text( + "Result:", + style: style, + ), + Text( + showingText.watch(this), + style: style, + ), + ], + ), + ))); + } +} diff --git a/example/lib/03_depend_observable_sample/main.dart b/example/lib/03_1_depend_observable_sample/main.dart similarity index 84% rename from example/lib/03_depend_observable_sample/main.dart rename to example/lib/03_1_depend_observable_sample/main.dart index fb42350..908a893 100644 --- a/example/lib/03_depend_observable_sample/main.dart +++ b/example/lib/03_1_depend_observable_sample/main.dart @@ -1,4 +1,4 @@ -import 'package:app/03_depend_observable_sample/depends_on_sample.dart'; +import 'package:app/03_1_depend_observable_sample/depends_on_sample.dart'; import 'package:flutter/material.dart'; void main() { diff --git a/example/lib/03_2_depends_on_async/depends_on_async_sample.dart b/example/lib/03_2_depends_on_async/depends_on_async_sample.dart new file mode 100644 index 0000000..8a5c6d5 --- /dev/null +++ b/example/lib/03_2_depends_on_async/depends_on_async_sample.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:telescope/telescope.dart'; + +class DependObservableAsyncSampleLayout extends StatefulWidget { + const DependObservableAsyncSampleLayout({Key? key}) : super(key: key); + + @override + State createState() => + DependObservableAsyncSampleLayoutState(); +} + +class DependObservableAsyncSampleLayoutState + extends State { + // observables + late Telescope height; + late Telescope weight; + late Telescope bmi; + late Telescope showingText; + + @override + void initState() { + super.initState(); + + height = Telescope(186); + weight = Telescope(72); + + bmi = Telescope.dependsOnAsync(0, [height, weight], () async { + return await calculateBMI(height.value, weight.value); + }); + + showingText = Telescope.dependsOn([height, weight, bmi], () { + var bmis = bmi.value.toString(); + bmis = bmis.length > 5 ? bmis.substring(0, 5) : bmis; + return "weight is ${weight.value} and height is ${height.value} so bmi will be $bmis"; + }); + } + + @override + Widget build(BuildContext context) { + var style = const TextStyle(fontSize: 40); + return Material( + type: MaterialType.transparency, + child: SafeArea( + child: Container( + color: Colors.white, + child: Column( + children: [ + const SizedBox(height: 20), + Text( + "Weight(kg):", + style: style, + ), + Slider( + value: weight.watch(this).toDouble(), + min: 1, + max: 200, + label: weight.watch(this).round().toString(), + onChanged: (double value) { + weight.value = value.toInt(); + }), + Text( + "Height(cm):", + style: style, + ), + Slider( + value: height.watch(this).toDouble(), + min: 1, + max: 200, + label: height.watch(this).round().toString(), + onChanged: (double value) { + height.value = value.toInt(); + }), + Text( + "Result:", + style: style, + ), + Text( + showingText.watch(this), + style: style, + ), + ], + ), + ))); + } +} + +Future calculateBMI(int w, int h) async { + await Future.delayed(const Duration(seconds: 2)); + return w / ((h / 100) * (h / 100)); +} diff --git a/example/lib/03_2_depends_on_async/main.dart b/example/lib/03_2_depends_on_async/main.dart new file mode 100644 index 0000000..31e7996 --- /dev/null +++ b/example/lib/03_2_depends_on_async/main.dart @@ -0,0 +1,18 @@ +import 'package:app/03_2_depends_on_async/depends_on_async_sample.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(fontFamily: 'IranSans'), + debugShowCheckedModeBanner: false, + home: const DependObservableAsyncSampleLayout()); + } +} diff --git a/example/lib/03_depend_observable_sample/depends_on_sample.dart b/example/lib/03_depend_observable_sample/depends_on_sample.dart deleted file mode 100644 index 6ea698b..0000000 --- a/example/lib/03_depend_observable_sample/depends_on_sample.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:telescope/telescope.dart'; - -class DependObservableSampleLayout extends StatefulWidget { - const DependObservableSampleLayout({Key? key}) : super(key: key); - - @override - State createState() => - DependObservableSampleLayoutState(); -} - -class DependObservableSampleLayoutState - extends State { - // observables - late Telescope height; - late Telescope weight; - late Telescope bmi; - late Telescope showingText; - - @override - void initState() { - super.initState(); - - height = Telescope(186); - weight = Telescope(72); - - bmi = Telescope.dependsOn([height, weight], () { - return weight.value / ((height.value / 100) * (height.value / 100)); - }); - - showingText = Telescope.dependsOn([bmi], () { - return "weight is ${weight.value} and height is ${height.value} so bmi will be ${bmi.value.toString().substring(0, 5)}"; - }); - } - - @override - Widget build(BuildContext context) { - var style = const TextStyle(fontSize: 40); - return Material( - type: MaterialType.transparency, - child: SafeArea( - child: Container( - color: Colors.white, - child: Column( - children: [ - const SizedBox(height: 20), - Text( - "Weight(kg):", - style: style, - ), - Slider( - value: weight.watch(this).toDouble(), - min: 1, - max: 200, - label: weight.watch(this).round().toString(), - onChanged: (double value) { - weight.value = value.toInt(); - }), - Text( - "Height(cm):", - style: style, - ), - Slider( - value: height.watch(this).toDouble(), - min: 1, - max: 200, - label: height.watch(this).round().toString(), - onChanged: (double value) { - height.value = value.toInt(); - }), - Text( - "Result:", - style: style, - ), - Text( - showingText.watch(this), - style: style, - ), - ], - ), - ))); - } -} diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index a359e10..2a2b819 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -5,5 +5,4 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -void main() { -} +void main() {} diff --git a/lib/src/fs/save_and_load.dart b/lib/src/fs/save_and_load.dart index d780986..7261c2a 100644 --- a/lib/src/fs/save_and_load.dart +++ b/lib/src/fs/save_and_load.dart @@ -5,7 +5,8 @@ import 'on_disk_save_ability.dart'; class SaveAndLoad { static const String prefix = "TELESCOPE_"; - static save(String onDiskId, OnDiskSaveAbility? onDiskSaveAbility, T value) async { + static save( + String onDiskId, OnDiskSaveAbility? onDiskSaveAbility, T value) async { onDiskId += prefix; var pref = await SharedPreferences.getInstance(); @@ -23,12 +24,14 @@ class SaveAndLoad { await pref.setBool(onDiskId, value as bool); break; default: - await pref.setString(onDiskId, onDiskSaveAbility!.toOnDiskString(value)); + await pref.setString( + onDiskId, onDiskSaveAbility!.toOnDiskString(value)); break; } } - static Future load(String onDiskId, OnDiskSaveAbility? onDiskSaveAbility) async { + static Future load( + String onDiskId, OnDiskSaveAbility? onDiskSaveAbility) async { onDiskId += prefix; var pref = await SharedPreferences.getInstance(); // not assign while its not on disk yet (keeps default value) @@ -53,7 +56,8 @@ class SaveAndLoad { // list save and load static const String sep = '~'; - static saveList(String onDiskId, OnDiskSaveAbility? onDiskSaveAbility, List items) async{ + static saveList(String onDiskId, OnDiskSaveAbility? onDiskSaveAbility, + List items) async { onDiskId += prefix; var stringifies = items.map((i) { @@ -70,7 +74,8 @@ class SaveAndLoad { await pref.setString(onDiskId, stringifies); } - static Future?> loadList(String onDiskId, OnDiskSaveAbility? onDiskSaveAbility) async { + static Future?> loadList( + String onDiskId, OnDiskSaveAbility? onDiskSaveAbility) async { onDiskId += prefix; var pref = await SharedPreferences.getInstance(); diff --git a/lib/src/telescope.dart b/lib/src/telescope.dart index 155ed71..b346afe 100644 --- a/lib/src/telescope.dart +++ b/lib/src/telescope.dart @@ -42,6 +42,25 @@ class Telescope { } } + /// Async version of [Telescope.dependsOn] + /// Use this if you need to use await in calculate function + Telescope.dependsOnAsync( + this.holden, List dependencies, Future Function() calculate, + {this.iWillCallNotifyAll = false}) { + calculate().then((value) { + holden = value; + notifyAll(); + }); + for (var o in dependencies) { + o.subscribe(() { + calculate().then((value) { + holden = value; + notifyAll(); + }); + }); + } + } + /// you can save built in type easily on disk by using this constructor /// [onDiskId] should be unique id. Telescope.saveOnDiskForBuiltInType(this.holden, this.onDiskId) { @@ -50,8 +69,8 @@ class Telescope { " use saveOnDiskForNonBuiltInType and provide OnDiskSaveAbility"; } - SaveAndLoad.load(onDiskId!, onDiskSaveAbility).then((loaded){ - if(loaded!=null){ + SaveAndLoad.load(onDiskId!, onDiskSaveAbility).then((loaded) { + if (loaded != null) { holden = loaded; notifyAll(); } @@ -67,13 +86,12 @@ class Telescope { {this.iWillCallNotifyAll = false}) { TypeCheck.checkIsValidType(holden, iWillCallNotifyAll); - SaveAndLoad.load(onDiskId!, onDiskSaveAbility!).then((loaded){ - if(loaded != null){ + SaveAndLoad.load(onDiskId!, onDiskSaveAbility!).then((loaded) { + if (loaded != null) { holden = loaded; notifyAll(); } }); - } /// [callback] will call when ever value get change diff --git a/lib/src/telescope_list.dart b/lib/src/telescope_list.dart index 13f098d..875614a 100644 --- a/lib/src/telescope_list.dart +++ b/lib/src/telescope_list.dart @@ -37,8 +37,8 @@ class TelescopeList extends Telescope> { " use saveOnDiskForNonBuiltInType and provide OnDiskSaveAbility"; } - SaveAndLoad.loadList(onDiskId, null).then((loaded){ - if(loaded!=null){ + SaveAndLoad.loadList(onDiskId, null).then((loaded) { + if (loaded != null) { holden = loaded; notifyAll(); } @@ -56,8 +56,8 @@ class TelescopeList extends Telescope> { TypeCheck.checkIsValidTypeForItems(items, iWillCallNotifyAllForItems); super.onDiskId = onDiskId; - SaveAndLoad.loadList(onDiskId, onDiskSaveAbilityForItems).then((loaded){ - if(loaded!=null){ + SaveAndLoad.loadList(onDiskId, onDiskSaveAbilityForItems).then((loaded) { + if (loaded != null) { holden = loaded; notifyAll(); } diff --git a/test/save_and_load_test.dart b/test/save_and_load_test.dart index 1df6eda..28b7d2e 100644 --- a/test/save_and_load_test.dart +++ b/test/save_and_load_test.dart @@ -10,62 +10,57 @@ void main() { saveAndLoadList(); } -class Human{ - String name=""; +class Human { + String name = ""; @override int get hashCode => name.hashCode; } -class HumanSerializer extends OnDiskSaveAbility{ +class HumanSerializer extends OnDiskSaveAbility { @override - Human parseOnDiskString(String data) => Human()..name=data; + Human parseOnDiskString(String data) => Human()..name = data; @override String toOnDiskString(Human instance) => instance.name; } - void saveAndLoad() { group("save and load", () { - test("string", () async { await SaveAndLoad.save("save-me", null, "value"); var loaded = await SaveAndLoad.load("save-me", null); expect(loaded, "value"); }); - - test("non built-in", () async { + test("non built-in", () async { var human = Human()..name = "ali"; await SaveAndLoad.save("save-human", HumanSerializer(), human); - var loaded = await SaveAndLoad.load("save-human", HumanSerializer()); + var loaded = + await SaveAndLoad.load("save-human", HumanSerializer()); expect(loaded.hashCode, human.hashCode); }); - }); } -void saveAndLoadList(){ +void saveAndLoadList() { group("save and load list", () { - test("string", () async { await SaveAndLoad.saveList("save-list", null, ["ali", "sohrab"]); var loaded = await SaveAndLoad.loadList("save-list", null); expect(loaded![0], "ali"); - expect(loaded[1] , "sohrab"); + expect(loaded[1], "sohrab"); }); test("non built-in", () async { - var human = Human()..name = "ali"; var human2 = Human()..name = "ali2"; - await SaveAndLoad.saveList("save-humans", HumanSerializer(), [human,human2]); - var loaded = await SaveAndLoad.loadList("save-humans", HumanSerializer()); + await SaveAndLoad.saveList( + "save-humans", HumanSerializer(), [human, human2]); + var loaded = + await SaveAndLoad.loadList("save-humans", HumanSerializer()); expect(loaded![0].name, "ali"); expect(loaded[1].name, "ali2"); }); - }); - } diff --git a/test/telescope_test.dart b/test/telescope_test.dart index b5a4b79..14d73d5 100644 --- a/test/telescope_test.dart +++ b/test/telescope_test.dart @@ -1,5 +1,4 @@ - // Unit tests are here // Integration tests are in /examples -void main() {} \ No newline at end of file +void main() {} diff --git a/test/type_check_test.dart b/test/type_check_test.dart index 607e884..c60cab6 100644 --- a/test/type_check_test.dart +++ b/test/type_check_test.dart @@ -6,7 +6,7 @@ import 'package:test/test.dart'; // Unit tests are here // Integration tests are in /examples -void main(){ +void main() { typeCheckBuiltIn(); typeCheckNonBuiltIn(); implementsHashCodeTest(); @@ -14,9 +14,8 @@ void main(){ checkValidTypeItemsTest(); } -void typeCheckBuiltIn(){ - group("TypeCheck built-in", (){ - +void typeCheckBuiltIn() { + group("TypeCheck built-in", () { test("string", () { expect(TypeCheck.isBuiltIn(), true); }); @@ -35,9 +34,8 @@ void typeCheckBuiltIn(){ }); } -void typeCheckNonBuiltIn(){ - group("TypeCheck not built-in", (){ - +void typeCheckNonBuiltIn() { + group("TypeCheck not built-in", () { test("Widget", () { expect(TypeCheck.isBuiltIn(), false); }); @@ -56,81 +54,81 @@ void typeCheckNonBuiltIn(){ }); } - -class Human{ - int age=18; +class Human { + int age = 18; } -class HashCodeHuman{ - int age=18; + +class HashCodeHuman { + int age = 18; @override - int get hashCode=>age.hashCode; + int get hashCode => age.hashCode; } -void implementsHashCodeTest(){ - group("implements hash code", (){ - test("null", (){ +void implementsHashCodeTest() { + group("implements hash code", () { + test("null", () { expect(TypeCheck.implementsHashCodeProperty(null), true); }); - test("human", (){ + test("human", () { expect(TypeCheck.implementsHashCodeProperty(Human()), false); }); - test("hash code human", (){ + test("hash code human", () { expect(TypeCheck.implementsHashCodeProperty(HashCodeHuman()), true); }); - }); } -void checkValidTypeTest(){ - group("check valid type test", (){ - - test("supported String", (){ +void checkValidTypeTest() { + group("check valid type test", () { + test("supported String", () { expect(TypeCheck.checkIsValidType("string", false), null); }); - test("supported Human", (){ - expect(TypeCheck.checkIsValidType(HashCodeHuman(), false), null); + test("supported Human", () { + expect(TypeCheck.checkIsValidType(HashCodeHuman(), false), + null); }); - test("not supported Human", (){ - expect((){ + test("not supported Human", () { + expect(() { TypeCheck.checkIsValidType(Human(), false); }, throwsA(TypeMatcher())); }); - test("not supported Human iWillCallNotifyAll", (){ + test("not supported Human iWillCallNotifyAll", () { expect(TypeCheck.checkIsValidType(Human(), true), null); }); - }); } -void checkValidTypeItemsTest(){ - - group("check valid type items test", (){ - - test("supported empty list", (){ +void checkValidTypeItemsTest() { + group("check valid type items test", () { + test("supported empty list", () { expect(TypeCheck.checkIsValidTypeForItems([], false), null); }); - test("supported String list", (){ - expect(TypeCheck.checkIsValidTypeForItems(["1","2"], false), null); + test("supported String list", () { + expect(TypeCheck.checkIsValidTypeForItems(["1", "2"], false), null); }); - test("supported Human list", (){ - expect(TypeCheck.checkIsValidTypeForItems([HashCodeHuman(),HashCodeHuman()], false), null); + test("supported Human list", () { + expect( + TypeCheck.checkIsValidTypeForItems( + [HashCodeHuman(), HashCodeHuman()], false), + null); }); - test("not supported Human", (){ - expect((){ - TypeCheck.checkIsValidTypeForItems([Human(),Human()], false); + test("not supported Human", () { + expect(() { + TypeCheck.checkIsValidTypeForItems([Human(), Human()], false); }, throwsA(TypeMatcher())); }); - test("not supported Human list iWillCallNotifyAll", (){ - expect(TypeCheck.checkIsValidTypeForItems([Human(),Human()], true), null); + test("not supported Human list iWillCallNotifyAll", () { + expect( + TypeCheck.checkIsValidTypeForItems([Human(), Human()], true), null); }); }); }