mike4aday / SwiftlySalesforce

The Swift-est way to build native mobile apps that connect to Salesforce.
MIT License
136 stars 43 forks source link

Working example please #117

Closed amalashin closed 4 years ago

amalashin commented 4 years ago

Hello! It seems like SwiftySalesforce is an awesome package but I totally cannot set it up. I tried almost everything and spend around two days 😱 with trying to bring things to work. Explored README as well as #114 and #115 and totally misunderstood what I'm doing wrong. So, do you have a working example?...

For now I have in SceneDelegate.swift:

func sceneDidBecomeActive(_ scene: UIScene) {
        print("become active")

        let consumerKey = "3MVG9wEVwV0C9e.....................82X"
        let callbackURL = URL(string: "sfdc://authorization/done")!
        let connectedApp = ConnectedApp(consumerKey: consumerKey, callbackURL: callbackURL)
        sfdc = Salesforce(connectedApp: connectedApp)

        let subscription = sfdc.identity().sink(receiveCompletion: { (completion) in
            print(completion)
        }) { identity in
            print(identity)
        }
}

When I start an app, I got a message to allow to authorize. After authorization I got nothing.... What I am doing wrong? 😒

mike4aday commented 4 years ago

Hi @amalashin - subscription is not being retained so the network request gets canceled before your closures are called. Try:

var subscriptions = Set<AnyCancellable>()
//... other stuff in SceneDelegate
func sceneDidBecomeActive(_ scene: UIScene) {
    // Set up Salesforce with Connected App info
    sfdc.identity().sink(receiveCompletion: { (completion) in
        print(completion)
    }) { identity in
        print(identity)
    }.store(in: &subscriptions)
}
amalashin commented 4 years ago

🤦🏻‍♂️ It seems like I need to learn a little bit more about Combine. Thank you for clarification.

Am I right that the authentication question will be automatically opened with any request in case of the expired token?

amalashin commented 4 years ago

It looks like I finally got it! At least, it works for me (and may be will be useful for someone) :) Could you please check.

UserSettings.swift

final class UserSettings: ObservableObject {
    private var sfdc: Salesforce
    private var disposables = Set<AnyCancellable>()
    @Published var identity: Identity? = nil

    init() {
        let consumerKey = "3MVG9wEVwV..................HkrL82X"
        let callbackURL = URL(string: "sfdc://authorization/done")!
        let connectedApp = ConnectedApp(consumerKey: consumerKey, callbackURL: callbackURL)
        self.sfdc = Salesforce(connectedApp: connectedApp)

        self.sfdc.identity()
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
        .sink(
            receiveCompletion: { [weak self] value in
                guard let self = self else { return }
                switch value {
                case let .failure(error):
                    print("Error: \(error)")
                    self.identity = nil
                    break
                case .finished:
                    print("Finished receiving Identity!")
                    break
                }
            },
            receiveValue: {
                self.identity = $0
                print("Identity user: \($0.displayName)")
            }
        ).store(in: &disposables)
    }
}

SceneDelegate.swift

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        let mainView = MainView().environmentObject(UserSettings())

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: mainView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

MainView.swift

struct MainView: View {
    @EnvironmentObject var userSettings: UserSettings

    var body: some View {
        if userSettings.identity != nil {
            return AnyView(UserView())
        } else {
            return AnyView(LoadingView())
        }
    }
}

struct UserView: View {
    @EnvironmentObject var userSettings: UserSettings

    var body: some View {
        VStack {
            VStack {
                CircleImage(image: Image("none"))
                Text(userSettings.identity != nil ? userSettings.identity!.displayName : "none")
            }
        }
    }
}

struct LoadingView: View {
    var body: some View {
        Text("Loading...")
    }
}
mike4aday commented 4 years ago

Hi @amalashin in answer to your question on authentication:

Yes, correct - Swiftly Salesforce will automatically initiate re-authentication if the stored token has expired.

You could override this default behavior if the user might be confused by the appearance of the Salesforce login form -- see RequestConfig and set authenticateIfRequired to false.

mike4aday commented 4 years ago

@amalashin I will close this issue for now - if you have more questions, please re-open or create another.