Skip to content

Commit 70976ee

Browse files
authored
Add a pigeon sample that demonstrates a "realistic" integration scenario with middleware and business logic (#465)
1 parent 87c9cfa commit 70976ee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1966
-3
lines changed

add_to_app/README.md

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ standalone Flutter module.
1010
- Whether to build the Flutter module from source each time the app builds or
1111
rely on a separately pre-built module.
1212
- Whether plugins are needed by the Flutter module used in the app.
13+
* Show Flutter being integrated ergonomically with applications with existing
14+
middleware and business logic data classes.
1315

1416
## Installing Cocoapods
1517

1618
The iOS samples in this repo require the latest version of Cocoapods. To install
17-
it, run the following command on a MacOS machine:
19+
it, run the following command on a macOS machine:
1820

1921
```bash
2022
sudo gem install cocoapods
@@ -26,20 +28,26 @@ See https://guides.cocoapods.org/using/getting-started.html for more details.
2628

2729
### Flutter modules
2830

29-
There are two Flutter modules included in the codebase:
31+
There are three Flutter modules included in the codebase:
3032

3133
* `flutter_module` displays the dimensions of the screen, a button that
3234
increments a simple counter, and an optional exit button.
3335
* `flutter_module_using_plugin` does everything `flutter_module` does and adds
3436
another button that will open the Flutter documentation in a browser using the
3537
[`url_launcher`](https://pub.dev/packages/url_launcher) Flutter plugin.
38+
* `flutter_module_books` simulates an integration scenario with existing
39+
platform business logic and middleware. It uses the [`pigeon`](https://pub.dev/packages/pigeon)
40+
plugin to make integration easier by generating the platform channel
41+
interop inside wrapper API and data classes that are shared between the
42+
platform and Flutter.
43+
3644

3745
Before using them, you need to resolve the Flutter modules' dependencies. Do so
3846
by running this command from within the `flutter_module` and
3947
`flutter_module_using_plugin` directories:
4048

4149
```bash
42-
flutter packages get
50+
flutter pub get
4351
```
4452

4553
### Android and iOS applications
@@ -139,6 +147,50 @@ Flutter frameworks, see this article in the Flutter GitHub wiki:
139147

140148
https://flutter.dev/docs/development/add-to-app/ios/project-setup
141149

150+
### `android_books` and `ios_books (TODO)`
151+
152+
These apps integrate the `flutter_books` module using the simpler build-together
153+
project setup. They simulate a mock scenario where an existing book catalog
154+
list app already exists. Flutter is used to implement an additional book details
155+
page.
156+
157+
* Similar to `android_fullscreen` and `ios_fullscreen`.
158+
* An existing books catalog app is already implemented in Kotlin and Swift.
159+
* The platform-side app has existing middleware constraints that should also
160+
be the middleware foundation for the additional Flutter screen.
161+
* On Android, the Kotlin app already uses GSON and OkHttp for networking and
162+
references the Google Books API as a data source. These same libraries
163+
also underpin the data fetched and shown in the Flutter screen.
164+
* iOS TODO.
165+
* The platform application interfaces with the Flutter book details page using
166+
idiomatic platform API conventions rather than Flutter conventions.
167+
* On Android, the Flutter activity receives the book to show via activity
168+
intent and returns the edited book by setting the result intent on the
169+
activity. No Flutter concepts are leaked into the consumer activity.
170+
* iOS TODO.
171+
* The [pigeon](https://pub.dev/packages/pigeon) plugin is used to generate
172+
interop APIs and data classes. The same `Book` model class is used within the
173+
Kotlin/Swift program, the Dart program and in the interop between Kotlin/Swift
174+
and Dart. No manual platform channel plumbing needed for interop.
175+
* The `api.dart/java/mm` files generated from the
176+
`flutter_module_books/pigeon/schema.dart` file are checked into source
177+
control. Therefore `pigeon` is only a dev dependency with no runtime
178+
requirements.
179+
* If the `schema.dart` is modified, the generated classes can be updated with
180+
181+
```shell
182+
flutter pub run pigeon \
183+
--input pigeon/schema.dart \
184+
--java_out ../android_books/app/src/main/java/dev/flutter/example/books/Api.java \
185+
--java_package "dev.flutter.example.books"
186+
```
187+
188+
in the `flutter_module_books` directory.
189+
190+
Once you've understood the basics of add-to-app with `android_fullscreen` and
191+
`ios_fullscreen`, this is a good sample to demonstrate how to integrate Flutter
192+
in a slightly more realistic setting with existing business logic.
193+
142194
## Questions/issues
143195
144196
If you have a general question about incorporating Flutter into an existing

add_to_app/android_books/.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
/.idea/caches
5+
/.idea/libraries
6+
/.idea/modules.xml
7+
/.idea/workspace.xml
8+
/.idea/navEditor.xml
9+
/.idea/assetWizardSettings.xml
10+
.DS_Store
11+
/build
12+
/captures
13+
.externalNativeBuild
14+
.cxx
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
apply plugin: 'com.android.application'
2+
apply plugin: 'kotlin-android'
3+
apply plugin: 'kotlin-android-extensions'
4+
5+
android {
6+
compileSdkVersion 29
7+
buildToolsVersion "29.0.3"
8+
9+
defaultConfig {
10+
applicationId "dev.flutter.example.books"
11+
minSdkVersion 16
12+
targetSdkVersion 29
13+
versionCode 1
14+
versionName "1.0"
15+
16+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17+
}
18+
19+
buildTypes {
20+
release {
21+
minifyEnabled false
22+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23+
}
24+
}
25+
compileOptions {
26+
sourceCompatibility JavaVersion.VERSION_1_8
27+
targetCompatibility JavaVersion.VERSION_1_8
28+
}
29+
kotlinOptions {
30+
jvmTarget = "1.8"
31+
}
32+
}
33+
34+
dependencies {
35+
implementation fileTree(dir: "libs", include: ["*.jar"])
36+
implementation "com.squareup.okhttp3:okhttp:4.7.2"
37+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
38+
implementation 'androidx.core:core-ktx:1.3.0'
39+
implementation 'androidx.appcompat:appcompat:1.1.0'
40+
implementation "androidx.activity:activity-ktx:1.1.0"
41+
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
42+
implementation 'com.google.android.material:material:1.1.0'
43+
implementation 'com.google.code.gson:gson:2.8.6'
44+
implementation project(path: ':flutter')
45+
testImplementation 'junit:junit:4.13'
46+
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
47+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
48+
49+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.flutter.example.books
2+
3+
import androidx.test.platform.app.InstrumentationRegistry
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
6+
import org.junit.Test
7+
import org.junit.runner.RunWith
8+
9+
import org.junit.Assert.*
10+
11+
/**
12+
* Instrumented test, which will execute on an Android device.
13+
*
14+
* See [testing documentation](http://d.android.com/tools/testing).
15+
*/
16+
@RunWith(AndroidJUnit4::class)
17+
class MainActivityTest {
18+
@Test
19+
fun useAppContext() {
20+
// Context of the app under test.
21+
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22+
assertEquals("dev.flutter.example.books", appContext.packageName)
23+
}
24+
25+
// The app should be hermetic (with offline books JSON) before adding
26+
// more tests.
27+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="dev.flutter.example.books">
4+
5+
<application
6+
android:name=".BookApplication"
7+
android:allowBackup="true"
8+
android:icon="@mipmap/ic_launcher"
9+
android:label="@string/app_name"
10+
android:roundIcon="@mipmap/ic_launcher_round"
11+
android:supportsRtl="true"
12+
android:theme="@style/AppTheme">
13+
<activity android:name=".MainActivity">
14+
<intent-filter>
15+
<action android:name="android.intent.action.MAIN" />
16+
17+
<category android:name="android.intent.category.LAUNCHER" />
18+
</intent-filter>
19+
</activity>
20+
<activity
21+
android:name=".FlutterBookActivity"
22+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
23+
android:hardwareAccelerated="true"
24+
android:windowSoftInputMode="adjustResize" />
25+
</application>
26+
<uses-permission android:name="android.permission.INTERNET" />
27+
28+
</manifest>
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Autogenerated from Pigeon (v0.1.0), do not edit directly.
2+
// See also: https://pub.dev/packages/pigeon
3+
4+
package dev.flutter.example.books;
5+
6+
import java.util.HashMap;
7+
8+
import io.flutter.plugin.common.BasicMessageChannel;
9+
import io.flutter.plugin.common.BinaryMessenger;
10+
import io.flutter.plugin.common.StandardMessageCodec;
11+
12+
/** Generated class from Pigeon. */
13+
public class Api {
14+
15+
/** Generated class from Pigeon that represents data sent in messages. */
16+
public static class Book {
17+
private String title;
18+
public String getTitle() { return title; }
19+
public void setTitle(String setterArg) { this.title = setterArg; }
20+
21+
private String subtitle;
22+
public String getSubtitle() { return subtitle; }
23+
public void setSubtitle(String setterArg) { this.subtitle = setterArg; }
24+
25+
private String author;
26+
public String getAuthor() { return author; }
27+
public void setAuthor(String setterArg) { this.author = setterArg; }
28+
29+
private String description;
30+
public String getDescription() { return description; }
31+
public void setDescription(String setterArg) { this.description = setterArg; }
32+
33+
private String publishDate;
34+
public String getPublishDate() { return publishDate; }
35+
public void setPublishDate(String setterArg) { this.publishDate = setterArg; }
36+
37+
private Long pageCount;
38+
public Long getPageCount() { return pageCount; }
39+
public void setPageCount(Long setterArg) { this.pageCount = setterArg; }
40+
41+
HashMap toMap() {
42+
HashMap<String, Object> toMapResult = new HashMap<String, Object>();
43+
toMapResult.put("title", title);
44+
toMapResult.put("subtitle", subtitle);
45+
toMapResult.put("author", author);
46+
toMapResult.put("description", description);
47+
toMapResult.put("publishDate", publishDate);
48+
toMapResult.put("pageCount", pageCount);
49+
return toMapResult;
50+
}
51+
static Book fromMap(HashMap map) {
52+
Book fromMapResult = new Book();
53+
fromMapResult.title = (String)map.get("title");
54+
fromMapResult.subtitle = (String)map.get("subtitle");
55+
fromMapResult.author = (String)map.get("author");
56+
fromMapResult.description = (String)map.get("description");
57+
fromMapResult.publishDate = (String)map.get("publishDate");
58+
fromMapResult.pageCount = (map.get("pageCount") instanceof Integer) ? (Integer)map.get("pageCount") : (Long)map.get("pageCount");
59+
return fromMapResult;
60+
}
61+
}
62+
63+
/** Generated class from Pigeon that represents Flutter messages that can be called from Java.*/
64+
public static class FlutterBookApi {
65+
private BinaryMessenger binaryMessenger;
66+
public FlutterBookApi(BinaryMessenger argBinaryMessenger){
67+
this.binaryMessenger = argBinaryMessenger;
68+
}
69+
public interface Reply<T> {
70+
void reply(T reply);
71+
}
72+
public void displayBookDetails(Book argInput, Reply<Void> callback) {
73+
BasicMessageChannel<Object> channel =
74+
new BasicMessageChannel<Object>(binaryMessenger, "dev.flutter.pigeon.FlutterBookApi.displayBookDetails", new StandardMessageCodec());
75+
HashMap inputMap = argInput.toMap();
76+
channel.send(inputMap, new BasicMessageChannel.Reply<Object>() {
77+
public void reply(Object channelReply) {
78+
callback.reply(null);
79+
}
80+
});
81+
}
82+
}
83+
84+
/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/
85+
public interface HostBookApi {
86+
void cancel();
87+
void finishEditingBook(Book arg);
88+
89+
/** Sets up an instance of `HostBookApi` to handle messages through the `binaryMessenger` */
90+
static void setup(BinaryMessenger binaryMessenger, HostBookApi api) {
91+
{
92+
BasicMessageChannel<Object> channel =
93+
new BasicMessageChannel<Object>(binaryMessenger, "dev.flutter.pigeon.HostBookApi.cancel", new StandardMessageCodec());
94+
if (api != null) {
95+
channel.setMessageHandler(new BasicMessageChannel.MessageHandler<Object>() {
96+
public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
97+
HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
98+
try {
99+
api.cancel();
100+
wrapped.put("result", null);
101+
}
102+
catch (Exception exception) {
103+
wrapped.put("error", wrapError(exception));
104+
}
105+
reply.reply(wrapped);
106+
}
107+
});
108+
} else {
109+
channel.setMessageHandler(null);
110+
}
111+
}
112+
{
113+
BasicMessageChannel<Object> channel =
114+
new BasicMessageChannel<Object>(binaryMessenger, "dev.flutter.pigeon.HostBookApi.finishEditingBook", new StandardMessageCodec());
115+
if (api != null) {
116+
channel.setMessageHandler(new BasicMessageChannel.MessageHandler<Object>() {
117+
public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) {
118+
Book input = Book.fromMap((HashMap)message);
119+
HashMap<String, HashMap> wrapped = new HashMap<String, HashMap>();
120+
try {
121+
api.finishEditingBook(input);
122+
wrapped.put("result", null);
123+
}
124+
catch (Exception exception) {
125+
wrapped.put("error", wrapError(exception));
126+
}
127+
reply.reply(wrapped);
128+
}
129+
});
130+
} else {
131+
channel.setMessageHandler(null);
132+
}
133+
}
134+
}
135+
}
136+
private static HashMap wrapError(Exception exception) {
137+
HashMap<String, Object> errorMap = new HashMap<String, Object>();
138+
errorMap.put("message", exception.toString());
139+
errorMap.put("code", null);
140+
errorMap.put("details", null);
141+
return errorMap;
142+
}
143+
}

0 commit comments

Comments
 (0)