# flutter 接上 python (使用Chaquopy)

Chaquopy 的念法是 cha-ko-pi (掐狗屁...?) [由來](https://chaquo.com/Chaquopy/doc/current/faq.html#faq-name)
可以讓flutter/dart 接上python,可以讓flutter/dart 的技術邊界更加的寬廣。
以下會以介紹Chaquopy 的用途、缺點和建構方式。
[TOC]
## 💡舉例來說
* 利用python 爬蟲,把資訊擷取下來後轉給app 使用
* 使用相機做人臉辨識,影像處理等
比起用dart 的html 來處理DOM,用python 來做爬蟲相當快速也方便;而在python 裡,numpy、scipy 不用說,那些鼎鼎有名的opencv、matplotlib、pytorch等等,都可以支援。
## 🚨缺點
但Chaquopy 並不是萬能的,少數python pacakge 並不支援,而且目前的Chaquopy 只能使用在android 平台上,所以在支援多平台的專案裡,自行手寫一份stub 是必要的。
* 不支援部分package (所有支援的[package list](https://chaquo.com/pypi-7.0/))
* 僅支援android platform,多平台專案需寫stub
* build time、app size 顯著增加
## 🛠建構方式
### 0. 安裝python
建議安裝[python 3.8](https://www.python.org/downloads/),更高的版本可能會少一些package 可以安裝。
### 1. Gradle 設定
Chaquopy 的建構方式,和android 原生的增加依賴是一樣的
在Chaquopy 的[Gradle Setup](https://chaquo.com/Chaquopy/doc/current/android.html) 中,基本上說明的已經非常完備了,這裡只針對flutter 專案會遇到的不同來說明。
1. 首先,要先在top-level 的build.gradle 中(通常是 `android/build.gradle`)
新增下列兩行
```gradle
buildscript {
repositories {
google()
jcenter()
maven { url "https://chaquo.com/maven" } //新增這行
}
dependencies {
...
classpath "com.chaquo.python:gradle:latest-version" //新增這行
}
}
```
2. 在module-level 的build.gradle (通常是 `android/app/build.gradle`)中,在 `Plugins`區塊裡放入
```gradle
apply plugin: 'com.android.application'
apply plugin: 'com.chaquo.python' //新增這行,要在Android plugin 後面
```
3. 同樣在module-level 的build.gradle裡,設定ndk-abiFilters。
```gradle
defaultConfig {
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
}
```
:::warning
💣 容量炸彈警告💣
設定這個等於是告訴Chaquopy 需要準備哪些cpu arch 的package,也就是當你的支援度如上,可支援4種cpu arch 時,在執行 `flutter build` 的時候,Chaquopy 就會根據支援度,去下載對應的python package,也是apk 大小開始膨大的第一步。
要避免大小過大,請參考[1.1](#1.1.降低封裝大小)
:::
以下是各個cpu arch 的說明:
* armeabi-v7a, 32-bit arch,幾乎涵蓋所有android 裝置。
* arm64-v8a, 64-bit arch,涵蓋2018 後的大部分裝置。
* x86, Android 模擬器使用。
* x86_64, Android 模擬器使用。
4. 在`defaultConfig` 區塊裡,新增需要的package。
如果是要使用`requirements.txt` 的方式,則要把`requirements.txt` 放在`android/app`下
```gradle
defaultConfig {
python {
pip {
// A requirement specifier, with or without a version number:
install "scipy"
install "requests==2.24.0"
// An sdist or wheel filename, relative to the project directory:
install "MyPackage-1.2.3-py2.py3-none-any.whl"
// A directory containing a setup.py, relative to the project
// directory (must contain at least one slash):
install "./MyPackage"
// "-r"` followed by a requirements filename, relative to the
// project directory:
install "-r", "requirements.txt"
}
}
}
```
:::warning
💣 容量炸彈警告💣
如同上述的abiFilter,這裡的package 的多寡,會直接的影響到最終封裝的大小。
而當你有10 個package,支援4 種cpu 架構,在初次build 的時候,Chaquopy 會安裝
10 * 4 個package 到專案內,而且build time 也會大幅上升。
要避免大小過大,請參考[1.1](#1.1.降低封裝大小)
:::
5. 在Android Studio 裡Sync Project 或是build 一下專案,在`android\app\src\main` 裡會多出一個`python` 的資料夾,把python 的原始碼放到裡面即可。
### 1.1.降低封裝大小

當你單個apk 大小過大,你可以:
1. 檢查你的python 原始碼 (廢話)
2. 檢查cpu arch 支援度,刪除不需要的支援
3. 整理package、requirements.txt。tensorflow 改用tflite 會好很多
4. 將不同abi 分別打包
最後一點比較複雜,因為在flutter 裡也有分abi 打包的指令(`--split-per-abi`)
但如果使用了該指令,因為我們已經在`build.gradle` 裡加上了`abiFilters`,會互相衝突,所以會出現下列錯誤:
`abiFilters cannot be present when splits abi filters are set`
但若把`build.gradle` 裡的`abiFilters` 移除,使用`flutter build apk --split-per-abi` 時,則會被警告Chaquopy 需要設定`abiFilters`,而中斷build process。
所以在分割abi 包時,我的作法是:
在module-level 的build.gradle `android` 區塊中, 增加以下code
```gradle
flavorDimensions "abi"
productFlavors {
arm32 {
dimension "abi"
ndk { abiFilters "armeabi-v7a" }
versionCode 1000000 + flutterVersionCode.toInteger()
}
arm64 {
dimension "abi"
ndk { abiFilters "arm64-v8a" }
versionCode 2000000 + flutterVersionCode.toInteger()
}
...
}
```
下指令時使用一般的`flutter build apk` 就可以了。
### 2. Flutter 依賴
在pub.dev 上有一個[chaquopy](https://pub.dev/packages/chaquopy) 的包,這裡也是要使用他,不過作者神遊(?)的關係,[chaquopy 已經把minSdkVersion 提升到21](https://chaquo.com/chaquopy/doc/current/versions.html),如果直接使用了上面的包,會沒辦法build 成功。
這裡使用BeMain 提供的解法[^來源]
1. clone [作者的repo](https://github.com/jaydangar/chaquopy_flutter) 到自己的專案內
2. 在專案內的`pubspec.yaml` 加入
```yaml
dependencies:
chaquopy:
path: /path/to/chaquopy
```
3. 把`android/build.gradle` 的`minSdkVersion` 改至`21` 就可以了。
🎉🎉最後,在`android\app\src\main\python` 裡,放入[scripy.py](https://drive.google.com/file/d/1D4Hjt66f0MXkaeAQ8WLX3DEebX3BrFvM/view),就完成設定步驟了。🎉🎉
### 3. 使用
在flutter/dart 裡使用時,只要用`Chaquopy.executeCode(code)` 就行了。
```dart=
import 'package:chaquopy/chaquopy.dart';
const String pyCode ="""print("hello from python");"""
final Map<String,dynamic> result = await Chaquopy.executeCode(pyCode);
```
若要查看`result`,則
```dart=+
print(result['textOutputOrError']);
```
若是在`android\app\src\main\python`中,有放python 的原始碼的話,則可以直接import 到python
```dart=+
const String importPy ="""
import hello
hello.world()
""";
final Map<String,dynamic> importResult = await Chaquopy.executeCode(importPy);
print(importResult['textOutputOrError']);
```
其他進階的使用,請參考[Chaquopy FAQ](https://chaquo.com/chaquopy/doc/current/faq.html#how-do-i)
[^來源]: https://github.com/jaydangar/chaquopy_flutter/issues/42#issuecomment-1385991032