gdelataillade / alarm

A Flutter plugin to easily manage alarms on iOS and Android
https://pub.dev/packages/alarm
MIT License
132 stars 86 forks source link

Alarm plugin does not start an alarm when the alarm time is reached #132

Closed Flash0509 closed 9 months ago

Flash0509 commented 10 months ago

Version 3.0.2

Alarm plugin does not start an alarm when the alarm time is reached.

The code works with alarm version 2.1.1, but not any longer with version 3.0.2. I only adjusted the property volume in the code. In addition, I no longer start the stream call when the wake-up times change, otherwise this leads to the error: Bad state: Stream has already been listened to.

main.dart

void startBackgroundTask() async {
    timeFound = false;
    if (alarmRun == false) {
      // nächster Alarm wird gestellt
      String? einnahmezeit = fachData[nextCompartmentNumber
          .toString()]?['einnahmezeit'];
      if (einnahmezeit != null) {
        final einnahmezeitParts = einnahmezeit.split(':');
        final einnahmeTime = TimeOfDay(
          hour: int.parse(einnahmezeitParts[0]),
          minute: int.parse(einnahmezeitParts[1]),
        );
        DateTime now = DateTime.now();
        DateTime scheduledTime = DateTime(
          now.year,
          now.month,
          now.day,
          einnahmeTime.hour,
          einnahmeTime.minute,
        );
        print("Alarm Setting für nächsten Alarm werden gelesen:$scheduledTime");
        final alarmSettings = AlarmSettings(
          id: 42,
          dateTime: scheduledTime,
          assetAudioPath: 'assets/audio/beep.mp3',
          loopAudio: false,
          vibrate: false,
          volume: 0.5,
          fadeDuration: 0,
          notificationTitle: 'Tabletten Erinnerung',
          notificationBody: 'Einnahmezeit für Fach ' +
              nextCompartmentNumber.toString() +
              ' erreicht. Bitte einnehmen!',
          enableNotificationOnKill: true,
        );
        await Alarm.set(alarmSettings: alarmSettings);
        print("Alarm String wird übergeben");
        if (!Alarm.ringStream.hasListener) {
          print("Alarm STREAMING");
          Alarm.ringStream.stream.listen((_) => checkNextCompartmentTime2());
        }
      }
     }
  }

AndroidManifest.xlm

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
    <uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <!-- For apps with targetSDK=31 (Android 12) -->
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
    <application
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:enableOnBackInvokedCallback="true">
        <service android:name="com.gdelataillade.alarm.services.NotificationOnKillService" />
        <activity
            android:showWhenLocked="true"
            android:turnScreenOn="true"
            android:name="com.example.smartabox_app.MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize"
            android:largeHeap="true">

            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>

                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
    </activity>

            <service android:name="com.gdelataillade.alarm.services.NotificationOnKillService" />

             <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
        </application>
</manifest>

Device info Samsung A52, android, V. 13

gdelataillade commented 10 months ago

Hi @Flash0509

I just released version 3.0.3 with some Android fixes. Let me know if it fixes your issue.

Flash0509 commented 10 months ago

Hi, thank you for your efforts. Unfortunately it still doesn't work. In version 2.1.1 I don't get any problems with the stream when it runs through every time change:

//if (!Alarm.ringStream.hasListener) {
          print("Alarm STREAMING");
          Alarm.ringStream.stream.listen((_) => checkNextCompartmentTime2());
        //}

But with version 3.0.3 I get this error message:

Terminal output

I/flutter ( 5524): [Alarm] [DEV] Alarm with id 42 stopped
I/flutter ( 5524): [Alarm] [DEV] Alarm with id 42 scheduled
I/flutter ( 5524): Alarm String wird übergeben
I/flutter ( 5524): Alarm STREAMING
E/flutter ( 5524): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Stream has already been listened to.
E/flutter ( 5524): #0      _StreamController._subscribe (dart:async/stream_controller.dart:686:7)
E/flutter ( 5524): #1      _ControllerStream._createSubscription (dart:async/stream_controller.dart:836:19)
E/flutter ( 5524): #2      _StreamImpl.listen (dart:async/stream_impl.dart:471:9)
E/flutter ( 5524): #3      _HomeScreenState.startBackgroundTask (package:com.example.smartabox_app/main.dart:1092:35)
E/flutter ( 5524): <asynchronous suspension>
gdelataillade commented 10 months ago

Hi @Flash0509

Thanks for your feedback.

The error "Bad state: Stream has already been listened to" occurs when you attempt to add more than one listener to a single-subscription stream in Dart. Single-subscription streams are designed to have only one active listener at any given time.

To ensure that there is only one listener, maintain a reference to the stream subscription and cancel it before adding a new listener. This means calling cancel() on the subscription reference whenever you're about to add a new listener. Additionally, it's crucial to cancel the subscription in the dispose method of your widget or class. This practice prevents memory leaks and ensures that your stream does not have any active listeners when the object is no longer in use.

