I can't play music with Spotify iOS SDK with Swift 4.0 - ios

I have a struggle with Spotify SDK, I followed every step correctly, but I can't play music with my premium account on my project. There is no error or crash, my app directs me to the Spotify login page and after facebook login, It brings me back to my app again. Yesterday I'd get "logged in" print but today I can't. I'm trying to play a song after login and also manually as you see below. I'm wondering, am I lucky to find an answer?
override func viewDidLoad() {
super.viewDidLoad()
self.spotify()
NotificationCenter.default.addObserver(self, selector: #selector(GeneralNewsViewController.updateAfterFirstLogin), name: NSNotification.Name(rawValue: "SpotifySession"), object: nil)
}
func spotify() {
// insert redirect your url and client ID below
auth.redirectURL = URL(string: "correctCallbackURl")
auth.clientID = "correctClientID"
auth.requestedScopes = [SPTAuthStreamingScope, SPTAuthPlaylistReadPrivateScope, SPTAuthPlaylistModifyPublicScope, SPTAuthPlaylistModifyPrivateScope]
loginUrl = auth.spotifyWebAuthenticationURL()
}
func initializaPlayer(authSession:SPTSession){
if self.player == nil {
self.player = SPTAudioStreamingController.sharedInstance()
self.player!.playbackDelegate = self
self.player!.delegate = self
try! player?.start(withClientId: auth.clientID)
self.player!.login(withAccessToken: authSession.accessToken)
}
}
#objc func updateAfterFirstLogin () {
loginButton.isHidden = true
let userDefaults = UserDefaults.standard
if let sessionObj:AnyObject = userDefaults.object(forKey: "SpotifySession") as AnyObject? {
let sessionDataObj = sessionObj as! Data
let firstTimeSession = NSKeyedUnarchiver.unarchiveObject(with: sessionDataObj) as! SPTSession
self.session = firstTimeSession
initializaPlayer(authSession: session)
self.loginButton.isHidden = true
// self.loadingLabel.isHidden = false
}
}
func audioStreamingDidLogin(_ audioStreaming: SPTAudioStreamingController!) {
// after a user authenticates a session, the SPTAudioStreamingController is then initialized and this method called
print("logged in")
self.player?.playSpotifyURI("spotify:track:4aDLPXlzHZm26GppvRwms8", startingWith: 0, startingWithPosition: 0, callback: { (error) in
if (error != nil) {
print("playing!")
} else {
print(error?.localizedDescription)
}
})
}
#objc func play() {
player?.playSpotifyURI("spotify:track:4aDLPXlzHZm26GppvRwms8", startingWith: 0, startingWithPosition: 0, callback: { (err) in
if err != nil {
print(err?.localizedDescription)
} else {
}
})
}
class AppDelegate: UIResponder, UIApplicationDelegate ,UNUserNotificationCenterDelegate{
auth.redirectURL = URL(string: "correctCallbackURl")
auth.sessionUserDefaultsKey = "current session"}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// 2- check if app can handle redirect URL
if auth.canHandle(auth.redirectURL) {
// 3 - handle callback in closure
auth.handleAuthCallback(withTriggeredAuthURL: url, callback: { (error, session) in
// 4- handle error
if error != nil {
print("error!")
}
// 5- Add session to User Defaults
let userDefaults = UserDefaults.standard
let sessionData = NSKeyedArchiver.archivedData(withRootObject: session!)
userDefaults.set(sessionData, forKey: "SpotifySession")
userDefaults.synchronize()
// 6 - Tell notification center login is successful
NotificationCenter.default.post(name: Notification.Name(rawValue: "loginSuccessfull"), object: nil)
})
return true
}
return false
}}

It looks like you're subscribed to the wrong notification, you're subscribing to "SpotifySession" but posting "loginSuccessfull"
this answer will help you making this kind of mistake in the future by having enums for events in a simple way.
good debugging is also key, you could've probably solved this problem with some breakpoints to see where you went wrong.
cheers

Related

How to Show Firebase Auth User UID and also my Firestore doc uid as Stripe customer id

