How we added settings searching to the LINE app

LINE has become a large platform that is host to a myriad of features over the years, and with each new feature added, so were new menus and items added to the settings menu as well. With more than 47 settings menu and 185 items that cover everything from personal info, themes, permission, and notifications to name a few, it has become increasingly difficult for our users to find the exact settings that they want to adjust. If one did not know exactly where a certain setting was, they would have had to navigate through several pages and levels before they would eventually find what they were looking for. To alleviate this problem and provide our users with a more streamlined user experience, we’ve decided to add a search bar to the settings menu.

Settings search demo

With the new search bar, users can type in a keyword to find the setting they are looking for, no matter how deep the setting is nested under other settings. When you tap on one of the search results, the app will now bring you to the corresponding menu, scroll to the position of the setting, and highlight the setting with a blinking animation as you can see in the picture above. In some cases, the search results can take you directly to the details page of the setting as well.

The first question we asked ourselves when building the new search screen was “How will we get the data for displaying the search result items?”. To answer this question, we had two methods to consider:

  • Building a separate set of search data that has the same items as the current settings menu.The advantage of this solution was that we wouldn’t need to touch the current codebase, but it would take a huge effort to set up and maintain because we have a lot of items and it would be easy to make a mistake or forget to update the search data when there is a change to the settings data. The setting items displayed would differ from the corresponding configurations, and it would be hard to correct them whenever they change in two places.
  • Synchronizing data from setting menus to search data.This solution abandons all disadvantages of the first solution, but the search data would always reflect setting data updates, which will make the search system work well and cost next to nothing in terms of maintenance.

To reduce future maintenance costs for other developers, we decided to go ahead with the second solution even though we needed to retouch many lines of code to get the work done.

Before getting our hands dirty, we dug into the current settings menu structures to find the best way to apply the synchronizing mechanism. Unfortunately, the settings data was set up in different structures; some of them were implemented with a clear data structure, while other ones were collected from many sources with unclear definitions. It would take time to understand each settings menu and revamp the data to be uniform if needed before thinking about synchronizing it with search results. Besides, some of the codebase was written a long time ago, and it would be hard to maintain if we scale up or update the design of the product in the future. 

This led to the big decision to refactor all settings menus, unify them to the same structure that would help reduce maintenance costs, increase expandability, and make synchronizing to search data more efficient.

The before and after of our refactored structure

Implementation

Search Data Source

First of all, each settings menu contains many items inside, and each item can point to a sub-setting menu, or is an action item or information item.

To build search data, we needed to have item data from all settings menu levels so that each item that has a sub-setting menu can provide the sub-set items inside it. Using a tree to represent the data structure for items was suitable in this case. We hypothesized that if setting item are nodes and the whole setting system was a tree, each node would have a branch of the tree (child nodes) or be a leaf. 

We defined the search node model (SearchNode) that contains the information of the search item (SearchItem) and data source (SearchDataSource) which represents the branch of that node if it exists.

SearchNode

public final class SearchNode {
    public let item: SearchItem
    public let dataSource: SearchDataSource?
}

SearchItem

public final class SearchItem {
    public enum Action {
        case select
        case highlight
    }
 
    public let id: String
    public let title: String
    public let value: NSAttributedString?
    public let category: String?
    public let keywords: [String]
    public let icon: UIImage?
    public let action: () -> Action
}

Each settings menu will provide the SearchDataSource that contains: 

  • searchTree: Search nodes that corresponds with items in that menu.
  • ownedViewControllerFactory: Factory to create setting menu view controller.
public protocol SearchDataSource {
    var ownedViewControllerFactory: ((_ inStackViewControllers: [UIViewController]) -> UIViewController)? { get }
    var searchTree: [SearchNode] { get }
}

You can see the expression of search data sources in the picture below.

Synchronize

Because we unified the settings data from menus into one data structure, we defined the new settings model that has a number of sections model that each contains item models inside them.