This management of the stream subscription will prevent the error and ensure proper handling of the stream in your application.

Let me know if it helps.

Flash0509 commented 10 months ago

Sorry for the misunderstanding. I had already made sure that there could not be a second listener. See code.

if (!Alarm.ringStream.hasListener) {
          print("Alarm STREAMING");
          Alarm.ringStream.stream.listen((_) => checkNextCompartmentTime2());
        }

So that the stream can be accessed again and again, I have revised the code as you suggested. After the alarm is stopped, I set a subscription?.cancel(); and also took this into account when disposing the class. Unfortunately the error exists. No alarm starts properly.

gdelataillade commented 10 months ago

Hi @Flash0509

I understand, I will investigate the issue.

In the meantime, try to call cancel() instead of your if (!Alarm.ringStream.hasListener), we never know. Also move your listener to a widget that will not be rebuilt. I'll keep you updated. Sorry for the inconvenience.

Flash0509 commented 10 months ago

Hi Gautier,

Thanks for your feedback. However, I had already switched to canceling the stream. Unfortunately again without success. For verification purposes, here are the relevant excerpts from the code. By the way, there is only one alarm entry at a time, which I pass on to your plugin. This is always the next valid alarm. Only after the alarm has ended or the alarm time has been changed by the user will the void backgroundtask () be run again. If I'm not mistaken, that would make the listener call as it is now okay?

void startBackgroundTask() async {
    timeFound = false;

      if (alarmRun == false) {

        onClose();

        // nächster Alarm wird gestellt
      String? einnahmezeit = fachData[nextCompartmentNumber
          .toString()]?['einnahmezeit'];
      if (einnahmezeit != null) {
        final einnahmezeitParts = einnahmezeit.split(':');
        final einnahmeTime = TimeOfDay(
          hour: int.parse(einnahmezeitParts[0]),
          minute: int.parse(einnahmezeitParts[1]),
        );

        DateTime now = DateTime.now();
        DateTime scheduledTime = DateTime(
          now.year,
          now.month,
          now.day,
          einnahmeTime.hour,
          einnahmeTime.minute,
        );
        print("Alarm Setting für nächsten Alarm werden gelesen:$scheduledTime");
        final alarmSettings = AlarmSettings(
          id: 42,
          dateTime: scheduledTime,
          assetAudioPath: 'assets/audio/beep.mp3',
          loopAudio: true,
          vibrate: true,
          volume: 0.5,
          fadeDuration: 0,
          notificationTitle: 'Tabletten Erinnerung',
          notificationBody: 'Einnahmezeit für Fach ' +
              nextCompartmentNumber.toString() +
              ' erreicht. Bitte einnehmen!',
          enableNotificationOnKill: true,
        );

        await Alarm.set(alarmSettings: alarmSettings);

        print("Alarm String wird übergeben");

        onInit();
      }
    }
  }

@override
  void onInit() {
    print("Alarm STREAMING");
    subscription ??= Alarm.ringStream.stream.listen((event) => checkNextCompartmentTime2());
  }

  @override
  void onClose() async {
    subscription?.cancel();
    print('HomeController/ STREAMING: onClose');
  }
gdelataillade commented 10 months ago

Hi @Flash0509

Have you tried setting breakpoints ? Can you provide your debug console logs ? Could be very helpful to find what's causing your issue.

Flash0509 commented 10 months ago

Hi Gautier,

Yes, of course. I'm preparing this. At the moment I'm working more with the "Print command" for debugging purposes. Please give me some time to familiarize myself with DevTools.

Here are my current observations: After executing the command subscription ??= Alarm.ringStream.stream.listen((event) => checkNextCompartmentTime2()); Subscription has the content "Instance of '_ControllerSubscription'". Thanks to your ingenious way of working with "null-aware assignment operator" (??=)", the stream is not started a second time. So I actually don't need the "await subscription?.cancel();". Isn't that the case? The stream can actually be used as long as the app is running. When setting the alarm settings with Alarm.set, ALARM reports "[Alarm] [DEV] Alarm with id 42 scheduled". So it actually looks quite good from my point of view. Doesn't that already clarify that the stream doesn't get any data when the dateTime is reached and that's the issue? . Please excuse me if what I'm saying is total nonsense. Unfortunately, I'm not a professional software developer..

Flash0509 commented 10 months ago

By the way, issue #122 sounds very similar.

gdelataillade commented 9 months ago

Hi @Flash0509

Do you still have this issue ? Sorry I've been very busy lately.

I'm closing this issue because it looks very similar with #122. Let's discuss about this stream issue in the other thread.

Flash0509 commented 9 months ago

okay, I agree to summarize my open issue in #122. However, I will include my last input and question for you to which you have not yet answered. ;-) I'm still interested in a solution. My project has now been delayed because of this.

gdelataillade commented 9 months ago

Yes of course.