I am trying to Show Firestore doc uid as Stripe custommer id.and to link Stripe to Firestore cloud but it show error - "error loading page: failed to decode response from the server" as shown in the image -
My cloud function i.e. index.js -
const admin = require('firebase-admin');
const express = require('express')
admin.initializeApp();
const functions = require('firebase-functions');
const stripe = require('stripe') ("sk_________");
// When a user is created, register them with Stripe
exports.createStripeCustomer = functions.auth.user().onCreate(async (user) => {
const customer = await stripe.customers.create({email: user.email});
return admin.firestore().collection('stripe_customers').doc(user.uid).set({customer_id: customer.id});
});
exports.createPaymentIntent = functions.https.onCall(async (req, res) => {
const amount = req.amount;
const customer = req.customer;
console.log(customer)
const paymentIntent = await stripe.paymentIntents.create({
amount: 1099,
currency: 'usd',
});
const clientSecret = paymentIntent.clientSecret
console.log(clientSecret)
return clientSecret
});
exports.getPaymentMethods = functions.https.onCall(async (req, res) => {
const customer = req.customer;
const type = "card"
stripe.paymentMethods.list({customer : customer, type: type}, function(err, paymentMethods) {
if (err !== null) {
console.log("ERROR")
} else {
return paymentMethods
}
return
})
});
exports.createEphemeralKey = functions.https.onCall(async(data, context) => {
const stripeVersion = data.stripe_version;
const customerId = data.customer_id;
return stripe.ephemeralKeys.create(
{customer: customerId},
{stripe_version: stripeVersion}
).then((key) => {
return key
}).catch((err) => {
console.log(err)
})
})
My CheckoutViewController -
import UIKit
import Stripe
import Firebase
let backendUrl = "https://fireupgoods-a0036.firebaseio.com/"
class CheckoutViewController: UIViewController {
var paymentIntentClientSecret: String?
lazy var cardTextField: STPPaymentCardTextField = {
let cardTextField = STPPaymentCardTextField()
return cardTextField
}()
lazy var payButton: UIButton = {
let button = UIButton(type: .custom)
button.layer.cornerRadius = 5
button.backgroundColor = .systemBlue
button.titleLabel?.font = UIFont.systemFont(ofSize: 22)
button.setTitle("Pay now", for: .normal)
button.addTarget(self, action: #selector(pay), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
StripeAPI.defaultPublishableKey = "pk_test_TYooMQauvdEDq54NiTphI7jxpk_test_51HmAsMFzRM1fSBZ2tTT1SxPxRBzParFfjm6s0aXD0F5dYLeOSVCKtiZ4lq0TGwJxPhHpsq6Hga7I0QzRWisPYMdj00bDxAMTKf"
view.backgroundColor = .white
let stackView = UIStackView(arrangedSubviews: [cardTextField, payButton])
stackView.axis = .vertical
stackView.spacing = 20
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.leftAnchor.constraint(equalToSystemSpacingAfter: view.leftAnchor, multiplier: 2),
view.rightAnchor.constraint(equalToSystemSpacingAfter: stackView.rightAnchor, multiplier: 2),
stackView.topAnchor.constraint(equalToSystemSpacingBelow: view.safeAreaLayoutGuide.topAnchor, multiplier: 2),
])
startCheckout()
}
func displayAlert(title: String, message: String, restartDemo: Bool = false) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel))
self.present(alert, animated: true, completion: nil)
}
}
func startCheckout() {
// Create a PaymentIntent as soon as the view loads
let url = URL(string: backendUrl + "create-payment-intent")!
let json: [String: Any] = [
"items": [
["id": "xl-shirt"]
]
]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
let task = URLSession.shared.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any],
let clientSecret = json["clientSecret"] as? String else {
let message = error?.localizedDescription ?? "Failed to decode response from server."
self?.displayAlert(title: "Error loading page", message: message)
return
}
print("Created PaymentIntent")
self?.paymentIntentClientSecret = clientSecret
})
task.resume()
}
#objc
func pay() {
guard let paymentIntentClientSecret = paymentIntentClientSecret else {
return;
}
// Collect card details
let cardParams = cardTextField.cardParams
let paymentMethodParams = STPPaymentMethodParams(card: cardParams, billingDetails: nil, metadata: nil)
let paymentIntentParams = STPPaymentIntentParams(clientSecret: paymentIntentClientSecret)
paymentIntentParams.paymentMethodParams = paymentMethodParams
// Submit the payment
let paymentHandler = STPPaymentHandler.shared()
paymentHandler.confirmPayment(paymentIntentParams, with: self) { (status, paymentIntent, error) in
switch (status) {
case .failed:
self.displayAlert(title: "Payment failed", message: error?.localizedDescription ?? "")
break
case .canceled:
self.displayAlert(title: "Payment canceled", message: error?.localizedDescription ?? "")
break
case .succeeded:
self.displayAlert(title: "Payment succeeded", message: paymentIntent?.description ?? "")
break
#unknown default:
fatalError()
break
}
}
}
}
extension CheckoutViewController: STPAuthenticationContext {
func authenticationPresentingViewController() -> UIViewController {
return self
}
}
screenshot of the error -
Firebase code which writes my Firebase Auth User UID to Firestore Document ID is -
import UIKit
import Firebase
import FirebaseFirestore
class UserIdtoFirestoreViewController: UIViewController {
let db = Firestore.firestore()
var ref: DocumentReference? = nil
let email = Auth.auth().currentUser!.email
let id = Auth.auth().currentUser!.uid
let fullName = Auth.auth().currentUser!.displayName
#IBAction func GetUserId(_ sender: UIButton) {
db
.collection("users")
.document(id)
.setData([
"email":email,"fullName":fullName])
}
}
The above code writes my Auth User UIDfrom here (see image below):-
to here (see image below):-
As you can see in the 2 images above, the Auth User UID (which begins with 2k3k) gets written to Firestore Document ID. I want the same ID to be shown in Stripe customer ID and logs and events in Stripe, but as you can see below, there is nothing on the Stripe Dashboard: -
How to sort it out ?
I know I am missing out on many things, but my immediate issue is to make the error go away and the Firestore Document ID to be added as Stripe customer id. programmatically.
Any help will be appreciated.
Edit: I am also adding AppDelegate code and MyAPIClient code below:-
AppDelegate code -
import UIKit
import GoogleSignIn
import Firebase
import CoreData
import Stripe
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Stripe.setDefaultPublishableKey( "pk_test_51HmAsMFzRM1fSBZ2tTT1SxPxRBzParFfjm6s0aXD0F5dYLeOSVCKtiZ4lq0TGwJxPhHpsq6Hga7I0QzRWisPYMdj00bDxAMTKf")
FirebaseApp.configure()
// Override point for customization after application launch.
GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
GIDSignIn.sharedInstance().delegate = self
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance().handle(url as URL?)
}
func sign(_ signIn: GIDSignIn!,
didSignInFor user: GIDGoogleUser!,
withError error: Error!) {
// Check for sign in error
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
} else {
print("\(error.localizedDescription)")
}
return
}
// Get credential object using Google ID token and Google access token
guard let authentication = user.authentication else {
return
}
let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken,
accessToken: authentication.accessToken)
// Authenticate with Firebase using the credential object
Auth.auth().signIn(with: credential) { (authResult, error) in
if let error = error {
print("Error occurs when authenticate with Firebase: \(error.localizedDescription)")
}
// Post notification after user successfully sign in
// NotificationCenter.default.post(name: .signInGoogleCompleted, object: nil)
}
}
func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!, withError error: Error!) {
// Perform any operations when the user disconnects from app here.
print("User has disconnected")
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
MyAPIClient code:-
import Stripe
import UIKit
class MyAPIClient: NSObject, STPCustomerEphemeralKeyProvider {
let baseURL = URL(string: "https://api.stripe.com")!
func createCustomerKey(withAPIVersion apiVersion: String, completion: #escaping STPJSONResponseCompletionBlock) {
let url = self.baseURL.appendingPathComponent("ephemeral_keys")
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)!
urlComponents.queryItems = [URLQueryItem(name: "api_version", value: apiVersion)]
var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
guard let response = response as? HTTPURLResponse,
response.statusCode == 200,
let data = data,
let json = ((try? JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]) as [String : Any]??) else {
completion(nil, error)
return
}
completion(json, nil)
})
task.resume()
}
}
Thanks.

