1
0
mirror of https://github.com/flutter/samples.git synced 2026-03-22 04:17:50 +00:00

Add code sharing sample (#1444)

* code-sharing boilerplate

* initial commit of code-sharing logic

* documentation improvements

* added code-sharing to samples.yaml

* Updated counter UI to visually indicate communication with the server

* added code_sharing to CI

* CI changes to code_sharing

* fixed test with DI

* formatting

* added shared module to CI

* adds forgotten CI change
This commit is contained in:
Craig Labenz
2022-10-05 08:47:30 -07:00
committed by GitHub
parent 9d86093342
commit fc6222ebc8
152 changed files with 5219 additions and 1 deletions

View File

@@ -0,0 +1,9 @@
.dockerignore
Dockerfile
build/
.dart_tool/
.git/
.github/
.gitignore
.idea/
.packages

6
code_sharing/server/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build output.
build/

View File

@@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

View File

@@ -0,0 +1,20 @@
# Use latest stable channel SDK.
FROM dart:stable AS build
# Resolve app dependencies.
WORKDIR /app
COPY . .
RUN dart pub get
# AOT compile app.
RUN dart compile exe bin/server.dart -o bin/server
# Build minimal serving image from AOT-compiled `/server`
# and the pre-built AOT-runtime in the `/runtime/` directory of the base image.
FROM scratch
COPY --from=build /runtime/ /
COPY --from=build /app/bin/server /app/bin/
# Start server.
EXPOSE 8080
CMD ["/app/bin/server"]

View File

@@ -0,0 +1,49 @@
A server app built using [Shelf](https://pub.dev/packages/shelf),
configured to enable running with [Docker](https://www.docker.com/).
This sample code handles HTTP GET requests to `/` and `/echo/<message>`
# Running the sample
## Running with the Dart SDK
You can run the example with the [Dart SDK](https://dart.dev/get-dart)
like this:
```
$ dart run bin/server.dart
Server listening on port 8080
```
And then from a second terminal:
```
$ curl http://0.0.0.0:8080
Hello, World!
$ curl http://0.0.0.0:8080/echo/I_love_Dart
I_love_Dart
```
## Running with Docker
If you have [Docker Desktop](https://www.docker.com/get-started) installed, you
can build and run with the `docker` command:
```
$ docker build . -t myserver
$ docker run -it -p 8080:8080 myserver
Server listening on port 8080
```
And then from a second terminal:
```
$ curl http://0.0.0.0:8080
Hello, World!
$ curl http://0.0.0.0:8080/echo/I_love_Dart
I_love_Dart
```
You should see the logging printed in the first terminal:
```
2021-05-06T15:47:04.620417 0:00:00.000158 GET [200] /
2021-05-06T15:47:08.392928 0:00:00.001216 GET [200] /echo/I_love_Dart
```

View File

@@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,36 @@
import 'dart:convert';
import 'dart:io';
import 'package:shared/shared.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
int count = 0;
// Configure routes.
final _router = Router()
..post('/', _incrementHandler)
..get('/', _getValueHandler);
Future<Response> _incrementHandler(Request request) async {
final incr = Increment.fromJson(json.decode(await request.readAsString()));
count += incr.by;
return Response.ok(json.encode(Count(count).toJson()));
}
Response _getValueHandler(Request request) =>
Response.ok(json.encode(Count(count).toJson()));
void main(List<String> args) async {
// Use any available host or container IP (usually `0.0.0.0`).
final ip = InternetAddress.anyIPv4;
// Configure a pipeline that logs requests.
final handler = Pipeline().addMiddleware(logRequests()).addHandler(_router);
// For running in containers, we respect the PORT environment variable.
final port = int.parse(Platform.environment['PORT'] ?? '8080');
final server = await serve(handler, ip, port);
print('Server listening on port ${server.port}');
}

View File

@@ -0,0 +1,19 @@
name: server
description: A server app using the shelf package and Docker.
version: 1.0.0
publish_to: "none"
environment:
sdk: ">=2.18.1 <3.0.0"
dependencies:
args: ^2.0.0
shelf: ^1.1.0
shelf_router: ^1.0.0
shared:
path: ./shared
dev_dependencies:
http: ^0.13.0
lints: ^2.0.0
test: ^1.15.0

10
code_sharing/server/shared/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build outputs.
build/
# Omit committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock

View File

@@ -0,0 +1,3 @@
## 1.0.0
- Initial version.

View File

@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/developing-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.

View File

@@ -0,0 +1,29 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
analyzer:
exclude:
- "**/*.g.dart"
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options

View File

@@ -0,0 +1,4 @@
/// Common data models required by our client and server.
library shared;
export 'src/models.dart';

View File

@@ -0,0 +1,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'models.freezed.dart';
part 'models.g.dart';
@Freezed()
class Increment with _$Increment {
const factory Increment({required int by}) = _Increment;
factory Increment.fromJson(Map<String, dynamic> json) =>
_$IncrementFromJson(json);
}
@Freezed()
class Count with _$Count {
const factory Count(int value) = _Count;
factory Count.fromJson(Map<String, dynamic> json) => _$CountFromJson(json);
}

View File

@@ -0,0 +1,271 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'models.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
Increment _$IncrementFromJson(Map<String, dynamic> json) {
return _Increment.fromJson(json);
}
/// @nodoc
mixin _$Increment {
int get by => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$IncrementCopyWith<Increment> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IncrementCopyWith<$Res> {
factory $IncrementCopyWith(Increment value, $Res Function(Increment) then) =
_$IncrementCopyWithImpl<$Res>;
$Res call({int by});
}
/// @nodoc
class _$IncrementCopyWithImpl<$Res> implements $IncrementCopyWith<$Res> {
_$IncrementCopyWithImpl(this._value, this._then);
final Increment _value;
// ignore: unused_field
final $Res Function(Increment) _then;
@override
$Res call({
Object? by = freezed,
}) {
return _then(_value.copyWith(
by: by == freezed
? _value.by
: by // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
abstract class _$$_IncrementCopyWith<$Res> implements $IncrementCopyWith<$Res> {
factory _$$_IncrementCopyWith(
_$_Increment value, $Res Function(_$_Increment) then) =
__$$_IncrementCopyWithImpl<$Res>;
@override
$Res call({int by});
}
/// @nodoc
class __$$_IncrementCopyWithImpl<$Res> extends _$IncrementCopyWithImpl<$Res>
implements _$$_IncrementCopyWith<$Res> {
__$$_IncrementCopyWithImpl(
_$_Increment _value, $Res Function(_$_Increment) _then)
: super(_value, (v) => _then(v as _$_Increment));
@override
_$_Increment get _value => super._value as _$_Increment;
@override
$Res call({
Object? by = freezed,
}) {
return _then(_$_Increment(
by: by == freezed
? _value.by
: by // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_Increment implements _Increment {
const _$_Increment({required this.by});
factory _$_Increment.fromJson(Map<String, dynamic> json) =>
_$$_IncrementFromJson(json);
@override
final int by;
@override
String toString() {
return 'Increment(by: $by)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_Increment &&
const DeepCollectionEquality().equals(other.by, by));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(by));
@JsonKey(ignore: true)
@override
_$$_IncrementCopyWith<_$_Increment> get copyWith =>
__$$_IncrementCopyWithImpl<_$_Increment>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_IncrementToJson(
this,
);
}
}
abstract class _Increment implements Increment {
const factory _Increment({required final int by}) = _$_Increment;
factory _Increment.fromJson(Map<String, dynamic> json) =
_$_Increment.fromJson;
@override
int get by;
@override
@JsonKey(ignore: true)
_$$_IncrementCopyWith<_$_Increment> get copyWith =>
throw _privateConstructorUsedError;
}
Count _$CountFromJson(Map<String, dynamic> json) {
return _Count.fromJson(json);
}
/// @nodoc
mixin _$Count {
int get value => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$CountCopyWith<Count> get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $CountCopyWith<$Res> {
factory $CountCopyWith(Count value, $Res Function(Count) then) =
_$CountCopyWithImpl<$Res>;
$Res call({int value});
}
/// @nodoc
class _$CountCopyWithImpl<$Res> implements $CountCopyWith<$Res> {
_$CountCopyWithImpl(this._value, this._then);
final Count _value;
// ignore: unused_field
final $Res Function(Count) _then;
@override
$Res call({
Object? value = freezed,
}) {
return _then(_value.copyWith(
value: value == freezed
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
abstract class _$$_CountCopyWith<$Res> implements $CountCopyWith<$Res> {
factory _$$_CountCopyWith(_$_Count value, $Res Function(_$_Count) then) =
__$$_CountCopyWithImpl<$Res>;
@override
$Res call({int value});
}
/// @nodoc
class __$$_CountCopyWithImpl<$Res> extends _$CountCopyWithImpl<$Res>
implements _$$_CountCopyWith<$Res> {
__$$_CountCopyWithImpl(_$_Count _value, $Res Function(_$_Count) _then)
: super(_value, (v) => _then(v as _$_Count));
@override
_$_Count get _value => super._value as _$_Count;
@override
$Res call({
Object? value = freezed,
}) {
return _then(_$_Count(
value == freezed
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as int,
));
}
}
/// @nodoc
@JsonSerializable()
class _$_Count implements _Count {
const _$_Count(this.value);
factory _$_Count.fromJson(Map<String, dynamic> json) =>
_$$_CountFromJson(json);
@override
final int value;
@override
String toString() {
return 'Count(value: $value)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$_Count &&
const DeepCollectionEquality().equals(other.value, value));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(value));
@JsonKey(ignore: true)
@override
_$$_CountCopyWith<_$_Count> get copyWith =>
__$$_CountCopyWithImpl<_$_Count>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$_CountToJson(
this,
);
}
}
abstract class _Count implements Count {
const factory _Count(final int value) = _$_Count;
factory _Count.fromJson(Map<String, dynamic> json) = _$_Count.fromJson;
@override
int get value;
@override
@JsonKey(ignore: true)
_$$_CountCopyWith<_$_Count> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'models.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$_Increment _$$_IncrementFromJson(Map<String, dynamic> json) => _$_Increment(
by: json['by'] as int,
);
Map<String, dynamic> _$$_IncrementToJson(_$_Increment instance) =>
<String, dynamic>{
'by': instance.by,
};
_$_Count _$$_CountFromJson(Map<String, dynamic> json) => _$_Count(
json['value'] as int,
);
Map<String, dynamic> _$$_CountToJson(_$_Count instance) => <String, dynamic>{
'value': instance.value,
};

View File

@@ -0,0 +1,17 @@
name: shared
description: Common data models required by our client and server
version: 1.0.0
environment:
sdk: ">=2.18.1 <3.0.0"
dependencies:
freezed_annotation: ^2.1.0
json_annotation: ^4.7.0
dev_dependencies:
build_runner: ^2.2.1
freezed: ^2.1.1
json_serializable: ^6.4.0
lints: ^2.0.0
test: ^1.16.0

View File

@@ -0,0 +1,17 @@
import 'package:shared/shared.dart';
import 'package:test/test.dart';
void main() {
test('Increment serialization', () {
expect(Increment(by: 3).toJson(), <String, dynamic>{'by': 3});
});
test('Increment derialization', () {
expect(Increment.fromJson(<String, dynamic>{'by': 5}), Increment(by: 5));
});
test('Count serialization', () {
expect(Count(3).toJson(), <String, dynamic>{'value': 3});
});
test('Count derialization', () {
expect(Count.fromJson(<String, dynamic>{'value': 5}), Count(5));
});
}

View File

@@ -0,0 +1,40 @@
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart';
import 'package:test/test.dart';
// Manual test:
// $ dart bin/server.dart
// $ curl -X POST -d '{"by": 1}' -H "Content-Type: application/json" localhost:8080/
void main() {
final port = '8080';
final host = 'http://0.0.0.0:$port';
late Process p;
setUp(() async {
p = await Process.start(
'dart',
['run', 'bin/server.dart'],
environment: {'PORT': port},
);
// Wait for server to start and print to stdout.
await p.stdout.first;
});
tearDown(() => p.kill());
test('Increment', () async {
final response = await post(Uri.parse('$host/'), body: '{"by": 1}');
expect(response.statusCode, 200);
expect(response.body, '{"value":1}');
});
test('Get', () async {
final response = await get(Uri.parse('$host/'));
expect(response.statusCode, 200);
final resp = json.decode(response.body) as Map;
expect(resp.containsKey('value'), true);
});
}