How PugDemy changed the way we approach mobile development (Part 2 — The Refactor)

Adrian Rios
Docplanner Tech
Published in
6 min readDec 2, 2020

Welcome to our second part of the three-part series on how we transformed our widely used mobile application to a modern React native stack and helped spread the understanding of the application with the other engineers in our company. In this part, we are going to talk about the refactor and the steps we took to have a great application.

As we mentioned in the latest article, our Doctors App started being an MVP and it grew very fast.

Because of that, there was a moment where the tech debt of our application made it impossible or very difficult to do more iterations in our app, to add more features, in the end, to continue helping our doctors with our application.

To revert this situation, our first step was to detect improvement points. We had technical debt, ok. But the question was:

Could we split this tech debt? Could we really identify each point to improve? Could we split a big problem into small ones?

After an exhaustive analysis, there were the improvements points we detected:

  • THE USE OF REACT
  • THE GLOBAL STATE MANAGEMENT
  • THE LACK OF TEST
  • CI & RELEASE PROCESS

Down below, we are going to describe problems of each block, the solution we applied and the reason behind this solution.

THE USE OF REACT

React has changed and improved a lot from the first version, and in different aspects (components types, life cycle methods or ways to share logic between components). And the way we use React has evolved too.

Our code was based basically on big Class Based components in every screen. We identified the following problems from this approach:

Understand and follow the business logic was not easy. We had huge components with their logic distributed in functions inside the class. As a result, we had complex logic, big React states and readability problems.

Test the components was also a big challenge. The fact that the whole business logic was inside the component and generating side effects, made it very difficult to create any kind of unit test.

Poor component reusability. Direct consequence of having huge components.

Unnecessary renders. Having huge components, usually means having huge states. In a big component any small change in the state generates a render in all the three. With having small components instead, we can have better render management, and a performance improvement in the end.

Share logic between components. We were using non-recomended ways to reuse component logic like inheritance in some places, and using HOCs in another where there were better options.

Inline layouting

After analysing the improvement points described above, there were our action points and the benefits / improvements we got after we applied them:

  • Update React & React Native versions. The first step was to update the framework to the latest version to be able to use all the newest features.
  • Split the big components into smaller ones. With this action we got better rendering management, more reusability code and components way easy to read, understand and test.
  • Use hooks and custom hooks. With the use of hooks, we could extract some business logic that we had in different components and share them. Also with having an isolated piece with that logic, add testing is way more feasible.
  • Styled Components. We used styled components to get rid of inline components, with that we had even smaller and more beautiful components.

Nice! One step closer!

THE GLOBAL STATE MANAGEMENT

The second issue we had to address is the global state management. In Part 1 of this series, we already talked about the approach we were using (Redux & Saga) and the problems we were facing.

These were the alternatives we considered to improve this point:

Refactoring the current solution with Redux + Saga. We know that there are a lot of successful applications that were using this approach, so one of the options was refactoring the current solution focusing on:

  • Reduce the side effects, make more isolated sagas.
  • Rewrite the logic into smaller sagas giving them meaningful names.
  • Add unit testing to all these sagas.

Use React Context API. From the Context API release, React provides a native solution to share a global state.

Libraries like Redux or Mobx can be eventually replaced by this approach, especially in small apps with small quantities of data.

Use Apollo. Apollo is not only a framework to manage communications based on GraphQL, Apollo is a tool where you can also:

  • Work with Rest API.
  • Use a lot of utilities like cache policing, polling or refresh.
  • Use the Apollo cache to save data across the whole application (same as what we can do with Redux / Context API), and we can persist this information.

After checking all the options and, thinking of a future mobile gateway in Graph QL (maybe a topic for another article), we decided to use Apollo.

THE LACK OF TEST

Cool! So after the latest blocks, we have a nice, modern and scalable application.

However, considering that we were making a React Native application, we are releasing for both iOS / Android and consequently to hundreds of devices, add tests were a must:

Unit & Integration Test

After the refactor described above, it was way easier to add unit tests to our project (currently we have more than 700!). In order to add nice unit & integration tests we used the following libraries:

  • Jest: As the general test suit framework.
  • React Testing Library: To test React related things and interact with the components.

End to End Test

We are making a mobile application in React Native, and these are some of the facts related:

  • We are delivering our App to two different operating systems (iOS & Android).
  • We are delivering the App to hundreds of devices.
  • Releases on mobile take their time (create builds, send to stores, reviews).
  • The code deployed is a fixed code that can be there until a user updates the app.

Taking that into consideration, it was super necessary to set up an E2E test environment where we could be able to test the app, mimic the user behaviour and use different devices.

We chose Appium & BrowserStack for this purpose, check this article to go deeper in that topic.

CI & RELEASE PROCESS

The latest point of improvement was the CI and the release process. Related to that topic, we added some improvements at different levels.

Development level. We added Github Actions that run the tests and pass the linter as a necessary step to merge any PR. We also blocked master & develop branches and forced to have at least one approval to merge it.

Release level. We automated the release process using Azure Pipes and creating different pipes:

  • QA builds: To test features that are not available yet on production, and with the possibility to point to local environments to test.
  • Production Build: The ones that are sent to the stores.

Final thoughts

After our journey described in this article we finally had a reliable application with:

  • Modern React with small & isolated pieces (functional Components & Hooks).
  • Nice layouting tool (Styled Components).
  • Apollo, a powerful tool for share data, manage communications, and a door open for GraphQL.
  • Unit & Testing.
  • A release process.

So we deserved an awesome cake!

In the next article, we will discuss how we spread this knowledge with the goal to introduce more FE developers into our mobile app.

Continue with the series

Part 1 — The Motivation

Part 2 — The Refactor

Part 3 — The Communication

If you enjoyed this post, please hit the clap button below :) You can also follow us on Facebook, Twitter, and LinkedIn.

--

--

Adrian Rios
Docplanner Tech

Frontend Developer. Currently focus on React & React Native