Skip to content

Commit 9f58fa6

Browse files
authored
Merge pull request #26 from ilopX/add-flutter-adapter-v3
Add flutter adapter example.
2 parents 1843a38 + e17aa2f commit 9f58fa6

File tree

17 files changed

+512
-5
lines changed

17 files changed

+512
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.17.0
2+
Add "Adapter" pattern: adapt a non-reactive classic type application for Flutter.
3+
4+
## 0.16.5
5+
Add deploy_flutter_demos script.
6+
17
## 0.16.0
28
- Add complex example of an Observer pattern, connected to a Flutter application.
39
- Add new branch "web-demos" for online examples.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ It contains **Dart** examples for all classic **GoF** design patterns.
2222
- [ ] [**Visitor**](https://refactoring.guru/design-patterns/visitor)
2323
- [ ] [**Strategy**](https://refactoring.guru/design-patterns/strategy)
2424
- [ ] **Structural**
25-
- [x] [**Adapter**](https://refactoring.guru/design-patterns/adapter) - [[Text Graphics](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/adapter/text_graphics)] [[Square Round conflict](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/adapter/square_round_conflict)]
25+
- [x] [**Adapter**](https://refactoring.guru/design-patterns/adapter) - [[Text Graphics](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/adapter/text_graphics)] [[Square Round conflict](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/adapter/square_round_conflict)] [[Flutter Adapter](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/adapter/flutter_adapter)]
2626
- [x] [**Bridge**](https://refactoring.guru/design-patterns/bridge) - [[Remote Device Control](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/bridge/devices_remote_control)] [[Clock](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/bridge/clock)]
2727
- [x] [**Composite**](https://refactoring.guru/design-patterns/composite) - [[Image Editor](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/composite/image_editor)] [[Products and Boxes](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/composite/products_and_boxes)]
2828
- [x] [**Decorator**](https://refactoring.guru/design-patterns/decorator) - [[Data Source Decoder](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/decorator/data_source_decoder)]

bin/main.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import 'package:flutter/material.dart';
22
import '../patterns/observer/subscriber_flutter_widget/main.dart';
3+
import '../patterns/adapter/flutter_adapter/main.dart';
34

45
void main() {
56
runApp(MyApp());
67
}
78

89
class MyApp extends StatelessWidget {
9-
const MyApp({Key? key}) : super(key: key);
10-
1110
@override
1211
Widget build(BuildContext context) {
1312
return MaterialApp(
1413
title: 'Refactoring Guru: Flutter launcher',
1514
theme: ThemeData(primarySwatch: Colors.pink),
16-
initialRoute: '/observer/subscriber_flutter_widget',
15+
initialRoute: '/adapter/flutter_adapter',
1716
routes: {
1817
'/observer/subscriber_flutter_widget': (_) => SubscriberFlutterApp(),
18+
'/adapter/flutter_adapter': (_) => FlutterAdapterApp(),
1919
},
2020
);
2121
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
# Adapter pattern
3+
Adapter is a structural design pattern that allows objects with incompatible interfaces to collaborate.
4+
5+
Tutorial: [here](https://refactoring.guru/design-patterns/observer).
6+
7+
## Example: Flutter classic application adapter widget
8+
This example shows how to adapt a non-reactive classic type application for Flutter.
9+
10+
### Dependency
11+
This complex example includes these implementations:
12+
- [[AppObserver](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/observer/app_observer)]
13+
- [[SubscriberWidget](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/observer/subscriber_flutter_widget)]
14+
15+
### Online demo:
16+
Click on the picture to see a [demo](https://refactoringguru.github.io/design-patterns-dart/#/adapter/flutter_adapter).
17+
[![image](https://user-images.githubusercontent.com/8049534/152689272-d4bed484-e216-4eda-8833-928ada7d4051.png)](https://refactoringguru.github.io/design-patterns-dart/#/adapter/flutter_adapter)
18+
19+
### Diagram:
20+
![image](https://user-images.githubusercontent.com/8049534/152753162-1b9006ad-a633-4132-91b6-bb348559adec.png)
21+
22+
### Sequence [Classic application -> Change Text color]
23+
When user clicked to "Flutter Adapter" text.
24+
25+
![image](https://user-images.githubusercontent.com/8049534/152753714-84af5abd-85c0-4845-af2d-616f512ef633.png)
26+
27+
### Sequence [Flutter Widget -> Change Text color]
28+
When the user has selected a color in the color bar.
29+
30+
![image](https://user-images.githubusercontent.com/8049534/152753870-edeab3ae-8e79-4e9d-9049-7cd5a2100afa.png)
31+
32+
### Client code:
33+
```dart
34+
// classic application
35+
class App extends ClassicApp {
36+
@override
37+
void onPointerWheel(double deltaX, double deltaY) {
38+
// ...
39+
}
40+
41+
@override
42+
void onMouseDown() {
43+
// ...
44+
repaint();
45+
}
46+
47+
@override
48+
void onPaint(Canvas canvas, Size canvasSize) {
49+
//...
50+
}
51+
}
52+
53+
void main() {
54+
// adapting a classic application to a flutter widget tree
55+
ClassicAppAdapterWidget(
56+
classicApp: app,
57+
);
58+
}
59+
60+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:flutter/widgets.dart';
2+
3+
4+
import '../classic_app/classic_app.dart';
5+
import 'classic_app_render_object.dart';
6+
7+
class ClassicAppAdapterWidget extends LeafRenderObjectWidget {
8+
final ClassicApp classicApp;
9+
10+
ClassicAppAdapterWidget({required this.classicApp});
11+
12+
@override
13+
RenderObject createRenderObject(BuildContext context) {
14+
return ClassicAppRenderObject(classicApp);
15+
}
16+
17+
@override
18+
void updateRenderObject(
19+
BuildContext context,
20+
covariant ClassicAppRenderObject renderObject,
21+
) {
22+
renderObject.classicApp = classicApp;
23+
}
24+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import 'package:flutter/gestures.dart';
2+
import 'package:flutter/rendering.dart';
3+
4+
import '../classic_app/classic_app.dart';
5+
import '../classic_app/repaint_event.dart';
6+
7+
class ClassicAppRenderObject extends RenderBox {
8+
ClassicAppRenderObject(ClassicApp classicApp) {
9+
_classicApp = classicApp;
10+
_classicApp.events.subscribe(_clientAppRepaint);
11+
_isSubscribe = true;
12+
}
13+
14+
@override
15+
bool get isRepaintBoundary => true;
16+
17+
@override
18+
void performLayout() {
19+
size = Size(
20+
constraints.maxWidth == double.infinity ? 0 : constraints.maxWidth,
21+
constraints.maxHeight == double.infinity ? 0 : constraints.maxHeight,
22+
);
23+
}
24+
25+
@override
26+
void paint(PaintingContext context, Offset offset) {
27+
context.canvas.translate(offset.dx, offset.dy);
28+
context.canvas.clipRect(offset & size);
29+
_classicApp.onPaint(context.canvas, size);
30+
}
31+
32+
@override
33+
void dispose() {
34+
if (_isSubscribe) {
35+
_classicApp.events.unsubscribe(_clientAppRepaint);
36+
_isSubscribe = false;
37+
}
38+
super.dispose();
39+
}
40+
41+
late ClassicApp _classicApp;
42+
var _isSubscribe = false;
43+
44+
ClassicApp get classicApp => _classicApp;
45+
46+
set classicApp(ClassicApp newClassicApp) {
47+
if (newClassicApp == _classicApp) {
48+
return;
49+
}
50+
51+
if (_isSubscribe) {
52+
_classicApp.events.unsubscribe(_clientAppRepaint);
53+
_isSubscribe = false;
54+
}
55+
56+
_classicApp = newClassicApp;
57+
_classicApp.events.subscribe(_clientAppRepaint);
58+
_isSubscribe = true;
59+
}
60+
61+
void _clientAppRepaint(RepaintEvent e) => markNeedsPaint();
62+
63+
@override
64+
void handleEvent(PointerEvent event, covariant BoxHitTestEntry entry) {
65+
if (event is PointerHoverEvent || event is PointerMoveEvent) {
66+
} else if (event is PointerDownEvent) {
67+
if (event.buttons == kPrimaryMouseButton) {
68+
_classicApp.onMouseDown();
69+
} else if (event.buttons == kSecondaryMouseButton) {}
70+
else if (event.buttons == kMiddleMouseButton) {}
71+
} else if (event is PointerScrollEvent) {
72+
_classicApp.onPointerWheel(event.scrollDelta.dx, event.scrollDelta.dy);
73+
}
74+
}
75+
76+
@override
77+
bool hitTest(BoxHitTestResult result, {required Offset position}) {
78+
if (size.contains(position)) {
79+
result.add(BoxHitTestEntry(this, position));
80+
return true;
81+
}
82+
return false;
83+
}
84+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'dart:ui';
2+
3+
import '../../../observer/app_observer/observer/app_observer.dart';
4+
import 'repaint_event.dart';
5+
import 'repaint_compatible.dart';
6+
7+
abstract class ClassicApp implements RepaintCompatible {
8+
final events = AppObserver();
9+
10+
void onMouseDown() {}
11+
12+
void onPointerWheel(double deltaX, double deltaY) {}
13+
14+
@override
15+
void repaint() {
16+
events.notify(RepaintEvent());
17+
}
18+
19+
void onPaint(Canvas canvas, Size canvasSize);
20+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
abstract class RepaintCompatible {
2+
void repaint();
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import '../../../observer/app_observer/observer/event.dart';
2+
3+
class RepaintEvent extends Event {}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import 'dart:ui';
2+
3+
import '../classic_app/classic_app.dart';
4+
import 'business_rules/color_rules.dart';
5+
import 'business_rules/text_coloring.dart';
6+
7+
class App extends ClassicApp {
8+
late final TextColoring textColoring;
9+
late final ColorRules colorRules;
10+
11+
App() {
12+
textColoring = TextColoring(this);
13+
colorRules = ColorRules();
14+
}
15+
16+
@override
17+
void onPointerWheel(double deltaX, double deltaY) {
18+
textColoring.size += deltaY ~/ 10;
19+
}
20+
21+
@override
22+
void onMouseDown() {
23+
textColoring.color = colorRules.nextColor(textColoring.color);
24+
}
25+
26+
@override
27+
void onPaint(Canvas canvas, Size canvasSize) {
28+
paintText(
29+
canvas,
30+
'Flutter Adapter',
31+
canvasSize,
32+
textColoring.size.toDouble(),
33+
textColoring.color,
34+
);
35+
36+
paintText(
37+
canvas,
38+
'Click on the text to change the text color.\n'
39+
'Scroll the mouse wheel to change the text size.',
40+
canvasSize,
41+
16,
42+
Color(0xff848484),
43+
Offset(0, canvasSize.height - 50),
44+
);
45+
}
46+
}
47+
48+
void paintText(
49+
Canvas canvas,
50+
String text,
51+
Size boxSize,
52+
double textSize,
53+
Color color, [
54+
Offset? pos,
55+
]) {
56+
final builder = ParagraphBuilder(
57+
ParagraphStyle(
58+
textAlign: TextAlign.center,
59+
fontSize: textSize,
60+
),
61+
)
62+
..pushStyle(
63+
TextStyle(
64+
fontFamily: 'Arial',
65+
color: color,
66+
),
67+
)
68+
..addText(text);
69+
70+
final paragraph = builder.build()
71+
..layout(
72+
ParagraphConstraints(
73+
width: boxSize.width,
74+
),
75+
);
76+
77+
pos ??= Offset(0, (boxSize.height - paragraph.height) / 2);
78+
canvas.drawParagraph(paragraph, pos);
79+
}

0 commit comments

Comments
 (0)