barkibu / add_to_wallet

Flutter Plugin repo to display the native Add to Wallet Apple button
MIT License
7 stars 27 forks source link

Crash when AddToWalletButton is created #3

Closed garrettApproachableGeek closed 2 years ago

garrettApproachableGeek commented 2 years ago

The current application has the wallet capability Using Flutter: 3.0.3 add_to_wallet: 0.0.2

Code used, data is List<int> of https://github.com/barkibu/add_to_wallet/blob/main/example/assets/passes/health_id_card_sample.pkpass

AddToWalletButton(
   pkPass: data,
   width: 140,
   height: 40,
),

Crash message

Could not cast value of type '__NSArrayM' (0x1f6dd1868) to 'FlutterStandardTypedData' (0x1f6dd1fb0).
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00000001bca32bbc libsystem_kernel.dylib`__pthread_kill + 8
libsystem_kernel.dylib`__pthread_kill:
->  0x1bca32bbc <+8>:  b.lo   0x1bca32bd8               ; <+36>
    0x1bca32bc0 <+12>: stp    x29, x30, [sp, #-0x10]!
    0x1bca32bc4 <+16>: mov    x29, sp
    0x1bca32bc8 <+20>: bl     0x1bca2e60c               ; cerror_nocancel

Crashed thread stack trace

Thread 0 name:   Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib                 0x1bca32bbc __pthread_kill + 8
1   libsystem_pthread.dylib                0x1dd590854 pthread_kill + 208
2   libsystem_c.dylib                      0x18c8c60b0 __abort + 124
3   libsystem_c.dylib                      0x18c8716b8 abort + 136
4   libswiftCore.dylib                     0x186ba001c swift::fatalError(unsigned int, char const*, ...) + 140
5   libswiftCore.dylib                     0x186b98768 swift::swift_dynamicCastFailure(void const*, char const*, void const*, char const*, char const*) + 76
6   libswiftCore.dylib                     0x186b987e0 swift::swift_dynamicCastFailure(swift::TargetMetadata<swift::InProcess> const*, swift::TargetMetadata<swift::InProcess> const*, char const*) + 120
7   libswiftCore.dylib                     0x186b9c38c swift_dynamicCast + 280
8   add_to_wallet                          0x10509cc30 PKAddPassButtonNativeView.init(frame:viewIdentifier:arguments:binaryMessenger:channel:) + 564
9   add_to_wallet                          0x10509baec PKAddPassButtonNativeView.__allocating_init(frame:viewIdentifier:arguments:binaryMessenger:channel:) + 96
10  add_to_wallet                          0x10509b968 PKAddPassButtonNativeViewFactory.create(withFrame:viewIdentifier:arguments:) + 408
11  add_to_wallet                          0x10509bbdc @objc PKAddPassButtonNativeViewFactory.create(withFrame:viewIdentifier:arguments:) + 224
12  Flutter                                0x109de8ee4 __47-[FlutterEngine maybeSetupPlatformViewChannels]_block_invoke.183 + 1200
13  Flutter                                0x10a32aa30 __45-[FlutterMethodChannel setMethodCallHandler:]_block_invoke + 112
14  Flutter                                0x109e15d4c invocation function for block in flutter::PlatformMessageHandlerIos::HandlePlatformMessage(std::__1::unique_ptr<flutter::PlatformMessage, std::__1::default_delete<flutter::PlatformMessage> >) + 112
15  libdispatch.dylib                      0x181e47094 _dispatch_call_block_and_release + 24
16  libdispatch.dylib                      0x181e48094 _dispatch_client_callout + 16
17  libdispatch.dylib                      0x181df4d44 _dispatch_main_queue_drain + 928
18  libdispatch.dylib                      0x181df4994 _dispatch_main_queue_callback_4CF$VARIANT$mp + 36
19  CoreFoundation                         0x1821430d4 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
20  CoreFoundation                         0x1821005f8 __CFRunLoopRun + 2544
21  CoreFoundation                         0x182113250 CFRunLoopRunSpecific + 572
22  GraphicsServices                       0x1a2c1e988 GSEventRunModal + 160
23  UIKitCore                              0x184913a94 -[UIApplication _run] + 1080
24  UIKitCore                              0x1846acfd4 UIApplicationMain + 336
25  Runner                                 0x1040c71e8 main + 64
26  dyld                                   0x104d2c4d0 start + 444

I dug into this a bit and if SwiftAddToWalletPlugin.swift is modified to the following code, then casting error is fixed but then this error is thrown Error Domain=PKPassKitErrorDomain Code=1 "The pass cannot be read because it isn’t valid." UserInfo={NSLocalizedDescription=The pass cannot be read because it isn’t valid., NSUnderlyingError=0x281585bc0 {Error Domain=PKPassKitErrorDomain Code=1 "(null)"}}

import Flutter
import PassKit
import UIKit

import Flutter
import UIKit

class PKAddPassButtonNativeViewFactory: NSObject, FlutterPlatformViewFactory {
    private var messenger: FlutterBinaryMessenger
    private var channel: FlutterMethodChannel

    init(messenger: FlutterBinaryMessenger, channel: FlutterMethodChannel) {
        self.messenger = messenger
        self.channel = channel
        super.init()
    }

    func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
    ) -> FlutterPlatformView {
        return PKAddPassButtonNativeView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args as! [String: Any],
            binaryMessenger: messenger,
            channel: channel)
    }
    public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
          return FlutterStandardMessageCodec.sharedInstance()
    }
}

