Flutter安卓APP接入极光推送和本地通知

1.创建flutter项目

// cmd 输入

// org fun.tacotues为组织名

// eclipse_mark为项目名

bash 复制代码
flutter create --org fun.tacotues eclipse_mark

2. 接入极光推送(极光通道和华为通道)

极光推送通过极光通道和厂商通道进行消息下发,区别如下:

通道 描述 支持类型
极光通道 极光通道是自建通道,需要依赖长连接才能收到推送,设备离线消息不会下发。 所有可以成功注册极光通道的机型
厂商通道 厂商通道是系统通道,设备离线也可以收到推送。 Android:支持小米、华为、OPPO、vivo、魅族、荣耀、FCM通道 iOS:APNs通道

2.1 创建极光应用

进入极光控制台创建应用

填写应用包名,然后保存验证包名能否使用(某些厂商的推送要求包名不能重复)

2.2接入极光通道

2.2.1 jpush_flutter插件安装和配置

安装Flutter极光推送插件

进入极光Flutter的github地址查看插件安装步骤

安装插件

这里/android/app/build.gradle配置改为兼容.kts写法

在 android/app/main/AndroidManifest.xml 增加访问网络和显示通知权限

2.2.2 初始化插件

main.dart中初始化插件

setup需要填入创建的极光应用的appKey

dart 复制代码
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; // kIsWeb

// 极光(⚠️ 只能在 Android / iOS 使用)
import 'package:jpush_flutter/jpush_flutter.dart';
import 'package:jpush_flutter/jpush_interface.dart';

import 'package:eclipse_mark/pages/detail_page.dart';
import 'package:eclipse_mark/pages/home_page.dart';
import 'package:eclipse_mark/j_push/notification_payload.dart.dart';

// intent:#Intent;component=com.tacotues.eclipse_mark/com.tacotues.eclipse_mark.MainActivity;end
// RegistrationID 

/// 全局 NavigatorKey(用于点击通知跳转)
final navigatorKey = GlobalKey<NavigatorState>();

/// JPush 实例(延迟初始化,避免 Web 报错)
JPushFlutterInterface? jpush;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  /// 🚫 Web 平台不初始化极光
  if (!kIsWeb) {
    await initJPush();
  } else {
    debugPrint('🌐 当前是 Web 平台,已跳过极光推送初始化');
  }

  runApp(const MyAPP());
}

/// 初始化极光(仅 Android / iOS)
Future<void> initJPush() async {
  jpush = JPush.newJPush();

  // 注意addEventHandler 要在 setup 之前
  jpush!.addEventHandler(
    /// 前台收到通知
    onReceiveNotification: (notification) async {
      debugPrint("📩 onReceiveNotification: $notification");
    },

    /// 点击通知
    onOpenNotification: (notification) async {
      debugPrint("👉 onOpenNotification: $notification");
      _handleNotificationClick(notification);
    },

    /// 自定义消息
    onReceiveMessage: (message) async {
      debugPrint("✉️ onReceiveMessage: $message");
    },

    /// 连接状态
    onConnected: (message) async {
      debugPrint("🔌 onConnected: $message");
    },

    /// iOS DeviceToken
    onReceiveDeviceToken: (message) async {
      debugPrint("📱 onReceiveDeviceToken: $message");
    },
  );

  /// 设置 AppKey
  jpush!.setup(
    appKey: "替换成你自己的 appKey",
    channel: "developer-default",
    production: false,
    debug: true,
  );

  /// 获取 RegistrationID
  final rid = await jpush!.getRegistrationID() ?? '';
  debugPrint('🔥 JPush RegistrationID: $rid');
}


/// 统一处理点击通知跳转
void _handleNotificationClick(Map<String, dynamic> notification) {
  final extras = notification['extras'] as Map?;
  final rawData = extras?['cn.jpush.android.EXTRA'];

  if (rawData is! Map) return;

  final payload = NotificationPayload.fromRaw(rawData);

  navigatorKey.currentState?.pushNamed(
    '/detail',
    arguments: payload,
  );
}


