Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract layer for Push Notifications and Deep Linking #263

Merged

Conversation

rnr
Copy link
Contributor

@rnr rnr commented Jan 30, 2024

Draft PR to review and discuss an abstract implementation of Push notifications and deep link processing

What was done

  • added PushNotificationsManager and DeepLinkManager to DI
  • added protocols for PushNotificationsProvider and PushNotificationsListener
  • added protocol for DeepLinkService
  • added DeepLink and PushLink classes, which are used to redirect processes from deep link and from push notifications (DeepLink does not currently contain all possible types)
  • added configurations for Braze and Branch services
  • added empty implementation for FCM and Braze providers and listeners
  • added empty implementation for Branch deep linking service

How PushNotificationsManager works

PushNotificationsManager creates arrays of providers and listeners given the configuration (which service(s) are enabled). When any AppDelegate method is called (e.g. registerWithDeviceToken, getRemoteNotification), PushNotificationsManager translates that call to providers or listeners. Each provider/listener has its own initialization and processing functions. A listener can also call the DeepLinkManager to process the received notification data and trigger a redirect to the appropriate view.

How DeepLinkManager works

DeepLinkManager contains one service that will handle deep links. The type of redirect will be determined by the DeepLinkType from the DeepLink or PushLink classes. The service processes the URL and, if it is a supported deep link, calls DeepLinkManager.processDeepLink() with parameters and the manager performs the redirect.

@rnr rnr changed the title Abstract layer for push notifications Abstract layer for push notifications and Deep Linking Jan 30, 2024
@rnr rnr changed the title Abstract layer for push notifications and Deep Linking Abstract layer for Push Notifications and Deep Linking Jan 30, 2024
@saeedbashir
Copy link
Contributor

@rnr The suggested approach is very close to what we have on the production app, and the approach is modular and can adopt new providers very easily. We can go with this approach.

@rnr rnr marked this pull request as ready for review February 2, 2024 11:41
forgotvas
forgotvas previously approved these changes Feb 5, 2024
@@ -70,6 +75,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
if let config = Container.shared.resolve(ConfigProtocol.self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using guard let here for better indentation thoughts?

@@ -125,4 +137,32 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window?.rootViewController = RouteController()
}

// Push Notifications
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
if let pushManager = Container.shared.resolve(PushNotificationsManager.self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using guard let here?

}
}
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
if let pushManager = Container.shared.resolve(PushNotificationsManager.self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using guard let here and same for the following push notifications methods?


// Init manager
public init(config: ConfigProtocol) {
self.service = self.serviceFor(config: config)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DeeplinkManager can register just one service Branch service. How about configuring it to register multiple services thoughts?


// Configure services
func configureDeepLinkService(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
if let service = service {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about using guard let here?

func didReceiveRemoteNotification(userInfo: [AnyHashable: Any]) {
guard let dictionary = userInfo as? [String: Any], shouldListenNotification(userinfo: userInfo) else { return }
let link = PushLink(dictionary: dictionary)
if let deepLinkManager = Container.shared.resolve(DeepLinkManager.self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about moving it with guard let dictionary?

Comment on lines 39 to 40
self.providers = self.providersFor(config: config)
self.listeners = self.listenersFor(config: config)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self is redundant.

}

private func providersFor(config: ConfigProtocol) -> [PushNotificationsProvider] {
var rProviders: [PushNotificationsProvider] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming it to providers, pushProviders or pProviders? as rProviders doesn't go with the feature.

}

private func listenersFor(config: ConfigProtocol) -> [PushNotificationsListener] {
var rListeners: [PushNotificationsListener] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming it to listeners, pushlisteners, or pListeners? as rListeners doesn't go with the feature.


import Foundation

class BrazeListener: PushNotificationsListener {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it should also implement didReceiveRemoteNotification

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forgotvas
forgotvas previously approved these changes Feb 8, 2024
Comment on lines 86 to 93
if config.facebook.enabled {
ApplicationDelegate.shared.application(
app,
open: url,
sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
annotation: options[UIApplication.OpenURLOptionsKey.annotation]
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it's not part of the underlying PR, but as we are, how about fixing it as well, it can return false, whereas the method should have been called for Google or Microsoft. It would be nice if we see if the URL is for Facebook only then it will be returning the status.

didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void
) {
if let pushManager = Container.shared.resolve(PushNotificationsManager.self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if guard let can be used here as well.


// Deep link
func configureDeepLinkServices(launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
if let deepLinkManager = Container.shared.resolve(DeepLinkManager.self) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if guard let can be used here as well.


// Init manager
public init(config: ConfigProtocol) {
services = servicesFor(config: config)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about renaming servicesFor to something like configureServices(_ with config) and call it. The method will populate services for all enabled services?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with this name - this func doesn't configure services but just inits and returns services for current config

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok


// This method do redirect with link from push notification
func processLinkFromNotification(_ link: PushLink) {
if anyServiceEnabled {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this check isn't valid because it's for deep link services not for the push notifications.

@rnr rnr requested a review from saeedbashir February 9, 2024 12:29
}

private func servicesFor(config: ConfigProtocol) -> [DeepLinkService] {
var deepServices: [DeepLinkService] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not going with method name renaming then how about renaming it to services?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just a var to return from a function. Why is its name so important if you are asking me to rename it a second time?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the names should be meaningful according to their usage. It doesn't matter whether they are local or instance variables.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be renamed to services or deeplinkServices as per the usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you see a big difference between deepServices and deeplinkServices? Is it not "meaningful" enough?
Here #263 (comment) you asked me to rename to 'pushProviders' - why not to 'pushnotificationProviders' to ' be meaningful according to their usage'? I'm just following your example.
Don't you think we're just wasting time here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

push is a term that is mostly used for push notifications. That's why it was suggested to use a shorter name, but deep isn't used for deep links. I don't think is time wasting, proper naming enhances the code readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe var deepServices of type [DeepLinkService] is enough 'readable' (especially since it's not used anywhere but these 5 lines of code)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm ok if you are reluctant to make this change, then lets go with it.

Copy link
Contributor

@saeedbashir saeedbashir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@volodymyr-chekyrta volodymyr-chekyrta merged commit 35e7cbe into openedx:develop Feb 12, 2024
3 checks passed
@rnr rnr deleted the abstract_layer_for_push_notifications branch February 29, 2024 09:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants