flutter_downloader icon indicating copy to clipboard operation
flutter_downloader copied to clipboard

Download callbacks not firing in production for some users

Open jillellamudisurya opened this issue 9 months ago • 1 comments

We're experiencing a critical issue where download callbacks are not being triggered on certain Android devices, specifically affecting multiple device brands including Redmi, Vivo, Oppo, and OnePlus etc... This appears to be a broader compatibility issue across these manufacturers rather than being specific to particular models.

Issue symptoms:

  • ✅ Downloads enqueue successfully via FlutterDownloader.enqueue()
  • ✅ Download tasks appear in database with correct status when queried
  • ❌ Registered callback function (downloadCallback) is never invoked
  • ❌ Progress updates and completion notifications are completely lost
  • ❌ Users see downloads stuck at 0% progress indefinitely

Environment

  • flutter_downloader version: ^1.11.8

  • Calling this in the main.dart

Initialization

// Called during app startup in main.dart
@pragma('vm:entry-point')
Future<void> initializeBgDownloader() async {
  await FlutterDownloader.initialize(debug: kDebugMode);
}
  • This BgDownloaderHost is wrapper at the top level of App.

Callback Registration & Isolate Setup

class _BgDownloaderHostState extends State<BgDownloaderHost> {
  @override
  void initState() {
    super.initState();
    bgDownloader = GetIt.I.get<BgDownloader>();

    // First bind the background isolate
    bgDownloader._startListening();

    // Then register callback after isolate binding - recommended timing
    FlutterDownloader.registerCallback(downloadCallback);
  }
}

void _startListening({int attempt = 1}) {
  // Prevent infinite retry loops - give up after 5 attempts
  if (attempt > 5) {
    logNonFatal("Port registration failed after 5 attempts");
    return;
  }

  // Remove existing port mapping before registering
  IsolateNameServer.removePortNameMapping('downloader_send_port');

  // Register new port
  final registrationSuccess = IsolateNameServer.registerPortWithName(
      port.sendPort, 'downloader_send_port');

  if (!registrationSuccess) {
    _stopListening();
    _startListening(attempt: attempt + 1); // Retry logic
    return;
  }

  port.listen((dynamic data) async {
    String id = data[0];
    DownloadTaskStatus status = DownloadTaskStatus.fromInt(data[1]);
    int progress = data[2];
    _invokeListeners(id, status, progress);
  });
}

 bool isCallbackRegistered() {
    return PluginUtilities.getCallbackHandle(downloadCallback) != null;
  }

Download Callback Function

@pragma('vm:entry-point')
Future<void> downloadCallback(String id, int status, int progress) async {
  try {
    final SendPort? send = IsolateNameServer.lookupPortByName('downloader_send_port');

    if (send == null) {
      developer.log("SendPort lookup failed - progress update lost for task $id");
      return;
    }

    send.send([id, status, progress]);
  } catch (e, st) {
    developer.log("$e", name: "DownloadCallback", level: 1200, stackTrace: st);
  }
}

Additional Code

Future<Directory> _saveDirectory() async {
    Directory directory;
    if (Platform.isAndroid) {
      final path = await AndroidDownloadPath.downloadPath();
      directory = Directory(path);
    } else {
      directory = await getApplicationDocumentsDirectory();
    }
    return directory;
  }

final taskId = await FlutterDownloader.enqueue(
        url: url,
        savedDir: savedDir.absolute.path,
        showNotification: false,
        openFileFromNotification: true,
        saveInPublicStorage: true,
        fileName: fileName,
        requiresStorageNotLow: false,
      );

🔍 Diagnostic Information

Our comprehensive health checks reveal:

  • isCallbackRegistered() returns true (callback handle exists)
  • ✅ Port registration succeeds (IsolateNameServer.registerPortWithName returns true)
  • ✅ Downloads are enqueued successfully and appear in database

Any insights or potential fixes would be greatly appreciated. We're happy to provide additional debugging information or test potential solutions.


Labels: bug, android, callback, isolate-communication

jillellamudisurya avatar Jul 07 '25 05:07 jillellamudisurya

Hello, I do not know why, but for me the annotation of both function and class with @pragma('vm:entry-point') works. Without the annotation I had the same issue with no registration of callback.

link to the comment: https://github.com/fluttercommunity/flutter_downloader/issues/992#issuecomment-2761167390

skutimechanic avatar Aug 12 '25 06:08 skutimechanic