Sheshank S.
Sheshank S.

Reputation: 3298

Flutter send local notification by API in background

I have an API that I'm checking, and when the response changes I need to send a notification to the user. I would like to know how to do this without FCM Push Notifications.

I'm using flutter-local-notifications and background fetch (https://github.com/transistorsoft/flutter_background_fetch) to do it. On the background fetch docs it says that background fetch will do your function once every 15 minutes, which is good enough for me.

This is my initPlatformState():

Future<void> initPlatformState() async {
  // Load persisted fetch events from SharedPreferences
  SharedPreferences prefs = await SharedPreferences.getInstance();
  String json = prefs.getString(EVENTS_KEY);
  if (json != null) {
    setState(() {
      _events = jsonDecode(json).cast<String>();
    });
  }

  // Configure BackgroundFetch.
  BackgroundFetch.configure(
          BackgroundFetchConfig(
            minimumFetchInterval: 15,
            stopOnTerminate: false,
            enableHeadless: true,
            forceReload: true,
            startOnBoot: true,
          ),
          _onBackgroundFetch)
      .then((int status) {
    print('[BackgroundFetch] SUCCESS: $status');
    setState(() {
      _status = status;
    });
  }).catchError((e) {
    print('[BackgroundFetch] ERROR: $e');
    setState(() {
      _status = e;
    });
  });

  // Optionally query the current BackgroundFetch status.
  int status = await BackgroundFetch.status;
  setState(() {
    _status = status;
  });

  // If the widget was removed from the tree while the asynchronous platform
  // message was in flight, we want to discard the reply rather than calling
  // setState to update our non-existent appearance.
  if (!mounted) return;
}

I'm assuming that what's in the function that gets called in the fetch isn't needed for the question.

I tried this on my phone and simulator, and I used Xcode -> Simulate Background Fetch and it ran properly. It also ran properly when I opened the app. Unfortunately, it didn't run after 15 minutes. Is there something I'm missing?

How would I change my code to make the background fetch to happen every 15 minutes?

Upvotes: 2

Views: 6085

Answers (2)

Yuriy N.
Yuriy N.

Reputation: 6127

As the answer of @Habnarim suggested I have used workmanager with flutter_local_notifications to implement local notifications in the background.

Generally, I followed this article, so it can be referred to for more details, but the code there is a bit outdated. I have fixed some discrepancies and also put everything related to notifications in a separate library.

  1. Add workmanager, flutter_local_notifications and cupertino_icons to project.

  2. Add the below permission inside the 'manifest' tag

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

  3. Add the below permission inside the 'application' tag

          <receiver android:name="com.dexterous.flutterlocalnotifications.
                         ScheduledNotificationBootReceiver">
                 <intent-filter>
                     <action 
                     android:name="android.intent.action.BOOT_COMPLETED"/>
                     <action 
                android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
                 </intent-filter>
             </receiver>
    
  4. Add code:

notifications.dart

import 'dart:io';

import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:workmanager/workmanager.dart';

//  Workmanager  input data:
const _uniqueName = 'whatevername_1';
const _taskName = 'whatevername_2';
Duration? _frequency; // = Duration(minutes: 15); // cannot  be  less

_calculateFrequency() {
  // some logic here, db connection etc.
  return Duration(minutes: 15);
}

// Notifications plugin input data
const _channelId = '1234';
const _channelName = 'whatever_name';
const _channelDescription = 'whatever_description';
const _notificationTitle = 'probably just App name';
String _message = 'default  message'; //if message is always the same make const

_createMessage() {
  // some logic here, db connection etc.
  return 'Hello from my app!';
}

void initLocalNotifications() async {
  if (Platform.isWindows) {
    return;
  }
  _frequency = await _calculateFrequency();
  Workmanager().initialize(callbackDispatcher,
      isInDebugMode: true); //TODO change  debug mode
  Workmanager().registerPeriodicTask(
    _uniqueName,
    _taskName,
    frequency: _frequency,
  );
}

void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) {
    FlutterLocalNotificationsPlugin notifPlugin =
        new FlutterLocalNotificationsPlugin();

    var android = new AndroidInitializationSettings('@mipmap/ic_launcher');
    var ios = new DarwinInitializationSettings();

    var settings = new InitializationSettings(iOS: ios, android: android);
    notifPlugin.initialize(settings);
    _showNotificationWithDefaultSound(notifPlugin);
    return Future.value(true);
  });
}

Future _showNotificationWithDefaultSound(notifPlugin) async {
  _message = await _createMessage();

  var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
      _channelId, _channelName,
      channelDescription: _channelDescription,
      importance: Importance.max,
      priority: Priority.high);
  var iOSPlatformChannelSpecifics = new DarwinNotificationDetails();

  var platformChannelSpecifics = new NotificationDetails(
      android: androidPlatformChannelSpecifics,
      iOS: iOSPlatformChannelSpecifics);
  await notifPlugin.show(
      0, _notificationTitle, _message, platformChannelSpecifics,
      payload: 'Default_Sound');
}

main.dart

void main() async {
   initLocalNotifications();
    runApp(
    const ProviderScope(child: MainApp()),
  );
}
  1. Run on the device in debug mode
  2. On the device allow notifications for the app: long press app icon-> App info -> Notifications: allow everything except led light.

I have tested the above on an Android device and it kinda works. I.e. when the app is in the background notifications appear.

Two problems. First, I don't want to show notifications when the app is in the foreground. It can be done but the solution seems to be too complex. Second, notifications do not appear when the app is forced to quit. My guess is that users of low-end devices remove apps from memory frequently. So, currently, I think, that I will use push notifications instead.

Upvotes: 0

Habnarm
Habnarm

Reputation: 29

Yes, I spent lot of time trying to solve this but was not successful so I switched, I guessed what you are trying to do is to handle your own notification without Firebase or from an API like me, well this is it, after my search I was able to do this with the help of work manager package. So easy to use and implement, try it.

https://pub.dev/packages/workmanager

Upvotes: 3

Related Questions