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

Flutter通话结束后弹窗实现求助(Call Log包报错)

问题:Flutter实现通话结束弹窗时call_log包报Fatal: No callback registered错误

我想在Flutter中实现类似Truecaller的通话结束后弹出弹窗功能,使用call_log包获取通话记录时触发错误:Fatal: No callback registered。

实现代码

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

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
  }

  // Call
  Future<void> fetchCallLogs() async {
    var callLogs = await CallLog.query();
    if (callLogs.isNotEmpty) {
      final lastCall = callLogs.first;
      if (lastCall.callType == CallType.incoming) {
        showAlertDialog(context);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Call Dialog'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [],
          ),
        ),
      ),
    );
  }

  showAlertDialog(BuildContext context) {
    // Create button
    Widget okButton = TextButton(
      child: Text("OK"),
      onPressed: () {
        Navigator.of(context).pop();
      },
    );

    // Create AlertDialog
    AlertDialog alert = AlertDialog(
      title: Text("Simple Alert"),
      content: Text("This is an alert message."),
      actions: [
        okButton,
      ],
    );

    // show the dialog
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return alert;
      },
    );
  }
}

运行日志

D/me.sodipto.phone_state_background(10136): New broadcast event received...
W/FlutterJNI(10136): FlutterJNI.loadLibrary called more than once
W/FlutterJNI(10136): FlutterJNI.prefetchDefaultFontManager called more than once
I/ResourceExtractor(10136): Found extracted resources res_timestamp-1-1700557316079
W/FlutterJNI(10136): FlutterJNI.init called more than once
D/me.sodipto.phone_state_background(10136): Phone State event PHONE_RINGING number: 6505551212
E/me.sodipto.phone_state_background(10136): Fatal: No callback registered

解决方案

错误原因

Fatal: No callback registered是因为call_log包的后台广播接收器触发通话状态事件时,Flutter侧未注册对应的回调处理逻辑;同时你的代码仅实现了主动查询通话记录,未监听通话状态变化,无法在通话结束时自动触发弹窗。

修复步骤

  1. 添加权限到AndroidManifest.xml
    在android/app/src/main/AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
  1. 修改代码添加监听逻辑
    引入权限处理包,并注册通话状态流监听,在通话结束时触发弹窗:
import 'package:call_log/call_log.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Stream<CallEvent>? _callStream;

  @override
  void initState() {
    super.initState();
    _initCallListener();
  }

  // 初始化权限与通话监听
  Future<void> _initCallListener() async {
    // 请求必要权限
    final phonePerm = await Permission.phone.request();
    final callLogPerm = await Permission.callLogs.request();

    if (phonePerm.isGranted && callLogPerm.isGranted) {
      // 注册通话状态流监听,解决回调未注册问题
      _callStream = CallLog.stream();
      _callStream?.listen((CallEvent event) {
        // 监听通话结束事件
        if (event.event == CallEventTypes.callEnded) {
          _fetchLastCallAndShowDialog();
        }
      });
    }
  }

  // 获取最后一条通话记录并弹窗
  Future<void> _fetchLastCallAndShowDialog() async {
    final callLogs = await CallLog.query();
    if (callLogs.isNotEmpty) {
      final lastCall = callLogs.first;
      if (lastCall.callType == CallType.incoming && mounted) {
        showAlertDialog(context);
      }
    }
  }

  @override
  void dispose() {
    _callStream?.cancel(); // 销毁时取消监听,避免内存泄漏
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: const Text('Call Dialog')),
        body: const Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [],
          ),
        ),
      ),
    );
  }

  void showAlertDialog(BuildContext context) {
    final okButton = TextButton(
      child: const Text("OK"),
      onPressed: () => Navigator.of(context).pop(),
    );

    final alert = AlertDialog(
      title: const Text("通话结束提示"),
      content: const Text("您有一个来电已结束。"),
      actions: [okButton],
    );

    showDialog(context: context, builder: (_) => alert);
  }
}
  1. 添加依赖到pubspec.yaml
    在pubspec.yaml中补充权限处理包:
dependencies:
  call_log: ^latest_version
  permission_handler: ^latest_version

额外说明

  • 使用mounted判断可避免Widget已销毁时调用showDialog引发的错误
  • call_log包的广播接收器通常会自动注册,若出现问题可手动在AndroidManifest.xml中添加:
<receiver android:name="com.sodipto.call_log.CallLogReceiver" android:enabled="true" android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.PHONE_STATE" />
    <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
  </intent-filter>
</receiver>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 16:23:23