class MyAPP extends StatelessWidget {
  const MyAPP({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      navigatorKey: navigatorKey,
      home: const HomePage(),
      routes: {
        '/detail': (context) => const DetailPage(),
      },
    );
  }
}
dart 复制代码
// notification_payload.dart.dart
class NotificationPayload {
  final Map<String, dynamic> data;

  NotificationPayload(this.data);

  factory NotificationPayload.fromRaw(Map raw) {
    return NotificationPayload(
      raw.map((k, v) => MapEntry(k.toString(), v)),
    );
  }

  /// 安全取值
  T? get<T>(String key) {
    final value = data[key];
    if (value is T) return value;
    return null;
  }

  /// 常用兜底方法
  String getString(String key, {String defaultValue = ''}) {
    final value = data[key];
    return value?.toString() ?? defaultValue;
  }

  int getInt(String key, {int defaultValue = 0}) {
    final value = data[key];
    if (value is int) return value;
    if (value is String) return int.tryParse(value) ?? defaultValue;
    return defaultValue;
  }

  bool contains(String key) => data.containsKey(key);

  @override
  String toString() => data.toString();
}

创建主页和详情页面

dart 复制代码
// home_page.dart
import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pushNamed('/detail');
                },
                child: const Text('跳转到detail页面'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
dart 复制代码
// detail_page.dart
import 'package:flutter/material.dart';
import 'package:eclipse_mark/j_push/notification_payload.dart.dart';

class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    final payload =
        ModalRoute.of(context)?.settings.arguments as NotificationPayload?;

    if (payload == null) {
      return Scaffold(
        appBar: AppBar(title: const Text('详情页面')),
        body: Center(child: Text('无通知数据')),
      );
    }

    final id = payload.getInt('id');
    final label = payload.getString('label');

    return Scaffold(
      appBar: AppBar(title: const Text('详情页面')),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('ID: $id'),
          Text('Label: $label'),
          Text('Raw: ${payload.data}'),
        ],
      ),
    );
  }
}
2.2.3 测试极光通道推送

在控制台拿到注册id



2.3接入华为通道

2.3.1 申请华为厂商通道参数


华为参数获取指南

创建项目

开通推送服务

查看推送服务是否已经开启

添加应用

填写包名,添加应用

下载 agconnect-services.json 文件方案android/app目录下

添加证书指纹,这里我使用debug.keystore (本地调试证书)
如何添加证书指纹

通过 keytool获取

debug.keystore(Flutter 默认)

Windows:

bash 复制代码
keytool -list -v ^
-alias androiddebugkey ^
-keystore %USERPROFILE%\.android\debug.keystore ^
-storepass android ^
-keypass android

拿到 7B:A1:96:B5 这一串指纹复制到华为配置页面保存(不要复制 SHA256: 前缀)

2.3.2 华为通道参数配置


2.3.3 极光华为适配

将debug.keystore (本地调试证书)复制到android/app目录下

修改android/app/build.gradle.kts

kotlin 复制代码
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.tacotues.eclipse_mark"
    compileSdk = flutter.compileSdkVersion
    ndkVersion = flutter.ndkVersion

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

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId = "com.tacotues.eclipse_mark"
        // 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

        // 极光 JPush
        manifestPlaceholders["JPUSH_PKGNAME"] = "com.tacotues.eclipse_mark"
        manifestPlaceholders["JPUSH_APPKEY"] = "你的APPkey"
        manifestPlaceholders["JPUSH_CHANNEL"] = "developer-default"
    }

    signingConfigs {
        create("release") {
            storeFile = file("debug.keystore")
            storePassword = "android"
            keyAlias = "androiddebugkey"
            keyPassword = "android"
        }
    }

    buildTypes {
    release {
        signingConfig = signingConfigs.getByName("release")
        isMinifyEnabled = false     // 不开启混淆
        isShrinkResources = false   // ⚠ 关闭资源压缩
    }
    debug {
        signingConfig = signingConfigs.getByName("release")
        isMinifyEnabled = false
        isShrinkResources = false
     }
    }
}

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

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")

    // 华为推送 SDK
    implementation("com.huawei.hms:push:6.11.0.300")
    // 极光华为适配
    implementation("cn.jiguang.sdk.plugin:huawei:5.6.0")
}

