How to let Rick and Morty work together? Sharing code between similar iOS apps

By Piotr Torczyński & Dawid Markowski

Illustration by Magdalena Tomczyk

We, iOS developers, often struggle with working on white-label applications. These applications in majority are similar to each other, but there are some subtle differences, too. In this article we’ll show you some tips on sharing code between similar iOS apps to make your work well-organized, more effective and bug-free.

The setup

As a base, we will use simple iOS application that will use Rick and Morty API. The purpose of the app is to display some characters from series. We will have 3 applications. One of them will be a generic demo app which purpose is to get some clients. Two other apps will be customized according to the mock clients’ requirements.

Targets

The suggested solution for this kind of issues is using targets wisely. What we suggest is, however, having code divided into targets. That’s why we’ve split the code into:

  • main application that will be developed(like the one from original designs) — RickAndMorty,
  • first customized application(version for client A) — Rick,
  • second customized application(version for client B) — Morty.

On this basis, we will show you some useful tips and tricks. The main advantage of this idea is that you have a clear overview of what specifically should be shared and with whom. Ready steady?

source: https://imgflip.com/i/2adbax

Clean and concise project structure

It may sound obvious, but having a well-organized project structure makes handling code bases clear and painless.

We suggest applying the same structure in each application to easily manage and find certain files. Maybe it isn’t the easiest way to manage the whole codebase but it’s the most transparent and self-explanatory one. It allows us to see immediately where implementation differs between targets.

We can see that APICharactersService has 3 different implementations. Unfortunately, this solution has some cons. One of them is the amount of work to keep it synced. Every time we change the structure of catalogs in any of the targets, we should reflect it in the other two. At Indoorway we decided that clarity is more important than convenience.

Protocols — introducing different implementations for features

To have a good control over your project and targets, you need to provide a good architecture that will make your development easier. In our example app, we decided to use MVVM architecture to keep it rather simple. You should keep in mind that creating a protocol for things that will differ is very important and even essential if you want to have testable code.

Let’s say that we want to have a different layout for displaying data in the collection view.

You could have some user-defined variables to distinguish which one you should use in which app, or even create separate view classes for it. On the other hand, you can seamlessly use protocols to let you create things you need.

Create a protocol that will be used to configure your view. CharactersViewFlowLayout will be the class that has different implementations. Now, to start using it, just initialize your view with appropriate protocol implementation.

Layout for Morty(left) and RickAndMorty(right)

Assets — use your images

Using images is also easy to handle when you create separate assets catalogs for each target and use the same image name for the specific asset.

Color schemes

One of the most common issues in the setup like this presented in this post, are colors. Usually customized applications have different color scheme than the base one. How can we accomplish this? Pretty simple and probably you already suspect how to do this :) Protocol and different implementations across targets are again the miraculous solution. So, first of all, let’s make a protocol:

Now we’re ready to make different implementations per target, e.g. for RickAndMorty target:

It makes implementing our UI easy. It’s just as simple as calling:

We don’t have to implement a label in a different way — the correct implementation of Colorable will be chosen depending on the target we are building. Moreover, if we don’t implement each color property — target will not compile.

Testing

Testing this setup could be tricky. There are many possibilities of solving this problem. We chose one which depends mostly on the shared target. As you can see above we created 3 separate targets for tests. The idea is the same as with app targets. RickAndMortyTests is the one which is the most important. We test most of the code there because it is shared between 3 targets. In the other two targets, we just focus on the implementation differences. Let’s see an example. First of all, take a look at our view model:

It belongs to all three app targets, because implementation should be the same everywhere. That’s the reason why we want to test it just once in RickAndMortyTests target. There you have simple specs:

There is no need to repeat those tests in two other targets, because the logic behind them is the same. But what if there are some differences in logic? We have another example :) We assumed that the app built from Rick’s target shouldn’t show characters containing “Rick” in name and app from Morty’s target shouldn’t show “Morty” characters. This is our service, which is downloading characters:

Every target has its own implementation of this service, because we want to filter different names. The implementation of this protocol isn’t that important at this point, so let’s go straight to the tests! There is a shared part of this service, so we decided that it would be nice to test this part only once in RickAndMortyTests:

We know that every implementation sends a request for characters and every implementation should emit only “completed” event when there is no data in response, so there is no point to duplicate those tests in every target. Now we can go to specific parts of the service. Morty app had to filter Morties so the tests in this target look like this:

Similarly, Rick app had to filter Ricks, so the tests look like this:

This approach lets us reduce code repetition in tests. On our CI we just run RickAndMortyTests + those belongings to the target which is being built.

Summary

We can see that dealing with applications that share the code isn’t easy, but with some effort at the beginning you can make it work and be under control.

What are your thoughts? Do you have some better solutions? Feel free to share with us your comment!

All sources are available under repository: https://github.com/Myrenkar/sharingCode

--

--