Parcourir la source

feat: support building on Android (#3713)

* feat: support building on Android

* chore: add CI for mobile platform
Lucas.Xu il y a 1 an
Parent
commit
e565d0ee32

+ 113 - 0
.github/workflows/mobile_ci.yaml

@@ -0,0 +1,113 @@
+name: Mobile-CI
+
+on:
+  push:
+    branches:
+      - "main"
+    paths:
+      - ".github/workflows/mobile_ci.yaml"
+      - "frontend/**"
+      - "!frontend/appflowy_tauri/**"
+
+  pull_request:
+    branches:
+      - "main"
+    paths:
+      - ".github/workflows/mobile_ci.yaml"
+      - "frontend/**"
+      - "!frontend/appflowy_tauri/**"
+
+env:
+  FLUTTER_VERSION: "3.10.1"
+  RUST_TOOLCHAIN: "1.70"
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  build:
+    if: github.event.pull_request.draft != true
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+        include:
+          - os: ubuntu-latest
+            target: aarch64-linux-android
+    runs-on: ${{ matrix.os }}
+
+    steps:
+      # the following step is required to avoid running out of space
+      - name: Maximize build space
+        if: matrix.os == 'ubuntu-latest'
+        run: |
+          sudo rm -rf /usr/share/dotnet
+          sudo rm -rf /opt/ghc
+          sudo rm -rf "/usr/local/share/boost"
+          sudo rm -rf "$AGENT_TOOLSDIRECTORY"
+
+      - name: Checkout source code
+        uses: actions/checkout@v2
+
+      - name: Install Rust toolchain
+        id: rust_toolchain
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: ${{ env.RUST_TOOLCHAIN }}
+          target: ${{ matrix.target }}
+          override: true
+          profile: minimal
+
+      - name: Install flutter
+        id: flutter
+        uses: subosito/flutter-action@v2
+        with:
+          channel: "stable"
+          flutter-version: ${{ env.FLUTTER_VERSION }}
+          cache: true
+
+      - uses: nttld/setup-ndk@v1
+        id: setup-ndk
+        with:
+            ndk-version: "r24"
+            add-to-path: true
+
+      - uses: Swatinem/rust-cache@v2
+        with:
+          prefix-key: ${{ matrix.os }}
+          workspaces: |
+            frontend/rust-lib
+
+      - uses: davidB/rust-cargo-make@v1
+        with:
+          version: "0.36.6"
+
+      - name: Install prerequisites
+        working-directory: frontend
+        run: |
+          rustup target install aarch64-linux-android
+          rustup target install x86_64-linux-android
+          cargo install --force duckscript_cli
+          cargo install cargo-ndk
+          if [ "$RUNNER_OS" == "Linux" ]; then
+            sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
+            sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
+            sudo apt-get update
+            sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
+            sudo apt-get install keybinder-3.0 libnotify-dev
+            sudo apt-get install gcc-multilib
+          elif [ "$RUNNER_OS" == "Windows" ]; then
+            vcpkg integrate install
+          elif [ "$RUNNER_OS" == "macOS" ]; then
+            echo 'do nothing'
+          fi
+          cargo make appflowy-flutter-deps-tools
+        shell: bash
+
+      - name: Build AppFlowy
+        working-directory: frontend
+        env:
+          ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
+        run: |
+          cargo make --profile development-android appflowy-android-dev

+ 20 - 8
frontend/.vscode/launch.json

@@ -5,26 +5,27 @@
     "version": "0.2.0",
     "configurations": [
         {
-            // This task builds the Rust and Dart code of AppFlowy.
-            "name": "AF-desktop: Build All",
+            // This task only builds the Dart code of AppFlowy.
+            // It supports both the desktop and mobile version.
+            "name": "AF: Build Dart Only",
             "request": "launch",
             "program": "./lib/main.dart",
             "type": "dart",
-            "preLaunchTask": "AF: Build Appflowy Core",
             "env": {
-                "RUST_LOG": "trace",
-                "RUST_BACKTRACE": 1
+                "RUST_LOG": "debug",
             },
             "cwd": "${workspaceRoot}/appflowy_flutter"
         },
         {
-            //  This task only builds the Dart code of AppFlowy.
-            "name": "AF-desktop: Build Dart Only",
+            // This task builds the Rust and Dart code of AppFlowy.
+            "name": "AF-desktop: Build All",
             "request": "launch",
             "program": "./lib/main.dart",
             "type": "dart",
+            "preLaunchTask": "AF: Build Appflowy Core",
             "env": {
-                "RUST_LOG": "debug",
+                "RUST_LOG": "trace",
+                "RUST_BACKTRACE": 1
             },
             "cwd": "${workspaceRoot}/appflowy_flutter"
         },
@@ -65,6 +66,17 @@
             },
             "cwd": "${workspaceRoot}/appflowy_flutter"
         },
