Merge branch 'master' of https://github.com/flutter/samples into pr/1437
|
Before Width: | Height: | Size: 564 B After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1 @@
|
||||
include: ../analysis_options.yaml
|
||||
@ -0,0 +1,155 @@
|
||||
// Copyright 2022 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:io' show Directory;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart' as uuid;
|
||||
|
||||
import 'simple_database.dart';
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// This is the UI which will present the contents of the [SimpleDatabase]. To
|
||||
// see where Background Isolate Channels are used see simple_database.dart.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Background Isolate Channels',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: const MyHomePage(title: 'Background Isolate Channels'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() {
|
||||
return _MyHomePageState();
|
||||
}
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
/// The database that is running on a background [Isolate]. This is nullable
|
||||
/// because acquiring a [SimpleDatabase] is an asynchronous operation. This
|
||||
/// value is `null` until the database is initialized.
|
||||
SimpleDatabase? _database;
|
||||
|
||||
/// Local cache of the query results returned by the [SimpleDatabase] for the
|
||||
/// UI to render from. It is nullable since querying the results is
|
||||
/// asynchronous. The value is `null` before any result has been received.
|
||||
List<String>? _entries;
|
||||
|
||||
/// What is searched for in the [SimpleDatabase].
|
||||
String _query = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
// Write the value to [SharedPreferences] which will get read on the
|
||||
// [SimpleDatabase]'s isolate. For this example the value is always true
|
||||
// just for demonstration purposes.
|
||||
final Future<void> sharedPreferencesSet = SharedPreferences.getInstance()
|
||||
.then(
|
||||
(sharedPreferences) => sharedPreferences.setBool('isDebug', true));
|
||||
final Future<Directory> tempDirFuture =
|
||||
path_provider.getTemporaryDirectory();
|
||||
|
||||
// Wait until the [SharedPreferences] value is set and the temporary
|
||||
// directory is received before opening the database. If
|
||||
// [sharedPreferencesSet] does not happen before opening the
|
||||
// [SimpleDatabase] there has to be a way to refresh
|
||||
// [_SimpleDatabaseServer]'s [SharedPreferences] cached values.
|
||||
Future.wait([sharedPreferencesSet, tempDirFuture]).then((values) {
|
||||
final Directory? tempDir = values[1] as Directory?;
|
||||
final String dbPath = path.join(tempDir!.path, 'database.db');
|
||||
SimpleDatabase.open(dbPath).then((database) {
|
||||
setState(() {
|
||||
_database = database;
|
||||
});
|
||||
_refresh();
|
||||
});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_database?.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Performs a find on [SimpleDatabase] with [query] and updates the listed
|
||||
/// contents.
|
||||
void _refresh({String? query}) {
|
||||
if (query != null) {
|
||||
_query = query;
|
||||
}
|
||||
_database!.find(_query).toList().then((entries) {
|
||||
setState(() {
|
||||
_entries = entries;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds a UUID and a timestamp to the [SimpleDatabase].
|
||||
void _addDate() {
|
||||
final DateTime now = DateTime.now();
|
||||
final DateFormat formatter =
|
||||
DateFormat('EEEE MMMM d, HH:mm:ss\n${const uuid.Uuid().v4()}');
|
||||
final String formatted = formatter.format(now);
|
||||
_database!.addEntry(formatted).then((_) => _refresh());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
TextField(
|
||||
onChanged:
|
||||
_database == null ? null : (query) => _refresh(query: query),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Search',
|
||||
suffixIcon: Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(title: Text(_entries![index]));
|
||||
},
|
||||
itemCount: _entries?.length ?? 0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _database == null ? null : _addDate,
|
||||
tooltip: 'Add',
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,255 @@
|
||||
// Copyright 2022 The Flutter team. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// **WARNING:** This is not production code and is only intended to be used for
|
||||
// demonstration purposes.
|
||||
//
|
||||
// The following database works by spawning a background isolate and
|
||||
// communicating with it over Dart's SendPort API. It is presented below as a
|
||||
// demonstration of the feature "Background Isolate Channels" and shows using
|
||||
// plugins from a background isolate. The [SimpleDatabase] operates on the root
|
||||
// isolate and the [_SimpleDatabaseServer] operates on a background isolate.
|
||||
//
|
||||
// Here is an example of the protocol they use to communicate:
|
||||
//
|
||||
// _________________ ________________________
|
||||
// [:SimpleDatabase] [:_SimpleDatabaseServer]
|
||||
// ----------------- ------------------------
|
||||
// | |
|
||||
// |<---------------(init)------------------------|
|
||||
// |----------------(init)----------------------->|
|
||||
// |<---------------(ack)------------------------>|
|
||||
// | |
|
||||
// |----------------(add)------------------------>|
|
||||
// |<---------------(ack)-------------------------|
|
||||
// | |
|
||||
// |----------------(query)---------------------->|
|
||||
// |<---------------(result)----------------------|
|
||||
// |<---------------(result)----------------------|
|
||||
// |<---------------(done)------------------------|
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/// The size of the database entries in bytes.
|
||||
const int _entrySize = 256;
|
||||
|
||||
/// All the command codes that can be sent and received between [SimpleDatabase] and
|
||||
/// [_SimpleDatabaseServer].
|
||||
enum _Codes {
|
||||
init,
|
||||
add,
|
||||
query,
|
||||
ack,
|
||||
result,
|
||||
done,
|
||||
}
|
||||
|
||||
/// A command sent between [SimpleDatabase] and [_SimpleDatabaseServer].
|
||||
class _Command {
|
||||
const _Command(this.code, {this.arg0, this.arg1});
|
||||
|
||||
final _Codes code;
|
||||
final Object? arg0;
|
||||
final Object? arg1;
|
||||
}
|
||||
|
||||
/// A SimpleDatabase that stores entries of strings to disk where they can be
|
||||
/// queried.
|
||||
///
|
||||
/// All the disk operations and queries are executed in a background isolate
|
||||
/// operating. This class just sends and receives messages to the isolate.
|
||||
class SimpleDatabase {
|
||||
SimpleDatabase._(this._isolate, this._path);
|
||||
|
||||
final Isolate _isolate;
|
||||
final String _path;
|
||||
late final SendPort _sendPort;
|
||||
// Completers are stored in a queue so multiple commands can be queued up and
|
||||
// handled serially.
|
||||
final Queue<Completer<void>> _completers = Queue<Completer<void>>();
|
||||
// Similarly, StreamControllers are stored in a queue so they can be handled
|
||||
// asynchronously and serially.
|
||||
final Queue<StreamController<String>> _resultsStream =
|
||||
Queue<StreamController<String>>();
|
||||
|
||||
/// Open the database at [path] and launch the server on a background isolate..
|
||||
static Future<SimpleDatabase> open(String path) async {
|
||||
final ReceivePort receivePort = ReceivePort();
|
||||
final Isolate isolate =
|
||||
await Isolate.spawn(_SimpleDatabaseServer._run, receivePort.sendPort);
|
||||
final SimpleDatabase result = SimpleDatabase._(isolate, path);
|
||||
Completer<void> completer = Completer<void>();
|
||||
result._completers.addFirst(completer);
|
||||
receivePort.listen((message) {
|
||||
result._handleCommand(message as _Command);
|
||||
});
|
||||
await completer.future;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Writes [value] to the database.
|
||||
Future<void> addEntry(String value) {
|
||||
// No processing happens on the calling isolate, it gets delegated to the
|
||||
// background isolate, see [__SimpleDatabaseServer._doAddEntry].
|
||||
Completer<void> completer = Completer<void>();
|
||||
_completers.addFirst(completer);
|
||||
_sendPort.send(_Command(_Codes.add, arg0: value));
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Returns all the strings in the database that contain [query].
|
||||
Stream<String> find(String query) {
|
||||
// No processing happens on the calling isolate, it gets delegated to the
|
||||
// background isolate, see [__SimpleDatabaseServer._doFind].
|
||||
StreamController<String> resultsStream = StreamController<String>();
|
||||
_resultsStream.addFirst(resultsStream);
|
||||
_sendPort.send(_Command(_Codes.query, arg0: query));
|
||||
return resultsStream.stream;
|
||||
}
|
||||
|
||||
/// Handler invoked when a message is received from the port communicating
|
||||
/// with the database server.
|
||||
void _handleCommand(_Command command) {
|
||||
switch (command.code) {
|
||||
case _Codes.init:
|
||||
_sendPort = command.arg0 as SendPort;
|
||||
// ----------------------------------------------------------------------
|
||||
// Before using platform channels and plugins from background isolates we
|
||||
// need to register it with its root isolate. This is achieved by
|
||||
// acquiring a [RootIsolateToken] which the background isolate uses to
|
||||
// invoke [BackgroundIsolateBinaryMessenger.ensureInitialized].
|
||||
// ----------------------------------------------------------------------
|
||||
RootIsolateToken rootIsolateToken = RootIsolateToken.instance!;
|
||||
_sendPort
|
||||
.send(_Command(_Codes.init, arg0: _path, arg1: rootIsolateToken));
|
||||
break;
|
||||
case _Codes.ack:
|
||||
_completers.removeLast().complete();
|
||||
break;
|
||||
case _Codes.result:
|
||||
_resultsStream.last.add(command.arg0 as String);
|
||||
break;
|
||||
case _Codes.done:
|
||||
_resultsStream.removeLast().close();
|
||||
break;
|
||||
default:
|
||||
debugPrint('SimpleDatabase unrecognized command: ${command.code}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Kills the background isolate and its database server.
|
||||
void stop() {
|
||||
_isolate.kill();
|
||||
}
|
||||
}
|
||||
|
||||
/// The portion of the [SimpleDatabase] that runs on the background isolate.
|
||||
///
|
||||
/// This is where we use the new feature Background Isolate Channels, which
|
||||
/// allows us to use plugins from background isolates.
|
||||
class _SimpleDatabaseServer {
|
||||
_SimpleDatabaseServer(this._sendPort);
|
||||
|
||||
final SendPort _sendPort;
|
||||
late final String _path;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Here the plugin is used from the background isolate.
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/// The main entrypoint for the background isolate sent to [Isolate.spawn].
|
||||
static void _run(SendPort sendPort) {
|
||||
ReceivePort receivePort = ReceivePort();
|
||||
sendPort.send(_Command(_Codes.init, arg0: receivePort.sendPort));
|
||||
final _SimpleDatabaseServer server = _SimpleDatabaseServer(sendPort);
|
||||
receivePort.listen((message) async {
|
||||
final _Command command = message as _Command;
|
||||
await server._handleCommand(command);
|
||||
});
|
||||
}
|
||||
|
||||
/// Handle the [command] received from the [ReceivePort].
|
||||
Future<void> _handleCommand(_Command command) async {
|
||||
switch (command.code) {
|
||||
case _Codes.init:
|
||||
_path = command.arg0 as String;
|
||||
// ----------------------------------------------------------------------
|
||||
// The [RootIsolateToken] is required for
|
||||
// [BackgroundIsolateBinaryMessenger.ensureInitialized] and must be
|
||||
// obtained on the root isolate and passed into the background isolate via
|
||||
// a [SendPort].
|
||||
// ----------------------------------------------------------------------
|
||||
RootIsolateToken rootIsolateToken = command.arg1 as RootIsolateToken;
|
||||
// ----------------------------------------------------------------------
|
||||
// [BackgroundIsolateBinaryMessenger.ensureInitialized] for each
|
||||
// background isolate that will use plugins. This sets up the
|
||||
// [BinaryMessenger] that the Platform Channels will communicate with on
|
||||
// the background isolate.
|
||||
// ----------------------------------------------------------------------
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken);
|
||||
_sendPort.send(const _Command(_Codes.ack, arg0: null));
|
||||
break;
|
||||
case _Codes.add:
|
||||
_doAddEntry(command.arg0 as String);
|
||||
break;
|
||||
case _Codes.query:
|
||||
_doFind(command.arg0 as String);
|
||||
break;
|
||||
default:
|
||||
debugPrint(
|
||||
'_SimpleDatabaseServer unrecognized command ${command.code}');
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the add entry operation.
|
||||
void _doAddEntry(String value) {
|
||||
debugPrint('Performing add: $value');
|
||||
File file = File(_path);
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
}
|
||||
RandomAccessFile writer = file.openSync(mode: FileMode.append);
|
||||
List<int> bytes = utf8.encode(value);
|
||||
if (bytes.length > _entrySize) {
|
||||
bytes = bytes.sublist(0, _entrySize);
|
||||
} else if (bytes.length < _entrySize) {
|
||||
List<int> newBytes = List.filled(_entrySize, 0);
|
||||
for (int i = 0; i < bytes.length; ++i) {
|
||||
newBytes[i] = bytes[i];
|
||||
}
|
||||
bytes = newBytes;
|
||||
}
|
||||
writer.writeFromSync(bytes);
|
||||
writer.closeSync();
|
||||
_sendPort.send(const _Command(_Codes.ack, arg0: null));
|
||||
}
|
||||
|
||||
/// Perform the find entry operation.
|
||||
void _doFind(String query) {
|
||||
debugPrint('Performing find: $query');
|
||||
File file = File(_path);
|
||||
if (file.existsSync()) {
|
||||
RandomAccessFile reader = file.openSync();
|
||||
List<int> buffer = List.filled(_entrySize, 0);
|
||||
while (reader.readIntoSync(buffer) == _entrySize) {
|
||||
List<int> foo = buffer.takeWhile((value) => value != 0).toList();
|
||||
String string = utf8.decode(foo);
|
||||
if (string.contains(query)) {
|
||||
_sendPort.send(_Command(_Codes.result, arg0: string));
|
||||
}
|
||||
}
|
||||
reader.closeSync();
|
||||
}
|
||||
_sendPort.send(const _Command(_Codes.done, arg0: null));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
name: background_isolate_channels
|
||||
description: A new Flutter project.
|
||||
|
||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=2.19.0-224.0.dev <3.0.0'
|
||||
|
||||
dependencies:
|
||||
cupertino_icons: ^1.0.2
|
||||
flutter:
|
||||
sdk: flutter
|
||||
intl: ^0.18.0
|
||||
path: ^1.8.2
|
||||
path_provider: ^2.0.11
|
||||
shared_preferences: ^2.0.15
|
||||
uuid: ^3.0.6
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^2.0.1
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@ -0,0 +1,26 @@
|
||||
# Use latest stable channel SDK.
|
||||
FROM dart:stable AS build
|
||||
|
||||
# Copy shared code.
|
||||
WORKDIR /shared
|
||||
COPY /shared/. .
|
||||
|
||||
# Copy server code.
|
||||
WORKDIR /server
|
||||
COPY /server/. .
|
||||
|
||||
# Resolve app dependencies.
|
||||
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 /server/bin/server /app/bin/
|
||||
|
||||
# Start server.
|
||||
EXPOSE 8080
|
||||
CMD ["/app/bin/server"]
|
||||
@ -0,0 +1,24 @@
|
||||
# MacOS/Linux-only. Sadly, this is only helpful as documentation for Windows.
|
||||
|
||||
# LOCAL / DOCKER
|
||||
run-server:
|
||||
# Depends on `make build`
|
||||
docker-compose up -d
|
||||
|
||||
stop-server:
|
||||
docker-compose stop
|
||||
|
||||
# Flutter
|
||||
flutter-local:
|
||||
cd client && flutter run -d macos
|
||||
|
||||
test:
|
||||
(cd shared && dart test)
|
||||
(cd server && dart test)
|
||||
(cd client && flutter test)
|
||||
|
||||
# Docker
|
||||
build:
|
||||
(cd server && dart pub get)
|
||||
docker build . -t codesharingserver
|
||||
|
||||
@ -0,0 +1,191 @@
|
||||
|
||||
# Code Sharing
|
||||
|
||||
|
||||
|
||||
A full-stack Dart application using Flutter on the client and
|
||||
[`shelf`](https://pub.dev/packages/shelf) on the server. The Flutter app
|
||||
itself is still the counter app, but the actual number is stored on the server
|
||||
and incremented over HTTP using transport data classes understood by both the
|
||||
Flutter client and shelf server.
|
||||
|
||||
## Goals
|
||||
|
||||
The goal of this sample is to demonstrate the mechanics of sharing business
|
||||
logic between a Flutter client and a Dart server. The sample also includes a
|
||||
slightly modified Dockerfile which is required to build a Docker image from
|
||||
a Dart app containing nested packages.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The sample's project structure is as follows:
|
||||
|
||||
```
|
||||
code_sharing/
|
||||
# Flutter app
|
||||
client/
|
||||
lib/
|
||||
...
|
||||
pubspec.yaml
|
||||
...
|
||||
# Shelf
|
||||
server/
|
||||
bin/
|
||||
server.dart
|
||||
pubspec.yaml
|
||||
Dockerfile
|
||||
# Common business logic
|
||||
shared/
|
||||
pubspec.yaml
|
||||
lib/
|
||||
...
|
||||
```
|
||||
|
||||
## Recreating this on your own
|
||||
|
||||
Recreating this introductory project for yourself can be done in several steps.
|
||||
|
||||
1. Create a parent directory, likely sharing a name with your project or
|
||||
product, which will contain everything.
|
||||
2. Within that directory, run `flutter create client`. You may also name this
|
||||
Flutter project `app`, `mobile`, `<project-name>-app` or anything else that
|
||||
seems appropriate. At this point, your folder structure should look like this:
|
||||
```
|
||||
my_project/
|
||||
client/
|
||||
lib/
|
||||
main.dart
|
||||
pubspec.yaml
|
||||
...
|
||||
```
|
||||
3. From the same location where you ran `flutter create`, run
|
||||
`dart create -t server-shelf server`. You may also name this Shelf project,
|
||||
`backend`, `api`, `<project-name>-server`, or anything else that seems
|
||||
appropriate. At this point, your folder structure should look like this:
|
||||
```
|
||||
my_project/
|
||||
client/
|
||||
lib/
|
||||
main.dart
|
||||
pubspec.yaml
|
||||
...
|
||||
server/
|
||||
bin/
|
||||
server.dart
|
||||
Dockerfile
|
||||
pubspec.yaml
|
||||
...
|
||||
```
|
||||
4. Enter your server directory (`cd server`), and run `dart create -t package shared`.
|
||||
You may also name this package `common`, `domain`, `<project-name>-shared`, or
|
||||
anything else that seems appropriate. At this point, your folder structure should
|
||||
resemble the sample:
|
||||
```
|
||||
my_project/
|
||||
client/
|
||||
lib/
|
||||
main.dart
|
||||
pubspec.yaml
|
||||
...
|
||||
server/
|
||||
bin/
|
||||
server.dart
|
||||
shared/
|
||||
lib/
|
||||
src/
|
||||
...
|
||||
shared.dart
|
||||
pubspec.yaml
|
||||
...
|
||||
Dockerfile
|
||||
pubspec.yaml
|
||||
...
|
||||
```
|
||||
5. Next, begin granting access to your shared code by making the following
|
||||
edits to your Flutter app's `pubspec.yaml` file. Open that file
|
||||
(`client/pubspec.yaml`) and add the following dependency under the
|
||||
`dependencies` block:
|
||||
|
||||
```
|
||||
dependencies:
|
||||
# Add these two lines:
|
||||
shared:
|
||||
path: ../server/shared
|
||||
```
|
||||
6. Next, finish granting access to your shared code by making the following
|
||||
edits to your server's `pubspec.yaml` file. Open that file
|
||||
(`server/pubspec.yaml`) and add the following dependency under the
|
||||
`dependencies` block:
|
||||
|
||||
```
|
||||
dependencies:
|
||||
# Add these two lines:
|
||||
shared:
|
||||
path: ./shared
|
||||
```
|
||||
7. The final step is to adjust your `Dockerfile`, as it can no longer
|
||||
successfully run `dart pub get` after only copying over the `pubspec.yaml`
|
||||
file (that command now requires the entirety of your `shared` directory to be
|
||||
present.
|
||||
|
||||
- Find the line that says `COPY pubspec.* ./`, and change it to `COPY . .`.
|
||||
|
||||
With that, you're ready to build and run the app.
|
||||
|
||||
## Running the sample
|
||||
|
||||
To run the sample, or an equivalent you've reconstructed yourself, choose a
|
||||
runtime method below based on your needs.
|
||||
|
||||
### From the CLI
|
||||
|
||||
In one terminal window, run the following commands:
|
||||
|
||||
```sh
|
||||
cd my_project/server
|
||||
dart run bin/server.dart
|
||||
```
|
||||
|
||||
In a separate terminal window, run the following commands:
|
||||
|
||||
```sh
|
||||
cd my_project/client
|
||||
flutter run
|
||||
```
|
||||
|
||||
> Note: If you named your mobile client and backend servers something other
|
||||
than `client` and `server`, you will need to substitute in those values above.
|
||||
|
||||
### Build and run with Docker
|
||||
|
||||
To build your server's Docker image, run the following commands in a terminal
|
||||
window:
|
||||
|
||||
```sh
|
||||
cd my_project/server
|
||||
docker build . -t my_project_server
|
||||
```
|
||||
|
||||
To run that image as a Docker container, run the following commands in a
|
||||
terminal window:
|
||||
|
||||
```sh
|
||||
docker run -it my_project_server
|
||||
```
|
||||
|
||||
### Build and run with `docker-compose`
|
||||
|
||||
If you have [`docker-compose`](https://docs.docker.com/compose/install/)
|
||||
installed on your machine, you can run the following command to build and
|
||||
launch your server:
|
||||
|
||||
```sh
|
||||
cd my_project
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
And then later stop the server with:
|
||||
|
||||
```sh
|
||||
docker-compose stop
|
||||
```
|
||||
@ -0,0 +1,45 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
channel: main
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
base_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
- platform: android
|
||||
create_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
base_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
- platform: ios
|
||||
create_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
base_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
- platform: linux
|
||||
create_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
base_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
- platform: macos
|
||||
create_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
base_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
- platform: web
|
||||
create_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
base_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
- platform: windows
|
||||
create_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
base_revision: f16fe11ecf81a85d926ea3817ba1c87bdbbefa41
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@ -0,0 +1,16 @@
|
||||
# client
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.null_safe_app">
|
||||
package="com.example.client">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
@ -1,7 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.null_safe_app">
|
||||
package="com.example.client">
|
||||
<application
|
||||
android:label="null_safe_app"
|
||||
android:label="client"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
@ -1,4 +1,4 @@
|
||||
package com.example.null_safe_app
|
||||
package com.example.client
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@ -1,5 +1,5 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.example.null_safe_app">
|
||||
package="com.example.client">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
@ -1,12 +1,12 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.6.10'
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||