How to open a link from Smooch chat at webView instead Safari browser Swift

I'm using Smooch library for integration chat into the app. In addition, I've implemented Universal Links.
Programming language - Swift / iOS version - 12.4+ / Smooch SDK version 7.12
Problem:
When I am tapping on the link from Smooch chat it opens me Safari browser instead webView!!!
Universal link works correctly, I can be redirected by tapping on the link from other Apps (WhatsApp, Safari, telegram ...)
But just taping in the link from the smooch chat redirects me to the Safari browser. How I can stay in the App?
Here main VC where I'm loading webView and button with Smooch chat:
override func viewDidLoad() {
super.viewDidLoad()
if let data = userDefaults.dictionary(forKey: "data"){
responseData = data
}
loadWebKit()
checkToken()
initSmooch()
setupChatBtnView()
}
//showing smooch chat view
#IBAction func chatBtnDidPress(_ sender: UIButton) {
Smooch.show()
}
//chat button design
func setupChatBtnView(){
chatBtnOutlet.layer.cornerRadius = chatBtnOutlet.bounds.self.width / 2.0
chatBtnOutlet.clipsToBounds = true
chatBtnOutlet.layer.shadowRadius = 1
chatBtnOutlet.layer.shadowColor = UIColor(red: 255/255, green: 170/255, blue: 0/255, alpha: 0.5).cgColor
chatBtnOutlet.layer.shadowOpacity = 0.5
chatBtnOutlet.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
chatBtnOutlet.layer.masksToBounds = false
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
Smooch.close()
}
//webkit view url load and adding js event listeners
func loadWebKit(){
webView.navigationDelegate = self
webView.configuration.userContentController.add(self, name: "finishLoading")
webView.configuration.userContentController.add(self, name: "logout")
webView.isHidden = true
activityIndicator.isHidden = false
if appDelegate.externalUrl == nil {
webView.load(URLRequest(url: URL(string: "https://example.com")!))
} else {
deepLinkURL = self.appDelegate.externalUrl!
let urlRequest = URLRequest(url: deepLinkURL!)
self.webView.load(urlRequest)
isFromDeepLing = true
webView.isHidden = true
activityIndicator.isHidden = false
print(urlRequest)
}
}
//when loading url finish call a js functions
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard let dataForJS = userDefaults.string(forKey: "dataJs") else {
self.webView.isHidden = false
self.activityIndicator.isHidden = true
return
}
if !isFromDeepLing {
webView.evaluateJavaScript("handleNativeLogin('\(dataForJS)')") { (result, error) in
self.webView.isHidden = false
self.activityIndicator.isHidden = true
}
} else {
if !isUsedDeepLink {
webView.evaluateJavaScript("handleNativeLogin('\(dataForJS)')") { (result, error) in
}
webView.isHidden = true
activityIndicator.isHidden = false
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
let urlRequest = URLRequest(url: self.deepLinkURL!)
self.webView.load(urlRequest)
self.isUsedDeepLink = true
}
} else {
self.webView.isHidden = false
self.activityIndicator.isHidden = true
}
}
}
//segue to login screen and logout from smooch
func goToLoginScreen(){
Smooch.logout {(error:Error?, userInfo:[AnyHashable : Any]?) in
}
userDefaults.removeObject(forKey: "data")
performSegue(withIdentifier: "loginViewController", sender: self);
}
// check if token is valid . if not, go to login screen
func checkToken(){
if let token = userDefaults.dictionary(forKey: "data")?["access_token"]{
serverManager.checkToken(token: token as! String) { (code) in
if(code == "200"){
print("token is valid")
}else if(code == "401"){
print("token expierd")
self.goToLoginScreen()
}
}
}
}
// connect to smooch service , init and login
func initSmooch(){
guard let appId = responseData["smoochAppId"] ,let userId = responseData["smoochUserId"], let smoochToken = responseData["smoochJWTToken"] else {
chatBtnOutlet.isHidden = true
return
}
Smooch.initWith(SKTSettings(appId: appId as! String)) { (error:Error?, userInfo:[AnyHashable : Any]?) in
if(error == nil){
print("smooch init success")
Smooch.login(userId as! String , jwt: smoochToken as! String, completionHandler: { (error:Error?, userInfo: [AnyHashable : Any]?) in
if(error == nil){
print("smooch user login success")
}else{
print("smooch user login failed")
}
})
}else{
print("smooch init failed")
}
}
}
// listen to js event
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "finishLoading"){
print("finishLoading")
}else if (message.name == "logout"){
userDefaults.set(false, forKey: "isLoggedIn")
self.goToLoginScreen()
}
}
And here appDelegate where I'm using Universal link:
// MARK: - Deep Linking
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
var viewController = UIViewController()
let userDefaults = UserDefaults.standard
let isLoggedIn = userDefaults.bool(forKey: "isLoggedIn")
if url.path == "/login" || isLoggedIn == false {
viewController = self.getDestinationLogin(for: url)
} else {
viewController = self.getDestinationMain(for: url)
}
externalUrl = url
window?.rootViewController = viewController
window?.makeKeyAndVisible()
return (parseAppLinks(from: url) != nil)
}
func getDestinationLogin(for url: URL) -> UIViewController {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let loginVC = storyboard.instantiateViewController(withIdentifier: "loginViewController") as? LoginViewController
return loginVC!
}
func getDestinationMain(for url: URL) -> UIViewController {
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let mainVC = storyboard.instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
let userDefaults = UserDefaults.standard
userDefaults.set(true, forKey: "isLoggedIn")
return mainVC!
}
private func parseAppLinks(from url: URL) -> String? {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return .none }
guard let urlString = components.queryItems?.first?.value else { return .none }
return urlString
}

