Приложение на фоне: уведомление получено, но оно не запускает звук кольца. ..packageName ../ передний план).
Это мой main.dart:
Future _initializeApp() async {
try {
WidgetsFlutterBinding.ensureInitialized();
if (Firebase.apps.isEmpty) {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform
);
}
try {
PlatformChannels().setupChannels();
//FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
// Set up error handling
FlutterError.onError =
FirebaseCrashlytics.instance.recordFlutterFatalError;
// Platform specific initialization
if (Platform.isAndroid) {
await FirebaseMessaging.instance.setAutoInitEnabled(true);
} else if (Platform.isIOS) {
await FirebaseMessaging.instance.getAPNSToken();
}
} catch(e){
debugPrint('Firebase services initialization error (may be expected in tests): $e');
}
// Hive initialization
await Hive.initFlutter();
registerHiveAdapters();
await Hive.openBox('notifications');
await clearOldNotifications();
// System orientation
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
// Shared preferences
final sharedPref = await SharedPreferences.getInstance();
return sharedPref;
} catch (e, stackTrace) {
debugPrint('Initialization error: $e\n$stackTrace');
rethrow;
}
}
void main() async {
try {
final sharedPref = await _initializeApp();
print("
runApp(
ProviderScope(
overrides: [
sharedPreferencesProvider.overrideWithValue(sharedPref),
],
child: const MyApp(),
),
);
} catch (e, stackTrace) {
debugPrint('Error in main/start app: $e\n$stackTrace');
}
}
class MyApp extends ConsumerStatefulWidget {
const MyApp({super.key});
@override
ConsumerState createState() => _MyAppState();
}
class _MyAppState extends ConsumerState {
@override
void initState() {
super.initState();
NewNotificationService().initNotifications(ref);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
PlatformChannels.processPendingNavigation(context);
}
@override
Widget build(BuildContext context) {
return Builder(
builder: (context) {
final mediaQuery = MediaQuery.of(context);
return MediaQuery(
data: mediaQuery.copyWith(
textScaler: TextScaler.linear(mediaQuery.textScaleFactor * 1.0), // Adjust as needed
),
child: Sizer(builder: (context, orientation, deviceType) {
return MaterialApp.router(
routerConfig: ref.watch(goRouterProvider),
supportedLocales: const [Locale("en")],
localizationsDelegates: const [
CountryLocalizations.delegate,
],
theme: theme,
debugShowCheckedModeBanner: false,
);
}));});
}
}
< /code>
Это мой класс платформы: < /p>
class PlatformChannels {
static final PlatformChannels _instance = PlatformChannels._internal();
factory PlatformChannels() => _instance;
PlatformChannels._internal();
static const incomingCallChannel =
MethodChannel('
/incoming_call_channel');
static const regularNotificationChannel =
MethodChannel('/regular_notification_channel');
void setupChannels() {
print("
checkCallAction();
regularNotificationAction();
}
static Map? _pendingRouteParams;
static String? _pendingRouteName;
void checkCallAction() async {
print("Setting up incomingCallChannel handler..."); // Add this line
try {
incomingCallChannel.setMethodCallHandler((call) async {
print('
print('
print('
if (call.method == 'getCallActionAndNavigate') {
debugPrint(
"Received method call: ${call.method} with arguments: ${call.arguments}"); // Debug log
final String? action = call.arguments['action'];
final String? message = call.arguments['message'];
if (action == "ACCEPT_CALL" || action == "CALL_ACCEPTED") {
_pendingRouteName = AppRoute.callCorridor.name;
_pendingRouteParams = {'message': message};
if(rootNavigatorKey.currentContext != null){
processPendingNavigation(rootNavigatorKey.currentContext!);
}else{
debugPrint("
}
}
}
});
print('
} catch (e) {
print('
}
}
static void processPendingNavigation(BuildContext context) {
if (_pendingRouteName != null) {
try {
// Use whatever navigation method works for your app
if(_pendingRouteParams != null ) {
context.goNamed(
_pendingRouteName!,
queryParameters: _pendingRouteParams!
);
}else{
context.goNamed(_pendingRouteName!);
}
// Clear the pending navigation after processing
_pendingRouteName = null;
_pendingRouteParams = null;
} catch (e) {
print('Navigation error: $e');
}
}
}
}
< /code>
Часть моего mainActivity.kt < /p>
class MainActivity : FlutterActivity() {
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntentFromNotification(intent)
}
private fun handleIntentFromNotification(intent: Intent?) {
if (intent == null) return
val callAction = intent.getStringExtra("call_action")
val callerName = intent.getStringExtra("caller_name")
val message = intent.getStringExtra("message")
Log.d(
"MainActivity",
"Intent received: call_action=$callAction, callerName=$callerName, message=$message"
)
if (callAction == "ACCEPT_CALL" || callAction == "REJECT_CALL") {
// Cancel the notification
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(CALL_NOTIFICATION_ID) // Use the same ID from your notification
}
when (callAction) {
"ACCEPT_CALL" -> {
Log.d("MainActivity", "Call accepted (from intent)")
val engine = getFlutterEngine(this) // Get cached engine()
// Navigate to Flutter
if (engine.dartExecutor.isExecutingDart) {
navigateToFlutterCallCorridor(callerName, message, callAction)
} else {
Log.e("MainActivity", "FlutterEngine is not executing Dart. Retrying...")
Handler(Looper.getMainLooper()).postDelayed({
navigateToFlutterCallCorridor(callerName, message, callAction)
}, 1000)
}
}
"REJECT_CALL" -> {
Log.d("MainActivity", "Call rejected (from intent)")
ringtone?.stop()
}
}
val notificationType = intent.getStringExtra("notification_type")
if (notificationType == "regular") {
Log.d(
"MainActivity",
"Regular notification:???????"
)
val engine = getFlutterEngine(this)
if (engine.dartExecutor.isExecutingDart) {
// Route to appropriate Flutter screen
val methodChannel =
MethodChannel(engine.dartExecutor.binaryMessenger, regularNotificationChannel)
methodChannel.invokeMethod("handleNotification", null)
}
}
Log.d("MainActivity", "No intent extras received")
}
fun navigateToFlutterCallCorridor(
callerName: String?,
message: String?,
callAction: String?
) {
try {
val engine = getFlutterEngine(this) // Get cached engine
if (!engine.dartExecutor.isExecutingDart) {
// Wait a bit for Dart to initialize
Handler(Looper.getMainLooper()).postDelayed({
sendNavigationMessage(engine, callerName, message, callAction)
if (engine.dartExecutor.isExecutingDart) {
sendNavigationMessage(engine, callerName, message, callAction)
} else {
Log.e("MainActivity", "Flutter engine still not ready after delay")
}
}, 2000)
} else {
sendNavigationMessage(engine, callerName, message, callAction)
}
} catch (e: Exception) {
Log.e("MainActivity", "Error in navigateToFlutterCallCorridor: $message", e)
}
}
private fun sendNavigationMessage(
engine: FlutterEngine,
callerName: String?,
message: String?,
callAction: String?
) {
try {
if (!engine.dartExecutor.isExecutingDart) {
Log.e("MainActivity", "Dart executor not running, cannot send message")
return
}
MethodChannel(engine.dartExecutor.binaryMessenger, incomingCallChannel)
.invokeMethod("getCallActionAndNavigate",
mapOf(
"callerName" to callerName,
"message" to message,
"action" to callAction,
),
object : MethodChannel.Result {
override fun success(result: Any?) {
Log.d("MainActivity", "Navigation method called successfully")
}
override fun error(
errorCode: String,
errorMessage: String?,
errorDetails: Any?
) {
Log.e("MainActivity", "
Log.e("MainActivity", "
Log.e("MainActivity", "
Log.e("MainActivity", "
}
override fun notImplemented() {
Log.e("MainActivity", "Navigation method not implemented")
}
}
)
} catch (e: Exception) {
Log.e("MainActivity", "Error in navigation: $e")
}
}
}
< /code>
Итак, когда вы нажимаете уведомление, я вижу некоторые из моих журналов, я даже получаю журнал внутри моего канала Invokemethod Inside My MainActivity: < /p>
Log.d("MainActivity", "Navigation method called successfully")
< /code>
Но я все еще получаю замороженный экран, когда уведомление нажимается, и это не является уведомлением о вызове даже при регулярном уведомлении.
ниже приведены некоторые из моих журналов; включая отпечатки из моего приложения: < /p>
Geolocator foreground service connected
Initializing Geolocator services
Flutter engine connected. Connected engine count 1
Received Firebase message: com.google.firebase.messaging.RemoteMessage@780c310
Message data: {JsonData={"Message":"consultation session with Dr Seun. Time: 02-Apr-25, 11:24 AM (UTC+01:00) West Central Africa has started ID:[565900b9-c1e0-433d-a875-7c7d763e92fb]","Recipient":"cedec025-d08f-4482-ad49-0cdc9f07fe58","MessageType":12,"MessageTitle":"[APP TEST] Your Teleconsultation Session Has Started.","EncodedLink":null,"TicketId":null}, Message=Teleconsultation session with Dr Seun Ajadi. Time: 02-Apr-25, 11:24 AM (UTC+01:00) West Central Africa has started ID:[565900b9-c1e0-433d-a875-7c7d763e92fb], Recipient=cedec025-d08f-4482-ad49-0cdc9f07fe58, MessageTitle=[APP TEST] Your Teleconsultation Session Has Started., sound=ringtone, click_action=FLUTTER_NOTIFICATION_CLICK, MessageType=TELECONSULTATION, full_screen_intent=true}
Starting foreground service from broadcast
Sent broadcast for incoming call
broadcast received for message
Compat change id reported: 194532703; UID 10790; state: ENABLED
Broadcast received: .CALL_ACTION_RECEIVED
Action: .CALL_ACTION_RECEIVED, Caller: Dr Seun Ajadi, Needs service: true
Incoming call received (from broadcast)
Compat change id reported: 160794467; UID 10790; state: ENABLED
Setting up incomingCallChannel handler...
Installing profile for
updateSurface: has no frame
updateSurface: has no frame
onResume called - Registering BroadcastReceiver
Sending lifecycle 1 to service
Activity foregrounding at 1706801774.
Cold start detected.
check: return. pkg= parent=null callers=com.android.internal.policy.DecorView.setVisibility:4416 android.app.ActivityThread.handleResumeActivity:5476 android.app.servertransaction.ResumeActivityItem.execute:54 android.app.servertransaction.ActivityTransactionItem.execute:45 android.app.servertransaction.TransactionExecutor.executeLifecycleState:176
removeMultiSplitHandler: no exist. decor=DecorView@98baea0[]
Generated new session 6fc0d99ef94f44dcb6d8ae3e32b1a719
Broadcasting new session: SessionDetails(sessionId=6fc0d99ef94f44dcb6d8ae3e32b1a719, firstSessionId=6fc0d99ef94f44dcb6d8ae3e32b1a719, sessionIndex=0, sessionStartTimestampUs=1743598135955000)
Data Collection is enabled for at least one Subscriber
[NativeCFMS] BpCustomFrequencyManager::BpCustomFrequencyManager()
notifyKeepScreenOnChanged: keepScreenOn=false
Session Event: {"eventType":1,"sessionData":{"sessionId":"6fc0d99ef94f44dcb6d8ae3e32b1a719","firstSessionId":"6fc0d99ef94f44dcb6d8ae3e32b1a719","sessionIndex":0,"eventTimestampUs":1743598135955000,"dataCollectionStatus":{"performance":1,"crashlytics":2,"sessionSamplingRate":1.0},"firebaseInstallationId":"fQzvL21hQjqd14q3Gu4422","firebaseAuthenticationToken":"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6IjE6NjA3MTQxODUyNDkxOmFuZHJvaWQ6MDMzNmJiNjVmNzc5ZDU5M2VhYTRjZSIsImV4cCI6MTc0NDA3MDA4MywiZmlkIjoiZlF6dkwyMWhRanFkMTRxM0d1NDQyMiIsInByb2plY3ROdW1iZXIiOjYwNzE0MTg1MjQ5MX0.AB2LPV8wRQIgFZ2j-QmbU9vMalnrorEgfZV1yH4dvkEp_6cdLy8tfAMCIQD9Z_HKHNZZjCWKNo-W0xHHIdDzkavETIjbuc5D9mKOag"},"applicationInfo":{"appId":"1:607141852491:android:0336bb65f779d593eaa4ce","deviceModel":"SM-G991U","sessionSdkVersion":"2.0.6","osVersion":"13","logEnvironment":3,"androidAppInfo":{"packageName":"","versionName":"1.0.4a","appBuildVersion":"5","deviceManufacturer":"samsung","currentProcessDetails":{"processName":"","pid":17175,"importance":100,"defaultProcess":true},"appProcessDetails":[{"processName":"","pid":17175,"importance":100,"defaultProcess":true}]}}}
Successfully logged Session Start event: 6fc0d99ef94f44dcb6d8ae3e32b1a719
onStateChanged: InsetsState: {mDisplayFrame=Rect(0, 0 - 1080, 2400), mDisplayCutout=DisplayCutout{insets=Rect(0, 80 - 0, 0) waterfall=Insets{left=0, top=0, right=0, bottom=0} boundingRect={Bounds=[Rect(0, 0 - 0, 0), Rect(510, 0 - 570, 80), Rect(0, 0 - 0, 0), Rect(0, 0 - 0, 0)]} cutoutPathParserInfo={CutoutPathParserInfo{displayWidth=1080 displayHeight=2400 physicalDisplayWidth=1080 physicalDisplayHeight=2400 density={3.0} cutoutSpec={M 0,0 H -10 V 26.66666666666667 H 10 V 0 H 0 Z @dp} rotation={0} scale={1.0} physicalPixelDisplaySizeRatio={1.0}}}}, mRoundedCorners=RoundedCorners{[RoundedCorner{position=TopLeft, radius=108, center=Point(108, 108)}, RoundedCorner{position=TopRight, radius=108, center=Point(972, 108)}, RoundedCorner{position=BottomRight, radius=108, center=Point(972, 2292)}, RoundedCorner{position=BottomLeft, radius=108, center=Point(108, 2292)}]} mRoundedCornerFrame=Rect(0, 0 - 1080, 2400), mPrivacyIndicatorBounds=PrivacyIndicatorBounds {static bounds=Rect(931, 0 - 1080, 80) rotation=0}, mSources= { InsetsSource: {mType=ITYPE_STATUS_BAR, mFrame=[0,0][1080,80], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_NAVIGATION_BAR, mFrame=[0,2349][1080,2400], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_LEFT_GESTURES, mFrame=[0,0][101,2400], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_RIGHT_GESTURES, mFrame=[979,0][1080,2400], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_TOP_MANDATORY_GESTURES, mFrame=[0,0][1080,121], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_BOTTOM_MANDATORY_GESTURES, mFrame=[0,2292][1080,2400], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_LEFT_DISPLAY_CUTOUT, mFrame=[0,0][-100000,2400], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_TOP_DISPLAY_CUTOUT, mFrame=[0,0][1080,80], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_RIGHT_DISPLAY_CUTOUT, mFrame=[100000,0][1080,2400], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_BOTTOM_DISPLAY_CUTOUT, mFrame=[0,100000][1080,2400], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_TOP_TAPPABLE_ELEMENT, mFrame=[0,0][1080,80], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_BOTTOM_TAPPABLE_ELEMENT, mFrame=[0,0][0,0], mVisible=true, mInsetsRoundedCornerFrame=false}, InsetsSource: {mType=ITYPE_IME, mFrame=[0,0][0,0], mVisibleFrame=[0,1430][1080,2400], mVisible=false, mInsetsRoundedCornerFrame=false} } host=/.MainActivity from=android.view.ViewRootImpl.setView:1732
setView = com.android.internal.policy.DecorView@98baea0 TM=true
removeMultiSplitHandler: no exist. decor=DecorView@98baea0[MainActivity]
invoke 76
Session update received: 6fc0d99ef94f44dcb6d8ae3e32b1a719
Notified CRASHLYTICS of new session 6fc0d99ef94f44dcb6d8ae3e32b1a719
A background message could not be handled in Dart as no onBackgroundMessage handler has been registered.
Wake lock released
Intent received: call_action=ACCEPT_CALL, callerName=Dr Seun Ajadi, message=Teleconsultation session with Dr Seun. Time: 02-Apr-25, 11:24 AM (UTC+01:00) West Central Africa has started ID:[565900b9-c1e0-433d-a875-7c7d763e92fb]
Call accepted (from intent)
No intent extras received
Received method call: getCallActionAndNavigate with arguments: {callerName: Dr Seun Ajadi, message:consultation session with Dr Seun Ajadi. Time: 02-Apr-25, 11:24 AM (UTC+01:00) West Central Africa has started ID:[565900b9-c1e0-433d-a875-7c7d763e92fb], action: ACCEPT_CALL}
Navigation method called successfully
< /code>
Что я попробовал: < /strong>
Убедился, что полезная нагрузка уведомления содержит правильные данные, которые в моем журнале, разделенном выше, это делает. < /p>
Обеспеченное полноэкранное намерение. Поведение:
Когда пользователь нажимает уведомление о вызове, когда приложение закрыто/завершено, приложение должно открывать и перемещаться в Call_corridor.dart.
Фактическое поведение:
Forgeground: работает правильно. /> Вопрос:
Как я могу убедиться, что нажатие уведомления о вызове открывает приложение и перемещается на правильный экран, даже если приложение находится в закрытом/прекращении?>
Подробнее здесь: https://stackoverflow.com/questions/795 ... cm-android
Мобильная версия