This article is published under @androidvitc, which is a mobile first community and deals with everything android. Follow us for more such articles.
I am writing this article solely to document my experience while developing and publishing my react-native applications.
I hope you get a gist of how to start building mobile apps.
When I started building this app for my technical club (androidclubvit), I soon released that even though a to-do app is very trivial to build, there are still few complexities that are often ignored by people demonstrating how to build a to-do app in 10 minutes.
The functionality is not the only thing that a developer should focus on. Things like edge-case handling, abstraction, and robustness is an important aspect of a developer's journey.
Imagine how Visual Studio Code (VS Code) is able to support so many extensions and use cases without ever changing the way it looks and feels. Everything seems so seamlessly integrated.
These are the various tools and technologies I used and honesty I am impressed.
Today, react-native is the language of choice for many developers for building mobile applications. There are obviously other tools but I found the react-native community to be very active.
Bad code works but in the long run or when there is an increase in user base, the application might break.
Again typescript is not the perfect solution but it makes us less inclined towards using bad coding practices.
Firebase is a very useful platform to easily deploy databases and messaging services on the go. It is maintained by google and it's close to perfect for personal projects.
Checkout react-native-firebase, it has brilliant guides on integrating Firebase with react-native
For the primary release I took into account two types of use cases for the app.
- Private To-Do
- Public Shareable To-Do
My aim was to build these features without handling specific edge cases, making the code reusable. This thought had a massive impact on some the coding practices I used.
A set or list of To-Do added by the user which are private. In the sense of the application these To-Do doesn't need to have a public footprint and will be stored locally.
A set or list of To-Do added by the user which are public. In the sense of the application these To-Do need to have a public footprint and will be stored on a remote database, I have used Firestore as the database provided by Firebase.
This public data will also be shareable i.e other people can also access this data through the app.
Now the problem was how to restrict this access so that only the publisher of the To-Do's can decide accessibility.
I didn't want to complicate the app by building a full proof login system, so I created something less secure and in future I can update it anytime.
This again brings me to the point of decoupling functionalities in the code base. The login system is used as a separate layer which resides before other functionalities and any future updates won't affect the core functionalities of the code base.
I went with a component based structure with a service layer. Let's see by example what I mean by that:
Project | App.tsx | |__ components | | ToDoCard | | ToDoForm | | Loader | | CreateProfile | | ... | |__ routes | | Public | | Personal | |__ screens | | ToDoAdd | | ToDoListView | | LoginProfileScreen | | CreateProfileScreen | | ... | |__ services | asyncStoreService.ts | firestoreService.ts
I'll brief what each directory is meant to achieve
App.tsx : Root component of the tree. Mostly is used to define various paths/endpoints that lead to deeper levels into the app tree. In this scenario it packs the root navigator with two routes, the public and the personal.
components : One of the core aspects about react-native is modularisation, which directly can be handled by breaking bigger structures into smaller components. For example cards, forms, modals and loaders are components that can be used while building a bigger components or screens in mobile development.
routes : It takes care of further breakdown of the App tree into branches, that is, it holds other nested navigators.
screens : Screens are standalone components in react-native that has both a user-interface and related functionality linked to it. For example login screen.
service : This layer acts as an API to add functionality to screens while abstracting the service specific complexities. The app has only two services, a remote service (firebase) and a local service (async-storage).
Components can be thought as basic building blocks that can be used to build full fledged screens.
Let's take a look at how at the Form components
- Add New ToDo : A new entry is to be made
- Update ToDo : An old entry needs to be identified and updated
Idea is to create an interface such that it handles both the use-cases seamlessly.
Pay attention to the interface todoFormProps (extends todoProps), look at the types of each parameter. The questions mark indicates that the field is not required and can be omitted.
If you think about it, can you figure out the condition when we don't need to send the various parameters.
When we need to add a new To-Do, the initial state of the form needs to be empty. While for updating we need to pass the various parameters which act as the initial state of form.
So, you might already have an idea that we can make this component versatile for both the use-cases by simply checking if parameters are passed or not (lines 30-38).
Another key aspect of components is that we don't want to handle use-case effects like submitting a To-Do inside the components, rather allow the parent to dictate how to handle the specific use-case effect. Although the component can maintain state for its internal components (eg. loader) which does not effect the use-case in anyway.
This aspect is handled by the storeToDo and onSubmitRoute props (lines 11-15) in the above component.
Following the same methodologies components like Card can also be abstracted. Below I have added the snippet for Card component.
The service layer handles the various effects that might be exited by different screens or components by providing an API like structure.
Starting a project without a planned structure will often lead to bigger code base and unwanted complexity.
Many effects/functionalities can be abstracted by using modular services which is just a fancy way of saying writing abstract functions or class with methods to suite the use-cases.
For example: I easily abstracted the storing functionality into two services, the remote service and the local service.
Let's take a look
Async Storage Service (local service)
Firebase Service (remote service)
Whenever creating API in general always prefer using self explanatory names/definitions, it helps structure the project more (as I would like to say) poetically.
The component and screens doesn't need to know how to access the remote service or the local service. Instead they use this service API as an interface.
The snippets above are extremely user friendly and easy to understand by just reading the method definition.
Any component can access the services by a basic syntax:
Imagine migrating from Firebase to some other database, just the service methods needs to be changed while other layers remain intact.
I hope this article was able to demonstrate my development journey and provide you a gist of how you can start your own journey.
While for more such articles follow @androidvitc