Современную реальность невозможно представить без социальных сетей и различных рейтингов. В связи с этой тенденцией мобильные игры и приложения активно «социализируются». Однако, в то время как для игр такое явление обыденно и закономерно, добавить фунционал социальной сети в неигровые приложения не каждому покажется очевидным решением. Тем не менее, наши давние партнеры и друзья — компания-разработчик музыкальных приложений Music Paradise — решились на подобный шаг. Сегодня мы делимся их кейсом под кодовым названием «Как мы первые социальные пады делали».

«В этой статье мы хотели бы поделиться опытом создания социальной платформы для меломанов на базе музыкального приложения DJ Mix Pads 2.

Изначально приложение представляло собой простой набор инструментов, с помощью которого любители музыки могли записывать треки, даже не имея особых навыков и знаний. Всего в несколько кликов можно было получить законченную композицию со звучанием, близким к профессиональному.



Чтобы выделиться из множества схожих продуктов, мы решили сыграть на соревновательном элементе и сделать процесс создания композиций более целенаправленным. Открывать в себе музыканта будет интереснее, если дать людям возможность делиться своими творениями с другими пользователями, оценивать чужие, выбиваться в топы, объединяться в группы по музыкальным интересам и открывать новые возможности приложения, поднимая себе рейтинг.

В данной статье мы коснемся различных аспектов работы с сервером, расскажем о некоторых дополнительных функциях, которые потребовались для введения социальной составляющей, и о том, как они были реализованы в продукте.

Взаимодействие c клиент-сервером


Работа с серверным API осуществляется посредством POST и GET запросов. Тут хотелось бы подробно рассмотреть ухищрения, к которым мы прибегали, чтобы настроить работу с ними.

Рассказывать про формирования HTTP запросов особого смысла нет: гугл и так переполнен соответствующей информацией, да и на гитхабе можно найти множество полезных библиотек.

Поэтому разберем лучше работу с ответом от сервера на примере того, как получали список треков, которые пользуются популярностью у пользователей. Список треков приходит в таком формате:

{
    data = ();
    success = 1;
}

Либо в таком, если запрос неверный или произошла какая-то ошибка:

{
    error = "Error description";
    success = 0;
}

Для разбора ответа мы используем данную конструкцию:

NSDictionary *answer = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];//Где data - пришедший от сервера ответ
if([[answer objectForKey:@"success"] boolValue]){
	NSMutableArray *dataArray = [answer objectForKey:@"data"];
	[self doSomeStuff:dataArray];
}
else{
	if([answer objectForKey:@"error"])
		NSLog(@"%@",[answer objectForKey:@"error"]);
}

Передача изображений


При регистрации в приложении у пользователя есть возможность поставить себе аватарку. Соотвественно, встал вопрос о том, как изображения будут передаваться на сервер и с сервера.

Механизм циркуляции изображений мы выбрали весьма своеобразный. Самым простым решением было бы передавать сами файлы в качестве POST запроса, после чего при необходимости получать ссылку на файл на стороне сервера. Именно таким образом у нас реализовано хранение аудио-файлов, которые загружают на сервер пользователи.

Однако было решено передавать изображения в качестве base64 строки. Для этого на стороне клиента используются следующие методы преобразования строки.

Сначала мы преобразовываем картинку в строку, попутно изменяя размер, если в этом есть необходимость. Стоит упомянуть, что изображения к этому моменту уже квадратные:

-(NSString *)imageToNSString:(UIImage *)image
{
    if(!image)
        return nil;
    if(image.size.width>250&image.size.height>250)
        image = [self imageWithImage:image scaledToSize:CGSizeMake(250, 250)];
    NSData *imageData = UIImagePNGRepresentation(image);
    return [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
}

- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
    UIGraphicsBeginImageContext(newSize);
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

Теперь преобразовываем строку в картинку:

-(UIImage *)stringToUIImage:(NSString *)string
{
    if(string==nil)
        return nil;
    NSData *data = [[NSData alloc]initWithBase64EncodedString:string
                                                      options:NSDataBase64DecodingIgnoreUnknownCharacters];
    return [UIImage imageWithData:data];
}

На первый взгляд данный подход может показаться не самым эффективным, ведь приходится выполнять двойную работу, преобразовывая изображения туда и обратно. Однако для нас такое незначительное усложнение было оправдано: в конечном итоге оно существенно облегчило нам жизнь и ускорило процесс разработки.

Немного про пагинацию


Эта проблема встала перед нами неожиданно и несколько позже остальных, когда наши социальные нововведения уже начали набирать популярность. Люди стали загружать достаточно большое количество треков в день, массив аудиофайлов на сервере неумолимо рос — все это вызывало проблемы в получении списков топов. При попытке обработать весь список разом приложение подвисало и выдавало ошибки. Стало ясно, что придется переходить на частичную подгрузку контента, а значит, вводить пагинацию.



Списки стандартно создавались с помощью UITableView, и необходимо было разработать метод подгрузки новых ячеек по мере получения данных с сервера.

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    
    if(indexPath.row == self.tableContentArray.count-1 && !self.isFullContent)
    {
        [self getContentFrom:self.tableContentArray.count withCount:10];
    }
}

В первую очередь мы проверяем:

