GithubHelp home page GithubHelp logo

miiha / composable-user-notifications Goto Github PK

View Code? Open in Web Editor NEW
21.0 4.0 10.0 100 KB

A wrapper around UNUserNotificationCenter that makes it easy to use from a reducer, and easy to write tests for how your logic interacts with UNUserNotificationCenter's functionality.

License: MIT License

Swift 94.88% Makefile 5.12%
swift-composable-architecture unusernotifications

composable-user-notifications's Introduction

Composable User Notifications

CI

Composable User Notifications is library that bridges the Composable Architecture and User Notifications.

This library is modelling it's dependency using swift concurrency since version 0.3.0.

Example

Check out the Example demo to see how ComposableUserNotifications can be used.

Basic usage

To handle incoming user notification you can observe the UNUserNotificationCenterDelegate actions through UserNotificationClient.DelegateAction of the UserNotificationClient.delegate.

import ComposableUserNotifications

struct App: ReducerProtocol {
  enum Action {
  case userNotification(UserNotificationClient.DelegateAction)
  // Your domain's other actions:
...

The UserNotificationClient.DelegateAction holds the actions

  • for handling foreground notifications willPresentNotification(_:completion)
  • to process the user's response to a delivered notification didReceiveResponse(_:completionHandler:)
  • to display the in-app notification settings openSettingsForNotification(_:)

The wrapper around apple's UNUserNotificationCenter UserNotificationClient, is available on the DependencyValues and can be retrieved on using @Dependency(\.userNotifications).

At some point you need to subscribe to UserNotificationClient.DelegateAction in order not to miss any UNUserNotificationCenterDelegate related actions. This can be done early after starting the application.

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
  switch action {
  case let .didFinishLaunching(notification):
    ...
    return .run { send in
        for await event in self.userNotifications.delegate() {
          await send(.userNotifications(event))
        }
      }
    }
  }
}

When subscribing to these actions we can handle them as follows.

...
  case let .userNotification(.willPresentNotification(notification, completion)):
    return .fireAndForget {
      completion([.list, .banner, .sound])
    }

  case let .userNotification(.didReceiveResponse(response, completion)):
    return .fireAndForget {
      completion()
    }

  case .userNotification(.openSettingsForNotification):
    return .none
...

To request authorization from the user you can use requestAuthorization and handle the users choice as a new action.

func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
  switch action {
  case .didFinishLaunching:
    return .task {
      .requestAuthorizationResponse(
        TaskResult {
          try await self.userNotifications.requestAuthorization([.alert, .badge, .sound])
        }
      )
    }
  }
  ...
}

Adding notification requests is also straight forward. It can be done using UNNotificationRequest in conjunction with UserNotificationClient.add(_:).

  case .tappedScheduleButton:
    let content = UNMutableNotificationContent()
    content.title = "Example title"
    content.body = "Example body"

    let request = UNNotificationRequest(
      identifier: "example_notification",
      content: content,
      trigger: UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
    )

    return .task {
      await self.userNotifications
        .removePendingNotificationRequestsWithIdentifiers(["example_notification"])
      return await .addNotificationResponse(
        TaskResult {
          Unit(try await self.userNotifications.add(request))
        }
      )
    }
  ...

All API calls to UNUserNotificationCenter are available through UserNotificationClient. The true power of this approach lies in the testability of your notification logic. For more info around testability have a look at ExampleTests.swift.

Installation

You can add ComposableUserNotifications to an Xcode project by adding it as a package dependency.

  1. From the File menu, select Swift Packages › Add Package Dependency…
  2. Enter "https://github.com/miiha/composable-user-notifications" into the package repository URL text field

License

This library is released under the MIT license. See LICENSE for details.

composable-user-notifications's People

Contributors

andreyz avatar miiha avatar sboddeus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

composable-user-notifications's Issues

Xcode 15 compilation error

0.3.0 -- Xcode 15 beta 1, building our watch app and eight compiler errors: Stored properties cannot be marked unavailable with '@available'

/path/to/app-dfeqxwkzwoqkiyfunsmqwfdtuvgu/SourcePackages/checkouts/composable-user-notifications/Sources/ComposableUserNotifications/Model.swift:105:6: error: stored properties cannot be marked unavailable with '@available'
    @available(watchOS, unavailable)
     ^
