I am making an alarm application with Flutter, I coded a foreground service with PlatformChannel and MethodChannel. When countdown finishes I want to show an Alarm page but I can’t deal with it. The problem is When alarm finishes android shows a notification but couldn’t open the actual dart page, when I tap the notification firstly shows a white page after shows my apps home page. On the other hand notification can deletable but when alarm ticks notification comeback.
Flutter side code :
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:kirinti/controllers/kirintila_controller.dart';
import 'package:kirinti/controllers/timer_controller.dart';
class CountDownScreen extends StatefulWidget {
const CountDownScreen({Key? key}) : super(key: key);
@override
State<CountDownScreen> createState() => _CountDownScreenState();
}
class _CountDownScreenState extends State<CountDownScreen> {
static const MethodChannel _channel = MethodChannel("KirintilaChannel");
static const EventChannel _eventChannel =
EventChannel("KirintilaEventChannel");
StreamSubscription<dynamic>? _timerSubscription;
final KirintilaController kirintilaController = Get.find();
final TimerController timerController = Get.find();
Future<void> _startKirintila() async {
debugPrint("Servis başlatılmaya çalışılıyor");
if (kirintilaController.kirintiModel?.duration != null) {
try {
await _channel.invokeMethod("StartKirintila", {
"duration": kirintilaController.kirintiModel!.duration,
});
debugPrint("Servis başlatma çağrısı yapıldı");
} on PlatformException catch (e) {
debugPrint("Servis başlatma hatası: ${e.message}");
}
} else {
debugPrint("Kirintila duration boş");
}
}
Future<void> _stopKirintila() async {
debugPrint("Servis durdurulmaya çalışılıyor");
try {
await _channel.invokeMethod('StopKirintila');
Get.back();
debugPrint("Servis durduruldu");
} on PlatformException catch (e) {
debugPrint("Servis durdurma hatası: ${e.message}");
}
}
void _listenToTimerUpdates() {
_timerSubscription = _eventChannel.receiveBroadcastStream().listen(
(event) {
if (event is Map) {
setState(() {
timerController.remainingMinutes.value = event['minutes'] as int;
timerController.remainingSeconds.value = event['seconds'] as int;
});
}
},
onError: (error) {
debugPrint("Timer update error: $error");
},
);
}
@override
void initState() {
super.initState();
_startKirintila();
_listenToTimerUpdates();
}
@override
void dispose() {
_timerSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey.shade100,
appBar: AppBar(
backgroundColor: Colors.grey.shade100,
automaticallyImplyLeading: false,
title: const Text("Geri Sayım", style: TextStyle(color: Colors.black)),
centerTitle: true,
),
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() {
int hours = timerController.remainingMinutes.value ~/ 60;
int minutes = timerController.remainingMinutes.value % 60;
int seconds = timerController.remainingSeconds.value;
return Text(
"${hours.toString().padLeft(2, '0')}:"
"${minutes.toString().padLeft(2, '0')}:"
"${seconds.toString().padLeft(2, '0')}",
style: const TextStyle(
fontSize: 64, fontWeight: FontWeight.bold),
);
}),
const Gap(50),
InfoWidget(),
const Gap(30),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _stopKirintila,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
"Güvendeyim",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
),
const Gap(10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _stopKirintila,
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text(
"İptal Et",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white),
),
),
),
],
),
),
),
),
);
}
Widget InfoWidget() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
spreadRadius: 1,
blurRadius: 5,
offset: const Offset(0, 3),
),
],
),
child: Obx(() => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
kirintilaController.kirintiModel!.activity,
style:
const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const Gap(8),
Text(
"Acil durum kişisi: ${kirintilaController.kirintiModel?.person?.fullName}",
style: const TextStyle(fontSize: 16),
),
const Gap(4),
Text(
"Ayarlanan süre: ${kirintilaController.kirintiModel?.duration} dakika",
style: const TextStyle(fontSize: 16),
),
],
)),
);
}
}
MainActivity.kt :
package com.example.kirinti
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.media.RingtoneManager
import android.os.Build
import android.os.Bundle
import android.os.PowerManager
import android.util.Log
import android.view.WindowManager
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity() {
companion object {
private const val CHANNEL_NAME = "KirintilaChannel"
private const val EVENT_CHANNEL_NAME = "KirintilaEventChannel"
private const val TAG = "MainActivity"
private const val NOTIFICATION_PERMISSION_CODE = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate called")
checkNotificationPermission()
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.d(TAG, "onNewIntent called")
handleIntent(intent)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NAME).setMethodCallHandler { call, result ->
when (call.method) {
"StartKirintila" -> {
val duration = call.argument<Int>("duration")
if (duration != null) {
startKirintilaService(duration)
result.success(null)
} else {
result.error("INVALID_ARGUMENT", "Duration is null", null)
}
}
"StopKirintila" -> {
stopKirintilaService()
result.success(null)
}
"stopAlarm" -> {
KirintilaService.stopTimer()
result.success(null)
}
else -> result.notImplemented()
}
}
EventChannel(flutterEngine.dartExecutor.binaryMessenger, EVENT_CHANNEL_NAME).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
KirintilaService.setEventSink(events)
}
override fun onCancel(arguments: Any?) {
KirintilaService.setEventSink(null)
}
}
)
}
private fun checkNotificationPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.POST_NOTIFICATIONS), NOTIFICATION_PERMISSION_CODE)
}
}
}
private fun startKirintilaService(duration: Int) {
val serviceIntent = Intent(this, KirintilaService::class.java).apply {
putExtra("duration", duration)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent)
} else {
startService(serviceIntent)
}
Log.d(TAG, "Kirintila service started with duration: $duration minutes")
}
private fun stopKirintilaService() {
val serviceIntent = Intent(this, KirintilaService::class.java)
stopService(serviceIntent)
KirintilaService.stopTimer()
Log.d(TAG, "Kirintila service stopped")
}
private fun handleIntent(intent: Intent?) {
Log.d(TAG, "handleIntent called with action: ${intent?.action}")
when (intent?.action) {
"ALARM_TRIGGERED" -> handleAlarmTriggered()
"SAFE_ACTION" -> handleSafeAction()
"HELP_ACTION" -> handleHelpAction()
}
}
private fun handleAlarmTriggered() {
Log.d(TAG, "Alarm triggered, opening app")
// Ekranı aç
val pm = getSystemService(Context.POWER_SERVICE) as PowerManager
val wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, "MyApp:WakeLock")
wakeLock.acquire(10*60*1000L /*10 minutes*/)
// Alarm sesi çal
val notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)
val r = RingtoneManager.getRingtone(applicationContext, notification)
r.play()
// Kilit ekranını kaldır
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
setShowWhenLocked(true)
setTurnScreenOn(true)
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as android.app.KeyguardManager
keyguardManager.requestDismissKeyguard(this, null)
} else {
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON)
}
wakeLock.release()
}
private fun handleSafeAction() {
Log.d(TAG, "User is safe")
stopKirintilaService()
}
private fun handleHelpAction() {
Log.d(TAG, "User needs help")
// Yardım İste butonuna basıldığında yapılacak işlemler
// Örneğin: Acil durum kontaklarına mesaj gönderme, yardım çağrısı başlatma, vs.
}
}
KirintilaService.kt :
import android.app.*
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.graphics.Color
import android.media.RingtoneManager
import android.os.Build
import android.os.CountDownTimer
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import io.flutter.plugin.common.EventChannel
class KirintilaService : Service() {
companion object {
private const val CHANNEL_ID = "KirintilaChannel"
private const val NOTIFICATION_ID = 1
private const val URGENT_NOTIFICATION_ID = 2
private const val TAG = "KirintilaService"
private var eventSink: EventChannel.EventSink? = null
private var timer: CountDownTimer? = null
fun setEventSink(sink: EventChannel.EventSink?) {
eventSink = sink
}
fun stopTimer() {
timer?.cancel()
timer = null
}
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val minutes = intent?.getIntExtra("duration", 1) ?: 1
val millisInFuture = minutes * 60 * 1000L
val notification = createNotification("Kirintila Başladı")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
} else {
startForeground(NOTIFICATION_ID, notification)
}
startCountDownTimer(millisInFuture)
return START_STICKY
}
private fun startCountDownTimer(millisInFuture: Long) {
timer?.cancel()
timer = object : CountDownTimer(millisInFuture, 1000) {
override fun onTick(millisUntilFinished: Long) {
val remainingMinutes = millisUntilFinished / 60000
val remainingSeconds = (millisUntilFinished % 60000) / 1000
updateNotification("Kalan süre: $remainingMinutes dakika $remainingSeconds saniye")
eventSink?.success(mapOf("minutes" to remainingMinutes, "seconds" to remainingSeconds))
Log.d(TAG, "Timer tick: $remainingMinutes:$remainingSeconds")
}
override fun onFinish() {
Log.d(TAG, "Timer finished")
showUrgentNotification()
stopSelf()
}
}.start()
}
private fun showUrgentNotification() {
Log.d(TAG, "Showing urgent notification")
val intent = Intent(this, MainActivity::class.java).apply {
action = "ALARM_TRIGGERED"
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(this, 100, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("KIRINTI - Acil Durum")
.setContentText("Alarm Süresi bitti")
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setCategory(NotificationCompat.CATEGORY_ALARM)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(pendingIntent)
.setFullScreenIntent(pendingIntent, true)
.setAutoCancel(false)
.build()
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(URGENT_NOTIFICATION_ID, notification)
Log.d(TAG, "Urgent notification has been sent")
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(CHANNEL_ID, "Kirintila Channel", NotificationManager.IMPORTANCE_HIGH).apply {
description = "Kirintila bildirim kanalı"
enableLights(true)
lightColor = Color.RED
enableVibration(true)
vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private fun createNotification(content: String): Notification {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 3, intent, PendingIntent.FLAG_IMMUTABLE)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("KIRINTI")
.setContentText(content)
.setSmallIcon(android.R.drawable.ic_dialog_info)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent)
.setAutoCancel(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.build()
}
private fun updateNotification(content: String) {
val notification = createNotification(content)
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.notify(NOTIFICATION_ID, notification)
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
super.onDestroy()
stopTimer()
stopForeground(true)
Log.d(TAG, "Service destroyed")
}
} ```