Thread 1: EXC_BAD_ACCESS (code=1, address=0x0) When working in swift trying to login to spotify

Code:
//
// AppDelegate.swift
// SplitterSwift3
//
// Created by VideoLabN on 4/8/18.
// Copyright © 2018 VideoLabN. All rights reserved.
//
import UIKit
import AWSAuthCore
import AWSMobileClient
import AWSCore
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var auth = SPTAuth()
// Add a AWSMobileClient call in application:open url
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// called when user signs into spotify. Session data saved into user defaults, then notification posted to call updateAfterFirstLogin in ViewController.swift. Modeled off recommneded auth flow suggested by Spotify documentation
if auth.canHandle(auth.redirectURL) {
auth.handleAuthCallback(withTriggeredAuthURL: url, callback: { (error, session) in
if error != nil {
print("error!")
}
let userDefaults = UserDefaults.standard
let sessionData = NSKeyedArchiver.archivedData(withRootObject: session)
print(sessionData)
userDefaults.set(sessionData, forKey: "SpotifySession")
userDefaults.synchronize()
NotificationCenter.default.post(name: Notification.Name(rawValue: "loginSuccessfull"), object: nil)
})
return true
}
return false
}
//Add a AWSMobileClient call in application:didFinishLaunching
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
return AWSMobileClient.sharedInstance().interceptApplication(
application, didFinishLaunchingWithOptions:
launchOptions)
}
}
//
// ViewController.swift
// SplitterSwift3
//
// Created by VideoLabN on 4/8/18.
// Copyright © 2018 VideoLabN. All rights reserved.
//
import UIKit
import SafariServices
import AVFoundation
import AWSAuthCore
import AWSAuthUI
class ViewController: UIViewController, SPTAudioStreamingPlaybackDelegate, SPTAudioStreamingDelegate {
// Variables
var auth = SPTAuth.defaultInstance()!
var session:SPTSession!
// Initialzed in either updateAfterFirstLogin: (if first time login) or in viewDidLoad (when there is a check for a session object in User Defaults
var player: SPTAudioStreamingController?
var loginUrl: URL?
// Outlets
#IBOutlet weak var loginSpotify: UIButton!
#IBOutlet weak var loginSplitter: UIButton!
#IBOutlet weak var testLabel: UILabel!
override func viewDidLoad() {
print("test")
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.setup()
//NotificationCenter.default.addObserver(self, selector: #selector(ViewController.updateAfterFirstLogin, name: NSNotification.Name(rawValue: "loginSuccessfull"), object: nil)
//self.updateAfterFirstLogin()
}
func setup () {
// insert redirect your url and client ID below
let redirectURL = "splitter-app://callback" // put your redirect URL here
let clientID = "207ce42c908f42e485c540be11720888" // put your client ID here
auth.redirectURL = URL(string: redirectURL)
auth.clientID = "client id goes here"
auth.requestedScopes = [SPTAuthStreamingScope, SPTAuthPlaylistReadPrivateScope, SPTAuthPlaylistModifyPublicScope, SPTAuthPlaylistModifyPrivateScope]
loginUrl = auth.spotifyWebAuthenticationURL()
//print("test")
}
func initializePlayer(authSession:SPTSession){
if self.player == nil {
self.player = SPTAudioStreamingController.sharedInstance()
self.player!.playbackDelegate = self
self.player!.delegate = self
try! player!.start(withClientId: auth.clientID)
self.player!.login(withAccessToken: authSession.accessToken)
}
}
#objc func updateAfterFirstLogin () {
loginSpotify.isHidden = true
let userDefaults = UserDefaults.standard
if let sessionObj:AnyObject = userDefaults.object(forKey: "SpotifySession") as AnyObject? {
let sessionDataObj = sessionObj as! Data
let firstTimeSession = NSKeyedUnarchiver.unarchiveObject(with: sessionDataObj) as! SPTSession
self.session = firstTimeSession
initializePlayer(authSession: session)
}
}
func audioStreamingDidLogin(_ audioStreaming: SPTAudioStreamingController!) {
// after a user authenticates a session, the SPTAudioStreamingController is then initialized and this method called
print("logged in")
//
self.player?.playSpotifyURI("spotify:track:58s6EuEYJdlb0kO7awm3Vp",
startingWith: 0, startingWithPosition: 0, callback: { (error) in
// if (error != nil) {
// print("playing!")
// }
//
// })
}
#IBAction func spotifyButtonPressed(_ sender: Any) {
let svc = SFSafariViewController(url: loginUrl!)
self.present(svc, animated: true, completion: nil)
//UIApplication.shared.open(loginUrl!, options: [:])
}
}
The application compiles just fine and launches on the simulator IPhone. The button to login to Spotify works, and opens a Safari instance prompting the user to login to Spotify.
Once the user has logged in, it then asks for permissions. Once the user accepts the permissions, the app crashes on line 16 of the appDelegate class with this error:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x0).
I have read up on this online and people are saying it is the equivalent to a null pointer exception, but I cannot find what is causing this error. Can anyone find the error?
Edit: Thanks to those that have responded! Here is my console output:
objc[19082]: Class VCWeakObjectHolder is implemented in both
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/AVConference.framework/Frameworks/ViceroyTrace.framework/ViceroyTrace
(0x12b9174d0) and
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/AVConference.framework/AVConference
(0x12aa65e38). One of the two will be used. Which one is undefined.
test 2018-04-15 13:33:50.341600-0400 SplitterSwift3[19082:1059086]
[AXRun-PID] Client requesting unsuspension of PID:-1 Name:
2018-04-15 13:33:50.441522-0400 SplitterSwift3[19082:1058985] [MC]
System group container for systemgroup.com.apple.configurationprofiles
path is
/Users/videolabn/Library/Developer/CoreSimulator/Devices/CEC32A65-63E0-4499-AB25-6BD13A7AE013/data/Containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2018-04-15 13:33:50.442782-0400 SplitterSwift3[19082:1058985] [MC]
Reading from private effective user settings. 2018-04-15
13:33:50.536744-0400 SplitterSwift3[19082:1058985] [App] if we're in
the real pre-commit handler we can't actually add any new fences due
to CA restriction (lldb)
The UIApplicationDelegate function application(_:open:sourceApplication:annotation:) was deprecated in iOS 10.
As of iOS 11.3 it seems that apps using this function will crash when it is invoked. The solution is is to use the replacement application(_:open:options:) instead.
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if auth.canHandle(auth.redirectURL) {
auth.handleAuthCallback(withTriggeredAuthURL: url, callback: { (error, session) in
if error != nil {
print("error!")
}
let userDefaults = UserDefaults.standard
let sessionData = NSKeyedArchiver.archivedData(withRootObject: session)
print(sessionData)
userDefaults.set(sessionData, forKey: "SpotifySession")
NotificationCenter.default.post(name: Notification.Name(rawValue: "loginSuccessfull"), object: nil)
})
return true
}
return false
}
Also, there is no need to call synchronize for UserDefaults

