Hi, there! We are Hyeonji Jo and Keonhong Lee, responsible for LINE iOS client development. Here we’d like to share our experience during the development of the Share module development on LINE for iOS, focusing on the difficulties we faced and how we overcame them.
Overview: Share module
The LINE app offers various services such as Chats, Album, Notes, Timeline and Keep, allowing users to freely upload and view posts, images or videos. In addition, the LINE app supports sharing content between its services; such as forwarding a message from one chatroom to another or saving content in Keep. Previously this share feature was developed and maintained by different developers per service or page. This feature offered by different services did a similar job but was developed and maintained separately, causing many issues such as below:
- Inconsistent UI/UX on different pages
- Different data models for the same content
- Similar or redundant code
- Complicated maintenance and update due to difficulty in understanding logic and requirements
To resolve such issues, we embarked on a mission to consolidate the share feature in LINE. As a result, we introduced the new share sheet with improved UX and integrated the share logic of many services to apply the single uniform Share module in LINE.
|Image viewer||Save and share menu (Before)||Save and share menu (After)|
Our goal was to resolve these issues by integrating legacy code and save development resources required for the expansion and maintenance of the share feature. As there were so many different types of services and content in LINE, it was a challenge to design and develop a single module that could fit all existing scenarios.
In this post, we will share how we found a solution in the process under the following six categories. We’ll cover the first two categories in Part I and continue with the remaining categories in Part II.
- Integrating conventions on data models
- Integrating logic on sharing data
- Identifying a list of eligible services
- Providing customized/additional actions
- Generalizing exceptions
- Improving UI/UX
1. Integrating conventions on data models
In the past, it required complicated conversions to transfer data from one service to another since each service used different data models. We defined new data model conventions for all services to clean up and streamline the messy state it was in before.
|Figure 1-1. Data conversion between services (Before)||Figure 1-2. Data conversion between services after implementing a single model|
Many of LINE’s services provided a share feature but they did not use a uniform data model. Consequently, a series of data conversions had to take place for sharing content between services as shown in Figure 1-1 above. For instance, if a user wanted to save a message from a chatroom to Keep, this message had to be converted for Keep. Again, if a user wanted to share something from Keep to Timeline, it had to be converted for Timeline. There were a series of data conversions separately implemented for the corresponding pair of LINE services.
Let’s take a look at the code for sharing in chatrooms as an example. This had a long history of maintenance and it was particularly difficult to understand due to complicated data types. There was a class called
NLSharableObject which had the following information and represented data transferred to chatrooms. However, this class was used only for chatrooms as other services did not treat data the same way.
- Data of the content for sharing –
- Content type information – Text, image, file, location and etc.
Content type information declared to identify content type in
NLSharableObject was closely related to the internal logic of data transfer. For instance, when sharing an image, content type was classified differently based on the following scenarios. It was difficult to understand how data type was defined just by looking at the code because of tricky classification categories.
- When there is a
UIImageinstance in the memory
- When there is a file saved on the device
- When the image can be downloaded from the server
There was another class called
InAppShareContext used to share data in chatrooms. This class handled various data such as
NLSharableObject. In other words, actual data and a class that wrapped data were treated as the same-level property. As
InAppShareContext could process various types of data, developers could select an appropriate data type for their purpose and freely write code. On the other hand, there were many lines of code to review in order to find out what type of content was being transferred. This complicated tasks such as exception handling based on respective data types.
dictionary was used to transfer data for sharing, which also made it difficult to identify what content was being transferred. It was easy to add information when you transfer with
Dictionary. But, at the same time, it increased the risk of sending unnecessary data along and it made it difficult to identify which data was saved with which key.
Lastly, a model called
ShareTarget was added with the intention of easing the use of existing data models. This model wrapped existing classes. However, it ended up adding an extra step when tracking single data transfer.
Consequently, the end result was Figure 2 above where a series of data conversions were taking place just to share a single image in the chatroom. This flow started with converting
Image data into
ShareTarget, and it was sent to
InAppSharing, which extracted and converted data for
InAppShareContext converted it to
NLSharableObject and again to
Message data finally for sharing. Such a complicated data conversion flow made it difficult to maintain data types and track information flow. Naturally, it was tricky to conduct the maintenance of data conversion related code.
We defined a uniform data model for all services with the minimum number of variables to represent the required data in order to eliminate code for one-to-one data conversion between services.
For content sharing, some information is commonly required while some are specific to each service. We’ll elaborate on how we classified such information and defined data models based on the classification.
Commonly required data
First, we started by defining the basics such as texts, images and thumbnails. We also defined some common data for images or videos as a struct. For example,
ContentInfo was used for content attributes including the size of images, videos or files and the run time of audios or videos.
OBSInfo was for identifiers or URLs commonly required for content on the server.
Under an ideal scenario after adopting uniform data models, we would not need extra data for individual services on top of commonly required data for the Share module. Unfortunately for the LINE app, we still needed service-specific data due to distinct characteristics of respective services. The case in point is sharing messages in chatrooms as shown below.
- Additional mandatory data to handle content sharing per service
- Regardless of type of messages, unique message ID or sender’s ID are required to send messages. If a LINE sticon is included in a message, its metadata should be sent along with the message. This data is meaningful only for sharing messages in Chat, not for Keep or Album.
- Data to carry the status value of the target service for sharing
- It requires a chatroom ID or type to collect statistics on message traffic. This data is directly related to the target service instead of content being shared.
This data is required in various formats in chatrooms as well as Keep and Timeline. For the first category of service-specific data, we defined structs such as
AlbumInfo, and use
SharableObject data. For the second category of service-specific data, we defined a struct called
InAppShareSourceInfo, which has information irrelevant to the content itself but used by
InAppShareContext. This allowed to keep the minimum required variables for the content in
Under this new design, all LINE services can convert their data into
SharableObject is the only information required for the Share module. This simplifies content sharing as each service needs to simply convert and send the content for the selected target service. In short, the new conversion process starts from data of the source service to
SharableObject and finally to data of the target service as a final destination for sharing. Refer to the Figure 1-2 below on this new structure. It is now much simpler than the previous conversion flow.
2. Integrating logic on sharing data
We integrated the data transfer logic and eliminated redundant code previously implemented for many different service pairs. We also implemented the
InAppShareContext class to use the single logic for content sharing for all services regardless of which service initiated sharing.
|Figure 4-1. Structure of data transfer logic (Before)||Figure 4-2. Structure of data transfer logic (After)|
Previously the data transfer logic was separately implemented and consequently inconsistent between services from the source service to the target service. In addition, the data transfer methods were different, such as sending the required data in
Dictionary between classes or using the iOS Notification Center. Previously the
InAppShareContext class provided a method to simply share the content in a chatroom but did not support sharing with other services.
As shown in Figure 4-1 above, when there are n services as a starting point and m services on the receiving end, code for the sharing feature could theoretically increase up to n x m pairs. As a result, it incurs high maintenance costs to review abundant and redundant code.
We used the existing
InAppShareContext class to provide a standard sharing between any services. We expanded its sharing method to transfer data to all services instead of sharing only in chatrooms. Then, we eliminated the share logic separately implemented by each service.
As introduced under “1. Integrating conventions on data models”, when data for sharing and the status value of the source service are sent, using
InAppShareSourceInfo, content can be shared with any service via
InAppShareContext. Figure 4-2 shows the current structure where single class serves as a bridge between all services.
Lastly, we provide a method to more conveniently write code for sharing in the source service.
SharableObject has to be generated to use
InAppSahreContext for sharing. Given that there are frequently used data types in LINE such as texts, images and locations, we provide the following method to call the Share module by sending ordinary data types. When data is sent to a static class called
InAppSharing, it internally generates
In conclusion, there is no need to explicitly write the code to generate and send
SharableObject for frequently used data. You can simply open the sharing page with a single line of code to call the
We focused on uniform data models and sharing logic, which were prerequisites for implementing the Share module, in this post. We will come back with how we fulfilled various requirements and implemented the structure for easier maintenance and upgrades. See you soon with Part II!