Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 40 additions & 3 deletions loans/ios/Classes/ScLoanFlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,47 @@ import Flutter
import UIKit
import Loans

@MainActor
public class SwiftScLoanFlutterPlugin: NSObject, FlutterPlugin {

@MainActor
let currentViewController: UIViewController = (UIApplication.shared.delegate?.window??.rootViewController)!

var currentViewController: UIViewController {
let foregroundScene = UIApplication.shared.connectedScenes
.filter({ $0.activationState == .foregroundActive })
.compactMap({ $0 as? UIWindowScene })
.first

if let scene = foregroundScene {
let keyWindow: UIWindow?
if #available(iOS 15.0, *) {
keyWindow = scene.keyWindow
} else {
keyWindow = scene.windows.first(where: { $0.isKeyWindow })
}
if let rootVC = keyWindow?.rootViewController {
return rootVC
}
}

if let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first {
let window: UIWindow?
if #available(iOS 15.0, *) {
window = windowScene.keyWindow ?? windowScene.windows.first
} else {
window = windowScene.windows.first(where: { $0.isKeyWindow }) ?? windowScene.windows.first
}
if let rootVC = window?.rootViewController {
return rootVC
}
}

if let rootVC = UIApplication.shared.delegate?.window??.rootViewController {
return rootVC
}

fatalError("SwiftScLoanFlutterPlugin: No root view controller found. Ensure a UIWindow is set up in SceneDelegate or AppDelegate.")
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[CRITICAL] fatalError will hard-crash the app if called during edge-case lifecycle moments — cold launch, background transitions, or multi-window on iPad. Change the return type to UIViewController? and let call sites handle nil gracefully instead of crashing unconditionally.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Flutter's method channel dispatch (handle(_:result:)) cannot fire before the FlutterViewController is installed as the root VC — the Flutter engine itself requires a valid window to initialize. By the time any Flutter call arrives, all three fallback chains (foreground scene → any scene → AppDelegate window) already have a rootViewController.


public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "scloans", binaryMessenger: registrar.messenger())
Expand Down
2 changes: 1 addition & 1 deletion scgateway/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,5 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.smallcase.gateway:sdk:6.0.8'
implementation 'com.smallcase.gateway:sdk:6.1.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ fun ScgatewayFlutterPlugin.txnOnSuccessCallback(transactionResult: TransactionRe

result.success(transRes.toString())

}
SmallcaseGatewaySdk.Result.IMR_SETUP -> {

val transRes = JSONObject(transactionResult.data!!)
transRes.put("success", true)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[CRITICAL] transactionResult.data!! throws NullPointerException if data is null. Use a safe null check and return a structured error to the Flutter layer.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It was not a threat as its already in try-catch block.So, no hard crash but still added result.error("TXN_ERROR", e.message ?: "Unknown error in transaction callback", null) logging for local insight

transRes.put("transaction", "IMR_SETUP")

result.success(transRes.toString())

}
else -> {

Expand All @@ -68,6 +77,7 @@ fun ScgatewayFlutterPlugin.txnOnSuccessCallback(transactionResult: TransactionRe

} catch (e: Exception) {
e.printStackTrace()
result.error("TXN_ERROR", e.message ?: "Unknown error in transaction callback", null)
}
}

Expand Down
80 changes: 77 additions & 3 deletions scgateway/ios/Classes/SwiftScgatewayFlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,57 @@ enum IntentType: String {
case holding = "HOLDINGS_IMPORT"
case fetchFunds = "FETCH_FUNDS"
case sipSetup = "SIP_SETUP"
case imrSetup = "IMR_SETUP"
case authoriseHoldings = "AUTHORISE_HOLDINGS"
}

@MainActor
public class SwiftScgatewayFlutterPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {

@MainActor
let currentViewController: UIViewController = (UIApplication.shared.delegate?.window??.rootViewController)!

private var eventSink: FlutterEventSink?
private var notificationObserver: NSObjectProtocol?

var currentViewController: UIViewController {
let foregroundScene = UIApplication.shared.connectedScenes

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM] currentViewController is duplicated verbatim from ScLoanFlutterPlugin. Extract it into a shared internal utility (e.g. FlutterWindowResolver.swift) so any future fix applies to both plugins automatically.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Both ScLoan & ScGateway are published separately.

.filter({ $0.activationState == .foregroundActive })
.compactMap({ $0 as? UIWindowScene })
.first

if let scene = foregroundScene {
let keyWindow: UIWindow?
if #available(iOS 15.0, *) {
keyWindow = scene.keyWindow
} else {
keyWindow = scene.windows.first(where: { $0.isKeyWindow })
}
if let rootVC = keyWindow?.rootViewController {
return rootVC
}
}

