Connect with us

Разработка

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

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

Опубликовано

/

     
     

Являясь ежедневным пользователем приложения Zomato для Android и инженером по разработке приложений, я полюбил дизайн приложения и то удобство, которое оно предлагает для заказа еды из любого места. Однако мне всегда было неудобно постоянно открывать приложение, чтобы проверить статус доставки еды. Хотя в приложении есть отличный экран отслеживания заказов, ему не хватает целостности и легкости доступа, которые предлагает iOS-вариант приложения Zomato благодаря использованию в iOS Live Activity. Вдохновившись этим, я решил взять дело в свои руки и провести реверс-инжиниринг приложения Zomato для Android, чтобы создать собственное решение для отслеживания заказов. В этой публикации я расскажу о том, как нашел необходимые конечные точки API, разработал системную архитектуру приложения и реализовал уведомление, которое предоставляет информацию о заказе практически в режиме реального времени. И все это без необходимости постоянно открывать приложение Zomato.

Часть 1: Понимание трафика приложения

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

Для этого я воспользовался следующими инструментами:

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

Часть 2: Анализ трафика приложений

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

https://api.zomato.com/gw/order/history/online_order
Как я исследовал приложение Zomato, чтобы создать свою собственную систему уведомлений

Сетевая активность приложения Zomato на экране истории заказов

Давайте проверим ответ:

