I'm using Spotify iOS SDK beta 6 and trying to get a list of the tracks i've saved.
There is a getSavedTracks method that I'm trying to call:
[self getSavedTracks:(SPTSavedTrack *__autoreleasing *) range:(NSRange)]
And the same method I'm trying to implement:
-(void)getSavedTracks:(object-type **)buffer range:(NSRange)inRange
Right now, I'm at a lost as to what to put in the parameters. I'm thinking this for the implementation?
-(void)getSavedTracks:(SPTSavedTrack **)buffer range:(NSRange)inRange {
[SPTRequest savedTracksForUserInSession:self.session callback:^(NSError error, SPTSavedTrack trackObjects) {
if (error != nil) {
NSLog(#"*** Enabling playback got error: %#", error);
return;
}
self.savedTracks = [[NSMutableArray alloc]initWithObjects:trackObjects, nil];
}];
}
There's nothing in their SDK API reference that mention this method. Thanks
The methods you're talking about don't exist in the SDK.
You need SPTRequest's +savedTracksForUserInSession:callback: method. Like this:
[SPTRequest savedTracksForUserInSession:session callback:^(NSError *error, id object) {
// Object contains the `SPTListPage` containing the first page of saved tracks
}];
Related
I am having difficulty in obtaining a collection of playlists for the iPhone with Xamarin.iOS. I want to populate a list of my custom objects with the playlist descriptions. I found a page in Apple documentation on how to do it in Obj-C. However, whatever I do in Xamarin I either get invalid cast exceptions or some other error.
Getting the playlists into a collection is OK. However, it is getting the name of the playlist after that where the problem lies.
private List<Playlist> GetPlaylists()
{
MPMediaQuery mq = MPMediaQuery.playlistsQuery;
// At this point mq.Collections has a reference to the playlists. However I have no way of getting the Name/Description of the playlist.
// Thats why I was attempting to use MPMediaPlaylist
// MPMediaPlaylist[] mp1 = mq.Collections as MPMediaPlaylist[]; - doesn't work - results in mp1 being null
List<Playlist> playlists = (from p in mq.Collections
select new Playlist () { PlaylistDescription = ((MPMediaPlaylist)p).ValueForProperty("MPMediaPlaylistPropertyName").ToString()}
).ToList();
return playlists;
}
Another variation I had was as follows (but again received an invalid cast exception in the iteration of the forearch i.e. MediaPlaylist in mp1):
MPMediaQuery mq = MPMediaQuery.playlistsQuery;
Array mp1 = mq.Collections as Array;
List<Playlist> playlists = new List<Playlist> ();
foreach (MPMediaPlaylist playlist in mp1) {
playlists.Add (new Playlist () { PlaylistDescription = playlist.ValueForProperty ("MPMediaPlaylistPropertyName").ToString() });
}
As I have commented i the code, I can get a handle on the playlists through the collections property on the query. However, I cannot get the Name/Title/Description of the playlist. That is why I was attempting to use the MPMediaPlaylist casting.
In the immediate window and watch window I tried to cast (MPMediaPlaylist)mq.collections[0] but again got an invalid cast exception.
I came across the following documentation in apple documentation for Obj-C but I have been unsuccessful in reproducing it as you can see above. I'd be very grateful if someone could cast an eye over what I have above and also this Objective-C link and advise.
Many thanks,
Tomás
I am just going to put this here.... if someone runs in to this problem in the future!
So to get list of playlists you should do the following:
MPMediaQuery playlistQuery = MPMediaQuery.PlaylistsQuery;
MPMediaItemCollection[] playlistCollection = playlistQuery.Collections;
foreach(MPMediaItemCollection collection in playlistCollection){
NSString key = MPMediaPlaylistProperty.Name;
string name = collection.ValueForKey(key).ToString();
//Do whatever you need with the info
}
I'm using the following code to load a playlist
-(void)loadPlaylist:(NSString *)playlistURI withCompletionBlock:(spotifycompletionWithData)completionBlock andfailed:(failedBlock)failedBlock {
NSURL *playlistURL = [NSURL URLWithString:playlistURI];
[[SPSession sharedSession] playlistForURL:playlistURL callback:^(SPPlaylist *playlist) {
[SPAsyncLoading waitUntilLoaded:playlist timeout:kSPAsyncLoadingDefaultTimeout+10 then:^(NSArray *loadedItems, NSArray *notLoadedItems) {
if(notLoadedItems.count >= 1){
[SVProgressHUD dismiss];
failedBlock();
return;
}
self.playlist = [loadedItems lastObject];
}];
}];
}
I'm observing the playlist.loaded property and I see its YES
but when I'm looking into the playlist.items lots of them are null.
What can I do?
The playlist.loaded == YES just means that the playlist's own metadata is loaded - the name, owner, number of items etc. The items themselves load separately, so you need to separately use SPAsyncLoading to load them.
Note that loading the entire contents of a playlist at once is a pretty bad idea - playlists get huge, and if you try to load 10,000 items at once things are going to get bad fast on an iOS device.
Instead, you should consider loading the items in chunks as the user scrolls around your UI.
I'm using AFNetworking and MagicalRecord (the current develop branch) and I'm trying to figure out how to import a lot of objects which are dependent on each other. Each resource/entity has multiple pages worth of downloads. I have a class managing the downloads for a given entity and saving them using MagicalDataImport (which has been amazing).
I believe my issue is that the imports aren't happening on the same thread. So I think what is happening is:
In one thread, EntityA is getting saved properly and propagated to the parent entity.
Then in another thread, EntityB is being saved, and along with it it's relationship to EntityA is built. That means a blank (fault?) object is being created. Then when it gets propagated to the parent entity, I believe EntityA is overwriting the EntityA that is there. Thus I'm left with some objects that don't have all of the attributes.
At least, I think that is what is happening. What I'm seeing via the UI is actually that the relationships between entities aren't always built correctly.
My end goal is to get the entire download/import process to be done in the background, not effecting the UI at all.
Here is my AFJSONRequest:
AFJSONRequestOperation *operation = [AFJSONRequestOperation
JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
[self saveResources:[JSON objectForKey:#"data"]];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON)
{
DLog(#"%#",error.userInfo);
[self.webService command:self didFail:error.localizedDescription];
}];
[operation setQueuePriority:self.priority];
And it calls saveResources::
- (void)saveResources:(NSArray*)resources {
BOOL stopDownloads = [self stopDownloadsBasedOnDate:resources];
if ([resources count] > 0 && !stopDownloads){
self.offset = #([offset intValue] + [resources count]);
[self send];
}
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *blockLocalContext) {
[self.classRef MR_importFromArray:resources inContext:blockLocalContext];
} completion:^(BOOL success, NSError *error) {
if (error){
// ... handle errors
}
else {
// ... handle callbacks
}
}];
}
This kicks off another download ([self send]) and then saves the objects.
I know by default AFNetworking calls the callback in the main queue, and I've tried setting the SuccessCallbackQueue/FailureCallbackQueue to my background thread, but that doesn't seem to solve all the issues, I still have some relationships going to faulted objects, though I think I do need to do that to keep everything going in a background thread.
Is there anything else I need to call in order to properly propagate these changes to the main context? Or is there a different way I need to set this up in order to make sure that all the objects are saved correctly and the relationships are properly built?
Update
I've rewritten the issue to try to give more clarification to the issues.
Update
If you need more code I created a gist with (I believe) everything.
I ended up having this exact same issue a few days ago. My issue was I had received a customer record from my API with AFNetworking. That customer could have pets, but at this point I didn't have the petTypes to correspond to the customers pet record.
What I did to resolve this was create a transformable attribute with an NSArray which would temporarly store my pets until my petTypes were imported. Upon the importation of petTypes I then triggered an NSNotificationCenter postNotification (or you can just do the pet import in the completion).
I enumerated through the temporary transformable attribute that stored my pet records and then associated the with the petType
Also I see you are doing your import inside of a save handler. This is not needed. Doing your MR_importFromArray will save automatically. If you are not using an MR_import method then you would use the saveToPersistentStore methods.
One thing is I don't see where you are associating the relationships. Is EntityB's relationship to EntityA being sent over via JSON with the EntityA objecting being in EntityB?
If so then this is where the relationship is getting messed up as it is creating / overwriting the existing EntityA for the one provided in EntityB. My recommendation would be to do something like this.
NSArray *petFactors = [responseObject valueForKeyPath:#"details.items"];
NSManagedObjectContext *currentContext = [NSManagedObjectContext MR_context];
Pets *pet = [Pets MR_findFirstByAttribute:#"id" withValue:petId inContext:currentContext];
pet.petFactors = nil;
for (id factor in petFactors) {
[pet addPetFactorsObject:[PetFactors MR_findFirstByAttribute:#"id" withValue:[factor valueForKey:#"factorId"]]];
}
[currentContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"SAVED PET FACTORS");
[[NSNotificationCenter defaultCenter] postNotificationName:kPetFactorsSavedSuccessfully object:nil];
} else {
NSLog(#"Error: %#", error);
}
}];
I'm putting this as an answer, though I'm not 100% sure if this is your issue or not. I think the issue stems from your localContext. Here is a sample web request method from an app we wrote that uses data importing, you may be able to use it as an example to get yours working.
Note that the AFNetworking performs its completion block on the main thread, then the MagicalRecord saveInBackground method switches back to a background thread to do the importing and processing, then the final MR completion block performs the handler block on the main thread again. The localContext that's used to import is created/managed by the saveInBackground method. Once that method is complete the context is saved and merged with the app's main context and all the data can then be accessed.
- (void)listWithCompletionHandler:(void (^)(BOOL success))handler{
[[MyAPIClient sharedClient] getPath:#"list.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject){
NSString *statusString = [responseObject objectForKey:#"status"];
// Handle an error response
if(![statusString isKindOfClass:[NSString class]] || ![statusString isEqualToString:#"success"]){
// Request failure
NSLog(#"List Request Error: %#", statusString);
NSLog(#"%#", [responseObject objectForKey:#"message"]);
if(handler)
handler(NO);
return;
}
NSArray *itemsArray = [responseObject objectForKey:#"items"];
[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
// Load into internal database
NSArray *fetchedItems = [Item importFromArray:itemsArray inContext:localContext];
NSLog(#"Loaded %d Items", [fetchedItems count]);
} completion:^{
if(handler)
handler(YES);
}];
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(#"Fail: %#", error);
if(handler)
handler(NO);
}];
}
I have an entity called Practice and I use a View Controller called SelectorViewController to select one of the practices, selectedPractice. I then return selectedPractice to a view Controller called RegularViewController where I display some of the selectedPractice attributes. All of this works fine. However the app has a number of other View Controllers which can be reached by modal segues from instances of RegularViewController. As a result, if I leave and then come back to RegularViewController, selectedPractice is reset as null. I would also like to save selectedPractice so that it is available at app initialisation if it has previously been set in SelectorViewController. How do I achieve this by making selectedPractice persistent across the app, and available at runtime?
Regards
Thanks to the post above, which was great, I managed to sort it. Here is my code, which may be very clumsy, but it works.
Firstly, as I loaded the fetchedObjects into a PickerView in SelectorView Controller, I set an attribute "isSelectedPractice" to "NO" with the following code:
for (Practice *fetchedPractice in [self.fetchedResultsController fetchedObjects]) {
[fetchedPractice setValue:#"NO" forKey:#"isSelectedPractice"];
[self.managedObjectContext save:nil];
I then identified for the selected Practice:
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
Practice *practice = [[self.fetchedResultsController fetchedObjects] objectAtIndex:row];
self.selectedPractice = practice;
NSLog(#"The '%#' practice was selected using the picker", self.selectedPractice.name);
}
as the view Segue'd back to RegularViewController I set the isSelectedPractice attribute for selectedPractice to YES. I kept it this late as I didn't want more than one selection in the PickerView to result in multiple objects with isSelectedPractice YES.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SavedPractice Segue"])
{
[self.selectedPractice setValue:#"YES" forKey:#"isSelectedPractice"];
[self.managedObjectContext save:nil];
NSLog(#"Setting SelectedPractice as '%#' in RegularViewController with isSelectedPractice as '%#'",self.selectedPractice.name,self.selectedPractice.isSelectedPractice );
RegularViewController *rvc= segue.destinationViewController;
rvc.delegate = self;
rvc.selectedPractice = self.selectedPractice;
}
else {
NSLog(#"Unidentified Segue Attempted!");
}
}
I then set the following Predicate in the setupFetchedResultsController method of RegularViewController:
request.predicate = [NSPredicate predicateWithFormat:#"isSelectedPractice = %#", #"YES"];
Many thanks for the help
Without seeing your actual project, one way I know will work but might be a little too round a bout would be to add an attribute "isSelectedPractice" to your entity. You could make it a BOOL, but I've had mixed results with BOOL's in Core Data, I prefer to just leave it as a NSString and set it to "yes" or "no". Then when you pull it down, modify it or add it to core Data as a entity with isSelectedPractice set to "yes". Then in your other controllers, do a
if (self.managedObjectContext == nil) {
self.managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
}
then do a fetch request to get entities with a predicate which is looking for isSelectedPractice equaling "yes". If you need actual code samples on how to do this let me know and I'll edit them in.
I have created few entities in context for saving it in db using
AppCalendarEntity *appCalendar = [AppCalendarEntity getInstanceWithManagedDocument:manageDocument];
After adding a few entities I execute flowing fetch request
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:#"AppCalendarEntity"];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError] ;
It returns me the result including only the entities I have added in context using first command and NOT the entries already saved in database. I have made sure that at this stage the document state is UIDocumentStateNormal.
When I add this line to already open document (UIDocumentStateNormal) it returns me the expected result, i.e. it fetch results from db as well as memory context which has not yet been saved to db.
[managedDocument openWithCompletionHandler:^(BOOL success)
{
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:#"AppCalendarEntity"];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError] ;
}
My question is
1- I expect that the result of query should be the same in both cases. Why it is not so in the above case.
2- To me if document state is UIDocumentStateNormal I should not be calling "openWithCompletionHandler" in context to open the document. In this particular scenario what difference it is making in NSFetchRequest which gives me the desired result after adding this.
Please let me know if I'm getting wrong
Here is the complete code
This is the complete code of the function
+ (void ) saveCalendarArrayInDbIfItAlreadyDoesNotExist : (NSArray*) appCalendarArray managedDocument: (UIManagedDocument*) managedDocument completionBlock : ( void(^) (NSArray* ObjectSavedSuccesfully, NSError *InternalError)) handler
{
// i dont know why i have to do it :( if i dont add openWithCompletionHandler my query doesnt fetch result from db rather just do query in-memory context and not db
[managedDocument openWithCompletionHandler:^(BOOL success)
{
void (^completionHandler)(NSArray* , NSError* );
completionHandler = [handler copy ];
NSError *error = nil;
NSMutableArray *array = [[NSMutableArray alloc] init];
for (id appCalendar in appCalendarArray) {
if([appCalendar isKindOfClass:[AppCalendarEntity class]])
{
AppCalendarEntity *appCalendarEntity = (AppCalendarEntity*) appCalendar;
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:#"MyEntity"];
requestToSeeIfCalendarWithIdExist.predicate = [NSPredicate predicateWithFormat:#"identifier = %#", appCalendarEntity.identifier];
NSError *InternalError = nil;
[requestToSeeIfCalendarWithIdExist setShouldRefreshRefetchedObjects:YES];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError] ;
// "result" is different when we encapsulate it in openWithCompletionHandler and when we don't…….MY PROBLEM
if(result == nil)
{
// return error
}
// 1 object always return that depict the in memory(context) object we created but not saved. I expect it should be zero because no object has yet been saved to database..
else if(result.count > 1)
{
[managedDocument.managedObjectContext deleteObject:appCalendar];
}
else
{
[array addObject:appCalendarEntity];
}
}
else
{
// error handling
}
}
if (error != nil)
{
completionHandler (nil, error);
return;
}
// saving all the objects
[ managedDocument updateChangeCount:UIDocumentChangeDone ];
}
When using UIManagedDocument, you do not call save on the MOC because it implements auto-save. however, it needs to be told that an auto-save should take place at some point in the future.
Get rid of that call to openWithCompletionHandler in that function (I know it was just there for purposes of debugging this problem).
Replace
[managedDocument.managedObjectContext save:&InternalError ]
with
[managedDocument updateChangeCount:UIDocumentChangeDone];
This will notify the document that it can now be saved.
EDIT
First, I think you should get rid of the debugging hacks. You can add NSLog or NSAssert, but the rest of that stuff just makes it hard to tell why you want, and confuses the real issue.
Second, what is your real goal here? I can see the name of the method, and I can see the code, but they do not match.
There is so much "cruft" here, it is hard to understand your problem. I am going to repost your code, along with an edit to remove the "open" stuff, and annotate it with questions as code comments.
Hopefully, this change will help you solve your problem.
// First, the method name seems to indicate that some objects will be added
// to the database. however, the only database work in this method is removal.
// I don't get it.
+ (void ) saveCalendarArrayInDbIfItAlreadyDoesNotExist : (NSArray*) appCalendarArray managedDocument: (UIManagedDocument*) managedDocument
{
NSError *error = nil;
NSMutableArray *array = [[NSMutableArray alloc] init];
for (id appCalendar in appCalendarArray) {
if([appCalendar isKindOfClass:[AppCalendarEntity class]]) {
// OK, we are filtering the array of objects. We are only interested in
// objects of type AppCalendarEntity, and are going to use its identity
// property to look for objects of type MyEntity.
// What is the relationship between AppCalendarEntity and MyEntity?
AppCalendarEntity *appCalendarEntity = (AppCalendarEntity*) appCalendar;
NSFetchRequest *requestToSeeIfCalendarWithIdExist = [NSFetchRequest fetchRequestWithEntityName:#"MyEntity"];
requestToSeeIfCalendarWithIdExist.predicate = [NSPredicate predicateWithFormat:#"identifier = %#", appCalendarEntity.identifier];
NSError *InternalError = nil;
[requestToSeeIfCalendarWithIdExist setShouldRefreshRefetchedObjects:YES];
NSArray *result = [managedDocument.managedObjectContext executeFetchRequest:requestToSeeIfCalendarWithIdExist error:&InternalError];
// OK, now we just got a result from searching for a MyEntity, where
// its identifier is the same as the appCalendarEntity.
if(result == nil)
{
// return error
}
// 1 object always return that depict the in memory(context) object we created but not saved. I expect it should be zero because no object has yet been saved to database..
else if(result.count > 1)
{
// I am extremely confused by this code. First, why are you
// checking for more than 1 object? The method name indicates
// you are going to insert something. Furthermore, you are only
// deleting one object. How many do you expect? Also, why are
// you deleting an appCalendar? You were searching for a MyEntity.
// If an appCalendar is a MyEntity, then that's terrible naming.
// Furthermore, it would explain why you are finding it...
// because you create entities by inserting them in a MOC to
// begin with!
[managedDocument.managedObjectContext deleteObject:appCalendar];
}
else
{
// Even more confusion. You are adding this object to an internal
// array, not the database. Furthermore, you are doing it if there
// are either 0 or 1 MyEntity objects in the database with matching
// identifier.
[array addObject:appCalendarEntity];
}
}
}
// saving all the objects
// OK - but the only thing being saved are the ones you deleted...
[ managedDocument updateChangeCount:UIDocumentChangeDone ];
}
Finally, if my hunch is correct, and the calendar objects are actually MyEntity objects, they are already in the MOC - because that's how they get created. When you do a fetch, you can force the search to ignore pending changes (as noted in one of my previous comments) and only accept saved changes.
If you want to ignore pending changes,
fetchRequest.includesPendingChanges = NO;
#Jody Problem has been resolved and thank you for giving time to this question.
First let me address your confusions
1- Yes function is intended to save in the database and it is a helping function. The parameter "appCalendarArray" being passed to this function consist of entities that has already been created in context. I intentionally eliminated the logic since it involves communicating with external apis, parsing json etc etc. The code required for inserting entities in context has already been included in first part of the question.
AppCalendarEntity *appCalendar = [AppCalendarEntity getInstanceWithManagedDocument:manageDocument];
2- I eliminate the entities from context which has been constructed but not yet saved from context, based upon a column in database that should be unique. If we have identifier for object already in database, we do not want to resave it. So, I simply delete it from context. This function works as expected, entities are not re-saved in database. The last line do save the objects that are left in context if any. Most of the time there are a lot.
3- Sorry for mistyping AppCalendarEntity and MyEntity are the same.
Solution
I have added this flag fetchRequest.includesPendingChanges = NO; , delete db, restarted Xcode and it started working. Thank you for your persistence