This file contains some tips and guidelines on building our front-end React Native app. Please add to it!
- App development
assets
directory which is used by Expo for things like the app icon and splash screen.
Most files live within the src
directory.
Below is a brief rundown of the different directories inside /src
and what they're for.
/Assets
-- fonts, images and other static files that are core to the app/Components
-- custom components that make up different parts of an app screen, e.g. a date search that might belong inside an event search container/Config
-- overall app configuration settings, e.g. the URL of the API/Containers
-- screens content and logic/Hooks
-- React custom hooks/NativeBase
-- assets, components and containers for the new app designs, using the NativeBase component library (see more below)/Navigators
-- defines the bottom tabs, which screens the user can navigate to within the app and which container to use for which screen/Services
-- external services like our API, which we get projects and events data from/Store
-- the Redux store we use for more complex data sharing between containers/components/Theme
-- existing (old) theme for controlling overall colours, fonts, spacing, etc - we are switching to handling this using NativeBase/Translations
-- app text in different languages, currently we largely don't use this/Types
-- some extra Typescript setup to enable different data types/Utils
-- useful reusable functions to work with e.g. lists, searches, event dates and times
The app designs are produced in Figma. You can inspect different elements within a design by clicking on them (you might have to double-click to get to an element inside a group) -- and then on the right-hand side of the screen you can inspect different properties e.g. the exact size in pixels, the spacing around it, colours, etc.
Our app is built using React Native working with the Expo CLI (command-line tool). and Expo Go.
It's easy to be confused about React Native and Expo, partly because they have changed over time as has the relationship between them (it's easy to find articles which are out of date) and because there are different ways to use them. This article has quite a good background.
We are using the Expo CLI and Expo Go because they make development and deployment of the app a lot easier. The main trade-off is that we cannot use every npm package that works with React Native.
There are many React Native packages we can use, but when it involves interacting with the device hardware (e.g. camera, GPS, etc) or the OS (e.g. the clipboard, file storage or sharing) or occasionally other areas (e.g. SVGs) then we are usually limited to packages that are supported as part of the Expo SDK library (but for the kind of app we're building, this should be ok).
You can see the full list of Expo SDK libraries here -- browse the menu to see all the different packages.
To install new npm packages for the front-end app, always use npx expo install name-of-the-package-goes-here
instead of npm install name-of-the-package-goes-here
-- this ensures the Expo CLI will install it properly if it's part of the Expo SDK library / needs some specific Expo setup that needs to be included with the installation of your package (if not, it'll simply install it using npm
).
For dev packages (for use in the development environment only), use npm install name-of-the-package-goes-here --save-dev
You can use an .env
file to store environment variables (often sensitive details like API keys, etc).
Be careful about using sensitive details like passwords and API keys in the front-end app as it's possible for someone to break into an app on their phone and get this data. Consider whether this is the best approach and discuss with others in the team if you're not sure.
There are a few steps to add and use an environment variable using Expo:
- Add your variable to the
.env
file, e.g.AMAZING_API_KEY="abc123def456ghi789"
- Edit the
app.config.ts
file and add a new entry inside theextra
object, e.g.amazingApiKey: process.env.AMAZING_API_KEY,
- In the file in your code where you want to use this variable, add an import statement at the top of the file
import Constants from 'expo-constants'
- In the same file, at the place in the code where you want to use the variable, you can access your variable with e.g.
Constants.expoConfig?.extra?.amazingApiKey
(note: Typescript considers that the value of this could beundefined
, so you may need to handle that possibility)
The app.config.ts
file contains some configuration settings for the Expo CLI. In some Expo documentation you might see references to updating a config.json
file -- this is the same thing, we just store it in app.config.ts
because it's more flexible (e.g. we can insert environment variables into it when the app is built and deployed).
- If the app on your phone isn't showing the latest changes in your code first you could try reloading it -- while viewing the app in Expo Go, shake your phone and it should show you some options include 'Reload'
- Sometimes the Expo dev server loses connection with your phone in which case you can try restarting Expo in your terminal -- press Ctrl+C to stop it, then run
npm start
again - If you're still having problems not seeing changes you have made on your phone instead of
npm start
trynpm run start-clear-cache
(this does the same thing but also clears the Expo cache, so it'll be slower to load, but should force any changes to come through)
We are moving to the NativeBase component library as part of implementing new app designs. Find the official docs here.
Changing a container/component to NativeBase involves (amongst other things):
- Using a NativeBase approach (see e.g. the Core Concepts, Features and Theme sections of the official docs)
- Using the new StaTheme set up for NativeBase
- Removing uses of the old theme
- Replacing any uses of
styled
from'styled-components/native'
with NativeBase components and theming - Using NativeBase readymade components wherever possible
Please raise on the Slack channel any questions about how best we can use NativeBase, and how to keep coding approaches / ways of working consistent -- and add to this file updates that might help other people. This is especially important while we're in the early stages of figuring it all out.
Currently, while we are in the process of transitioning different parts of the app to NativeBase and the new app designs, please put new assets (e.g. images), components and containers inside the src/NativeBase
directory (e.g. src/NativeBase/Components
).
Parts of the app like Config
, Hooks
, Services
, Store
don't need to move. There may be some other things where we need to make a judgement call.
If it makes sense to do so, you can a name new file the same as the old one it replaces (e.g. src/Components/MyThing.tsx
can be src/NativeBase/Components/MyThing.tsx
).
If an old non-NativeBase file is no longer needed in the app, please delete it as part of the pull request you're working on, so we don't have old files hanging around that aren't used any more.
NativeBase has lots of handy out-of-the-box defaults set on the theme.
When we need to override this, we can do that in our theme file src/NativeBase/Theme/StaTheme.tsx
See here for the docs on theme customising. To see the full rundown of all the different things you can set, check out the NativeBase extendTheme
file referenced in our StaTheme file (Ctrl-click or Command-click on extendTheme
in the import
statement at the top of the file to open it).
If you need to set something like colours, spacing, sizing, etc on a component or container you're working on, always think first "could this be set as a theme default, rather than just setting it specifically on my component"? Think ahead to whether this would help others in the team (and you!) in the future and help keep the app as consistent as possible, using as little code as needed in each individual component file.
We allow the user to set their dark mode preference in the SettingsContainer
. Code there and in Navigators/Application
and ColourModeManager
handle dark/light mode (what React Native and NativeBase call colour mode).
When you're building (or changing) a component or container, or changing a theme setting, please always check it works in dark mode as well as light mode.
NativeBase does some handling of dark mode straight out of the box, so you may not need to change anything.
If you're switching a container to use NativeBase check out SettingsContainer
and VerticalStackContainer
examples as they're already working reasonably well with dark mode. One of the things you'll need to do in your container is switch from using the old theme and switch from using any styled
components/views.
If you need to set colours based on dark/light mode see the docs here and wherever possible set _light
and _dark
properties in the StaTheme
file (approach 1. in the docs) rather than setting them on your individual component -- i.e. try to make settings as universal and as easily reusable as possible.
In case you need it, you can also use useColorMode
or useColorModeValue
to detect dark/light mode -- see docs here and an example in src/NativeBase/Components/Brand
. But often you can do it using _light
and _dark
properties as described above.
To find which colours to use for dark mode in Figma see Design System in the list of Pages on the left-hand side of the screen. There are examples of some components using dark mode.
Most containers are specific to a particular screen, but a few are reusable across multiple screens.
Displays an external web page inside our app, filling the available height
These are custom components we've developed, in addition to NativeBase readymade components:
Displays the wide version of the STA logo
A Single STA Matchstick that can be reused in any component with the appropriate colour
A reusable checkbox component that can be configured for multiple different values.
A dynamic custom progress bar for reuse in the Profile containers (or elsewhere). The choice of progress bar colour can be modified.
Tappable list of options to choose from, with arrows
Text input for searching
A modal used to show the user a message. Can optionally include buttons to get the user's response/choice.
Shown at the top of some screens - a screen title and (optionally) a back button
Useful for choosing between 2-3 choices, text must be kept very short (probably one word) for each
Shown at the top of some screens - a small STA logo and (optionally) a search icon button
Default text input, label, required indicator and validation/error message.
Icons we use in the app are normally from the Material Icons library, implemented using the react-native-vector-icons package.
To add an icon into your component:
-
Add imports at the top of your file from NativeBase and the icons library:
import { Icon } from 'native-base'
(or add this to your existingnative-base
import statement)import MaterialIcons from 'react-native-vector-icons/MaterialIcons'
-
Insert the
<Icon/>
component:<Icon as={MaterialIcons} name="xxxxxx" />
- Replace
xxxxxx
with the name of the icon you want to use:- To find the name of the icon you're looking for, inspect the icon in Figma, in the Design panel on the right-hand side of the screen, go down to the Export section, and you should see the name beginning with
material-symbols:
- If you're not sure exactly how the icon is named, go to the MaterialIcons font library and you can browse/search. Take the name as it appears there and convert it to kebab case -- e.g. for
Info
useinfo
, forCheck Circle
usecheck-circle
- To find the name of the icon you're looking for, inspect the icon in Figma, in the Design panel on the right-hand side of the screen, go down to the Export section, and you should see the name beginning with
- See the Icon docs for other properties you can set
- See also the IconButton component
For any images that are not photos and not icons -- for example, logos or any other vector designs -- it's better to use SVG format wherever possible. SVGs are smaller in file size and can scale to any width and height on-screen.
PNG, JPEG, GIF, etc images are better suited to photos. For icons use the NativeBase <Icon />
component (see above).
We have the react-native-svg package set up so you can use SVG images easily:
- Copy the .svg file into the
src/NativeBase/Assets/Images
directory (or a sub-directory) - Import it at the top of your component file, e.g.
import StaLogoWide from '@/NativeBase/Assets/Images/Logos/sta-logo-wide.svg'
- Use it like you would a normal component, e.g.
<StaLogoWide />
- See
interface SvgProps
in this file for common props you can use, and here for touch events
We use Bugsnag to log errors and crashes in the front-end app when it's running on people's phones -- (otherwise we might not know if the app's going wrong when people are using it). To log errors to Bugsnag in the app in production, always use the logError
function in src/Service/modules/logging/index.ts
instead of console.error
(it calls console.error
itself anyway as part of what it does).
(While you're developing and testing out your code, you can still use console.log
and console.error
to see errors in your terminal.)
There are broadly two kinds of things that can go wrong on the front-end app:
- Errors Errors that happen in our React/Typescript code, either unforeseen or (ideally) caught in a
try...catch
statement. These are what's most likely to go wrong. The user can opt in/out of sending these kinds of errors, which are more likely to contain personal data. Normally, these are only logged to Bugsnag when the app is installed on an actual device, rather than running in Expo Go during development. This is so that we don't get flooded by lots of errors that occur during development, and because we're on a free tier package that only allows a limited number of error reports per day/month so we want to minimise the errors reported to Bugsnag to only include issues in production. - Crashes The app can crash entirely e.g. if something goes wrong in a native library or the app runs out of memory, although this is probably unlikely with Expo. Logging these crash reports cannot be switched off, because Bugsnag starts when the first app loads. From an initial look, it appears these crash reports don't contain personal data.
Ordinarily, when you are developing using Expo Go, you should not have BUGSNAG_API_KEY
set to the real API key value, otherwise it will send crash logs to Bugsnag which we usually don't want (ordinarily we only want to use Bugsnag to track errors and crashes on real devices).
You do need to set a value otherwise the app will not run, so you can use e.g.
BUGSNAG_API_KEY="no_api_key"
During development, in the terminal window where you are running Expo you may see the message
[bugsnag] Bugsnag.start() was called more than once. Ignoring.
You don't need to worry about this, it's just the app reloading and trying to start Bugsnag again.
Ask one of the team to add you to the it470-volunteer-app-errors Slack channel where you can see the latest bugs coming in from the app on real devices, and the API production server.
If you know a bug has been fixed or can safely be ignored, please click the 'Mark as fixed' or 'Ignore' button on the error in the Slack message.
To get more details on a bug you'll need to go to our Bugsnag inbox here -- you'll need the Bugsnag login details from another team member.
Note: there are two Projects in Bugsnag -- one for the front-end app ('Volunteer app'), another for the API ('Volunteer app API'). Make sure you're looking at the right one. You can also filter by development/production.
When you look into an error, click on it in the Inbox in Bugsnag, then on the 'Stacktrace' tab you'll need to find where the error originated. The first entry in the stacktrace is just the
logging
module, you need to find what's below that and click to expand it to see where in the code the error actually occurred.
You can log errors to Bugsnag when developing in Expo Go if you really need to. You shouldn't normally need to do this -- Bugsnag error logging is usually to monitor crashes and errors in the production app. You should only use this in development when normal error detection is insufficient e.g. because you want to figure out why the app is crashing due to a lack of memory. Don't use this in place of normal code tools like console.error
and console.log
and other normal testing approaches.
You can force the app to report errors to Bugsnag during development using Expo Go (this also overrides the user permissions setting which normally determines whether or not send error reports). To do this:
- Create a
.env
file if you don't have one already, and addBUGSNAG_API_KEY="xxxxxxxxxxxxxxxx"
repacingxxxxxxxxxxxxxxxx
with the value of our actual Bugsnag API key for the app (ask on the team Slack channel and someone can give you this -- note: the front-end app and the API use different Bugsnag API keys) - In your
.env
file include the lineBUGSNAG_ALWAYS_SEND_BUGS="true"
(this forces the API to send errors to Bugsnag, even though you're in a development environment) - Start/restart Expo and reload the app
After you've finished testing you must:
- Remove the
BUGSNAG_API_KEY
line from your.env
file or comment it out by adding a#
at the start of the line - Remove the
BUGSNAG_ALWAYS_SEND_BUGS
line from your.env
file or set it toBUGSNAG_ALWAYS_SEND_BUGS="false"
- Restart Expo and reload the app
Have noticed occasional performance issues in the app emulator e.g. warnings that serializablestateinvariantmiddleware
took a long time.
NativeBase does also have some documented performance issues but hopefully these won't hold us back (our app is relatively simple after all) and it looks like there are ongoing efforts to address these.