// ⚡ 华为 AGC 插件只需要 apply
apply(plugin = "com.huawei.agconnect")

修改andorid/build.gradle.kts

kotlin 复制代码
buildscript {
    repositories {
        google()
        mavenCentral()
        maven("https://developer.huawei.com/repo/") // 华为仓库
    }
    dependencies {
        classpath("com.android.tools.build:gradle:8.1.1") // ⚡ 对应 gradle 8.14
        classpath("com.huawei.agconnect:agcp:1.9.1.301")
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
        maven("https://developer.huawei.com/repo/") // 华为仓库
    }
}

val newBuildDir: Directory =
    rootProject.layout.buildDirectory
        .dir("../../build")
        .get()
rootProject.layout.buildDirectory.value(newBuildDir)

subprojects {
    val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
    project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
    project.evaluationDependsOn(":app")
}

tasks.register<Delete>("clean") {
    delete(rootProject.layout.buildDirectory)
}
2.3.4 打包APP离线测试消息推送
bash 复制代码
// usb连接手机
flutter clean  
flutter build apk --release
flutter install

3.flutter本地通知

3.1 依赖安装

flutter pub get 安装几个依赖

bash 复制代码
name: eclipse_mark
description: "A new Flutter project."

version: 1.0.0+1

environment:
  sdk: ^3.10.1

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.8
  jpush_flutter: ^3.4.0
  path_provider: ^2.1.5 // 路径插件
  flutter_local_notifications: ^19.5.0 // 本地消息推送
  timezone: ^0.10.1 // 时区插件
  permission_handler: ^12.0.1 // 权限处理

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^6.0.0

flutter:
  uses-material-design: true
  assets:
    - assets/images/ # 加载整个 images 文件夹下的图片

在项目里面放几张图片用于本地消息测试,(安卓的消息不支持在线图片, ios支持在线图片)

android/app/build.gradle.kts 开启 isCoreLibraryDesugaringEnabled

3.2 创建本地通知类和工具

local_notification_service.dart

dart 复制代码
import 'dart:convert';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:path_provider/path_provider.dart';
import '../main.dart';
import 'package:timezone/data/latest.dart' as tz;

class LocalNotificationService {
  LocalNotificationService._internal();
  static final LocalNotificationService _instance =
      LocalNotificationService._internal();
  factory LocalNotificationService() => _instance;

  final FlutterLocalNotificationsPlugin _plugin =
      FlutterLocalNotificationsPlugin();

  Future<void> init() async {
    tz.initializeTimeZones();

    const androidSettings = AndroidInitializationSettings(
      '@mipmap/ic_launcher',
    );
    const iosSettings = DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );

    const settings = InitializationSettings(
      android: androidSettings,
      iOS: iosSettings,
    );

    await _plugin.initialize(
      settings,
      onDidReceiveNotificationResponse: _onNotificationClick,
    );
  }

  static void _onNotificationClick(NotificationResponse response) {
    final payload = response.payload;
    if (payload != null) {
      navigatorKey.currentState?.pushNamed(
        '/notification_screen',
        arguments: payload,
      );
    }
  }

  /// 把 assets 图片复制到本地缓存
  Future<String> copyAssetToFile(String assetPath, String fileName) async {
    final bytes = await rootBundle.load(assetPath);
    final dir = await getTemporaryDirectory();
    final file = File('${dir.path}/$fileName');
    await file.writeAsBytes(bytes.buffer.asUint8List());
    return file.path;
  }

  /// 1️⃣ 纯文字通知
  Future<void> showTextNotification({
    required int id,
    required String title,
    required String body,
    required String type,
  }) async {
    final androidDetails = AndroidNotificationDetails(
      'channel_id',
      '文字通知',
      importance: Importance.max,
      priority: Priority.high,
      styleInformation: BigTextStyleInformation(
        body, // 展开显示长文本
        contentTitle: title,
      ),
    );

    const iosDetails = DarwinNotificationDetails();

    final details = NotificationDetails(
      android: androidDetails,
      iOS: iosDetails,
    );

    final payloadData = {
      'id': id,
      'type': type,
      'title': title,
      'body': body,
    };

    await _plugin.show(
      id,
      title,
      body,
      details,
      payload: jsonEncode(payloadData),
    );
  }

  /// 2️⃣ 文字 + 小图通知
  Future<void> showTextWithSmallIcon({
    required int id,
    required String title,
    required String body,
    required String type,
    required String assetImagePath,
  }) async {
    final imagePath = await copyAssetToFile(assetImagePath, 'notification_icon.jpg');

    final androidDetails = AndroidNotificationDetails(
      'channel_id',
      '小图通知',
      importance: Importance.max,
      priority: Priority.high,
      largeIcon: FilePathAndroidBitmap(imagePath),
      styleInformation: BigTextStyleInformation(body, contentTitle: title),
    );

    const iosDetails = DarwinNotificationDetails();

    final details = NotificationDetails(
      android: androidDetails,
      iOS: iosDetails,
    );

    final payloadData = {
      'id': id,
      'type': type,
      'title': title,
      'body': body,
      'extra': {'imagePath': imagePath},
    };

    await _plugin.show(
      id,
      title,
      body,
      details,
      payload: jsonEncode(payloadData),
    );
  }

  /// 3️⃣ 文字 + 大图 + 小图通知
  Future<void> showTextWithBigAndSmallImage({
    required int id,
    required String title,
    required String body,
    required String type,
    required String assetImagePath,
  }) async {
    final imagePath = await copyAssetToFile(assetImagePath, 'notification_image.jpg');

    final bigPictureStyle = BigPictureStyleInformation(
      FilePathAndroidBitmap(imagePath), // 大图
      contentTitle: title,
      summaryText: body,
      largeIcon: FilePathAndroidBitmap(imagePath), // 左侧小图
    );

    final androidDetails = AndroidNotificationDetails(
      'channel_id',
      '大图通知',
      importance: Importance.max,
      priority: Priority.high,
      styleInformation: bigPictureStyle,
      ticker: 'ticker',
    );

    const iosDetails = DarwinNotificationDetails();

    final details = NotificationDetails(
      android: androidDetails,
      iOS: iosDetails,
    );

    final payloadData = {
      'id': id,
      'type': type,
      'title': title,
      'body': body,
      'extra': {'imagePath': imagePath},
    };

    await _plugin.show(
      id,
      title,
      body,
      details,
      payload: jsonEncode(payloadData),
    );
  }
}

notification_permission_util.dart

dart 复制代码
import 'package:permission_handler/permission_handler.dart';

class NotificationPermissionUtil {
  /// 是否已开启通知权限
  static Future<bool> isGranted() async {
    final status = await Permission.notification.status;
    return status.isGranted;
  }

  /// 请求通知权限(首次)
  static Future<bool> request() async {
    final status = await Permission.notification.request();
    return status.isGranted;
  }

  /// 跳转系统设置
  static Future<void> openSettings() async {
    await openAppSettings();
  }
}

3.3 抽取权限设置页面组件

notification_permission_item.dart

dart 复制代码
import 'package:eclipse_mark/components/setting_item.dart';
import 'package:eclipse_mark/local_notification/notification_permission_util.dart';
import 'package:flutter/material.dart';

class NotificationPermissionItem extends StatefulWidget {
  const NotificationPermissionItem({super.key});

