Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.dart_tool/
.idea/
build/
windows/

.packages
pubspec.lock
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.15.0
- Add second "Observer" example. This example was created to be used in a more complex example.

## 0.14.0
- Add "Memento" conceptual pattern

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ It contains **Dart** examples for all classic **GoF** design patterns.
- [ ] Interpreter
- [ ] [**Iterator**](https://refactoring.guru/design-patterns/iterator)
- [ ] [**Mediator**](https://refactoring.guru/design-patterns/mediator)
- [x] [**Memento**](https://refactoring.guru/design-patterns/memento) [[Conceptual](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/memento/conceptual)]
- [x] [**Observer**](https://refactoring.guru/design-patterns/observer) - [[Open-Close Editor Events](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/observer/open_close_editor_events)]
- [x] [**Memento**](https://refactoring.guru/design-patterns/memento) - [[Conceptual](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/memento/conceptual)]
- [x] [**Observer**](https://refactoring.guru/design-patterns/observer) - [[Open-Close Editor Events](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/observer/open_close_editor_events)] [[AppObserver](https://github.com/RefactoringGuru/design-patterns-dart/tree/master/patterns/observer/app_observer)]
- [ ] [**State**](https://refactoring.guru/design-patterns/state)
- [ ] [**Template Method**](https://refactoring.guru/design-patterns/template-method)
- [ ] [**Visitor**](https://refactoring.guru/design-patterns/visitor)
Expand Down
20 changes: 20 additions & 0 deletions bin/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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(
title: 'Refactoring Guru: Flutter launcher',
theme: ThemeData(
primarySwatch: Colors.pink
),
home: Container(),
);
}
}
56 changes: 56 additions & 0 deletions patterns/observer/app_observer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Observer pattern
Observer is a behavioral design pattern that lets you define a subscription mechanism to notify
multiple objects about any events that happen to the object they’re observing.

Tutorial: [here](https://refactoring.guru/design-patterns/observer).

## AppObserver example
This example was created to be used in a more complex example.
A complex example will be implemented later.

### Diagram:
![image](https://user-images.githubusercontent.com/8049534/152049751-b111e02a-1d33-4796-810c-b7ed069cecdc.png)

### Sequence
![image](https://user-images.githubusercontent.com/8049534/152049996-72131655-402d-4b92-b5d0-10e3f2dd0e79.png)

### Client code:
```dart
void main() {
final observer = AppObserver();

observer.subscribe<FirstEvent>((e) {
print('First');
});


observer.subscribe((SecondEvent e) {
print('Second');
});

final saveThirdEvent = observer.subscribe((ThirdEvent e) {
print('Third');
});

observer.notify(FirstEvent());
observer.notify(SecondEvent());
observer.notify(ThirdEvent());

print('---unsubscribe "ThirdEvent"---');
observer.unsubscribe(saveThirdEvent);

observer.notify(FirstEvent());
observer.notify(SecondEvent());
observer.notify(ThirdEvent());
}
```

**Output:**
```
First
Second
Third
---unsubscribe "ThirdEvent"---
First
Second
```
36 changes: 36 additions & 0 deletions patterns/observer/app_observer/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'observer/app_observer.dart';
import 'observer/event.dart';

class FirstEvent extends Event {}

class SecondEvent extends Event {}

class ThirdEvent extends Event {}

void main() {
final observer = AppObserver();

observer.subscribe<FirstEvent>((e) {
print('First');
});


observer.subscribe((SecondEvent e) {
print('Second');
});

final saveThirdEvent = observer.subscribe((ThirdEvent e) {
print('Third');
});

observer.notify(FirstEvent());
observer.notify(SecondEvent());
observer.notify(ThirdEvent());

print('---unsubscribe "ThirdEvent"---');
observer.unsubscribe(saveThirdEvent);

observer.notify(FirstEvent());
observer.notify(SecondEvent());
observer.notify(ThirdEvent());
}
46 changes: 46 additions & 0 deletions patterns/observer/app_observer/observer/app_observer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'event.dart';

typedef EventFunction<T> = void Function(T e);

class AppObserver {
final _subscribers = <int, List<dynamic>>{};

EventFunction<T> subscribe<T>(EventFunction<T> func) {
assert(
Class<T>() is Class<Event>,
'\n\nThe callback argument must implement the "Event" class.\n'
'Correct use: \n'
'\tobserver.subscribe((MyEvent e) {}); \n'
'Mistaken usage: \n'
'\tobserver.subscribe((String e) {});\n'
'\tobserver.subscribe((e) {});\n',
);

_subscribers.putIfAbsent(T.hashCode, () => []).add(func);
return func;
}

void unsubscribe<T extends Event>(EventFunction<T> func) {
final isDelete = _subscribers[T.hashCode]?.remove(func) ?? false;

if (isDelete) {
return;
}

throw Exception('Subscriber not found.');
}

void notify<T extends Event>(T event) {
final subscribers = _subscribers[T.hashCode];

if (subscribers == null) {
return;
}

for (var sub in subscribers) {
sub.call(event);
}
}
}

class Class<T> {}
3 changes: 3 additions & 0 deletions patterns/observer/app_observer/observer/event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
abstract class Event {

}
6 changes: 5 additions & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: design_patterns_dart
description: Dart examples for all classic GoF design patterns.
version: 0.14.0
version: 0.15.0
homepage: https://refactoring.guru/design-patterns
repository: https://github.com/RefactoringGuru/design-patterns-dart
issue_tracker: https://github.com/RefactoringGuru/design-patterns-dart/issue
Expand All @@ -10,6 +10,10 @@ environment:

dependencies:
collection: ^1.15.0
flutter:
sdk: flutter

dev_dependencies:
lints: ^1.0.0


Binary file added web/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/icons/Icon-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/icons/Icon-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/icons/Icon-maskable-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/icons/Icon-maskable-512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.

The path provided below has to start and end with a slash "/" in order for
it to work correctly.

For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">

<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">

<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_launcher">
<link rel="apple-touch-icon" href="icons/Icon-192.png">

<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>

<title>flutter_launcher</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<script>
var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
if (scriptLoaded) {
return;
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
scriptTag.src = 'main.dart.js';
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}

if ('serviceWorker' in navigator) {
// Service workers are supported. Use them.
window.addEventListener('load', function () {
// Wait for registration to finish before dropping the <script> tag.
// Otherwise, the browser will load the script multiple times,
// potentially different versions.
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
navigator.serviceWorker.register(serviceWorkerUrl)
.then((reg) => {
function waitForActivation(serviceWorker) {
serviceWorker.addEventListener('statechange', () => {
if (serviceWorker.state == 'activated') {
console.log('Installed new service worker.');
loadMainDartJs();
}
});
}
if (!reg.active && (reg.installing || reg.waiting)) {
// No active web worker and we have installed or are installing
// one for the first time. Simply wait for it to activate.
waitForActivation(reg.installing || reg.waiting);
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
// When the app updates the serviceWorkerVersion changes, so we
// need to ask the service worker to update.
console.log('New service worker available.');
reg.update();
waitForActivation(reg.installing);
} else {
// Existing service worker is still good.
console.log('Loading app from service worker.');
loadMainDartJs();
}
});

// If service worker doesn't succeed in a reasonable amount of time,
// fallback to plaint <script> tag.
setTimeout(() => {
if (!scriptLoaded) {
console.warn(
'Failed to load app from service worker. Falling back to plain <script> tag.',
);
loadMainDartJs();
}
}, 4000);
});
} else {
// Service workers not supported. Just drop the <script> tag.
loadMainDartJs();
}
</script>
</body>
</html>
35 changes: 35 additions & 0 deletions web/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "flutter_launcher",
"short_name": "flutter_launcher",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}