...
{
	"status": "success",
	"has_more": true,
	"postback_params": "{\"last_created_timestamp\":{\"seconds\":1714292519},\"last_order_id\":5747518212}",
	"results": [{
		"layout_config": {
			"snippet_type": "filter_info_card",
			"layout_type": "carousel",
			"section_count": 1
		}
	}, {
		"layout_config": {
			"snippet_type": "order_history_snippet_type_2",
			"layout_type": "carousel",
			"section_count": 1
		},
		"order_history_snippet_type_2": {
			"click_action": {
				"type": "deeplink",
				"deeplink": {
					"url": "zomato://delivery/5777845053"
				}
			},
			"top_container": {
				"title": {
					"text": "\u003cmedium-400|{grey-900|Leon's Burgers \u0026 Wings}\u003e",
					"is_markdown": 1,
					"markdown_version": 2,
					"number_of_lines": 1
				},
				"subtitle1": {
					"text": "\u003cmedium-100|{grey-600|Whitefield, Bangalore}\u003e",
					"is_markdown": 1,
					"markdown_version": 2,
					"number_of_lines": 1
				},
				"subtitle2": {
					"text": "\u003cmedium-100|{grey-600|54 mins}\u003e",
					"is_markdown": 1,
					"markdown_version": 2,
					"number_of_lines": 1
				},
				"image": {
					"url": "https://b.zmtcdn.com/data/pictures/6/19337846/eefd6011022ffa01ec1c0c9becfaded1_featured_v2.jpg?fit=around%7C108%3A108\u0026crop=108%3A108%3B%2A%2C%2A"
				},
				"tag": {
					"title": {
						"text": "On the way",
						"color": {
							"tint": "500",
							"type": "blue"
						}
					},
					"bg_color": {
						"tint": "100",
						"type": "blue"
					},
					"border_color": {
						"tint": "400",
						"type": "blue"
					},
					"image": {
						"animation": {
							"url": "https://b.zmtcdn.com/data/file_assets/e2585eef505467d5ada82b1c0169fc161632987479.json",
							"duration_per_step": 0,
							"animate": false,
							"repeat": false
						}
					}
				},
				"right_button": {
					"type": "text",
					"text": "View menu",
					"suffix_icon": {
						"code": "e875",
						"color": {
							"tint": "500",
							"type": "red"
						}
					},
					"click_action": {
						"type": "deeplink",
						"deeplink": {
							"url": "zomato://order/19337846"
						}
					},
					"tracking_data": [{
						"table_name": "jevent",
						"payload": "{\"var1\":\"5777845053\",\"var2\":\"19337846\",\"var3\":\"On the way\",\"var4\":\"1\",\"var5\":\"your_orders\",\"var6\":\"view_menu\"}",
						"event_names": {
							"tap": "{\"ename\":\"order_history_snippet_tapped\"}"
						}
					}],
					"should_use_decoration": false,
					"should_use_squircle": false,
					"should_round_corner": false
				},
				"click_action": {
					"type": "deeplink",
					"deeplink": {
						"url": "zomato://order/19337846"
					}
				},
				"tracking_data": [{
					"table_name": "jevent",
					"payload": "{\"var1\":\"5777845053\",\"var2\":\"19337846\",\"var3\":\"On the way\",\"var4\":\"1\",\"var5\":\"your_orders\",\"var6\":\"res_card\"}",
					"event_names": {
						"tap": "{\"ename\":\"order_history_snippet_tapped\"}"
					}
				}]
			},
			"items": [{
				"title": {
					"text": "\u003csemibold-300|{grey-600|1 x}\u003e \u003csemibold-300|{grey-900|Peri Peri Chicken Wrap}\u003e",
					"is_markdown": 1,
					"markdown_version": 2,
					"number_of_lines": 1
				},
				"image": {
					"url": "https://b.zmtcdn.com/data/o2_assets/3e0b4b89a7a1d815a1adaf5ad216505f1657182448.png"
				}
			}, {
				"title": {
					"text": "\u003csemibold-300|{grey-600|1 x}\u003e \u003csemibold-300|{grey-900|Chicken Doner Salad}\u003e",
					"is_markdown": 1,
					"markdown_version": 2,
					"number_of_lines": 1
				},
				"image": {
					"url": "https://b.zmtcdn.com/data/o2_assets/3e0b4b89a7a1d815a1adaf5ad216505f1657182448.png"
				}
			}, {
				"title": {
					"text": "\u003csemibold-300|{grey-600|1 x}\u003e \u003csemibold-300|{grey-900|Hummus With Chicken and Pitta}\u003e",
					"is_markdown": 1,
					"markdown_version": 2,
					"number_of_lines": 1
				},
				"image": {
					"url": "https://b.zmtcdn.com/data/o2_assets/3e0b4b89a7a1d815a1adaf5ad216505f1657182448.png"
				}
			}],
			"bottom_container": {
				"title": {
					"text": "30 Apr 2024 at 9:11PM"
				},
				"subtitle1": {
					"text": "\u003csemibold-200|{grey-900|₹649.55}\u003e",
					"is_markdown": 1,
					"markdown_version": 2,
					"suffix_icon": {
						"code": "e822",
						"color": {
							"tint": "500",
							"type": "grey"
						}
					}
				},
				"buttons": [{
					"type": "solid",
					"text": "Track order",
					"click_action": {
						"type": "deeplink",
						"deeplink": {
							"url": "zomato://delivery/5777845053"
						}
					},
					"tracking_data": [{
						"table_name": "jevent",
						"payload": "{\"var1\":\"5777845053\",\"var2\":\"19337846\",\"var3\":\"On the way\",\"var4\":\"1\",\"var5\":\"your_orders\",\"var6\":\"track_order\"}",
						"event_names": {
							"tap": "{\"ename\":\"order_history_snippet_tapped\"}"
						}
					}],
					"should_use_decoration": false,
					"should_use_squircle": false,
					"should_round_corner": false
				}]
			},
			"tracking_data": [{
				"table_name": "jevent",
				"payload": "{\"var1\":\"5777845053\",\"var2\":\"19337846\",\"var3\":\"On the way\",\"var4\":\"1\",\"var5\":\"your_orders\"}",
				"event_names": {
					"impression": "{\"ename\":\"order_history_snippet_impression\"}"
				}
			}, {
				"table_name": "jevent",
				"payload": "{\"var1\":\"5777845053\",\"var2\":\"19337846\",\"var3\":\"On the way\",\"var4\":\"1\",\"var5\":\"your_orders\",\"var6\":\"order_dish_card\"}",
				"event_names": {
					"tap": "{\"ename\":\"order_history_snippet_tapped\"}"
				}
			}]
		}
	}
  
 ....

Это сокращенный ответ для примера, полный код ответа приведен здесь.

Если мы изучим эту структуру JSON, то увидим, что существует список результатов, в котором есть объект с ключами layout_config и order_history_snippet_type_2. Объект order_history_snippet_type_2 содержит полезную для нас информацию. Объект click_action содержит строку с глубокой ссылкой, которую мы можем использовать для извлечения идентификатора заказа. Идентификатор заказа присутствует и в других местах, но мне показалось, что в этом месте все просто. Есть также top_container и bottom_container, которые содержат всю остальную информацию, которая нам нужна. Название ресторана можно извлечь из текстового элемента внутри объекта title в top_container. Идентификатор заказа можно просто извлечь из deeplink, заменив начальные теги и сохранив номера. Статус заказа можно получить из текстового элемента внутри объекта title в top_container. Время доставки заказа можно получить из текстового элемента внутри объекта title в bottom_container.

Аналогично, если мы размещаем заказ и анализируем активность трафика на странице Сведений о заказе в приложении, то повторно вызывается следующая конечная точка API:

https://api.zomato.com/v2/order/crystal_v2

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

response -> order_details -> res_name = Restaurant Name
response -> order_details -> tab_id = Order ID
response -> header_data -> pill_data -> left_data -> title -> text  = Estimated Time (String) [ex: Arriving in 5 mins] 
response -> header_data -> subtitle2 -> text = Order Status in text [ex: On the way, Will be picked up soon etc]
response -> header_data -> pill_data -> right_data -> title -> text = Order Estimate Status [ex: On time, Delayed]

Часть 3. Разработка уведомления

Поняв часть, связанную с получением данных, я захотел создать систему уведомлений, которая вдохновлялась бы индикатором активности на iOS. Вот как Zomato использует службу Activity Indication в своем приложении для iOS:

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

Я создал аналогичный макет для уведомления Android. Вот как это выглядело в дизайне Figma, который я создал:

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

Как и индикатор активности в iOS, в уведомлении, созданном моим приложением, также будет отображаться название ресторана, текстовое представление текущего статуса заказа, оценка времени доставки и фактическая оценка времени передачи заказа.

Часть 3. Обработка потока информации и управление уведомлением

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

Схема: Обработка информации между activity — service — notification

Чтобы иметь уведомление, которое можно было бы регулярно обновлять, нам нужно использовать Foreground Service, а также некоторые функции для многократного получения сведений о заказе. Давайте назовем эту службу OrderTrackService. Основная цель этого сервиса — принять идентификатор заказа и вызвать crystal_v2 API для получения информации о статусе заказа, а затем отобразить ее в уведомлении. Эта задача будет повторяться с интервалом в 30 секунд, и этот процесс будет продолжаться до тех пор, пока статус заказа не станет “Доставлен”.

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

Чтобы управлять сервисом и запускать его, нам также понадобится Activity, основная цель которой — получить историю заказов и отобразить ее в виде списка. С помощью этого списка мы можем выбрать, какие идентификаторы заказов будут переданы в OrderTrackService для обработки уведомления.

Часть 4: Собираем все вместе

Теперь, когда дизайн и система понятны, пришло время воплотить идею в жизнь. Я начал с реализации MainActivity с помощью Jetpack Compose. MainActivity состоит из трех кнопок, первая кнопка предназначена для запроса разрешения POST_NOTIFICATION для приложения, вторая кнопка служит для запуска Foreground службы, а последняя кнопка используется для просмотра истории заказов. При нажатии на кнопку “Получить заказы” приложение вызывает online_order API и фильтрует результаты, в которых статус заказа не “Доставлен”. Отфильтрованные заказы — это те заказы, которые активны в данный момент.

Для фактического вызова конечных точек API я внедрил библиотеку Retrofit и создал подходящие интерфейсы с требуемыми заголовками для двух конечных точек API. Чтобы обеспечить качество кода, все здесь было реализовано с использованием шаблона MVVM, а также Hilt для внедрения зависимостей, чтобы упростить работу с экземпляром Retrofit.

Класс OrderTrackService является расширением класса Service. Как уже говорилось, здесь мы будем обрабатывать логику уведомлений. Связь между MainActivity и OrderTrackService устанавливается с помощью Broadcast Intent. Соответствующая кнопка для каждого заказа в списке, отображаемом в MainActivity, отправляет широковещательное намерение с идентификатором заказа как часть пакета данных для этого намерения. После получения этого широковещательного запроса Foreground служба начинает действовать, вызывая crystal_v2 API для получения сведений о заказе для этого указанного идентификатора. Затем она создает уведомление, в котором отображается информация. Логика получения и отправки сообщений создана для работы в Kotlin Flow, так что она может работать асинхронно, с поддержкой независимой обработки нескольких уведомлений, если требуется, поскольку для каждой полученной рассылки с действительным идентификатором заказа мы запускаем повторяющийся поток. С помощью Flow мы также можем приостановить/повторить действие по желанию, таким образом, это также позволяет нам повторять ту же задачу с задержками (в нашем случае 30 секунд).

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

Финал

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

Я разместил полный исходный код для этого проекта в моем репозитории на GitHub здесь.

Не стесняйтесь исследовать, экспериментировать и даже вносить свой вклад в проект, если у вас есть идеи по дальнейшему совершенствованию.

Исходник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.

Наши партнеры:

LEGALBET

Мобильные приложения для ставок на спорт
Хорошие новости

Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: