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 效果测试


相关推荐
火柴就是我6 小时前
让我们实现一个更好看的内部阴影按钮
android·flutter
王晓枫6 小时前
flutter接入三方库运行报错:Error running pod install
前端·flutter
砖厂小工12 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心13 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心13 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
shankss14 小时前
Flutter 下拉刷新库 pull_to_refresh_plus 设计与实现分析
flutter
Kapaseker16 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴16 小时前
Android17 为什么重写 MessageQueue
android
忆江南1 天前
iOS 深度解析
flutter·ios
明君879971 天前
Flutter 实现 AI 聊天页面 —— 记一次 Markdown 数学公式显示的踩坑之旅
前端·flutter