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

This commit is contained in:
upstream-mirror
2026-09-16 01:43:26 +05:00
commit 9ed6a80afa
2886 changed files with 140184 additions and 0 deletions

View 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

View 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

View 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).

View File

@@ -0,0 +1 @@
include: ../../../analysis_options.yaml

View 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'],
);
}
}
}

View 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),
),
],
],
),
);
}
}

View 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);
}

View 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

View 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"

View 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);
}
}