+        {
+            "name": "AF-Android-Simlator: Clean + Rebuild All",
+            "request": "launch",
+            "program": "./lib/main.dart",
+            "type": "dart",
+            "preLaunchTask": "AF: Clean + Rebuild All (Android Simulator)",
+            "env": {
+                "RUST_LOG": "trace"
+            },
+            "cwd": "${workspaceRoot}/appflowy_flutter"
+        },
         {
             "name": "AF-desktop: Debug Rust",
             "request": "attach",

+ 28 - 0
frontend/.vscode/tasks.json

@@ -84,6 +84,34 @@
         "cwd": "${workspaceFolder}"
       }
     },
+    {
+      "label": "AF: Clean + Rebuild All (Android)",
+      "type": "shell",
+      "dependsOrder": "sequence",
+      "dependsOn": [
+        "AF: Dart Clean",
+        "AF: Flutter Clean",
+        "AF: Build Appflowy Core For Android",
+        "AF: Flutter Pub Get",
+        "AF: Flutter Package Get",
+        "AF: Generate Language Files",
+        "AF: Generate Freezed Files",
+        "AF: Generate Svg Files"
+      ],
+      "presentation": {
+        "reveal": "always",
+        "panel": "new"
+      }
+    },
+    {
+      "label": "AF: Build Appflowy Core For Android",
+      "type": "shell",
+      "command": "cargo make --profile development-android appflowy-core-dev-android",
+      "group": "build",
+      "options": {
+        "cwd": "${workspaceFolder}"
+      }
+    },
     {
       "label": "AF: Build Appflowy Core",
       "type": "shell",

+ 8 - 15
frontend/Makefile.toml

@@ -186,6 +186,14 @@ RUST_COMPILE_TARGET = "aarch64-apple-ios"
 BUILD_ARCHS = "arm64"
 CRATE_TYPE = "staticlib"
 
+[env.development-android]
+BUILD_FLAG = "debug"
+TARGET_OS = "android"
+CRATE_TYPE = "cdylib"
+FLUTTER_OUTPUT_DIR = "Debug"
+LIB_EXT = "so"
+FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite,openssl_vendored"
+
 [tasks.echo_env]
 script = ['''
     echo "-------- Env Parameters --------"
@@ -201,21 +209,6 @@ script = ['''
     ''']
 script_runner = "@shell"
 
-[env.development-android]
-BUILD_FLAG = "debug"
-TARGET_OS = "android"
-CRATE_TYPE = "cdylib"
-FLUTTER_OUTPUT_DIR = "Debug"
-FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite,openssl_vendored"
-
-[env.production-android]
-CARGO_PROFILE = "release"
-BUILD_FLAG = "release"
-TARGET_OS = "android"
-CRATE_TYPE = "cdylib"
-FLUTTER_OUTPUT_DIR = "Release"
-FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite,openssl_vendored"
-
 [tasks.setup-crate-type]
 private = true
 script = [

+ 7 - 7
frontend/appflowy_flutter/.metadata

@@ -4,8 +4,8 @@
 # This file should be version controlled.
 
 version:
-  revision: 135454af32477f815a7525073027a3ff9eff1bfd
-  channel: stable
+  revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66
+  channel: unknown
 
 project_type: app
 
@@ -13,11 +13,11 @@ project_type: app
 migration:
   platforms:
     - platform: root
-      create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
-      base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
-    - platform: windows
-      create_revision: 135454af32477f815a7525073027a3ff9eff1bfd
-      base_revision: 135454af32477f815a7525073027a3ff9eff1bfd
+      create_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66
+      base_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66
+    - platform: android
+      create_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66
+      base_revision: 682aa387cfe4fbd71ccd5418b2c2a075729a1c66
 
   # User provided section
 

+ 2 - 0
frontend/appflowy_flutter/android/.gitignore

@@ -11,3 +11,5 @@ GeneratedPluginRegistrant.java
 key.properties
 **/*.keystore
 **/*.jks
+
+.cxx

+ 15 - 2
frontend/appflowy_flutter/android/app/build.gradle

@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
 
 android {
-    compileSdkVersion 31
+    compileSdkVersion 33
     ndkVersion "24.0.8215888"
 
     compileOptions {
@@ -46,11 +46,16 @@ android {
     defaultConfig {
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
         applicationId "io.appflowy.appflowy"
-        minSdkVersion 19
+        minSdkVersion 23
         targetSdkVersion 31
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
         multiDexEnabled true
+        externalNativeBuild {
+            cmake {
+                arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_STL=c++_shared"
+            }
+        }
     }
 
     buildTypes {
@@ -63,6 +68,14 @@ android {
             signingConfig signingConfigs.debug
         }
     }
+
+    namespace 'io.appflowy.appflowy'
+
+    externalNativeBuild {
+        cmake {
+            path "src/main/CMakeLists.txt"
+        }
+    }
 }
 
 flutter {

+ 1 - 2
frontend/appflowy_flutter/android/app/src/debug/AndroidManifest.xml

@@ -1,5 +1,4 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="io.appflowy.appflowy">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Flutter needs it to communicate with the running application
          to allow setting breakpoints, to provide hot reload, etc.
     -->

+ 38 - 42
frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml

@@ -1,42 +1,38 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="io.appflowy.appflowy">
-   <application
-        android:label="appflowy_flutter"
-        android:icon="@mipmap/ic_launcher"
-        android:name="${applicationName}">
-        <activity
-            android:name=".MainActivity"
-            android:launchMode="singleTop"
-            android:theme="@style/LaunchTheme"
-            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
-            android:hardwareAccelerated="true"
-            android:windowSoftInputMode="adjustResize">
-            <!-- Specifies an Android theme to apply to this Activity as soon as
-                 the Android process has started. This theme is visible to the user
-                 while the Flutter UI initializes. After that, this theme continues
-                 to determine the Window background behind the Flutter UI. -->
-            <meta-data
-              android:name="io.flutter.embedding.android.NormalTheme"
-              android:resource="@style/NormalTheme"
-              />
-            <!-- Displays an Android View that continues showing the launch screen
-                 Drawable until Flutter paints its first frame, then this splash
-                 screen fades out. A splash screen is useful to avoid any visual
-                 gap between the end of Android's launch screen and the painting of
-                 Flutter's first frame. -->
-            <meta-data
-              android:name="io.flutter.embedding.android.SplashScreenDrawable"
-              android:resource="@drawable/launch_background"
-              />
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-        <!-- Don't delete the meta-data below.
-             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
-        <meta-data
-            android:name="flutterEmbedding"
-            android:value="2" />
-    </application>
-</manifest>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+  <application android:label="appflowy_flutter" android:icon="@mipmap/ic_launcher"
+    android:name="${applicationName}">
+    <activity android:name=".MainActivity"
+      android:exported="true"
+      android:launchMode="singleTop"
+      android:theme="@style/LaunchTheme"
+      android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+      android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
+      <!--
+			Specifies an Android theme to apply to this Activity as soon as
+			the Android process has started. This theme is visible to the user
+			while the Flutter UI initializes. After that, this theme continues
+			to determine the Window background behind the Flutter UI.
+			-->
+      <meta-data android:name="io.flutter.embedding.android.NormalTheme"
+        android:resource="@style/NormalTheme" />
+      <!--
+			Displays an Android View that continues showing the launch screen
+			Drawable until Flutter paints its first frame, then this splash
+			screen fades out. A splash screen is useful to avoid any visual
+			gap between the end of Android's launch screen and the painting of
+			Flutter's first frame.
+			-->
+      <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable"
+        android:resource="@drawable/launch_background" />
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+      </intent-filter>
+    </activity>
+    <!--
+		Don't delete the meta-data below.
+		This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
+		-->
+    <meta-data android:name="flutterEmbedding" android:value="2" />
+  </application>
+</manifest>

+ 15 - 0
frontend/appflowy_flutter/android/app/src/main/CMakeLists.txt

@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 3.10.0)
+
+project(appflowy_flutter)
+
+# arm64-v8a
+file(COPY
+    ${ANDROID_NDK}/sources/cxx-stl/llvm-libc++/libs/arm64-v8a/libc++_shared.so
+    DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/jniLibs/arm64-v8a
+)
+
+# x86_64
+file(COPY
+    ${ANDROID_NDK}/sources/cxx-stl/llvm-libc++/libs/x86_64/libc++_shared.so
+    DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/jniLibs/x86_64
+)

+ 18 - 0
frontend/appflowy_flutter/android/app/src/main/Classes/binding.h

@@ -0,0 +1,18 @@
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+int64_t init_sdk(char *path);
+
+void async_event(int64_t port, const uint8_t *input, uintptr_t len);
+
+const uint8_t *sync_event(const uint8_t *input, uintptr_t len);
+
+int32_t set_stream_port(int64_t port);
+
+void link_me_please(void);
+
+void backend_log(int64_t level, const char *data);
+
+void set_env(const char *data);

+ 1 - 2
frontend/appflowy_flutter/android/app/src/profile/AndroidManifest.xml

@@ -1,5 +1,4 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="io.appflowy.appflowy">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Flutter needs it to communicate with the running application
          to allow setting breakpoints, to provide hot reload, etc.
     -->

+ 3 - 3
frontend/appflowy_flutter/android/build.gradle

@@ -1,12 +1,12 @@
 buildscript {
-    ext.kotlin_version = '1.6.10'
+    ext.kotlin_version = '1.8.0'
     repositories {
         google()
         mavenCentral()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath 'com.android.tools.build:gradle:7.4.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
@@ -24,6 +24,6 @@ subprojects {
     project.evaluationDependsOn(':app')
 }
 
-task clean(type: Delete) {
+tasks.register("clean", Delete) {
     delete rootProject.buildDir
 }

+ 1 - 0
frontend/appflowy_flutter/android/gradle.properties

@@ -2,3 +2,4 @@ org.gradle.jvmargs=-Xmx1536M
 android.useAndroidX=true
 android.enableJetifier=true
 org.gradle.caching=true
+android.suppressUnsupportedCompileSdk=33

+ 3 - 1
frontend/appflowy_flutter/android/gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,7 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

+ 2 - 2
frontend/appflowy_flutter/ios/Podfile.lock

@@ -170,6 +170,6 @@ SPEC CHECKSUMS:
   url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
   webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
 
-PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
+PODFILE CHECKSUM: 8c681999c7764593c94846b2a64b44d86f7a27ac
 
-COCOAPODS: 1.11.3
+COCOAPODS: 1.12.1

+ 4 - 4
frontend/appflowy_flutter/packages/appflowy_backend/android/build.gradle

@@ -2,14 +2,14 @@ group 'com.plugin.appflowy_backend'
 version '1.0-SNAPSHOT'
 
 buildscript {
-    ext.kotlin_version = '1.6.10'
+    ext.kotlin_version = '1.8.0'
     repositories {
         google()
         jcenter()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath 'com.android.tools.build:gradle:7.4.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
@@ -25,13 +25,13 @@ apply plugin: 'com.android.library'
 apply plugin: 'kotlin-android'
 
 android {
-    compileSdkVersion 31
+    compileSdkVersion 33
 
     sourceSets {
         main.java.srcDirs += 'src/main/kotlin'
     }
     defaultConfig {
-        minSdkVersion 16
+        minSdkVersion 23
     }
 }
 

+ 3 - 3
frontend/appflowy_flutter/packages/flowy_infra_ui/android/build.gradle

@@ -2,14 +2,14 @@ group 'com.example.flowy_infra_ui'
 version '1.0'
 
 buildscript {
-    ext.kotlin_version  = '1.6.10'
+    ext.kotlin_version  = '1.8.0'
     repositories {
         google()
         mavenCentral()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath 'com.android.tools.build:gradle:7.4.2'
     }
 }
 
@@ -23,7 +23,7 @@ rootProject.allprojects {
 apply plugin: 'com.android.library'
 
 android {
-    compileSdkVersion 30
+    compileSdkVersion 33
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8

+ 2 - 1
frontend/rust-lib/.gitignore

@@ -16,4 +16,5 @@ bin/
 AppFlowy-Collab/
 .env
 .env.**
-**/unit_test**
+**/unit_test**
+jniLibs/

+ 0 - 27
frontend/scripts/makefile/desktop.toml

@@ -72,32 +72,6 @@ script = [
 ]
 script_runner = "@shell"
 
-[tasks.sdk-dev-build-android]
-private = true
-script = [
-  """
-    cd rust-lib/
-    rustup show
-    rustup target add aarch64-linux-android \
-      armv7-linux-androideabi \
-      i686-linux-android \
-      x86_64-linux-android
-    DEST=${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/appflowy_flutter/android/app/src/main/jniLibs
-    rm -rf $DEST/arm64-v8a \
-      $DEST/armeabi-v7a \
-      $DEST/x86 \
-      $DEST/x86_64
-    cargo ndk \
-      -t arm64-v8a \
-      -t armeabi-v7a \
-      -t x86 \
-      -t x86_64 \
-      -o $DEST build
-    cd ../
-  """,
-]
-script_runner = "@shell"
-
 [tasks.sdk-dev-build.windows]
 private = true
 script = [
@@ -192,4 +166,3 @@ script = [
   """,
 ]
 script_runner = "@duckscript"
-

+ 26 - 1
frontend/scripts/makefile/flutter.toml

@@ -52,7 +52,16 @@ dependencies = ["appflowy-core-dev-ios"]
 run_task = { name = [
   "code_generation",
   "set-app-version",
-  "flutter-build",
+  "flutter-build-ios",
+] }
+script_runner = "@shell"
+
+[tasks.appflowy-android-dev]
+dependencies = ["appflowy-core-dev-android"]
+run_task = { name = [
+  "code_generation",
+  "set-app-version",
+  "flutter-build-android",
 ] }
 script_runner = "@shell"
 
@@ -187,6 +196,22 @@ script = ["""
   """]
 script_runner = "@shell"
 
+[tasks.flutter-build-ios]
+script = ["""
+  cd appflowy_flutter/
+  flutter pub get
+  flutter build ipa --verbose
+  """]
+script_runner = "@shell"
+
+[tasks.flutter-build-android]
+script = ["""
+  cd appflowy_flutter/
+  flutter pub get
+  flutter build apk --split-per-abi --verbose
+  """]
+script_runner = "@shell"
+
 [tasks.flutter-build.windows]
 script = ["""
   cd appflowy_flutter

+ 43 - 0
frontend/scripts/makefile/mobile.toml

@@ -33,6 +33,29 @@ script = [
 ]
 script_runner = "@shell"
 
+[tasks.appflowy-core-dev-android]
+category = "Build"
+dependencies = ["env_check"]
+run_task = { name = [
+  "setup-crate-type",
+  "sdk-build-android",
+  "post-mobile-android",
+  "restore-crate-type",
+] }
+
+[tasks.sdk-build-android]
+private = true
+script = [
+  """
+    cd rust-lib/
+    rustup show
+    echo "cargo ndk -t arm64-v8a -t x86_64 -o ./jniLibs build --features "${FLUTTER_DESKTOP_FEATURES}""
+    cargo ndk -t arm64-v8a -t x86_64 -o ./jniLibs build --features "${FLUTTER_DESKTOP_FEATURES}"
+    cd ../
+  """,
+]
+script_runner = "@shell"
+
 [tasks.post-mobile-ios]
 private = true
 script = [
@@ -53,3 +76,23 @@ script = [
   """,
 ]
 script_runner = "@duckscript"
+
+[tasks.post-mobile-android]
+script = [
+  """
+    echo "🚀 🚀 🚀  AppFlowy-Core for Android platform build success"
+    dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/appflowy_flutter/android/app/src/main
+    lib = set lib${LIB_NAME}.${LIB_EXT}
+
+    echo "💻 💻 💻  Copying ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/jniLibs/ to ${dart_ffi_dir}/"
+    rm -r ${dart_ffi_dir}/jniLibs/
+    cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/jniLibs/ \
+    ${dart_ffi_dir}/
+
+    echo "💻 💻 💻  Copying ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/binding.h to ${dart_ffi_dir}/Classes/binding.h"
+    rm -f ${dart_ffi_dir}/Classes/binding.h
+    cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/binding.h \
+    ${dart_ffi_dir}/Classes/binding.h
+  """,
+]
+script_runner = "@duckscript"