/path/to/app-dfeqxwkzwoqkiyfunsmqwfdtuvgu/SourcePackages/checkouts/composable-user-notifications/Sources/ComposableUserNotifications/Model.swift:109:6: error: stored properties cannot be marked unavailable with '@available'
    @available(watchOS, unavailable)
     ^
/path/to/app-dfeqxwkzwoqkiyfunsmqwfdtuvgu/SourcePackages/checkouts/composable-user-notifications/Sources/ComposableUserNotifications/Model.swift:445:6: error: stored properties cannot be marked unavailable with '@available'
    @available(watchOS, unavailable)
     ^
/path/to/app-dfeqxwkzwoqkiyfunsmqwfdtuvgu/SourcePackages/checkouts/composable-user-notifications/Sources/ComposableUserNotifications/Model.swift:458:6: error: stored properties cannot be marked unavailable with '@available'
    @available(watchOS, unavailable)
     ^
/path/to/app-dfeqxwkzwoqkiyfunsmqwfdtuvgu/SourcePackages/checkouts/composable-user-notifications/Sources/ComposableUserNotifications/Model.swift:463:6: error: stored properties cannot be marked unavailable with '@available'
    @available(watchOS, unavailable)
     ^
/path/to/app-dfeqxwkzwoqkiyfunsmqwfdtuvgu/SourcePackages/checkouts/composable-user-notifications/Sources/ComposableUserNotifications/Model.swift:472:6: error: stored properties cannot be marked unavailable with '@available'
    @available(watchOS, unavailable)
     ^
/path/to/app-dfeqxwkzwoqkiyfunsmqwfdtuvgu/SourcePackages/checkouts/composable-user-notifications/Sources/ComposableUserNotifications/Model.swift:486:6: error: stored properties cannot be marked unavailable with '@available'
    @available(watchOS, unavailable)
     ^
image

Bug: notification response is not handled when opened from terminated state

While testing the Example app, I discovered a bug where the library doesn't handle app being launched from a terminated state by a push notification.

Steps to reproduce:

  1. Schedule notification
  2. Terminate the app
  3. Tap the notification
  4. Notification is not handled

I didn't yet investigate why that is so any ideas are appreciated. My guess is that the UNUserNotificationCenterDelegate is not constructed in time for the delegate method userNotificationCenter(_:didReceive:withCompletionHandler:) to be handled.

'mock' is inaccessible due to 'internal' protection level

I tried to implement this library, but when I attempted to use .mock() in my View's preview, I get the above error message.

The example project doesn't build either, even after I added the ComposableUserNotifications Swift package.

I have attached a screen shot of the example project not building with a different error.

Screen Shot 2021-03-30 at 4 17 52 PM

Best way adding multiple notification ?

func buildNotifications(
      dayHours: [Int] = (09...20).map { $0 },
      words: [Word]) {

      var hourIndx = 0
      var startDate = DateComponents()
      startDate.calendar = Calendar.current
      let date = Date().get(.day, .month, .year)
      startDate.day = date.day
      let hour = Calendar.current.component(.hour, from: Date())

      var startHour = hour
      var fromDayStartHourToEndHour = [startHour...20]

      for word in words {

        print(#line, startDate.day)

        // 1st day start from 9 to 20
        if hourIndx == dayHours.count {
          hourIndx = 0
          // when 1st day is finished
          // start 2nd dayHours from 0 -> how i can start 2nd day here
          startHour = 9
          startDate.day! += 1
        }

        startDate.hour = dayHours[hourIndx]
        let content = UNMutableNotificationContent()
        content.title = word.englishWord
        content.body = word.englishDefinition
        content.categoryIdentifier = "com.addame.words300"
        content.sound = UNNotificationSound.default

        let trigger = UNCalendarNotificationTrigger(dateMatching: startDate, repeats: true)
        let request = UNNotificationRequest(identifier: word.id, content: content, trigger: trigger)


//        UNUserNotificationCenter.current().add(request) { error in
//           if let e = error {
//               print("Error \(e.localizedDescription)")
//           }
//        }

        hourIndx += 1

       return environment.userNotifications.add(request)
          .map(Unit.init)
          .catchToEffect()
          .map(AppAction.addNotificationResponse)

      }

   }

some experimental code but not working any idea how it will be better
for example for 100 word from backend and add those hour base notification

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.