Expo + React Native Navigation? Yes!
Building lite handy Reddit client for iOS and Android using Expo and React Native Navigation.
Hi guys!
I am going to walk you through the whole process of building a mobile app using React Native. It is going to be a simple one but I might be adding more features with time. But the most exciting thing about this app is that it is using Expo SDK with React Native Navigation that gives you a powerful set of tools and truly native navigation. Instead of using the default solution (react-navigation) in Expo (which is great, honestly), I prefer RNN as I love the native feel (iOS & Android native). I have been using React Native Navigation for years (in total) building apps with React Native. I also contribute to RNN community by providing the most up-to-date boilerplate/starter for the time (old). I have recently released a new version of a boilerplate/starter expo-rnn-starter which includes pre-configured up-to-date versions of Expo SDK, React Native Navigation, Reanimated 2, MobX, Dark Mode support, Expo Updates. It has been a not so fast process due to some tricks with Expo SDK and React Native Navigation integration but this process was also pretty exciting: I couldn't find any related tutorial or information on how to do it. And yes, this starter is ready for production and was used to bootstrap some of my apps which are published in the App Store and Google Play. That’s why, in order to start up very fast with the Rabbit app, we will use expo-rnn-starter.
If you would like to try out the app, here are links for App Store and Google Play (direct .apk file link).
If you would like to dig into the code, here is a link to the Github repo. Also, it’s worth saying that it is not going to be a copy-paste tutorial but something like a learn-by-doing process and approach from an indie developer.
Why?
There are two reasons:
- I use Reddit quite often and mostly for reading r/reactnative thread. That’s why I have decided to create a lite handy app because the official app and other clients are too overwhelming for me, I love simplicity.
- A year ago I have been working on a side project, trying out Flutter, and getting some new knowledge in Golang. So the app was kind of a news app but all content was parsed from actual websites. I wanted to try something new in Golang and did a good job for my self-progress by learning how to crawl sites and publishing a server on DigitalOcean behind Traefik proxy. And as a framework for creating mobile apps, I have decided to pick Flutter. So here is the Github repo but I have never published it or continued working on other parts of that tutorial because Flutter performance in release mode on iOS was pretty bad, not native feel at all. Maybe things have changed since then, I have never tried it again. And yes, this is the reason I have built the Rabbit app and would like to share the process with the community.
Let’s start
The first thing we need to do is to bootstrap our new app from expo-rnn-starter:
Open the terminal and follow next steps:~ cd <to-your-desired-folder>
~ git clone https://github.com/kanzitelli/expo-rnn-starter.git rabbitapp && cd rabbitapp
~ rm -rf .git # to remove starter's git history
~ yarn && yarn ios:pods # it might take some time
After the process is done, let’s rename our app because information such as bundle identifiers, etc. is from the starter. For this purpose, I recommend using react-native-rename-next, which is the most up-to-date version I could find. Now please open its Github repository and follow the installation steps. After you are done, you can rename the app (any name and bundle identifier):
~ react-native-rename-next "rabbitapp" -b io.batyr.rabbitapp
~ yarn ios:pods
As mentioned on the main page of the library, we need to change the bundle identifier for iOS in XCode in the General section.
Now you should be able to successfully launch an app:
~ yarn ios # or
~ yarn android # or yarn android:release - for release build
Now we start coding! Open the app’s folder in your favorite text editor (for example, VSCode which is really good!) and get ready for some fun time.
App functionality & structure
Ooops…, I forgot to talk about app functionality and structure.
This is going to be a very alpha version with minimum functionality. Below you can find a list of main functionalities in the big picture as you would write them down briefly on a napkin:
- A screen with a list of subreddits. Add/Delete functionality.
- A screen with a list of posts of a subreddit.
- A screen with a post, open in a webview, for example. Save/Remove functionality.
- A screen with saved posts. Remove functionality (same as 3rd).
- Settings. Just simple actions as app rate, mail composer to write to support, etc.
- So it seems like a tab-based app would be a perfect fit for this structure with the following tabs: Subreddits, Saved, Settings.
- Very minimalistic, fast, and easy to use.
The main question here is how we are going to get the data. Basically, I use this pattern to get data from Reddit https://www.reddit.com/r/${subreddit}.json
, for example, https://www.reddit.com/r/reactnative.json
, which gives a .json
object with all needed data (first 25 posts). Each post and response from Reddit has a specific structure that I have already identified and we will add all the types to /utils/types.d.ts
once we start coding. As you are here, probably the app has already been launched and you can see what you get from the starter. So now it’s the right time to start coding!
Coding
Setting up navigation
So as was mentioned before, we are going to have a tab-based app with 3 tabs. Let’s create 3 screens which will be presented on each tab. In order to make it more convenient and faster for you, you could make 3 copies of example-screen.tsx
file in screens
folder and rename them to SubredditsScreen.tsx
, SavedScreen.tsx
and SettingsScreen.tsx
, also change the names of components for each screen, like:
Now we need to define titles and unique names (for registering screens in navigation) for each screen and for that, we use Contants
object which is initialized in src/utils/contants.ts
file. Let’s open it and make the following changes (you can put any string prefix
variable as only screen names have to be unique). We are also going to change bottom tabs’ screens’ titles to actual values and add some more colors.
In order to see our app without errors, we have to make some changes to src/App.ts
file where we register screens and do some pre-launch stuff like hydrating stores and initializing services.
The last step before going to the next part is to change the default options of navigation as I would love titles to be the same color as tabs. So it is done in src/services/navigation.ts
and we are going to add more properties to setDefaultOptions({ ... })
as well as delete some unnecessary methods.
We can remove src/screens/ExpoScreen.tsx
and scr/screens/CounterScreen.tsx
as they are from the starter and we don’t need them anymore.
Subreddits Screen
As we are done with setting up our navigation, we can now start with our first screen SubredditsScreen.tsx
. On this screen, we are going to have a list of subreddits with a delete option. It should also have an “Add” button which can be added as a right button of a navigation bar. In order to be a bit more user-oriented, we shall add some placeholder for the case when there are no subreddits. Next, we are going to add a new MobX store to manage a state for subreddits as it will be expanded while adding new functionality.
Now we remove src/stores/counterStore.ts
which is from the starter and add a new file subredditsStore.ts
. We are going to add one array and two actions (add & delete) to this store for now.
In order to have access to this store within our screens, we need to register it in src/stores/index.tsx
file.
Now we can start building our screen. In order to skip some routine by building simple components such as an empty list placeholder or subreddit tile, open the components folder on Github and copy 3 files to your local folder: AppleStyleSwipeableRow.js, EmptyListComponent.tsx, and Subreddit.tsx. As the subreddit component has to have a delete action, I have decided to use something that exists and provides this functionality: Swipeable component from react-native-gesture-handler
meets all the requirements and is presented in AppleStyleSwipeableRow.js
file with tiny modifications from my side (some extra props).
Once you are done, open SubredditsScreen.tsx
and add the code for displaying a list of subreddits and some actions.
We have some useful hooks (which are available from the starter) such as useStores()
that gives access to all stores you define; useServices()
that opens an easy way to get access to services like navigation; useStyles(_styles)
that returns an object with styles (and theme) depending on the current mode (dark
or light
) and it could be used to define your own themes as well. Also one more good thing about theuseStyles()
hook is that it does normalization under the hood (it could be configured not to) meaning all numbers in _styles
object are going to be normalized to make the app responsive through all sizes of screens (iPhone, iPad). I have really taken advantage of normalization when I have been working on this app (you can check screenshots for iPhone and iPad). So no more pain while doing styles. You can also see that styles now have theme
object which has your predefined colors, sizes, etc.
Now we need to add text for the empty list component to constants as well as options for theAdd
button which will be implemented soon.
You should see the app running but we don’t have any data. So it’s the best time to work on the Add
button that is going to open modal with text input. Once we add the subreddit, it will be added to the list and a modal will be closed. What we need to do now is to create a new screen with text input, register it, add a method to the navigation service, and theAdd
button itself.
In order to make the process faster, follow this link, download (copy content) TextInputPrompt.tsx
file and add it to thescreens
folder. As we are going to use some custom button component in this screen, also download (copy content) this file and replace it with existing src/components/Button.tsx
.
Once you are done, you can add the values to constants and register it.
And in order to present this screen, we need to add a button and we are going to use a hook for react-native-navigation
that simplifies work by listening to navigation’s buttons’ presses.
We are almost done with the Subreddits screen, you can play around with adding and removing subreddits. And the only missing thing here is a push to a screen with posts of selected subreddit, its value will be passed to PostsScreen.tsx
. This is what we are going to work on in the following part. Meanwhile, I think it would be a good practice to repeat the same process for PostsScreen.tsx
as it was done for our first screens while setting up navigation and register that screen in App.ts
.
Posts Screen
Assuming that you have added PostsScreen.tsx
, we need to push the screen with properties (subreddit name) when a subreddit is pressed. First, we are going to define a new type in utils/types.d.ts
which describes properties that will be sent when a posts screen is pushed.
We can test it now! But… we forgot to somehow display the selected subreddit, let’s say, we will add it as a title for the navigation bar. However, we will use it to fetch some data later.
You can see a new method generateSubredditString(...)
. It is used to have one unified string format for displaying subreddit name with r/
, for example, r/reactnative
. Add this method to utils/helpMethods.ts
.
When you push a posts screen, you can see that our title depends on the selected subreddit. The next step is to fetch posts of a subreddit and display them in a list. The first thing we need to start with is to create api
service that will contain only one method. But to simplify our lives, let’s copy types.d.ts
and classes.ts
files from here to our local utils
folder, so we don’t spend a lot of time on it. Once you are done, create api.ts
under services
folder and check the code below.
getSubredditPosts(...)
function returns a promise with RedditJsonResponse
result. You can check the type in types.d.ts
and see that it includes others like a chain. The last one is RedditPost
where we have the data we need to properly display a post and do all necessary actions. Also, don’t forget to include api
service in services/index.tsx
.
As you may have guessed we will need some extra variables in subredditsStore.ts
. We have an array of strings with all subreddits, and we can have a dictionary (map) to store corresponding posts of a subreddit (subreddit will be a key), for example, {“react”: {“posts”: [Array]}}
. I wanted to have something like {“react”: [Array of posts]}
but mobx-persist
couldn’t handle this case, and I have decided to make it with an extra property posts
in an object. And in order to use mobx-persist
properly with your own types, we need to create classes (not types) which you can check in utils/classes.ts
, they are just copies of types that we have but with extra persist
decorator.
We are going to call getPostsForSubreddit(...)
when posts screen is presented and when we add a new subreddit. I think this is kind of a good practice to pre-fetch (if it doesn’t harm your app’s performance) data to make user’s experience smoother (users don’t like waiting).
We can check if it actually loads posts and persists all data. That is really good progress. Our next task is to display posts’ tiles in a FlatList. I made it easier and prepared a component that will be used for post’s tile. Please follow this link, download (copy content) Post.tsx
and save it under components
folder. As you can see, there are some unknown methods from utils/helpMethods.ts
which we are going to copy as well. You can check it here or just download that file and replace it with the existing one. Then you also need to install dayjs
library (yarn add dayjs
)as we use it for counting a time ago for each post.
I hope everything looks pretty intuitive and easy, and you can see posts in a list. We are pretty much done with this screen and now can move on to creating a screen for displaying post content. I have chosen a simple path to just use a webview for displaying it. Before proceeding to the next part, we will need to create PostScreen.tsx, register it as we have done before, and install react-native-webview (yarn add react-native-webview && yarn ios:pods
).
Post Screen
In this part, we are going to build a screen for displaying a post in webview and it would be cool to have two more features like share and open in a browser. Let’s start with creating a new method in navigation service, pushing a post screen from posts, and setting title depending on passed props.
After finishing those steps, you should be able to push a new post screen with title and subtitle. Now we add webview and display the actual post’s webpage:
Here I am using useLocalObservalble
from mobx-react
for creating a local store with state of the loading indicator. We will show it on the toolbar below webview which will contain two more buttons Share
and Open in browser
. For these purposes, we will use Share
and Linking
modules from react-native
.
And there is one more thing left for this screen which is saving to subreddits store (saved
array). We are going to show all saved posts in the second tab in the next part. The possibility to save a post means that we should also be able to remove it and check if it is in the saved array. Which is why we will need to make changes to subreddits store by adding saved
array and corresponding methods, to post screen by adding a button to the navigation bar and to constants.
If all steps are followed correctly you should be able to save/remove a post to saved
array. However, now we are not able to show it inside the app. This is what we are going to do in the next part.
Saved Screen
We are ready to start working on a screen with saved posts. We already have a component for it and all the logic of the subreddits store from the previous part. They must be displayed as on PostsScreen.tsx
with only one difference, swipe-to-remove action for each post. And in order to display a saved post, we will reuse a screen from the previous part. However, saved posts mean that it should be cached and available offline but I have a thought that it would be a great future improvement.
Firstly, you need to add the code below to utils/constants.ts
And as our logic for saved posts screen is almost the same with posts screen (with one additional function), we can copy the content of this file from here and enjoy it!
Settings Screen
We are almost done with our application! The only thing left is a screen with settings. It is nothing special, just a few sections with some useful actions. I already use this simple template in one of my apps and it seems to be working well. And of course, it can be modified in accordance with your needs.
Let’s talk about what we are going to have on the settings screen. There are going to be 3 sections: General (with share, rate, and support actions); Links (with useful links related to the app, e.g. website, privacy policy or others); About (with the current version of the app). We will need to install some packages: one is for app rate action which includes native in-app rating for both platforms, another one is a mail composer from Expo — yarn add react-native-rate expo-mail-composer && yarn ios:pods
. Rebuild the app as those packages have native dependencies.
As we are going to have 3 sections with the same styles and behavior, it makes sense to create a small component for these purposes. If you follow this link, you will find Section.tsx
file, please copy the content of it to your local project under components
folder.
Before we start filling up sections for the settings screen, we need to have some preconfigured options for the app rating, and mail composing actions which will be added to constants, as well as app links, etc.
We are ready to start building our sections. If you are tired and would like to finish with the settings screen asap, you can just copy the content of this file and paste to SettingsScreen.tsx
as everything there is very intuitive.
Note that mail composing doesn’t work on iOS simulators. Other things like a rate action are done easily in one line. So now we just need to add other sections and corresponding methods.
We are done! There are more things I would like to add to this app such as the offline reading of saved articles, push notifications, etc. It should be kind of a playground with the implementation of things that could be helpful when your app goes into production.
End
As I have published this app to both App Store and Google Play, I thought it would be cool to make a dedicated article to a publishing process; which tools I have used; how to manage expo-updates in production, and other things. And of course, it would make sense to do only if you will find this tutorial useful.
I hope you have learned something new while building the app. If so, I am very happy about that!
Please, feel free to ask me any questions!