class PKAddPassButtonNativeView: NSObject, FlutterPlatformView {
    private var _view: UIView
    private var _data: Data
    private var _width: CGFloat
    private var _height: CGFloat
    private var _key: String
    private var _channel: FlutterMethodChannel

    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: [String: Any],
        binaryMessenger messenger: FlutterBinaryMessenger?,
        channel: FlutterMethodChannel
    ) {
        _view = UIView()
        let _pass = args["pass"] as! NSArray
        _data = try! NSKeyedArchiver.archivedData(withRootObject: _pass, requiringSecureCoding: true)
        _width = args["width"] as? CGFloat ?? 140
        _height = args["height"] as? CGFloat ?? 30
        _key = args["key"] as! String
        _channel = channel
        super.init()
        createAddPassButton()
    }

    func view() -> UIView {
        _view
    }

    func createAddPassButton() {
        let passButton = PKAddPassButton(addPassButtonStyle: PKAddPassButtonStyle.black)
        passButton.frame = CGRect(x: 0, y: 0, width: _width, height: _height)
        passButton.addTarget(self, action: #selector(passButtonAction), for: .touchUpInside)
        _view.addSubview(passButton)
    }

    @objc func passButtonAction() {
        var newPass: PKPass
        do {
            newPass = try PKPass(data: _data)
        } catch {
            print("Error info: \(error)")
            print("No valid Pass data passed")
            return
        }
        guard let addPassViewController = PKAddPassesViewController(pass: newPass) else {
            print("View controller messed up")
            return
        }

        guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else {
            print("Root VC unavailable")
            return
        }
        rootVC.present(addPassViewController, animated: true)
        _invokeAddButtonPressed()
    }

    func _invokeAddButtonPressed() {
        _channel.invokeMethod(AddToWalletEvent.addButtonPressed.rawValue, arguments: ["key": _key])
    }
}

public class SwiftAddToWalletPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "add_to_wallet", binaryMessenger: registrar.messenger())
    let instance = SwiftAddToWalletPlugin()
    let factory = PKAddPassButtonNativeViewFactory(messenger: registrar.messenger(), channel: channel)
    registrar.register(factory, withId: "PKAddPassButton")
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        return result(FlutterMethodNotImplemented)
    }
}
KPatel91 commented 2 years ago

I am also struggling with this issue. Unsure how to proceed as I believe I need to use a native PKAddPassButton to provision cards, but unsure how to do this in Flutter.

I will note though, that the example app does run (once you run a flutter upgrade) - but I need to create a pass from an issuance token which is where my problems lie.

garrettApproachableGeek commented 2 years ago

KPatel91 are you attempting to add passes to Apple wallet or a credit card to Apple wallet? This package is built to add passes to Apple wallet not credit cards.

Since you are using the word provision and cards, I think you are referring to push provisioning, which this package doesn't cover at all.

KPatel91 commented 2 years ago

Ah, yeah indeed I am trying to provisioning an Access Card rather than a QR pass.

This is becoming quite the struggle for me then, guess I need to learn how to create native iOS widgets in Flutter since no package exists for my use case.

garrettApproachableGeek commented 2 years ago

Resolved by using https://pub.dev/packages/pass_flutter and https://pub.dev/packages/add_to_wallet together to add passes directly to the Apple Wallet