snapshot of https://github.com/flutter/samples.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled
Some checks failed
Main Branch CI / Test Flutter beta on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter stable on ubuntu-latest (push) Has been skipped
Main Branch CI / Test Flutter beta on macos-latest (push) Has been cancelled
Main Branch CI / Test Flutter stable on macos-latest (push) Has been cancelled
This commit is contained in:
48
add_to_app/books/flutter_module_books/.gitignore
vendored
Normal file
48
add_to_app/books/flutter_module_books/.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
.DS_Store
|
||||
.dart_tool/
|
||||
|
||||
.packages
|
||||
.pub/
|
||||
|
||||
.idea/
|
||||
.vagrant/
|
||||
.sconsign.dblite
|
||||
.svn/
|
||||
|
||||
*.swp
|
||||
profile
|
||||
|
||||
DerivedData/
|
||||
|
||||
.generated/
|
||||
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.perspectivev3
|
||||
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
|
||||
xcuserdata
|
||||
|
||||
*.moved-aside
|
||||
|
||||
*.pyc
|
||||
*sync/
|
||||
Icon?
|
||||
.tags*
|
||||
|
||||
build/
|
||||
.android/
|
||||
.ios/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
10
add_to_app/books/flutter_module_books/.metadata
Normal file
10
add_to_app/books/flutter_module_books/.metadata
Normal file
@@ -0,0 +1,10 @@
|
||||
# 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 and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: c0091b289e9966b6da99647b2f18dcb93de1f207
|
||||
channel: master
|
||||
|
||||
project_type: module
|
||||
53
add_to_app/books/flutter_module_books/README.md
Normal file
53
add_to_app/books/flutter_module_books/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Books add-to-app sample
|
||||
|
||||
This application simulates a mock scenario in which an existing app with
|
||||
business logic and middleware already exists. It demonstrates how to add Flutter
|
||||
to an app that has established patterns for these domains. For more information
|
||||
on how to use it, see the [README.md](../README.md) parent directory.
|
||||
|
||||
This application also utilizes the [Pigeon](https://pub.dev/packages/pigeon)
|
||||
plugin to avoid manual platform channel wiring. Pigeon autogenerates the
|
||||
platform channel code in Dart/Java/Objective-C to allow interop using higher
|
||||
order functions and data classes instead of string-encoded methods and
|
||||
serialized primitives.
|
||||
|
||||
The Pigeon autogenerated code is checked-in and ready to use. If the schema
|
||||
in `pigeon/schema.dart` is updated, the generated classes can also be re-
|
||||
generated using:
|
||||
|
||||
```shell
|
||||
dart run pigeon --input pigeon/schema.dart \
|
||||
--dart_out lib/api.dart \
|
||||
--objc_header_out ../ios_books/IosBooks/api.h \
|
||||
--objc_source_out ../ios_books/IosBooks/api.m \
|
||||
--objc_prefix BK \
|
||||
--java_out ../android_books/app/src/main/java/dev/flutter/example/books/Api.java \
|
||||
--java_package "dev.flutter.example.books"
|
||||
```
|
||||
|
||||
## Demonstrated concepts
|
||||
|
||||
* An existing books catalog app is already implemented in Kotlin and Swift.
|
||||
* The platform-side app has existing middleware constraints that should also
|
||||
be the middleware foundation for the additional Flutter screen.
|
||||
* On Android, the Kotlin app already uses GSON and OkHttp for networking and
|
||||
references the Google Books API as a data source. These same libraries
|
||||
also underpin the data fetched and shown in the Flutter screen.
|
||||
* The platform application interfaces with the Flutter book details page using
|
||||
idiomatic platform API conventions rather than Flutter conventions.
|
||||
* On Android, the Flutter activity receives the book to show via activity
|
||||
intent and returns the edited book by setting the result intent on the
|
||||
activity. No Flutter concepts are leaked into the consumer activity.
|
||||
* The [pigeon](https://pub.dev/packages/pigeon) plugin is used to generate
|
||||
interop APIs and data classes. The same `Book` model class is used within the
|
||||
Kotlin/Swift program, the Dart program and in the interop between Kotlin/Swift
|
||||
and Dart.
|
||||
|
||||
## More info
|
||||
|
||||
For more information about Flutter, check out
|
||||
[flutter.dev](https://flutter.dev).
|
||||
|
||||
For instructions on how to integrate Flutter modules into your existing
|
||||
applications, see Flutter's
|
||||
[add-to-app documentation](https://flutter.dev/docs/development/add-to-app).
|
||||
@@ -0,0 +1 @@
|
||||
include: ../../../analysis_options.yaml
|
||||
191
add_to_app/books/flutter_module_books/lib/api.dart
Normal file
191
add_to_app/books/flutter_module_books/lib/api.dart
Normal file
@@ -0,0 +1,191 @@
|
||||
// Autogenerated from Pigeon (v1.0.1), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
|
||||
// @dart = 2.12
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class Book {
|
||||
String? title;
|
||||
String? subtitle;
|
||||
String? author;
|
||||
String? summary;
|
||||
String? publishDate;
|
||||
int? pageCount;
|
||||
Thumbnail? thumbnail;
|
||||
|
||||
Object encode() {
|
||||
final pigeonMap = <Object?, Object?>{};
|
||||
pigeonMap['title'] = title;
|
||||
pigeonMap['subtitle'] = subtitle;
|
||||
pigeonMap['author'] = author;
|
||||
pigeonMap['summary'] = summary;
|
||||
pigeonMap['publishDate'] = publishDate;
|
||||
pigeonMap['pageCount'] = pageCount;
|
||||
pigeonMap['thumbnail'] = thumbnail == null ? null : thumbnail!.encode();
|
||||
return pigeonMap;
|
||||
}
|
||||
|
||||
static Book decode(Object message) {
|
||||
final pigeonMap = message as Map<Object?, Object?>;
|
||||
return Book()
|
||||
..title = pigeonMap['title'] as String?
|
||||
..subtitle = pigeonMap['subtitle'] as String?
|
||||
..author = pigeonMap['author'] as String?
|
||||
..summary = pigeonMap['summary'] as String?
|
||||
..publishDate = pigeonMap['publishDate'] as String?
|
||||
..pageCount = pigeonMap['pageCount'] as int?
|
||||
..thumbnail = pigeonMap['thumbnail'] != null
|
||||
? Thumbnail.decode(pigeonMap['thumbnail']!)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class Thumbnail {
|
||||
String? url;
|
||||
|
||||
Object encode() {
|
||||
final pigeonMap = <Object?, Object?>{};
|
||||
pigeonMap['url'] = url;
|
||||
return pigeonMap;
|
||||
}
|
||||
|
||||
static Thumbnail decode(Object message) {
|
||||
final pigeonMap = message as Map<Object?, Object?>;
|
||||
return Thumbnail()..url = pigeonMap['url'] as String?;
|
||||
}
|
||||
}
|
||||
|
||||
class _FlutterBookApiCodec extends StandardMessageCodec {
|
||||
const _FlutterBookApiCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is Book) {
|
||||
buffer.putUint8(128);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 128:
|
||||
return Book.decode(readValue(buffer)!);
|
||||
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class FlutterBookApi {
|
||||
static const MessageCodec<Object?> codec = _FlutterBookApiCodec();
|
||||
|
||||
void displayBookDetails(Book book);
|
||||
static void setup(FlutterBookApi? api) {
|
||||
{
|
||||
const channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.FlutterBookApi.displayBookDetails', codec);
|
||||
if (api == null) {
|
||||
channel.setMessageHandler(null);
|
||||
} else {
|
||||
// ignore: avoid_types_on_closure_parameters
|
||||
channel.setMessageHandler((Object? message) async {
|
||||
assert(message != null,
|
||||
'Argument for dev.flutter.pigeon.FlutterBookApi.displayBookDetails was null.');
|
||||
final List<Object?> args = (message as List<Object?>?)!;
|
||||
final Book? arg_book = args[0] as Book?;
|
||||
assert(arg_book != null,
|
||||
'Argument for dev.flutter.pigeon.FlutterBookApi.displayBookDetails was null, expected non-null Book.');
|
||||
api.displayBookDetails(arg_book!);
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _HostBookApiCodec extends StandardMessageCodec {
|
||||
const _HostBookApiCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is Book) {
|
||||
buffer.putUint8(128);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 128:
|
||||
return Book.decode(readValue(buffer)!);
|
||||
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HostBookApi {
|
||||
/// Constructor for [HostBookApi]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
HostBookApi({BinaryMessenger? binaryMessenger})
|
||||
: _binaryMessenger = binaryMessenger;
|
||||
|
||||
final BinaryMessenger? _binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> codec = _HostBookApiCodec();
|
||||
|
||||
Future<void> cancel() async {
|
||||
final channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.HostBookApi.cancel', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final Map<Object?, Object?>? replyMap =
|
||||
await channel.send(null) as Map<Object?, Object?>?;
|
||||
if (replyMap == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
details: null,
|
||||
);
|
||||
} else if (replyMap['error'] != null) {
|
||||
final error = (replyMap['error'] as Map<Object?, Object?>?)!;
|
||||
throw PlatformException(
|
||||
code: (error['code'] as String?)!,
|
||||
message: error['message'] as String?,
|
||||
details: error['details'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> finishEditingBook(Book arg_book) async {
|
||||
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
|
||||
'dev.flutter.pigeon.HostBookApi.finishEditingBook', codec,
|
||||
binaryMessenger: _binaryMessenger);
|
||||
final Map<Object?, Object?>? replyMap =
|
||||
await channel.send(<Object>[arg_book]) as Map<Object?, Object?>?;
|
||||
if (replyMap == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel.',
|
||||
details: null,
|
||||
);
|
||||
} else if (replyMap['error'] != null) {
|
||||
final Map<Object?, Object?> error =
|
||||
(replyMap['error'] as Map<Object?, Object?>?)!;
|
||||
throw PlatformException(
|
||||
code: (error['code'] as String?)!,
|
||||
message: error['message'] as String?,
|
||||
details: error['details'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
239
add_to_app/books/flutter_module_books/lib/main.dart
Normal file
239
add_to_app/books/flutter_module_books/lib/main.dart
Normal file
@@ -0,0 +1,239 @@
|
||||
// Copyright 2020 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 'package:flutter/material.dart';
|
||||
import 'package:flutter_module_books/api.dart';
|
||||
|
||||
void main() => runApp(const MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(primaryColor: const Color(0xff6200ee)),
|
||||
home: const BookDetail(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
typedef BookReceived = void Function(Book book);
|
||||
|
||||
class FlutterBookApiHandler extends FlutterBookApi {
|
||||
FlutterBookApiHandler(this.callback);
|
||||
|
||||
final BookReceived callback;
|
||||
|
||||
@override
|
||||
void displayBookDetails(Book book) {
|
||||
callback(book);
|
||||
}
|
||||
}
|
||||
|
||||
class BookDetail extends StatefulWidget {
|
||||
const BookDetail({super.key, this.hostApi, this.flutterApi, this.book});
|
||||
|
||||
// These are the outgoing and incoming APIs that are here for injection for
|
||||
// tests.
|
||||
final HostBookApi? hostApi;
|
||||
final FlutterBookApi? flutterApi;
|
||||
final Book? book;
|
||||
|
||||
@override
|
||||
State<BookDetail> createState() => _BookDetailState();
|
||||
}
|
||||
|
||||
class _BookDetailState extends State<BookDetail> {
|
||||
Book? book;
|
||||
|
||||
late HostBookApi hostApi;
|
||||
|
||||
FocusNode textFocusNode = FocusNode();
|
||||
TextEditingController titleTextController = TextEditingController();
|
||||
TextEditingController subtitleTextController = TextEditingController();
|
||||
TextEditingController authorTextController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
book = widget.book;
|
||||
|
||||
// This `HostBookApi` class instance lets us make outgoing calls to the
|
||||
// platform.
|
||||
hostApi = widget.hostApi ?? HostBookApi();
|
||||
|
||||
// Registering this `FlutterBookApiHandler` class lets us receive incoming
|
||||
// calls from the platform.
|
||||
FlutterBookApi.setup(
|
||||
FlutterBookApiHandler(
|
||||
// The `FlutterBookApi` just has one method. Just give a closure for that
|
||||
// method to the handler class.
|
||||
(book) {
|
||||
setState(() {
|
||||
// This book model is what we're going to return to Kotlin eventually.
|
||||
// Keep it bound to the UI.
|
||||
this.book = book;
|
||||
titleTextController.text = book.title ?? '';
|
||||
titleTextController.addListener(() {
|
||||
this.book!.title = titleTextController.text;
|
||||
});
|
||||
// Subtitle could be null.
|
||||
subtitleTextController.text = book.subtitle ?? '';
|
||||
subtitleTextController.addListener(() {
|
||||
this.book!.subtitle = subtitleTextController.text;
|
||||
});
|
||||
authorTextController.text = book.author ?? '';
|
||||
authorTextController.addListener(() {
|
||||
this.book!.author = authorTextController.text;
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Not overriding didUpdateWidget because the Android program can't change
|
||||
// the book given to Flutter on the Android side.
|
||||
|
||||
void clear() {
|
||||
book = null;
|
||||
// Keep focus if going to the home screen but unfocus if leaving
|
||||
// the activity.
|
||||
textFocusNode.unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Book Details'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
// Pressing clear cancels the edit and leaves the activity without
|
||||
// modification.
|
||||
onPressed: () {
|
||||
hostApi.cancel();
|
||||
clear();
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.check),
|
||||
// Pressing save sends the updated book to the platform.
|
||||
onPressed: book != null
|
||||
? () {
|
||||
hostApi.finishEditingBook(book!);
|
||||
clear();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: book == null
|
||||
// Draw a spinner until the platform gives us the book to show details
|
||||
// for.
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: BookForm(
|
||||
book: book!,
|
||||
focusNode: textFocusNode,
|
||||
authorTextController: authorTextController,
|
||||
subtitleTextController: subtitleTextController,
|
||||
titleTextController: titleTextController,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BookForm extends StatelessWidget {
|
||||
const BookForm({
|
||||
super.key,
|
||||
required this.book,
|
||||
required this.focusNode,
|
||||
required this.authorTextController,
|
||||
required this.subtitleTextController,
|
||||
required this.titleTextController,
|
||||
});
|
||||
|
||||
final Book book;
|
||||
final FocusNode focusNode;
|
||||
final TextEditingController titleTextController;
|
||||
final TextEditingController subtitleTextController;
|
||||
final TextEditingController authorTextController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
focusNode: focusNode,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
children: [
|
||||
TextField(
|
||||
controller: titleTextController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
filled: true,
|
||||
hintText: "Title",
|
||||
labelText: "Title",
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
TextField(
|
||||
controller: subtitleTextController,
|
||||
maxLines: 2,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
filled: true,
|
||||
hintText: "Subtitle",
|
||||
labelText: "Subtitle",
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
TextField(
|
||||
controller: authorTextController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
filled: true,
|
||||
hintText: "Author",
|
||||
labelText: "Author",
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
const Divider(),
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'${book.pageCount} pages ~ published ${book.publishDate}',
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 32),
|
||||
if (book.thumbnail?.url != null) ...[
|
||||
Center(child: Image.network(book.thumbnail!.url!)),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
if (book.summary != null) ...[
|
||||
const Center(
|
||||
child: Text(
|
||||
'BOOK DESCRIPTION',
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
book.summary ?? '',
|
||||
style: TextStyle(color: Colors.grey.shade600, height: 1.24),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
30
add_to_app/books/flutter_module_books/pigeon/schema.dart
Normal file
30
add_to_app/books/flutter_module_books/pigeon/schema.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2020 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 'package:pigeon/pigeon.dart';
|
||||
|
||||
class Book {
|
||||
String? title;
|
||||
String? subtitle;
|
||||
String? author;
|
||||
String? summary;
|
||||
String? publishDate;
|
||||
int? pageCount;
|
||||
Thumbnail? thumbnail;
|
||||
}
|
||||
|
||||
class Thumbnail {
|
||||
String? url;
|
||||
}
|
||||
|
||||
@FlutterApi()
|
||||
abstract class FlutterBookApi {
|
||||
void displayBookDetails(Book book);
|
||||
}
|
||||
|
||||
@HostApi()
|
||||
abstract class HostBookApi {
|
||||
void cancel();
|
||||
void finishEditingBook(Book book);
|
||||
}
|
||||
34
add_to_app/books/flutter_module_books/pubspec.yaml
Normal file
34
add_to_app/books/flutter_module_books/pubspec.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
name: flutter_module_books
|
||||
description: A Flutter module using the Pigeon package to demonstrate
|
||||
integrating Flutter in a realistic scenario where the existing platform app
|
||||
already has business logic and middleware constraints.
|
||||
version: 1.0.0+1
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.0-0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
analysis_defaults:
|
||||
path: ../../../analysis_defaults
|
||||
pigeon: ">=11.0.0 <27.0.0"
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
# This section identifies your Flutter project as a module meant for
|
||||
# embedding in a native host app. These identifiers should _not_ ordinarily
|
||||
# be changed after generation - they are used to ensure that the tooling can
|
||||
# maintain consistency when adding or modifying assets and plugins.
|
||||
# They also do not have any bearing on your native host application's
|
||||
# identifiers, which may be completely independent or the same as these.
|
||||
module:
|
||||
androidX: true
|
||||
androidPackage: dev.flutter.example.flutter_module_books
|
||||
iosBundleIdentifier: dev.flutter.example.flutterModuleBooks
|
||||
8
add_to_app/books/flutter_module_books/run_pigeon.sh
Executable file
8
add_to_app/books/flutter_module_books/run_pigeon.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
dart run pigeon --input pigeon/schema.dart \
|
||||
--dart_out lib/api.dart \
|
||||
--objc_header_out ../ios_books/IosBooks/api.h \
|
||||
--objc_source_out ../ios_books/IosBooks/api.m \
|
||||
--objc_prefix BK \
|
||||
--java_out ../android_books/app/src/main/java/dev/flutter/example/books/Api.java \
|
||||
--java_package "dev.flutter.example.books"
|
||||
54
add_to_app/books/flutter_module_books/test/widget_test.dart
Normal file
54
add_to_app/books/flutter_module_books/test/widget_test.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 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 'package:flutter/material.dart';
|
||||
import 'package:flutter_module_books/api.dart';
|
||||
import 'package:flutter_module_books/main.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Pressing clear calls the cancel API', (tester) async {
|
||||
MockHostBookApi mockHostApi = MockHostBookApi();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(home: BookDetail(hostApi: mockHostApi)),
|
||||
);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.clear));
|
||||
|
||||
expect(mockHostApi.cancelCalls, 1);
|
||||
});
|
||||
|
||||
testWidgets('Pressing done calls the finish editing API', (
|
||||
tester,
|
||||
) async {
|
||||
MockHostBookApi mockHostApi = MockHostBookApi();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: BookDetail(book: Book(), hostApi: mockHostApi),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.check));
|
||||
|
||||
expect(mockHostApi.booksFinished.length, 1);
|
||||
});
|
||||
}
|
||||
|
||||
// A super-simple mock for testing that calls are made to the API.
|
||||
class MockHostBookApi implements HostBookApi {
|
||||
int cancelCalls = 0;
|
||||
final booksFinished = <Book>[];
|
||||
|
||||
@override
|
||||
Future<void> cancel() async {
|
||||
cancelCalls++;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> finishEditingBook(Book arg) async {
|
||||
booksFinished.add(arg);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user