  @override
  State<NotificationPermissionItem> createState() =>
      _NotificationPermissionItemState();
}

class _NotificationPermissionItemState extends State<NotificationPermissionItem>
    with WidgetsBindingObserver {
  bool _enabled = true;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _checkPermission();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  /// 从系统设置回来时刷新
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _checkPermission();
    }
  }

  Future<void> _checkPermission() async {
    final granted = await NotificationPermissionUtil.isGranted();
    if (mounted) {
      setState(() => _enabled = granted);
    }
  }

  @override
  Widget build(BuildContext context) {
    return SettingItem(
      title: '系统通知权限',
      valueText: _enabled ? '已开启' : '未开启',
      enabled: !_enabled,
      onTap: _enabled
          ? null
          : () async {
              await NotificationPermissionUtil.openSettings();
            },
    );
  }
}

setting_item.dart

dart 复制代码
import 'package:flutter/material.dart';

class SettingItem extends StatelessWidget {
  final String title;
  final String valueText;
  final VoidCallback? onTap;
  final bool enabled;

  const SettingItem({
    super.key,
    required this.title,
    required this.valueText,
    this.onTap,
    this.enabled = true,
  });

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: enabled ? onTap : null,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
        decoration: const BoxDecoration(
          border: Border(
            bottom: BorderSide(color: Color(0xFFE5E5E5)),
          ),
        ),
        child: Row(
          children: [
            Expanded(
              child: Text(
                title,
                style: const TextStyle(fontSize: 16),
              ),
            ),
            Text(
              valueText,
              style: TextStyle(
                color: enabled ? Colors.grey[600] : Colors.red,
                fontSize: 14,
              ),
            ),
            if (onTap != null) ...[
              const SizedBox(width: 6),
              const Icon(Icons.chevron_right, size: 20, color: Colors.grey),
            ],
          ],
        ),
      ),
    );
  }
}

3.4 本地消息详情和设置页面代码

notification_page.dart

dart 复制代码
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';

class NotificationPage extends StatelessWidget {
  const NotificationPage({super.key});

  @override
  Widget build(BuildContext context) {
    final payloadStr = ModalRoute.of(context)?.settings.arguments as String?;

    String title = '暂无标题';
    String body = '暂无内容';
    String? imagePath;

    if (payloadStr != null) {
      try {
        final data = jsonDecode(payloadStr);
        title = data['title'] ?? title;
        body = data['body'] ?? body;
        final extra = data['extra'] ?? {};
        imagePath = extra['imagePath'];
      } catch (e) {}
    }

    return Scaffold(
      appBar: AppBar(title: const Text('消息详情')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('标题: $title', style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 8),
            Text('内容: $body', style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 8),
            if (imagePath != null) Image.file(File(imagePath)),
          ],
        ),
      ),
    );
  }
}

setting_page.dart

dart 复制代码
import 'package:eclipse_mark/components/notification_permission_item.dart';
import 'package:flutter/material.dart';

class SettingPage extends StatelessWidget {
  const SettingPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('设置')),
      body: ListView(
        children: const [
          NotificationPermissionItem(),
        ],
      ),
    );
  }
}

main.dart 里面初始化 LocalNotificationService

dart 复制代码
import 'package:eclipse_mark/local_notification/local_notification_service.dart';
import 'package:eclipse_mark/pages/notification_page.dart';
import 'package:eclipse_mark/pages/setting_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart'; // kIsWeb

// 极光(⚠️ 只能在 Android / iOS 使用)
import 'package:jpush_flutter/jpush_flutter.dart';
import 'package:jpush_flutter/jpush_interface.dart';

import 'package:eclipse_mark/pages/detail_page.dart';
import 'package:eclipse_mark/pages/home_page.dart';
import 'package:eclipse_mark/j_push/notification_payload.dart.dart';

// intent:#Intent;component=com.tacotues.eclipse_mark/com.tacotues.eclipse_mark.MainActivity;end
// RegistrationID  

/// 全局 NavigatorKey(用于点击通知跳转)
final navigatorKey = GlobalKey<NavigatorState>();

/// JPush 实例(延迟初始化,避免 Web 报错)
JPushFlutterInterface? jpush;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 初始化本地通知
  await LocalNotificationService().init();

  /// 🚫 Web 平台不初始化极光
  if (!kIsWeb) {
    await initJPush();
  } else {
    debugPrint('🌐 当前是 Web 平台,已跳过极光推送初始化');
  }

  runApp(const MyAPP());
}

/// 初始化极光(仅 Android / iOS)
Future<void> initJPush() async {
  jpush = JPush.newJPush();

  // 注意addEventHandler 要在 setup 之前
  jpush!.addEventHandler(
    /// 前台收到通知
    onReceiveNotification: (notification) async {
      debugPrint("📩 onReceiveNotification: $notification");
    },

    /// 点击通知
    onOpenNotification: (notification) async {
      debugPrint("👉 onOpenNotification: $notification");
      _handleNotificationClick(notification);
    },

    /// 自定义消息
    onReceiveMessage: (message) async {
      debugPrint("✉️ onReceiveMessage: $message");
    },

    /// 连接状态
    onConnected: (message) async {
      debugPrint("🔌 onConnected: $message");
    },

    /// iOS DeviceToken
    onReceiveDeviceToken: (message) async {
      debugPrint("📱 onReceiveDeviceToken: $message");
    },
  );

  /// 设置 AppKey
  jpush!.setup(
    appKey: "你的appKey",
    channel: "developer-default",
    production: false,
    debug: true,
  );

  /// 获取 RegistrationID
  final rid = await jpush!.getRegistrationID() ?? '';
  debugPrint('🔥 JPush RegistrationID: $rid');
}

/// 统一处理点击通知跳转
void _handleNotificationClick(Map<String, dynamic> notification) {
  final extras = notification['extras'] as Map?;
  final rawData = extras?['cn.jpush.android.EXTRA'];

  if (rawData is! Map) return;

  final payload = NotificationPayload.fromRaw(rawData);

  navigatorKey.currentState?.pushNamed('/detail', arguments: payload);
}

class MyAPP extends StatelessWidget {
  const MyAPP({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      navigatorKey: navigatorKey,
      home: const HomePage(),
      routes: {
        '/detail': (context) => const DetailPage(),
        '/notification_screen': (context) => const NotificationPage(),
        '/setting': (context) => const SettingPage(),
      },
    );
  }
}

3.3 效果测试


相关推荐
南村群童欺我老无力.2 小时前
Flutter应用鸿蒙迁移实战:性能优化与渐进式迁移指南
javascript·flutter·ci/cd·华为·性能优化·typescript·harmonyos
LawrenceLan2 小时前
Flutter 零基础入门(十二):枚举(enum)与状态管理的第一步
开发语言·前端·flutter·dart
Jony_2 小时前
Android 设计架构演进历程
android·android jetpack
犹若故人归3 小时前
Android开发应用--高级UI界面设计
android·ui
程序员老刘·4 小时前
重拾Eval能力:D4rt为Flutter注入AI进化基因
人工智能·flutter·跨平台开发·客户端开发
zzhongcy4 小时前
复合索引 (item1, item2, item3 ) > (?, ?, ?) 不起作用,EXPLAIN 后type=ALL(全表扫描)
android·数据库
kirk_wang4 小时前
Flutter艺术探索-Flutter响应式设计:MediaQuery与LayoutBuilder
flutter·移动开发·flutter教程·移动开发教程
冬奇Lab5 小时前
稳定性性能系列之十三——CPU与I/O性能优化:Simpleperf与存储优化实战
android·性能优化
像风一样自由5 小时前
android native 中的函数动态注册方式总结
android·java·服务器·安卓逆向分析·native函数动态注册·.so文件分析