GLOBAL SEARCH Functionality in Tokopedia iOS App

Edho Prasetyo
Tokopedia Engineering
6 min readApr 6, 2020

Our journey to create and maintain the most important feature of the app. And yes, it is a unified search page to cater to all kinds of searches like product, shop, digital goods and many more.

Search — /sərCH/

try to find something by looking or otherwise seeking carefully and thoroughly (Oxford Dictionary)

There was a time…when the Search feature of our Tokopedia iOS apps, had only one type of section used to represent Product, Shop or Profile and it was going smooth and stable to what you call a —

Source: https://knowyourmeme.com/memes/perfection

Nah, just kidding, nothing is perfect.

Anyway, have you ever tried to handle many different kinds of cells throughout one CollectionView? How’s the feel? Is it complicated? Takes many lines of code? Or, hard to maintain? Well, we do.

Chatty Chitty

Long ago, we used UICollectionView to handle all kinds of cells inside our Search Page with the extension inside our ViewController. Ever find piece of code like this?

extension ViewController: UICollectionViewDataSource {    func sizeForItem(at index: Int) -> CGSize{
let currentData = data[index]
switch(currentData) {
case is CellA: return CGSize...
case is CellB: return CGSize...
...
}
}
func cellForItemAt....{
let currentData = data[index]
switch(currentData) {
case is CellA: return cellA
case is CellB: return cellB
...
}
}
func heightForSupplementary...
func numberOfItems...
func numberOfItemsInSection...
}

The Pain Point

Looking at the code structures above, what change do you think would be needed when we would like to add a new cell?

Yes. We need to add another switch — case to each of the extension functions, but what if you have 50 or more cells in it, your code would be bloated. As the day and time go passed by, we realized that this kind of architecture is not Scalable and hard to maintain. Imagine all of the code from your cells, your collection, and many things inside one file of ViewController, would be a design nightmare.

Source : https://twitter.com/ios_memes/status/1214153372128993281/photo/1

Eureka!

Realize that our Search code lacked Scalability, we did some research to handle this kind of situation. As time pass by, many ideas come and go, and right now, we’ve accomplished scalability by tweaking our code the right way.

In case you don’t know, there are so many types of content that you can search from our Tokopedia app. Here are some screenshots of it:

Search Result Page (SRP)

As it shows on the screenshots, there are many result types, we can see: cards, empty result, overlay image on top of the card, and many more. We call these Types of result as Section. One section consists of one layout. Now, the challenge is how we can maintain our code to be scalable whenever there’s a new design or new section. We’re going to split this into two parts: The Outer Layer (where we handle search pages like Product, Shop and Profile) and The Inner Layer (where each of the page Product, Shop, and Profile handle their own section).

The Outer Layer

From the outer part of the result, we have one parent that we call Container. This Container holds the tabs below (Product, Shop, and Profile), as illustrated like this:

The Outer Part Architecture

Inside the Container, we got that one frame box which can be filled by any ViewController. Here we fill it with 3 of our pages (The Inner Layer): Product, Shop, and Profile. Every page can maintain its own system and processes separately. And if there’s gonna be a new page, we just need to make a new ViewController and fill it to the Container Frame Box without touching other pages. Everything is separated just like you and her/him (just kidding)🤦‍♀️🤦‍♂️.

Source : https://imgflip.com/i/tk8q3

The Inner Layer — The Search Result

Now for the inner layer (Product, Shop and Profile Page), we need to prepare our code to handle future improvements where they can be ruined by a lot of new sections and designs that are so variative.

We decided to use IGListKit as our framework that will handle all of our sections. The method is simple whenever there’s a new design or section, we just need to create a new Section Controller and register it to our Product Page ViewController. Done. No more switch — cases in one ViewController, no more bloated code.

Source : https://memeshappen.com/meme/steve-harvey/so-easy-113774

The Flow of the UI would be like this:

The Inner part Architecture

Depicted from the image above, now every section has its own cell, and their cell has its own business logic inside, separated from their parents. With this, every new section or new design can be handled without ruining other Sections or parent flow of code. And yeah, we are using MVVM too in case you wonder.

The reason why we use IGListKit as our section handler is because we can maintain less code and perform data update/section update seamlessly. Through this, we can avoid so many processes and logical operations in one class (which is the section controller parent). Instead, we move it to each of the “Section Controllers”.

Combined with AsyncDisplayKit, we don’t need to have a calculation process to measure our section height. It is all calculated automatically through the framework 🍻.

Lastly, IGListKit introduced Adapter class that is used to handle data changes and reloading, because we all know the pain of performUpdate, now with this adapter, those pain can be washed away 😌🎉.

One More Thing

Beside Scalability, we need to take care of the readability as well. Imagine you got all the logic inside one ViewModel and resulted in 1500 lines of codes. Then comes a newcomer that needs to add new logic to the same code. When he/she tries to read the code it will take a longer time to understand that 1500 lines before adding a new logic line.

Thanks to our team, we move every core and processing logic to the new thing we called “Processor” to get that separation and readability.

Processor — proc·es·sor

a machine that processes something. (Oxford Dictionary)

Yes, as the way it means, this processor takes input from ViewModel (VM), processes it, and passes it to the VM again. Inside the processor, we differentiate every logic to their separate function so that the code becomes more verbose and readable.

Time for “Wrap Up”

Up until now, We still need to improve what we’ve enhanced before for better user experience, as stated from one of our DNA: Focus On Consumer. We hope that we can do our best to deliver our features with the latest technology and better approaches, and clean up the bugs (if any 😉). Cheers! 🍻

--

--