// Any connected scene fallback (covers edge cases where no scene is .foregroundActive yet)
if let windowScene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first {
let window: UIWindow?
if #available(iOS 15.0, *) {
window = windowScene.keyWindow ?? windowScene.windows.first
} else {
window = windowScene.windows.first(where: { $0.isKeyWindow }) ?? windowScene.windows.first
}
if let rootVC = window?.rootViewController {
return rootVC
}
}

// Fallback (AppDelegate-based window)
if let rootVC = UIApplication.shared.delegate?.window??.rootViewController {
return rootVC
}

fatalError("SwiftScgatewayFlutterPlugin: No root view controller found. Ensure a UIWindow is set up in SceneDelegate or AppDelegate.")
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[CRITICAL] Same fatalError crash risk as in ScLoanFlutterPlugin. Return UIViewController? and handle nil at each call site.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Flutter's method channel dispatch (handle(_:result:)) cannot fire before the FlutterViewController is installed as the root VC — the Flutter engine itself requires a valid window to initialize. By the time any Flutter call arrives, all three fallback chains (foreground scene → any scene → AppDelegate window) already have a rootViewController.


public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "scgateway_flutter_plugin", binaryMessenger: registrar.messenger())
let instance = SwiftScgatewayFlutterPlugin()
Expand Down Expand Up @@ -342,6 +382,40 @@ public class SwiftScgatewayFlutterPlugin: NSObject, FlutterPlugin, FlutterStream
/// Catch exception
}

// MARK: IMR_SETUP
case .imrSetup(let smallcaseAuthToken, let imrAction, let transactionId, let signup):

do {
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(imrAction)

let data = try? JSONSerialization.jsonObject(with: jsonData, options: [])

if let imrResponse = data as? [String: Any] {

print("IMR_SETUP response: \(imrResponse)")

var resDict: [String: Any] = [:]

resDict["success"] = true
resDict["data"] = imrResponse
resDict["smallcaseAuthToken"] = smallcaseAuthToken
resDict["transaction"] = "IMR_SETUP"
resDict["transactionId"] = transactionId
resDict["signup"] = signup

let jsonData = try JSONSerialization.data(withJSONObject: resDict, options: [])
let jsonString = String(data: jsonData, encoding: .utf8)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM] try! will crash if resDict contains a non-JSON-serialisable value. Move this inside the existing do/catch block and call result(FlutterError(...)) on failure.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

shifted to try from try!


result(jsonString)

return
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM] If data is nil or cannot be cast to [String: Any], result() is never called and the Flutter method channel call hangs indefinitely. Add an explicit result(FlutterError(...)) in the else branch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

result(FlutterError(code: "IMR_SETUP_ERROR", message: error.localizedDescription, details: nil))

capturing for local insights now

} catch {
result(FlutterError(code: "IMR_SETUP_ERROR", message: error.localizedDescription, details: nil))
}

default:
return
}
Expand Down
2 changes: 1 addition & 1 deletion scgateway/ios/scgateway_flutter_plugin.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Scgateway Flutter plugin.
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.dependency 'Flutter'
s.dependency 'SCGateway', '7.1.7'
s.dependency 'SCGateway', '7.2.0'
s.xcconfig = {'BUILD_LIBRARY_FOR_DISTRIBUTION' => 'YES'}
s.vendored_frameworks = 'SCGateway.xcframework'
s.platform = :ios, '13.0'
Expand Down
1 change: 1 addition & 0 deletions scgateway/lib/scgateway_flutter_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class ScgatewayIntent {
static const AUTHORISE_HOLDINGS = "AUTHORISE_HOLDINGS";
static const FETCH_FUNDS = "FETCH_FUNDS";
static const SIP_SETUP = "SIP_SETUP";
static const IMR_SETUP = "IMR_SETUP";
static const CANCEL_AMO = "CANCEL_AMO";
static const MF_HOLDINGS_IMPORT = "MF_HOLDINGS_IMPORT";
}
Expand Down
2 changes: 1 addition & 1 deletion smart_investing/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '14.0'
platform :ios, '15.0'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM] Bumping to iOS 15 drops support for iOS 14 users. The currentViewController logic already has #available(iOS 15.0, *) guards with iOS 13-compatible fallbacks, so the 15.0 floor is not strictly required. Revert to 14.0 unless there are other hard dependencies on iOS 15+.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

