You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter后台处理问题:应用关闭时如何从SQLite触发本地通知

解决方案:Flutter应用关闭时从SQLite触发本地通知

Great question! When a Flutter app is closed (either by the user or system), Dart code stops executing immediately, so we need to lean on native platform capabilities to read from SQLite and trigger local notifications. Let's break down solutions for both your scenarios:


1. 应用主动关闭时触发通知(用户划掉应用)

In this case, we can listen for the app's lifecycle state to trigger a native task right before the app fully shuts down. The native task will handle reading from SQLite and sending the notification.

Step-by-Step Implementation

  • Setup Dependencies: Use flutter_local_notifications for cross-platform notification support, and create a MethodChannel to communicate with native code.
  • Lifecycle Listening: Detect when the app enters the detached state (final step before shutdown) and invoke a native method.
  • Native Task Execution: Use Android's WorkManager or iOS's BGTaskScheduler to run a one-time task that reads SQLite data and sends a notification.

Flutter Code (Lifecycle & Channel Communication)

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  final MethodChannel _notificationChannel = MethodChannel('com.your.app/notification_trigger');

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

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    // Trigger native task when app is fully detached
    if (state == AppLifecycleState.detached) {
      _notificationChannel.invokeMethod('triggerFromSQLite');
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(appBar: AppBar(title: Text("SQLite Notification Demo"))),
    );
  }
}

Android Code (Kotlin with WorkManager)

First, add WorkManager dependency to your build.gradle:

dependencies {
    implementation "androidx.work:work-runtime-ktx:2.8.1"
}

Then implement the worker and channel handler:

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.work.Worker
import androidx.work.WorkerParameters

class SQLiteNotificationWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // Access Flutter's SQLite database
        val dbPath = applicationContext.getDatabasePath("your_database_name.db").absolutePath
        val db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY)

        // Query example: Get unread message count
        val cursor = db.rawQuery("SELECT COUNT(*) FROM messages WHERE is_read = 0", null)
        val unreadCount = if (cursor.moveToFirst()) cursor.getInt(0) else 0
        cursor.close()
        db.close()

        // Send notification if there are unread items
        if (unreadCount > 0) {
            // Create notification channel (required for Android 8+)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                val channel = android.app.NotificationChannel(
                    "unread_channel",
                    "Unread Notifications",
                    android.app.NotificationManager.IMPORTANCE_DEFAULT
                )
                val notificationManager = applicationContext.getSystemService(android.app.NotificationManager::class.java)
                notificationManager.createNotificationChannel(channel)
            }

            val notification = NotificationCompat.Builder(applicationContext, "unread_channel")
                .setSmallIcon(R.drawable.ic_notification)
                .setContentTitle("Unread Messages")
                .setContentText("You have $unreadCount unread messages")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .build()

            NotificationManagerCompat.from(applicationContext).notify(1, notification)
        }
        return Result.success()
    }
}

// In your FlutterActivity or FlutterFragment
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.your.app/notification_trigger")
    .setMethodCallHandler { call, result ->
        if (call.method == "triggerFromSQLite") {
            val workRequest = androidx.work.OneTimeWorkRequestBuilder<SQLiteNotificationWorker>().build()
            androidx.work.WorkManager.getInstance(context).enqueue(workRequest)
            result.success(null)
        } else {
            result.notImplemented()
        }
    }

iOS Code (Swift with BGTaskScheduler)

First, add FMDB (SQLite wrapper) to your Podfile, and enable background modes in Info.plist (add fetch to UIBackgroundModes).

Then implement the task handler and channel communication:

import UIKit
import Flutter
import BackgroundTasks
import FMDB

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    let taskIdentifier = "com.your.app.sqlite_notification_task"

    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // Register background task
        BGTaskScheduler.shared.register(forTaskWithIdentifier: taskIdentifier, using: nil) { task in
            self.handleNotificationTask(task: task as! BGAppRefreshTask)
        }

        let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(name: "com.your.app/notification_trigger", binaryMessenger: controller.binaryMessenger)
        channel.setMethodCallHandler { [weak self] call, result in
            if call.method == "triggerFromSQLite" {
                let request = BGAppRefreshTaskRequest(identifier: self!.taskIdentifier)
                request.earliestBeginDate = Date(timeIntervalSinceNow: 1) // Run ASAP
                do {
                    try BGTaskScheduler.shared.submit(request)
                    result.success(nil)
                } catch {
                    result.error("SUBMIT_FAILED", "Could not schedule task", error.localizedDescription)
                }
            } else {
                result.notImplemented()
            }
        }

        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    func handleNotificationTask(task: BGAppRefreshTask) {
        // Get Flutter's SQLite database path
        let fileManager = FileManager.default
        guard let supportDir = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
            task.setTaskCompleted(success: false)
            return
        }
        let dbPath = supportDir.appendingPathComponent("your_database_name.db").path
        let db = FMDatabase(path: dbPath)

        guard db.open() else {
            task.setTaskCompleted(success: false)
            return
        }

        // Query unread messages
        guard let result = db.executeQuery("SELECT COUNT(*) FROM messages WHERE is_read = 0", withArgumentsIn: nil) else {
            db.close()
            task.setTaskCompleted(success: false)
            return
        }

        let unreadCount = result.next() ? Int(result.int(forColumnIndex: 0)) : 0
        result.close()
        db.close()

        // Send notification
        if unreadCount > 0 {
            let content = UNMutableNotificationContent()
            content.title = "Unread Messages"
            content.body = "You have \(unreadCount) unread messages"
            content.sound = UNNotificationSound.default

            let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
            UNUserNotificationCenter.current().add(request) { error in
                if let error = error {
                    print("Notification error: \(error.localizedDescription)")
                }
            }
        }

        task.setTaskCompleted(success: true)
    }
}

2. 后台处理场景下应用关闭时触发通知

If your app was running a background task when it got closed, you need to ensure the task persists natively. Flutter's background plugins (like workmanager) wrap native APIs, but for reliability, it's better to handle SQLite reading and notification sending directly in native background tasks.

Key Notes

  • Android: WorkManager tasks are persistent by default—they'll run even if the app is closed or the device reboots (if you add PersistedWorkPolicy.PERSISTED).
  • iOS: Use BGProcessingTask for longer-running tasks (like heavy SQLite queries) instead of BGAppRefreshTask. Remember iOS has strict background execution limits, so keep tasks short and focused.
  • Avoid Flutter Background Code: Once the app is closed, Dart isolates may be terminated, so native code is the only reliable way to complete the task.

Critical Considerations

  • SQLite Path: Double-check the database path on both platforms—Flutter's sqflite stores databases in getDatabasesPath(), which maps to different native locations as shown in the code examples.
  • Permissions:
    • Android 13+: Request POST_NOTIFICATIONS permission at runtime.
    • iOS: Request notification permission using UNUserNotificationCenter and enable background modes in Info.plist.
  • Testing:
    • Android: Use adb shell am kill com.your.app to force-close the app and verify the task runs.
    • iOS: Use Xcode's Debug > Simulate Background Fetch or wait for the system to trigger the task (may take a few minutes).

内容的提问来源于stack exchange,提问作者Mahesh Bhattarai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:47:49