— является ли данная ячейка последней, которую мы можем отобразить;
— получили ли мы весь контент который должны отобразить на данном контролере.

Расчет такой: если мы получили меньшее количество записей, чем запрашивали, (в данном случае меньше 10), то в этот момент помечаем переменную isFullContent как true.

Уведомления внутри приложения


Нам хотелось, чтобы внутри приложения пользователь имел доступ к полной информации о состоянии своего аккаунта (количество лайков, прослушиваний и т. д.). Для этого была введена система локальных уведомлений. Принцип работы у нее следующий — при входе пользователя в приложение мы сохраняем текущее состояние:

[[NSUserDefaults standardUserDefaults] setInteger:user.countOfLikes forKey:@"countOfLikes"];
[[NSUserDefaults standardUserDefaults] setInteger:user.countOfLikes integerValue] forKey:@"countOfListens"];
[[NSUserDefaults standardUserDefaults] setInteger:user.level forKey:@"countOfLevels"]; 

При следующем же входе данные значения сверяются с данными, полученными от сервера. Если они не совпадают, мы отображаем локальное уведомление, информируя пользователя о том что у него есть прогресс. Такие приятные новости положительно влияют на пользовательский опыт:

CongratsCustomPush *push = [[CongratsCustomPush alloc] initInController:[self topViewController]
                                                              withTitle:@"Changes since your last visit.\nGood job!"
                                                              withLevel:countOfLevels
                                                              withLikes:countOfLikes
                                                         andWithListens:countOfListens];
[push showAnimated:YES];



Достижения


Достижения — это не только интересный контент и вознаграждение пользователя за использование всех возможностей приложения, но также неплохой способ узнать больше о желаниях аудитории, вдобавок к данным, которые предоставляет аналитика.



На данный момент в приложении имеется 9 достижений:

  • Создать студию
  • Пройти мини-урок по эффектам
  • Получить 5 прослушиваний ваших треков
  • Опубликовать 3 трека
  • Опубликовать первый трек в студии
  • Записать свой первый трек
  • Провести 30 минут в приложении
  • Пройти мини-урок по созданию студии
  • Завершить начальный туториал

Зарабатывать ачивки — дело клиента, сервер лишь хранит информацию о полученных достижениях и некоторую статистику (например, сколько времени пользователь провел в приложение)

При рассылке оповещений о достижениях используется система удаленных PUSH-уведомлений для конкретных пользователей. Чтобы идентифицировать пользователя мы сохраняем UUID девайса, с которого был выполнен последний вход в систему, и при получении PUSH-уведомления идем одним из двух путей, в зависимости от обстоятельств.

Если пользователь в данный момент не находится в приложение, он получает стандартное оповещение с информацией о полученном достижении — это первый путь.

Второй же путь более сложный. Если в данный момент пользователь находится в приложении, мы перехватываем полученное сообщение в методе AppDelegate’а:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo


и отображаем его, в текущем ViewController’e.

Заключение


Обобщим, какие дополнительные возможности в итоге дало нам использование сервера:

  • Регистрация пользователей и сохранение данных из их профилей (почта или страница в Facebook, имя, аватарка).
  • Удаленное хранение треков на сервере.
  • Предоставление треков на общий суд.
  • Возможность отмечать чужие трэки как понравившиеся и сохранять их в собственную коллекцию.
  • «Студии» — группы пользователей по предпочтениям в музыкальных жанрах. Вступление в студию дает доступ к новым сэмплам, звукам.
  • Топы по трекам, пользователям, студиям
  • Отправка пуш-уведомлений конкретным пользователям по достижениям
  • Получение ачивок
  • Подгрузка части контента (звуки, сэмплы) с сервера. Это дало нам возможность ужать приложение до размера меньше 100мБ. Это важно: согласно правилам маркета, приложения размером более 100мБ весом не могут скачиваться через мобильные сети, только по wi-fi.
  • Сбор подробной, точечной статистики по пользователям, публикациям, предпочтениям. Количество публикуемых треков, самые популярные жанры, активность отдельных юзеров, частота получения ачивок – все это дало материал для более объективной оценки результатов наших трудов.? А также, скажем по секрету, возможность удаленно награждать самых активных пользователей».

P.S. Ребята также рассказали нам, что заметили забавную тенденцию: ведущие разработчики данного проекта женятся примерно через 2-3 месяца после того как начинают над ним работу. В данный момент счет 2:0 в пользу этого «поверья». Так что, если хотите кардинальных изменений в личной жизни, попробуйте реализовать подобный проект в своем приложении.
Поделиться с друзьями
-->

Комментарии (2)


  1. ExtremeCode
    13.01.2017 04:54

    Забавно, не ожидал здесь увидеть пост от Music Paradise. Само приложение было переработано полностью на obj-c? Или новая функциональность была внедрена в тот самый «злобный» кроссплатформеный движок? Насколько мне известно, проект ранее был кроссплатформенным. Если да, то было бы гораздо интереснее почитать про внедрение подобной нативной функциональности


    1. EverydayTools
      13.01.2017 05:31

      Спасибо за вопрос, приложение было полностью переписано на Objective c, при этом версии под другие платформы остались на том самом «злобном» кроссплатформенном движке под название Unity.