From d45b48d377202214f1e5c8a96bc04c3a25b03706 Mon Sep 17 00:00:00 2001 From: Marcelo dos Santos Date: Sun, 29 Dec 2019 22:30:58 -0300 Subject: [PATCH] Flutter Design Patterns: 3 - Template Method. --- .../assets/data/design_patterns.json | 9 +- .../template_method/template_method.png | Bin 0 -> 12604 bytes .../template_method_implementation.png | Bin 0 -> 55089 bytes .../assets/markdown/template-method.md | 331 ++++++++++++++++++ .../apis/json_students_api.dart | 36 ++ .../apis/xml_students_api.dart | 35 ++ .../json/students_json_bmi_calculator.dart | 28 ++ .../teenage_students_json_bmi_calculator.dart | 36 ++ .../xml/students_xml_bmi_calculator.dart | 33 ++ .../template_method/student.dart | 14 + .../students_bmi_calculator.dart | 33 ++ .../flutter-design-patterns/lib/router.dart | 8 + .../template_method/students_data_table.dart | 76 ++++ .../template_method/students_section.dart | 63 ++++ .../template_method_example.dart | 37 ++ .../flutter-design-patterns/pubspec.yaml | 1 + 16 files changed, 739 insertions(+), 1 deletion(-) create mode 100644 dart/community/flutter-design-patterns/assets/images/template_method/template_method.png create mode 100644 dart/community/flutter-design-patterns/assets/images/template_method/template_method_implementation.png create mode 100644 dart/community/flutter-design-patterns/assets/markdown/template-method.md create mode 100644 dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/json_students_api.dart create mode 100644 dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/xml_students_api.dart create mode 100644 dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/students_json_bmi_calculator.dart create mode 100644 dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/teenage_students_json_bmi_calculator.dart create mode 100644 dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/xml/students_xml_bmi_calculator.dart create mode 100644 dart/community/flutter-design-patterns/lib/design_patterns/template_method/student.dart create mode 100644 dart/community/flutter-design-patterns/lib/design_patterns/template_method/students_bmi_calculator.dart create mode 100644 dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_data_table.dart create mode 100644 dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_section.dart create mode 100644 dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/template_method_example.dart diff --git a/dart/community/flutter-design-patterns/assets/data/design_patterns.json b/dart/community/flutter-design-patterns/assets/data/design_patterns.json index b9f1cff86..091db8f31 100644 --- a/dart/community/flutter-design-patterns/assets/data/design_patterns.json +++ b/dart/community/flutter-design-patterns/assets/data/design_patterns.json @@ -29,6 +29,13 @@ "id": "behavioral", "title": "Behavioral", "color": "0xFFF78888", - "patterns": [] + "patterns": [ + { + "id": "template-method", + "title": "Template Method", + "description": "Define the skeleton of an algorithm in an operation, deferring some steps to sub­classes. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.", + "route": "/template-method" + } + ] } ] diff --git a/dart/community/flutter-design-patterns/assets/images/template_method/template_method.png b/dart/community/flutter-design-patterns/assets/images/template_method/template_method.png new file mode 100644 index 0000000000000000000000000000000000000000..897d37b843e5c8f3443820ea658a5c73161dc68d GIT binary patch literal 12604 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 Ip2@5K4@7Xf!2kdN literal 0 HcmV?d00001 diff --git a/dart/community/flutter-design-patterns/assets/markdown/template-method.md b/dart/community/flutter-design-patterns/assets/markdown/template-method.md new file mode 100644 index 000000000..6c5331ab6 --- /dev/null +++ b/dart/community/flutter-design-patterns/assets/markdown/template-method.md @@ -0,0 +1,331 @@ +## Class diagram + +![Template Method Class Diagram](resource:assets/images/template_method/template_method.png) + +## Implementation + +### Class diagram + +The class diagram below shows the implementation of **Template Method** design pattern. + +![Template Method Implementation Class Diagram](resource:assets/images/template_method/template_method_implementation.png) + +The main class in the diagram is _StudentsBmiCalculator_. Its primary purpose is to define a template of the BMI calculation algorithm which returns a list of _Student_ objects (with the calculated BMI for each student) as a result via _calculateBmiAndReturnStudentList()_ method. This abstract class is used as a template (base class) for the concrete implementations of the students' BMI calculation algorithm - _StudentsXmlBmiCalculator_, _StudentsJsonBmiCalculator_ and _TeenageStudentsJsonBmiCalculator_. _StudentsXmlBmiCalculator_ uses the _XmlStudentsApi_ to retrieve students information as an XML string and returns it as a list of _Student_ objects via the overridden _getStudentsData()_ method. Both of the other two implementations (_StudentsJsonBmiCalculator_ and _TeenageStudentsJsonBmiCalculator_) uses the _JsonStudentsApi_ to retrieve students information in JSON format and returns the parsed data via the overridden _getStudentsData()_ method. However, _TeenageStudentsJsonBmiCalculator_ additionally reimplements (overrides) the _doStudentsFiltering()_ hook method to filter out not teenage students before calculating the BMI values. _StudentsSection_ UI widget uses the _StudentsBmiCalculator_ abstraction to retrieve and represent the calculated results in _TemplateMethodExample_ widget. + +### StudentsBmiCalculator + +An abstract (template) class for the BMI calculation algorithm. The algorithm consists of several steps: + +1. Retrieve students data - _getStudentsData()_; +2. Do students filtering (if needed) - _doStudentsFiltering()_; +3. Calculate the BMI for each student - _\_calculateStudentsBmi()_; +4. Return students data - _return studentList_. + +The first step is mandatory and should be implemented in each concrete implementation of the students BMI calculator - that is, the method _getStudentsData()_ is abstract and must be overridden in the derived class. Students filtering step is optional, yet it could be overridden in the derived class. For this reason, _doStudentsFiltering()_ method has a default implementation which does not change the workflow of the algorithm by default - this kind of method is called a **hook** method. Other steps are defined in the algorithm's template itself, are common for all implementations and could not be changed. + +``` +abstract class StudentsBmiCalculator { + List calculateBmiAndReturnStudentList() { + var studentList = getStudentsData(); + studentList = doStudentsFiltering(studentList); + _calculateStudentsBmi(studentList); + return studentList; + } + + void _calculateStudentsBmi(List studentList) { + for (var student in studentList) { + student.bmi = _calculateBmi(student.height, student.weight); + } + } + + double _calculateBmi(double height, int weight) { + return weight / math.pow(height, 2); + } + + // Hook methods + @protected + List doStudentsFiltering(List studentList) { + return studentList; + } + + // Abstract methods + @protected + List getStudentsData(); +} +``` + +### StudentsXmlBmiCalculator + +A concrete implementation of the BMI calculation algorithm which uses _XmlStudentsApi_ to retrieve data and implements the _getStudentsData()_ method. + +``` +class StudentsXmlBmiCalculator extends StudentsBmiCalculator { + final XmlStudentsApi _api = XmlStudentsApi(); + + @override + @protected + List getStudentsData() { + var studentsXml = _api.getStudentsXml(); + var xmlDocument = xml.parse(studentsXml); + var studentsList = List(); + + for (var xmlElement in xmlDocument.findAllElements('student')) { + var fullName = xmlElement.findElements('fullname').single.text; + var age = int.parse(xmlElement.findElements('age').single.text); + var height = double.parse(xmlElement.findElements('height').single.text); + var weight = int.parse(xmlElement.findElements('weight').single.text); + + studentsList.add(Student( + fullName: fullName, + age: age, + height: height, + weight: weight, + )); + } + + return studentsList; + } +} +``` + +### StudentsJsonBmiCalculator + +A concrete implementation of the BMI calculation algorithm which uses _JsonStudentsApi_ to retrieve data and implements the _getStudentsData()_ method. + +``` +class StudentsJsonBmiCalculator extends StudentsBmiCalculator { + final JsonStudentsApi _api = JsonStudentsApi(); + + @override + @protected + List getStudentsData() { + var studentsJson = _api.getStudentsJson(); + var studentsMap = json.decode(studentsJson) as Map; + var studentsJsonList = studentsMap['students'] as List; + var studentsList = studentsJsonList + .map((json) => Student( + fullName: json['fullName'], + age: json['age'], + height: json['height'], + weight: json['weight'], + )) + .toList(); + + return studentsList; + } +} +``` + +### TeenageStudentsJsonBmiCalculator + +A concrete implementation of the BMI calculation algorithm which uses _JsonStudentsApi_ to retrieve data and implements the _getStudentsData()_ method. Additionally, the _doStudentsFiltering()_ hook method is overridden to filter out not teenage students. + +``` +class TeenageStudentsJsonBmiCalculator extends StudentsBmiCalculator { + final JsonStudentsApi _api = JsonStudentsApi(); + + @override + @protected + List getStudentsData() { + var studentsJson = _api.getStudentsJson(); + var studentsMap = json.decode(studentsJson) as Map; + var studentsJsonList = studentsMap['students'] as List; + var studentsList = studentsJsonList + .map((json) => Student( + fullName: json['fullName'], + age: json['age'], + height: json['height'], + weight: json['weight'], + )) + .toList(); + + return studentsList; + } + + @override + @protected + List doStudentsFiltering(List studentList) { + return studentList + .where((student) => student.age > 12 && student.age < 20) + .toList(); + } +} +``` + +### Student + +A simple class to store the student's information. + +``` +class Student { + final String fullName; + final int age; + final double height; + final int weight; + double bmi; + + Student({ + this.fullName, + this.age, + this.height, + this.weight, + }); +} +``` + +### JsonStudentsApi + +A fake API which returns students' information as JSON string. + +``` +class JsonStudentsApi { + final String _studentsJson = ''' + { + "students": [ + { + "fullName": "John Doe (JSON)", + "age": 12, + "height": 1.62, + "weight": 53 + }, + { + "fullName": "Emma Doe (JSON)", + "age": 15, + "height": 1.55, + "weight": 50 + }, + { + "fullName": "Michael Roe (JSON)", + "age": 18, + "height": 1.85, + "weight": 89 + }, + { + "fullName": "Emma Roe (JSON)", + "age": 20, + "height": 1.66, + "weight": 79 + } + ] + } + '''; + + String getStudentsJson() { + return _studentsJson; + } +} +``` + +### XmlStudentsApi + +A fake API which returns students' information as an XML string. + +``` +class XmlStudentsApi { + final String _studentsXml = ''' + + + + John Doe (XML) + 12 + 1.62 + 53 + + + Emma Doe (XML) + 15 + 1.55 + 50 + + + Michael Roe (XML) + 18 + 1.85 + 89 + + + Emma Roe (XML) + 20 + 1.66 + 79 + + + '''; + + String getStudentsXml() { + return _studentsXml; + } +} +``` + +### Example + +- TemplateMethodExample - implements the example widget. This widget uses _StudentsSection_ component which requires a specific BMI calculator of type _StudentsBmiCalculator_ to be provided via a constructor. For this example, we inject three different implementations of BMI calculator (_StudentsXmlBmiCalculator_, _StudentsJsonBmiCalculator_ and _TeenageStudentsJsonBmiCalculator_) which extend the same template (base class) - _StudentsBmiCalculator_ - to three different _StudentsSection_ widgets. + +``` +class TemplateMethodExample extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ScrollConfiguration( + behavior: ScrollBehavior(), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: paddingL), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StudentsSection( + bmiCalculator: StudentsXmlBmiCalculator(), + headerText: 'Students from XML data source:', + ), + const SizedBox(height: spaceL), + StudentsSection( + bmiCalculator: StudentsJsonBmiCalculator(), + headerText: 'Students from JSON data source:', + ), + const SizedBox(height: spaceL), + StudentsSection( + bmiCalculator: TeenageStudentsJsonBmiCalculator(), + headerText: 'Students from JSON data source (teenagers only):', + ), + ], + ), + ), + ); + } +} +``` + +- StudentsSection - uses the injected BMI calculator of type _StudentsBmiCalculator_. The widget does not care about the specific implementation of the BMI calculator as long as it uses (extends) the same template (base class). This lets us provide different students' BMI calculation algorithms/implementations without making any changes to the UI code. + +``` +class StudentsSection extends StatefulWidget { + final StudentsBmiCalculator bmiCalculator; + final String headerText; + + const StudentsSection({ + @required this.bmiCalculator, + @required this.headerText, + }) : assert(bmiCalculator != null), + assert(headerText != null); + + @override + _StudentsSectionState createState() => _StudentsSectionState(); +} + +class _StudentsSectionState extends State { + final List students = List(); + + void _calculateBmiAndGetStudentsData() { + setState(() { + students.addAll(widget.bmiCalculator.calculateBmiAndReturnStudentList()); + }); + } + + @override + Widget build(BuildContext context) { + return Column( + ... + ); + } +} +``` diff --git a/dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/json_students_api.dart b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/json_students_api.dart new file mode 100644 index 000000000..7dfd14499 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/json_students_api.dart @@ -0,0 +1,36 @@ +class JsonStudentsApi { + final String _studentsJson = ''' + { + "students": [ + { + "fullName": "John Doe (JSON)", + "age": 12, + "height": 1.62, + "weight": 53 + }, + { + "fullName": "Emma Doe (JSON)", + "age": 15, + "height": 1.55, + "weight": 50 + }, + { + "fullName": "Michael Roe (JSON)", + "age": 18, + "height": 1.85, + "weight": 89 + }, + { + "fullName": "Emma Roe (JSON)", + "age": 20, + "height": 1.66, + "weight": 79 + } + ] + } + '''; + + String getStudentsJson() { + return _studentsJson; + } +} diff --git a/dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/xml_students_api.dart b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/xml_students_api.dart new file mode 100644 index 000000000..a3b24a452 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/apis/xml_students_api.dart @@ -0,0 +1,35 @@ +class XmlStudentsApi { + final String _studentsXml = ''' + + + + John Doe (XML) + 12 + 1.62 + 53 + + + Emma Doe (XML) + 15 + 1.55 + 50 + + + Michael Roe (XML) + 18 + 1.85 + 89 + + + Emma Roe (XML) + 20 + 1.66 + 79 + + + '''; + + String getStudentsXml() { + return _studentsXml; + } +} diff --git a/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/students_json_bmi_calculator.dart b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/students_json_bmi_calculator.dart new file mode 100644 index 000000000..3f873f5e7 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/students_json_bmi_calculator.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'package:flutter_design_patterns/design_patterns/template_method/apis/json_students_api.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/student.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/students_bmi_calculator.dart'; +import 'package:meta/meta.dart'; + +class StudentsJsonBmiCalculator extends StudentsBmiCalculator { + final JsonStudentsApi _api = JsonStudentsApi(); + + @override + @protected + List getStudentsData() { + var studentsJson = _api.getStudentsJson(); + var studentsMap = json.decode(studentsJson) as Map; + var studentsJsonList = studentsMap['students'] as List; + var studentsList = studentsJsonList + .map((json) => Student( + fullName: json['fullName'], + age: json['age'], + height: json['height'], + weight: json['weight'], + )) + .toList(); + + return studentsList; + } +} diff --git a/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/teenage_students_json_bmi_calculator.dart b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/teenage_students_json_bmi_calculator.dart new file mode 100644 index 000000000..bccb02d34 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/json/teenage_students_json_bmi_calculator.dart @@ -0,0 +1,36 @@ +import 'dart:convert'; + +import 'package:flutter_design_patterns/design_patterns/template_method/apis/json_students_api.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/student.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/students_bmi_calculator.dart'; +import 'package:meta/meta.dart'; + +class TeenageStudentsJsonBmiCalculator extends StudentsBmiCalculator { + final JsonStudentsApi _api = JsonStudentsApi(); + + @override + @protected + List getStudentsData() { + var studentsJson = _api.getStudentsJson(); + var studentsMap = json.decode(studentsJson) as Map; + var studentsJsonList = studentsMap['students'] as List; + var studentsList = studentsJsonList + .map((json) => Student( + fullName: json['fullName'], + age: json['age'], + height: json['height'], + weight: json['weight'], + )) + .toList(); + + return studentsList; + } + + @override + @protected + List doStudentsFiltering(List studentList) { + return studentList + .where((student) => student.age > 12 && student.age < 20) + .toList(); + } +} diff --git a/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/xml/students_xml_bmi_calculator.dart b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/xml/students_xml_bmi_calculator.dart new file mode 100644 index 000000000..675b47d69 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/bmi_calculators/xml/students_xml_bmi_calculator.dart @@ -0,0 +1,33 @@ +import 'package:flutter_design_patterns/design_patterns/template_method/apis/xml_students_api.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/student.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/students_bmi_calculator.dart'; +import 'package:meta/meta.dart'; +import 'package:xml/xml.dart' as xml; + +class StudentsXmlBmiCalculator extends StudentsBmiCalculator { + final XmlStudentsApi _api = XmlStudentsApi(); + + @override + @protected + List getStudentsData() { + var studentsXml = _api.getStudentsXml(); + var xmlDocument = xml.parse(studentsXml); + var studentsList = List(); + + for (var xmlElement in xmlDocument.findAllElements('student')) { + var fullName = xmlElement.findElements('fullname').single.text; + var age = int.parse(xmlElement.findElements('age').single.text); + var height = double.parse(xmlElement.findElements('height').single.text); + var weight = int.parse(xmlElement.findElements('weight').single.text); + + studentsList.add(Student( + fullName: fullName, + age: age, + height: height, + weight: weight, + )); + } + + return studentsList; + } +} diff --git a/dart/community/flutter-design-patterns/lib/design_patterns/template_method/student.dart b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/student.dart new file mode 100644 index 000000000..4be599fb6 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/student.dart @@ -0,0 +1,14 @@ +class Student { + final String fullName; + final int age; + final double height; + final int weight; + double bmi; + + Student({ + this.fullName, + this.age, + this.height, + this.weight, + }); +} diff --git a/dart/community/flutter-design-patterns/lib/design_patterns/template_method/students_bmi_calculator.dart b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/students_bmi_calculator.dart new file mode 100644 index 000000000..261cf03c1 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/design_patterns/template_method/students_bmi_calculator.dart @@ -0,0 +1,33 @@ +import 'dart:math' as math; + +import 'package:flutter_design_patterns/design_patterns/template_method/student.dart'; +import 'package:meta/meta.dart'; + +abstract class StudentsBmiCalculator { + List calculateBmiAndReturnStudentList() { + var studentList = getStudentsData(); + studentList = doStudentsFiltering(studentList); + _calculateStudentsBmi(studentList); + return studentList; + } + + void _calculateStudentsBmi(List studentList) { + for (var student in studentList) { + student.bmi = _calculateBmi(student.height, student.weight); + } + } + + double _calculateBmi(double height, int weight) { + return weight / math.pow(height, 2); + } + + // Hook methods + @protected + List doStudentsFiltering(List studentList) { + return studentList; + } + + // Abstract methods + @protected + List getStudentsData(); +} diff --git a/dart/community/flutter-design-patterns/lib/router.dart b/dart/community/flutter-design-patterns/lib/router.dart index 7aadaeedf..0f8faf071 100644 --- a/dart/community/flutter-design-patterns/lib/router.dart +++ b/dart/community/flutter-design-patterns/lib/router.dart @@ -7,6 +7,7 @@ import 'package:flutter_design_patterns/screens/design_pattern_details/design_pa import 'package:flutter_design_patterns/screens/main_menu/main_menu.dart'; import 'package:flutter_design_patterns/widgets/design_patterns/adapter/adapter_example.dart'; import 'package:flutter_design_patterns/widgets/design_patterns/singleton/singleton_example.dart'; +import 'package:flutter_design_patterns/widgets/design_patterns/template_method/template_method_example.dart'; class Router { static Route generateRoute(RouteSettings settings) { @@ -34,6 +35,12 @@ class Router { settings, AdapterExample(), ); + // Behavioral + case _DesignPatternRoutes.templateMethodRoute: + return _buildDesignPatternDetailsPageRoute( + settings, + TemplateMethodExample(), + ); default: return MaterialPageRoute( builder: (_) => MainMenu(), @@ -58,4 +65,5 @@ class Router { class _DesignPatternRoutes { static const String singletonRoute = '/singleton'; static const String adapterRoute = '/adapter'; + static const String templateMethodRoute = '/template-method'; } diff --git a/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_data_table.dart b/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_data_table.dart new file mode 100644 index 000000000..55bde35f9 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_data_table.dart @@ -0,0 +1,76 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_design_patterns/constants.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/student.dart'; + +class StudentsDataTable extends StatelessWidget { + final List students; + + const StudentsDataTable({@required this.students}) : assert(students != null); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: DataTable( + columnSpacing: spaceM, + horizontalMargin: marginM, + headingRowHeight: spaceXL, + dataRowHeight: spaceXL, + columns: [ + DataColumn( + label: Text( + 'Name', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), + ), + ), + DataColumn( + label: Text( + 'Age', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), + ), + numeric: true, + ), + DataColumn( + label: Text( + 'Height', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), + ), + numeric: true, + ), + DataColumn( + label: Text( + 'Weight', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), + ), + numeric: true, + ), + DataColumn( + label: Text( + 'BMI', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), + ), + numeric: true, + ), + ], + rows: [ + for (var student in students) + DataRow( + cells: [ + DataCell(Text(student.fullName)), + DataCell(Text(student.age.toString())), + DataCell(Text(student.height.toString())), + DataCell(Text(student.weight.toString())), + DataCell( + Text( + student.bmi.toStringAsFixed(2), + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_section.dart b/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_section.dart new file mode 100644 index 000000000..8162f57cc --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/students_section.dart @@ -0,0 +1,63 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_design_patterns/constants.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/student.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/students_bmi_calculator.dart'; +import 'package:flutter_design_patterns/widgets/design_patterns/template_method/students_data_table.dart'; +import 'package:flutter_design_patterns/widgets/platform_specific/platform_button.dart'; + +class StudentsSection extends StatefulWidget { + final StudentsBmiCalculator bmiCalculator; + final String headerText; + + const StudentsSection({ + @required this.bmiCalculator, + @required this.headerText, + }) : assert(bmiCalculator != null), + assert(headerText != null); + + @override + _StudentsSectionState createState() => _StudentsSectionState(); +} + +class _StudentsSectionState extends State { + final List students = List(); + + void _calculateBmiAndGetStudentsData() { + setState(() { + students.addAll(widget.bmiCalculator.calculateBmiAndReturnStudentList()); + }); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.headerText), + const SizedBox(height: spaceM), + Stack( + children: [ + AnimatedOpacity( + duration: const Duration(milliseconds: 500), + opacity: students.length > 0 ? 1.0 : 0.0, + child: StudentsDataTable( + students: students, + ), + ), + AnimatedOpacity( + duration: const Duration(milliseconds: 250), + opacity: students.length == 0 ? 1.0 : 0.0, + child: PlatformButton( + child: Text('Calculate BMI and get students\' data'), + materialColor: Colors.black, + materialTextColor: Colors.white, + onPressed: _calculateBmiAndGetStudentsData, + ), + ), + ], + ), + ], + ); + } +} diff --git a/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/template_method_example.dart b/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/template_method_example.dart new file mode 100644 index 000000000..f8943e324 --- /dev/null +++ b/dart/community/flutter-design-patterns/lib/widgets/design_patterns/template_method/template_method_example.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_design_patterns/constants.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/bmi_calculators/json/students_json_bmi_calculator.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/bmi_calculators/json/teenage_students_json_bmi_calculator.dart'; +import 'package:flutter_design_patterns/design_patterns/template_method/bmi_calculators/xml/students_xml_bmi_calculator.dart'; +import 'package:flutter_design_patterns/widgets/design_patterns/template_method/students_section.dart'; + +class TemplateMethodExample extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ScrollConfiguration( + behavior: ScrollBehavior(), + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: paddingL), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StudentsSection( + bmiCalculator: StudentsXmlBmiCalculator(), + headerText: 'Students from XML data source:', + ), + const SizedBox(height: spaceL), + StudentsSection( + bmiCalculator: StudentsJsonBmiCalculator(), + headerText: 'Students from JSON data source:', + ), + const SizedBox(height: spaceL), + StudentsSection( + bmiCalculator: TeenageStudentsJsonBmiCalculator(), + headerText: 'Students from JSON data source (teenagers only):', + ), + ], + ), + ), + ); + } +} diff --git a/dart/community/flutter-design-patterns/pubspec.yaml b/dart/community/flutter-design-patterns/pubspec.yaml index b5c644705..e705e7208 100644 --- a/dart/community/flutter-design-patterns/pubspec.yaml +++ b/dart/community/flutter-design-patterns/pubspec.yaml @@ -27,6 +27,7 @@ flutter: - assets/markdown/ - assets/images/singleton/ - assets/images/adapter/ + - assets/images/template_method/ fonts: - family: Roboto fonts: