Have you ever struggled to keep a consistent look and feel across a suite of apps on several platforms? Have you yearned to fix a bug just once instead of once per app? Exodus have. They wanted to see if "write once and use everywhere" was possible for their development teams. They had a plan, but lacked the resources to execute it. This is where they called ST6 for help.
Exodus Bitcoin & Crypto Wallet allows you to manage and exchange 260+ cryptos and NFTs. With its simplicity, this wallet is great for beginners just getting into the crypto space. You can buy directly with USD, EUR, and GBP by using your credit/debit card, bank account or Apple Pay within the suite of apps:
Designed and developed by different teams, they have similar components with similar look and feel.
The cost of change in a single component is proportional to the number of apps that use it.
Again, the cost of fixing a bug is proportional to the number of apps where it appears. So what can we do about it?
You probably guessed it - we're talking about a cross-platform component library here.
We needed to validate whether the "write once, use everywhere" approach could:
- Significantly reduce the cost of change per UI component.
- Attain a consistent UI across the suite of apps.
- Provide identical API, regardless of the platform.
- Accelerate development by empowering Exodus teams to focus on building great apps by leaving aside the low-level details brought by an input, a checkbox or a button.
How it started
Before the two ST6 software craftsmen joined as a team extension, Exodus had already done a great job laying down the foundations in a highly professional and efficient manner. They had done their research, picked the technologies, and created a project with a few PoC components to demonstrate how they expected the development to happen, given their specific needs. The whole concept was well documented in a monorepo where you could find React Native for Web, TypeScript and a Storybook web app with three demo components.
We needed to adapt to the Exodus way of working by following their processes and practices for developing great apps. During this Sprint 0 we wanted to set up the project locally and start developing components.
Having a plan is a must, but updating it when needed is critical. A few days in, we acknowledged there was still some groundwork we needed to do before we started with the components.
Building a reliable, user-friendly cross-platform component library with identical APIs, regardless of whether it runs on the web, iOS or Android, requires two more pillars:
- A mobile playground app uploaded to appetize.io where developers can explore the library components on real devices.
- A test runner for each platform. We wanted to “write once and use everywhere,” but we certainly didn't want to test once. The source code was the same, but the output was not. We wanted to be sure that the platforms got only what it needed and understood.
Even though we hadn't planned for these milestones at the beginning, they definitely felt like ones when we delivered on the above! We created a Storybook native mobile app that used the already existing story declarations of the Storybook web app. This was a big win as we wouldn't have to keep in sync two demo files representing the same component. It also boosted our confidence in the end product as it revealed some bugs that the tests alone and the Storybook web app couldn't.
How this Sprint 0 unfolded turned out to be significant when Exodus priorities changed and the tech lead, who started all this, was called in on a higher-priority project. He felt comfortable leaving the project in our hands for this short period. We became an autonomous team responsible for the task-management board, the refinement meetings and basically any known and unknown obstacles we could encounter along the way.
Next: Let's fire some "tracer bullets"
Now that we had laid all the foundations, we were after the next milestone - closing the loop.
We wanted to validate that the initial plan was solid, so we could save time and resources if we missed something significant. It was the perfect time to fire some "tracer bullets”. We developed just a couple of production-ready components, released them and let other developers consume them in their apps. We started with the browser extension.
This end-to-end test was the validation we needed to keep on going. Successfully closing the loop meant we could now go on full power developing more and more components for other apps as well.
Going full power
We started developing components for the rest of the apps with priority on the mobile app. In three months, we delivered nearly 30 components: inputs, checkboxes, buttons, segmented controls, sliders, switchers, step sliders, some more complex domain-specific ones, badges, icons and so on.
As you might expect, "write once, use everywhere" didn't go that smoothly. There were bumps along the way, for sure. Some of them were related to the platform-specific building blocks that couldn't be used interchangeably, like
text gradients. However, we managed to hide these complications from the end users and, in the end, we delivered the identical APIs we longed for.
- Identical way to consume ready-to-use components.
- An even smaller gap between using React or React Native, making it easy for developers to switch teams without a significant productivity loss.
- Future-proof UI consistency with a single source of truth.
- UI components living in a single place. This is critical for achieving consistency across the suite of apps. Even when we have branched implementations, they still live next to each other, which is a premise that they will stick together regarding appearance and functionality.
- Reduced cost per change that pays even more with every other consumer app. Changing a button appearance across the apps requires a single package update.
Developing such a cross-platform solution requires more effort than the sum of developing them separately for a single web app and a single mobile app. Even though this might look unfavorable and close to breaking even, it's not, because we would still reap the benefits mentioned above. And it only gets better as more and more apps start consuming these components.
Playing the long-term game
The ultimate goal is to speed up app development, reduce bugs, and achieve UI consistency. This won't happen overnight, as it takes time to adopt within existing apps, but it is still the best investment you can make in terms of total development cost and efficiency.
Positive side effects
When different teams design the UI of different apps, they inherently end up with some slight differences that add up over time. One good thing that inevitably happens with the "write once, use everywhere" approach is that it forces your teams to watch out for those UI inconsistencies. You want to make that one component behaves and looks the same in all the apps. This forces UI teams to work closer together and even out any deviations that happen along the way.
Working with ST6 was a great experience. Not only did they adapt quickly to what was already built, but they also went ahead and suggested enhancements to the code and CI pipeline. Their proactivity and excellent work environment made our partnership really fruitful. They were able to meet the objectives, shipping quality code while improving DX. We definitely recommend working with them.
What about your cross-platform plans?
Do you need a hand executing them?
Reach out to us. We'd love to hear more!