點燈坊

失くすものさえない今が強くなるチャンスよ

將 Java Activity 整合進 Flutter 專案

Sam Xiao's Avatar 2024-08-21

可將 Java 的 Activity 整合進 Flutter 專案,由 Flutter Page 跳轉到 Java Page。

Version

Flutter 3.24
Dart 3.5

Flutter

native01

  • 主頁為 Flutter view,按下 Open Android Native Page 後顯示 Android 原生的 view

Create Project

$ flutter create -a java --platforms=android native_integration
  • 使用 flutter create 建立 Flutter 專案
  • -a java:以 Java 為 Android 的語言
  • --platforms=android:只包含 Android 平台
  • native_integration:專案名稱

Java Class

android/src/main/java/com.example.native_integration/NativeActivity.java

package com.example.native_integration;

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class NativePageActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    TextView textView = new TextView(this);
    textView.setText("Android Hello World");
    textView.setTextSize(24);

    setContentView(textView);
  }
}
  • Android 原生的 Java activity

Java Activity Wrapper

android/src/main/java/com.example.callback_plugin/MainActivity.java

package com.example.native_integration;

import android.content.Intent;
import android.os.Bundle;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "com.example/native_route";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    new MethodChannel(getFlutterEngine().getDartExecutor().getBinaryMessenger(), CHANNEL)
      .setMethodCallHandler(
        (call, result) -> {
          if (call.method.equals("openNativePage")) {
            openNativePage();
            result.success(null);
          } else {
            result.notImplemented();
          }
        }
      );
  }

  private void openNativePage() {
    Intent intent = new Intent(this, NativePageActivity.class);
    startActivity(intent);
  }
}
  • Java 的 activity wrapper

Line 10

public class MainActivity extends FlutterActivity {
}
  • MainActivity 必須繼承 FlutterActivity

Line 13

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  new MethodChannel(getFlutterEngine().getDartExecutor().getBinaryMessenger(), CHANNEL)
    .setMethodCallHandler(
      (call, result) -> {
        if (call.method.equals("openNativePage")) {
          openNativePage();
          result.success(null);
        } else {
          result.notImplemented();
        }
      }
    );
}
  • override onCreate() 顯示原生 activity
  • 提供 openNativePage() 給 MethodChannel 供 Flutter 呼叫

Manifest

android/app/src/main/AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="native_integration"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:taskAffinity=""
            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"
              />
            <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" />
        <activity android:name=".NativePageActivity" android:theme="@style/Theme.AppCompat.Light.DarkActionBar"/>
    </application>
    <!-- Required to query activities that can process text, see:
         https://developer.android.com/training/package-visibility and
         https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

         In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
    <queries>
        <intent>
            <action android:name="android.intent.action.PROCESS_TEXT"/>
            <data android:mimeType="text/plain"/>
        </intent>
    </queries>
</manifest>
  • 描述 Android app

Line 33

<activity android:name=".NativePageActivity" android:theme="@style/Theme.AppCompat.Light.DarkActionBar"/>
  • 新增 NativePageActivity

Gradle

android/app/build.gradle

plugins {
    id "com.android.application"
    id "kotlin-android"
    // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
    id "dev.flutter.flutter-gradle-plugin"
}

android {
    namespace = "com.example.native_integration"
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId = "com.example.native_integration"
        // You can update the following values to match your application needs.
        // For more information, see: https://flutter.dev/to/review-gradle-config.
        minSdk = flutter.minSdkVersion
        targetSdk = flutter.targetSdkVersion
        versionCode = flutter.versionCode
        versionName = flutter.versionName
    }

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig = signingConfigs.debug
        }
    }
}

flutter {
    source = "../.."
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'  // 添加这个依赖项
    implementation 'com.google.android.material:material:1.12.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
  • 定義 Gradle script

Line 46

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'  // 添加这个依赖项
    implementation 'com.google.android.material:material:1.12.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}
  • 新增 dependency

必須使用 androidx.appcompat:appcompat:1.6.1 ,使用 1.7 會有問題

Flutter Dart

lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Native Integration',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  static const platform = MethodChannel('com.example/native_route');

  Future<void> _openNativePage() async {
    try {
      await platform.invokeMethod('openNativePage');
    } on PlatformException catch (e) {
      print("Failed to open native page: '${e.message}'.");
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Home Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: _openNativePage,
          child: Text('Open Android Native Page'),
        ),
      ),
    );
  }
}
  • Flutter 呼叫 Java activity

Line 24

Future<void> _openNativePage() async {
  try {
    await platform.invokeMethod('openNativePage');
  } on PlatformException catch (e) {
    print("Failed to open native page: '${e.message}'.");
  }
}
  • 使用 MethodChannrel 透過 openNativePage() 呼叫 Java Activity

Conclusion

  • 若要將原本 Java activity 整合進 Flutter,有以下 SOP
    • 新增 Java 的 activity wrapper class
    • 修改 Java 的 manifest
    • 修改 Java 的 Gradle build script
    • Dart 呼叫 Java activity