Skip to content

Commit

Permalink
Merge pull request #2 from MaikuB/dev
Browse files Browse the repository at this point in the history
Add ability to specify sound and vibration
  • Loading branch information
MaikuB committed Mar 29, 2018
2 parents a879449 + 33c9954 commit da32ee5
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -27,3 +27,7 @@

* Fix notifications on Android so tapping on them will remove them and will also start the app if it has been terminated.

## [0.0.8]

* Enable ability to customise sound and vibration for Android notifications.

9 changes: 7 additions & 2 deletions README.md
Expand Up @@ -2,8 +2,7 @@

[![pub package](https://img.shields.io/pub/v/flutter_local_notifications.svg)](https://pub.dartlang.org/packages/flutter_local_notifications)

A cross platform plugin for displaying local notifications.
Note that this plugin aims to provide abstractions for all platforms as opposed to having methods that only work on specific platforms. However, each method allows passing in "platform-specifics" that contains data that is specific for customising notifications on each platform.
A cross platform plugin for displaying local notifications. Note that this plugin aims to provide abstractions for all platforms as opposed to having methods that only work on specific platforms. However, each method allows passing in "platform-specifics" that contains data that is specific for customising notifications on each platform. It is still under development so expect the API surface to change over time.

Uses the NotificationCompat APIs on Android for backwards compatibility support for devices with older versions of Android. For iOS, there is support for the User Notifications Framework introduced in iOS 10 but will use UILocalNotification for older versions of iOS.

Expand All @@ -19,6 +18,12 @@ If your application needs the ability to schedule notifications then you need to
<receiver android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" />
```

Notification icons should be added as a drawable resource. The sample code shows how to set default icon for all notifications and how to specify one for each notification.

Custom notification sounds should be added as a raw resource and the sample illustrates how to play a notification with a custom sound.

Note that with Android 8.0+, sounds and vibrations are associated with notification channels and can only be configured when they are first created. Showing/scheduling a notification will create a channel with the specified id if it doesn't exist already. If another notification specifies the same channel id but tries to specify another sound or vibration pattern then nothing occurs.

### iOS Integration

By design, iOS applications do not display notifications when they're in the foreground. For iOS 10+, use the presentation options to control the behaviour for when a notification is triggered while the app is in the foreground. For older versions of iOS, you will need update the AppDelegate class to handle when a local notification is received to display an alert. This is shown in the sample app within the `didReceiveLocalNotification` method of the `AppDelegate` class. The notification title can be found by looking up the `title` within the `userInfo` dictionary of the `UILocalNotification` object.
Expand Up @@ -8,6 +8,9 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
Expand Down Expand Up @@ -79,7 +82,7 @@ public void onMethodCall(MethodCall call, Result result) {
private void cancelNotification(Integer id) {
Context context = registrar.context();
Intent intent = new Intent(context, ScheduledNotificationReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = getAlarmManager();
alarmManager.cancel(pendingIntent);
NotificationManagerCompat notificationManager = getNotificationManager();
Expand All @@ -105,39 +108,76 @@ private void scheduleNotification(NotificationDetails notificationDetails) {

private Notification createNotification(NotificationDetails notificationDetails) {
Context context = registrar.context();

int resourceId = 0;
int resourceId;
if (notificationDetails.icon != null) {
resourceId = context.getResources().getIdentifier(notificationDetails.icon, "drawable", context.getPackageName());
} else {
resourceId = defaultIconResourceId;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = notificationManager.getNotificationChannel(notificationDetails.channelId);
if (notificationChannel == null) {
notificationChannel = new NotificationChannel(notificationDetails.channelId, notificationDetails.channelName, notificationDetails.importance);
notificationChannel.setDescription(notificationDetails.channelDescription);
notificationManager.createNotificationChannel(notificationChannel);
}
}

setupNotificationChannel(context, notificationDetails);
Intent intent = new Intent(context, registrar.activity().getClass());
intent.setAction(SELECT_NOTIFICATION);
PendingIntent pendingIntent = PendingIntent.getActivity(context, notificationDetails.id, intent, PendingIntent.FLAG_UPDATE_CURRENT);

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, notificationDetails.channelId)
.setSmallIcon(resourceId)
.setContentTitle(notificationDetails.title)
.setContentText(notificationDetails.body)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.setPriority(notificationDetails.priority);
if (notificationDetails.playSound) {
Uri uri = retrieveSoundResourceUri(context, notificationDetails);
builder.setSound(uri);
} else {
builder.setSound(null);
}

if (notificationDetails.enableVibration) {
if (notificationDetails.vibrationPattern != null && notificationDetails.vibrationPattern.length > 0) {
builder.setVibrate(notificationDetails.vibrationPattern);
}
} else {
builder.setVibrate(new long[] { 0 });
}
Notification notification = builder.build();
return notification;
}

private void setupNotificationChannel(Context context, NotificationDetails notificationDetails) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = notificationManager.getNotificationChannel(notificationDetails.channelId);
if (notificationChannel == null) {
notificationChannel = new NotificationChannel(notificationDetails.channelId, notificationDetails.channelName, notificationDetails.importance);
notificationChannel.setDescription(notificationDetails.channelDescription);
if(notificationDetails.playSound) {
AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build();
Uri uri = retrieveSoundResourceUri(context, notificationDetails);
notificationChannel.setSound(uri, audioAttributes);
} else {
notificationChannel.setSound(null, null);
}
notificationChannel.enableVibration(notificationDetails.enableVibration);
if (notificationDetails.vibrationPattern != null && notificationDetails.vibrationPattern.length > 0) {
notificationChannel.setVibrationPattern(notificationDetails.vibrationPattern);
}
notificationManager.createNotificationChannel(notificationChannel);
}
}
}

private Uri retrieveSoundResourceUri(Context context, NotificationDetails notificationDetails) {
Uri uri;
if (notificationDetails.sound == null || notificationDetails.sound.isEmpty()) {
uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
} else {

int soundResourceId = context.getResources().getIdentifier(notificationDetails.sound, "raw", context.getPackageName());
return Uri.parse("android.resource://" + context.getPackageName() + "/" + soundResourceId);
}
return uri;
}

private NotificationManagerCompat getNotificationManager() {
return NotificationManagerCompat.from(registrar.context());
}
Expand Down
Expand Up @@ -5,6 +5,7 @@
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;

import java.util.ArrayList;
import java.util.Map;

public class NotificationDetails {
Expand All @@ -18,6 +19,10 @@ public class NotificationDetails {
public String channelDescription;
public Integer importance;
public Integer priority;
public Boolean playSound;
public String sound;
public Boolean enableVibration;
public long[] vibrationPattern;


public static NotificationDetails from(Map<String, Object> arguments) {
Expand All @@ -32,6 +37,10 @@ public static NotificationDetails from(Map<String, Object> arguments) {
Map<String, Object> platformChannelSpecifics = (Map<String, Object>) arguments.get("platformSpecifics");
notificationDetails.icon = (String) platformChannelSpecifics.get("icon");
notificationDetails.priority = (Integer) platformChannelSpecifics.get("priority");
notificationDetails.playSound = (Boolean) platformChannelSpecifics.get("playSound");
notificationDetails.sound = (String) platformChannelSpecifics.get("sound");
notificationDetails.enableVibration = (Boolean) platformChannelSpecifics.get("enableVibration");
notificationDetails.vibrationPattern = (long[]) platformChannelSpecifics.get("vibrationPattern");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationDetails.channelId = (String) platformChannelSpecifics.get("channelId");
notificationDetails.channelName = (String) platformChannelSpecifics.get("channelName");
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion example/android/build.gradle
Expand Up @@ -5,7 +5,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle:3.1.0'
}
}

Expand Down
4 changes: 2 additions & 2 deletions example/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
#Thu Mar 29 16:20:55 AEDT 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
60 changes: 50 additions & 10 deletions example/lib/main.dart
@@ -1,3 +1,5 @@
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_local_notifications/platform_specifics/initialization_settings/initialization_settings.dart';
Expand All @@ -16,11 +18,14 @@ class MyApp extends StatefulWidget {

class _MyAppState extends State<MyApp> {
@override
initState(){
initState() {
super.initState();
InitializationSettingsAndroid initializationSettingsAndroid = new InitializationSettingsAndroid('app_icon');
InitializationSettingsIOS initializationSettingsIOS = new InitializationSettingsIOS();
InitializationSettings initializationSettings = new InitializationSettings(initializationSettingsAndroid, initializationSettingsIOS);
InitializationSettingsAndroid initializationSettingsAndroid =
new InitializationSettingsAndroid('app_icon');
InitializationSettingsIOS initializationSettingsIOS =
new InitializationSettingsIOS();
InitializationSettings initializationSettings = new InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
FlutterLocalNotifications.initialize(initializationSettings);
}

Expand Down Expand Up @@ -54,10 +59,17 @@ class _MyAppState extends State<MyApp> {
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
child: new RaisedButton(
child: new Text(
'Schedule notification to appear in 5 seconds'),
'Schedule notification to appear in 5 seconds, different sound'),
onPressed: () async {
await scheduleNotification();
})),
new Padding(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 8.0),
child: new RaisedButton(
child: new Text('Play notification with no sound'),
onPressed: () async {
await playNotificationWithNoSound();
})),
],
),
),
Expand All @@ -83,18 +95,46 @@ class _MyAppState extends State<MyApp> {
await FlutterLocalNotifications.cancel(0);
}

/// Schedules a notification that specifies a different icon, sound and vibration pattern
scheduleNotification() async {
var scheduledNotificationDateTime =
new DateTime.now().add(new Duration(seconds: 5));
var vibrationPattern = new Int64List(4);
vibrationPattern[0] = 0;
vibrationPattern[1] = 1000;
vibrationPattern[2] = 5000;
vibrationPattern[3] = 2000;
NotificationDetailsAndroid androidPlatformChannelSpecifics =
new NotificationDetailsAndroid(
'your channel id', 'your channel name', 'your channel description',
icon: 'secondary_icon');
new NotificationDetailsAndroid('your other channel id',
'your other channel name', 'your other channel description',
icon: 'secondary_icon',
sound: 'slow_spring_board',
vibrationPattern: vibrationPattern);
NotificationDetailsIOS iOSPlatformChannelSpecifics =
new NotificationDetailsIOS();
NotificationDetails platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await FlutterLocalNotifications.schedule(0, 'scheduled title', 'scheduled body',
scheduledNotificationDateTime, platformChannelSpecifics);
await FlutterLocalNotifications.schedule(
0,
'scheduled title',
'scheduled body',
scheduledNotificationDateTime,
platformChannelSpecifics);
}

playNotificationWithNoSound() async {
NotificationDetailsAndroid androidPlatformChannelSpecifics =
new NotificationDetailsAndroid('silent channel id',
'silent channel name', 'silent channel description',
playSound: false);
NotificationDetailsIOS iOSPlatformChannelSpecifics =
new NotificationDetailsIOS();
NotificationDetails platformChannelSpecifics = new NotificationDetails(
androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await FlutterLocalNotifications.show(
0,
'silent title',
'silent body',
platformChannelSpecifics);
}
}
@@ -1,5 +1,8 @@
/// The available importance levels for Android notifications.
/// Required for Android 8.0+
import 'dart:typed_data';

class Importance {
static const Unspecified = const Importance(-1000);
static const None = const Importance(0);
Expand Down Expand Up @@ -33,7 +36,7 @@ class Priority {

/// Configures the notification on Android
class NotificationDetailsAndroid {
/// The icon that should be used when displaying the notification
/// The icon that should be used when displaying the notification. When not specified, this will use the default icon that has been configured.
final String icon;

/// The channel's id. Required for Android 8.0+
Expand All @@ -46,13 +49,25 @@ class NotificationDetailsAndroid {
final String channelDescription;

/// The importance of the notification
Importance importance = Importance.Default;
Importance importance;

/// The priority of the notification
Priority priority = Priority.Default;
Priority priority;

/// Indicates if a sound should be played when the notification is displayed. For Android 8.0+, this is tied to the specified channel cannot be changed afterward the channel has been created for the first time.
bool playSound;

/// The sound to play for the notification. Requires setting [playSound] to true for it to work. If [playSound] is set to true but this is not specified then the default sound is played. For Android 8.0+, this is tied to the specified channel cannot be changed afterward the channel has been created for the first time.
String sound;

/// Indicates if vibration should be enabled when the notification is displayed. For Android 8.0+, this is tied to the specified channel cannot be changed afterward the channel has been created for the first time.
bool enableVibration;

/// The vibration pattern. Requires setting [enableVibration] to true for it to work. For Android 8.0+, this is tied to the specified channel cannot be changed afterward the channel has been created for the first time.
Int64List vibrationPattern;

NotificationDetailsAndroid(this.channelId,
this.channelName, this.channelDescription, {this.icon, this.importance = Importance.Default, this.priority = Priority.Default});
this.channelName, this.channelDescription, {this.icon, this.importance = Importance.Default, this.priority = Priority.Default, this.playSound = true, this.sound, this.enableVibration = true, this.vibrationPattern});

Map<String, dynamic> toJson() {
return <String, dynamic>{
Expand All @@ -61,7 +76,11 @@ class NotificationDetailsAndroid {
'channelName': channelName,
'channelDescription': channelDescription,
'importance': importance.value,
'priority': priority.value
'priority': priority.value,
'playSound': playSound,
'sound': sound,
'enableVibration': enableVibration,
'vibrationPattern': vibrationPattern
};
}
}
4 changes: 2 additions & 2 deletions pubspec.yaml
@@ -1,6 +1,6 @@
name: flutter_local_notifications
description: A cross platform plugin for displaying and scheduling local notifications for Flutter applications
version: 0.0.7
description: A cross platform plugin for displaying and scheduling local notifications for Flutter applications. Supports the legacy and new User Notifications framework for iOS and the NotificationCompat APIs provided by the support libraries for Android.
version: 0.0.8
author: Michael Bui
homepage: https://github.com/MaikuB/flutter_local_notifications

Expand Down

0 comments on commit da32ee5

Please sign in to comment.