Setting tokens in Spotify iOS app disables login callback

I am trying to set up the login for my iOS app using Spotify's SDK. I have the login working, but only without tokens. Once I add these two lines of code:
SPTAuth.defaultInstance().tokenSwapURL = NSURL(string: kTokenSwapURL)
SPTAuth.defaultInstance().tokenRefreshURL = NSURL(string: kTokenRefreshServiceURL)
The login does not work. This is my code for the login.
AppDelegate.swift
let kClientID = "my-client-id"
let kCallbackURL = "my-callback-url"
let kTokenSwapURL = "my-token-swap-url"
let kTokenRefreshServiceURL = "my-token-refresh-url"
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
// Override point for customization after application launch.
SPTAuth.defaultInstance().clientID = kClientID
SPTAuth.defaultInstance().redirectURL = NSURL(string: kCallbackURL)
SPTAuth.defaultInstance().requestedScopes = [SPTAuthStreamingScope, SPTAuthUserReadPrivateScope, SPTAuthPlaylistReadPrivateScope]
SPTAuth.defaultInstance().sessionUserDefaultsKey = "SpotifySession"
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let loginViewController = LoginViewController(nibName: "LogInViewController", bundle: nil)
let navigationController = UINavigationController(rootViewController: loginViewController)
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
return true
}
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
let authCallback : SPTAuthCallback = { error, session in
// This is the callback that'll be triggered when auth is completed (or fails).
if (error != nil) {
print(error);
return;
}
let userDefaults = NSUserDefaults.standardUserDefaults()
let sessionData = NSKeyedArchiver.archivedDataWithRootObject(session)
userDefaults.setObject(sessionData, forKey: SPTAuth.defaultInstance().sessionUserDefaultsKey)
userDefaults.synchronize()
AuthHandler.sharedHandler.loginWithSession(session)
};
if SPTAuth.defaultInstance().canHandleURL(url) {
SPTAuth.defaultInstance().handleAuthCallbackWithTriggeredAuthURL(url, callback:authCallback)
return true
}
return false;
}
LoginViewController.swift
class LoginViewController: UIViewController {
let kClientID = "my-client-id"
let kCallbackURL = "my-callback-url"
let kTokenSwapURL = "my-token-swap-url"
let kTokenRefreshServiceURL = "my-token-refresh-url"
var session: SPTSession!
var logIn: UIButton!
var auth : SPTAuthViewController?
override func viewWillAppear(animated: Bool) {
// set login callback for what happens when session is got
AuthHandler.sharedHandler.setLoginCallback({ success in
if (success) {
self.transitionToPlaylistScreen()
}
})
// if session is still valid, login
let userDefaults = NSUserDefaults.standardUserDefaults()
if let sessionObj:AnyObject = userDefaults.objectForKey("SpotifySession") { // session available
let sessionDataObj = sessionObj as! NSData
let session = NSKeyedUnarchiver.unarchiveObjectWithData(sessionDataObj) as! SPTSession
if !session.isValid() {
SPTAuth.defaultInstance().renewSession(session, callback: { (error:NSError!, renewdSession:SPTSession!) -> Void in
if error == nil {
let sessionData = NSKeyedArchiver.archivedDataWithRootObject(session)
userDefaults.setObject(sessionData, forKey: SPTAuth.defaultInstance().sessionUserDefaultsKey)
userDefaults.synchronize()
self.session = renewdSession
AuthHandler.sharedHandler.loginWithSession(self.session!)
} else {
print(error.localizedDescription)
}
})
} else {
self.session = session
AuthHandler.sharedHandler.loginWithSession(self.session!)
}
}
}
override func viewDidLoad() {
// add observer for login success
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("transitionToPlaylistScreen"), name: "loginSuccess", object: nil)
// code to set up the login button
}
func transitionToPlaylistScreen() {
if (self.auth != nil) {
self.dismissViewControllerAnimated(true, completion: nil)
self.auth = nil
}
let playlistScreen = PlaylistViewController()
let navigation = UINavigationController(rootViewController: playlistScreen)
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(navigation, animated: true, completion: nil)
})
}
func loginToSpotify() {
// if session isn't valid, login within app
dispatch_async(dispatch_get_main_queue(), {
self.auth = SPTAuthViewController.authenticationViewController()
self.auth?.delegate = AuthHandler.sharedHandler
self.auth!.modalPresentationStyle = .OverCurrentContext
self.auth!.modalTransitionStyle = .CrossDissolve
self.modalPresentationStyle = .CurrentContext
self.definesPresentationContext = true
self.auth!.clearCookies({
dispatch_async(dispatch_get_main_queue(), {
self.presentViewController(self.auth!, animated: false, completion: nil)
})
})
})
}
}
AuthHandler.swift
class AuthHandler: NSObject, SPTAuthViewDelegate {
static let sharedHandler = AuthHandler()
var session: SPTSession?
var callback: (Bool -> Void)?
func setLoginCallback(callback: (Bool -> Void)) {
self.callback = callback
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didFailToLogin error: NSError!) {
if let function = callback {
function(false)
}
}
func authenticationViewController(authenticationViewController: SPTAuthViewController!, didLoginWithSession session: SPTSession!) {
self.loginWithSession(session)
}
func authenticationViewControllerDidCancelLogin(authenticationViewController: SPTAuthViewController!) {
if let function = callback {
function(false)
}
}
func loginWithSession(session: SPTSession) {
self.session = session
SPTAuth.defaultInstance().session = session
if let function = callback {
function(true)
}
}
}
I guess that your backend (swap/refresh) server is not set up properly, because a not working server will cause log in to fail.
I recommend this repository, which you can set up a simple server on heroku.
What I would recomed you to try several things:
Could you please post what is in yours URL Types Identifier and URL Schemes?
I do not know is it important, but in my case Redirect URI and URL Schemes is the same. Redirect URI can be found here under My Applications.
Try to write second app which is opening your app using URL Scheme. If it will not work then here is problem in URL schemas.
As code snapshot is here:
let kCallbackURL = "myWhosampled://"
let url = NSURL(string: kCallbackURL)
UIApplication.sharedApplication().openURL(url!)
When generating files for tokens using spotify_token_swap.rb file you would need to set correct values for this:
CLIENT_ID = "e6695c6d22214e0f832006889566df9c"
CLIENT_SECRET = "29eb02041ba646179a1189dccac112c7"
ENCRYPTION_SECRET = "cFJLyifeUJUBFWdHzVbykfDmPHtLKLGzViHW9aHGmyTLD8hGXC"
CLIENT_CALLBACK_URL = "spotifyiossdkexample://"
AUTH_HEADER = "Basic " + Base64.strict_encode64(CLIENT_ID + ":" + CLIENT_SECRET)
SPOTIFY_ACCOUNTS_ENDPOINT = URI.parse("https://accounts.spotify.com")
set :port, 1234 # The port to bind to.
set :bind, '0.0.0.0' # IP address of the interface to listen on (all)