SmartInvesting level issue

source 'https://cdn.cocoapods.org'
# private podspec for smallcase
Expand Down
23 changes: 12 additions & 11 deletions smart_investing/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ PODS:
- mixpanel_flutter (2.4.4):
- Flutter
- Mixpanel-swift (= 5.1.0)
- SCGateway (7.1.7)
- SCGateway-dhruv-migrate-to-sceneDelegate-007ae6e (7.1.6-24-debug)
- scgateway_flutter_plugin (0.0.1):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[CRITICAL] These are debug branch builds (7.1.6-24-debug, 7.1.1-39-debug) pointing to cocoapodspec-internal. This lock file must be regenerated against the final release pod versions before merging to master.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

WIll be updated on release

- Flutter
- SCGateway (= 7.1.7)
- SCGateway-dhruv-migrate-to-sceneDelegate-007ae6e (= 7.1.6-24-debug)
- scloans (0.0.1):
- Flutter
- SCLoans (= 7.1.2)
- SCLoans (7.1.2)
- SCLoans-dhruv-migrate-to-sceneDelegate-80eca69 (= 7.1.1-39-debug)
- SCLoans-dhruv-migrate-to-sceneDelegate-80eca69 (7.1.1-39-debug)

DEPENDENCIES:
- Flutter (from `Flutter`)
Expand All @@ -22,10 +22,11 @@ DEPENDENCIES:
- scloans (from `.symlinks/plugins/scloans/ios`)

SPEC REPOS:
https://github.com/smallcase/cocoapodspec-internal.git:
- SCGateway-dhruv-migrate-to-sceneDelegate-007ae6e
- SCLoans-dhruv-migrate-to-sceneDelegate-80eca69
trunk:
- Mixpanel-swift
- SCGateway
- SCLoans

EXTERNAL SOURCES:
Flutter:
Expand All @@ -41,11 +42,11 @@ SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
Mixpanel-swift: 7b26468fc0e2e521104e51d65c4bbf7cab8162f8
mixpanel_flutter: a0b6b937035899cd01951735ad5f87718b2ffee5
SCGateway: a0f1bc86e449c7d440d8661e0936095b6f345179
scgateway_flutter_plugin: 604e1c130e22438e5243d8b841c97f06edff01be
SCLoans: 4accc0a0a483f3943cc513a50800485201fed4dd
scloans: b53430d71d481c4770f13506722d2ab6d26a92ff
SCGateway-dhruv-migrate-to-sceneDelegate-007ae6e: b09149fc113aa409ad828253763c720520a2b642
scgateway_flutter_plugin: 3ed28fc720e0fdf9ca79c638509a5310788ce48b
scloans: 3c4c1632b4a972373933c81cf91b70265b9a606b
SCLoans-dhruv-migrate-to-sceneDelegate-80eca69: ab2115d77604c531216613247d237a8b08e283a4

PODFILE CHECKSUM: 32b0659ca3529b1ef2c9e5c229290749c8c79710
PODFILE CHECKSUM: 06f3d79628f5881e8468842b1f97f7d12c291209

COCOAPODS: 1.16.2
7 changes: 5 additions & 2 deletions smart_investing/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[MEDIUM] FlutterImplicitEngineDelegate and FlutterImplicitEngineBridge are not part of the public Flutter Engine API. This is fragile and may break silently on Flutter upgrades. Confirm this is a sanctioned integration path before merging.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

SmartInvesting level issue: conforming to FlutterImplicitEngineDelegate is a hard requirement to which all SceneDelegate Migrated Apps should adhere to.

_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

nonisolated func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
21 changes: 21 additions & 0 deletions smart_investing/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,26 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
</dict>
</plist>
26 changes: 13 additions & 13 deletions smart_investing/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
checked_yaml:
dependency: transitive
description:
Expand Down Expand Up @@ -412,26 +412,26 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.17.0"
mime:
dependency: transitive
description:
Expand Down Expand Up @@ -518,14 +518,14 @@ packages:
path: "../scgateway"
relative: true
source: path
version: "7.0.7"
version: "7.0.5"
scloans:
dependency: "direct main"
description:
path: "../loans"
relative: true
source: path
version: "5.1.3"
version: "5.1.1"
shelf:
dependency: transitive
description:
Expand Down Expand Up @@ -607,10 +607,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.6"
version: "0.7.10"
timing:
dependency: transitive
description:
Expand Down Expand Up @@ -676,5 +676,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.8.1 <4.0.0"
dart: ">=3.9.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"