Item model stores:

  • Item data that is used to configure the corresponding item cell on the table view
  • Search data to provide the additional information used to configure the search nodes

We support multiple item model types storing the data for each corresponding cell type. Such as:

  • Profile picture model stores information for profile picture cell (display cover and profile picture of user)
  • Plain model stores information for plain cell (the cell with title, subtitle, and accessory view)
  • Switch model stores information for switch cell (the cell for toggling features)

So we define the SearchNodeSupportable protocol to extract the search node from the item types.

public protocol SearchNodeSupportable {
    func getSearchNode() -> SearchNode?
}

Each model type will conform to that protocol to output search nodes, while the special items can be excluded out of the search scope so the return type will be an optional type.

The picture below represents the new data structure applied to the refactored settings menus.

After we built the search data source, we planned to use it in the search screen as a primary source to query items matching with the given search term. When the user selects the search result item, a navigation stack for the settings menus is established (for example, when a settings item is in the third depth menu, the first and second depth menus are pushed into stack first before showing the last menu) then executes the corresponding action with each result item, includes action types that we defined in the SearchAction enum:

  • highlight: Scroll to the proper item and run highlight animation
  • select: Scroll to the proper item, run highlight animation, and perform the select action.

To support the highlight and selecting behavior, we defined protocols CellHighlightSupportable and CellSelectingSupportable to execute the highlighting and selecting actions with a given setting item id. The Setting view controller must conform to these protocols to run actions.

By default, we unified the actions animation effect in the default extension of those protocols. In a special case where the view controller executes a different animation, it can be customized easily by reimplementing the functions.

public protocol CellHighlightSupportable {
    func highlightCell(withID id: String, completion: (() -> Void)?)
}
 
public protocol CellSelectingSupportable {
    func selectCell(withID id: String)
}

Expandability

Up until now, we’ve supported 3 or 4 cell types that cover all cases in the settings menus, but we needed to prepare in case more cells are added in the future when there are changes to specs or design, and efforts must be made to prevent modification as much as possible. To achieve that, we use the TableViewCellConfigurable protocol that helps to match the view model with the corresponding cell and configure them.

public protocol TableViewCellConfigurable: TableViewCellConfigurationWrappable {
    associatedtype TableViewCell
    associatedtype ViewModel
 
    func registerCellType(for tableView: UITableView)
    func config(
        cell: TableViewCell,
        withViewModel viewModel: ViewModel,
        indexPath: IndexPath,
        for tableView: UITableView
    )
    func height(with viewModel: ViewModel, indexPath: IndexPath, for tableView: UITableView) -> CGFloat
    func estimateHeight(with viewModel: ViewModel, indexPath: IndexPath, for tableView: UITableView) -> CGFloat
}

When we want to add a new cell type, we only need to define the new Configurator that conforms to the TableViewCellConfigurable protocol to set up cell configurator separately without touching the other parts. This configurator is registered in the pool when initializing the view controller and the system will automatically transfer item models to the corresponding cells through the pool.

The side effects

Because we refactored a lot of settings menu, we faced unpredictable issues that made us change the structure to adapt to current behaviors.

One of them is support for reloading changed rows with animation. While we called it manually in the old structure, and we still can in the new structure, it isn’t effective if the data source changes dynamically. We decided to use a diff algorithm called DifferenceKit that has good performance with O(n) timing complexity. Each time the setting model changes, the system will calculate the changeset with the previous state and output the corresponding batch update actions. This helps us reduce pain points when updating the table view manually and reduce crashes caused by a mismatch between update actions and data source.

Conclusion

After some blood, sweat, and tears we released the first version of the settings search feature in LINE version 11.7.0. The feedback from users during the first week was quite impressive:

  • 104K users used the settings search feature.
  • 47% of users entering the settings home menu used the search feature.
  • 37% of users found the proper settings items.

With the above analytics, we can confirm this feature is helpful for users so far. However, the percentage of users who found the proper settings items is still lower than expected. We are going to work on improving and making search more efficient. We hope users have a better experience in the next version.