How to properly login to spotify?

I have an application that allow user to stream song from spotify. I'm using swift here. I'm trying to allow user to login with spotify account, but I got nothing in return. My app did open safari in order to open spotify login page. I've done login but still returns nothing.
I've make sure that my callbackuri is exactly the same as in my spotify dev page, my url schemes is the first section before colon of my callback uri, and my otherlinker is filled with -ObjC
Here is what I've tried
login action:
let spotifyAuth = SPTAuth.defaultInstance()
spotifyAuth.clientID = kSpotifyClientID
spotifyAuth.redirectURL = NSURL(string: kSpotifyCallBackUrl)
spotifyAuth.requestedScopes = [SPTAuthStreamingScope]
let spotifyLoginUrl : NSURL = spotifyAuth.loginURL
UIApplication.sharedApplication().openURL(spotifyLoginUrl)
my appdelegate:
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject?) -> Bool{
println("rene jos1")
if (SPTAuth.defaultInstance().canHandleURL(url)) {
println("rene jos2")
SPTAuth.defaultInstance().handleAuthCallbackWithTriggeredAuthURL(url, callback: { (error : NSError?, session : SPTSession?) -> Void in
println("rene jos3")
if error != nil {
println("Auth error : \(url.description)")
return
}
let userDefaults = NSUserDefaults.standardUserDefaults()
let sessionData = NSKeyedArchiver.archivedDataWithRootObject(session!)
userDefaults.setObject(sessionData, forKey: "spotifySession")
userDefaults.synchronize()
NSNotificationCenter.defaultCenter().postNotificationName("spotifyLoginSuccesfull", object: nil)
})
return true
}
if (FBSDKApplicationDelegate.sharedInstance().application(application, openURL: url, sourceApplication: sourceApplication, annotation: annotation)) {
return true
}
return false
}
and my viewdidload:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateAfterFirstlogin", name: "spotifyLoginSuccesfull", object: nil)
// Do any additional setup after loading the view.
spotifyLoginButton.hidden = true
let userDefaults = NSUserDefaults.standardUserDefaults()
if let sessionObj : AnyObject = NSUserDefaults.standardUserDefaults().objectForKey("spotifySession") {
println("rene 2")
let sessionDataObj : NSData = sessionObj as! NSData
let session = NSKeyedUnarchiver.unarchiveObjectWithData(sessionDataObj) as! SPTSession
self.playUsingSession(session)
if !session.isValid() {
SPTAuth.defaultInstance().renewSession(session, callback: { (error : NSError!, newsession : SPTSession!) -> Void in
let sessionData = NSKeyedArchiver.archivedDataWithRootObject(session)
userDefaults.setObject(sessionData, forKey: "spotifySession")
userDefaults.synchronize()
self.session = newsession
self.playUsingSession(newsession)
println("rene 3")
})
}else{
println("error refreshing new spotify session")
}
}else{
spotifyLoginButton.hidden = false
println("rene 4")
}
So, Am I missing something?
Any suggestion would be great
make sure your callback uri is all lowercase letters, I had issues with that.

Resources