CM3050 Topic 03: Programming User Interfaces
Main Info
Title: Programming User Interfaces
Teachers: Joe McAlister
Semester Taken: April 2022
Parent Module: cm3050: Mobile Development
Description
In this topic, we start programming using core React Native components such as images, buttons and pressable elements. We also take an in-depth look at hooks and navigation.
Key Reading
Supplementary Reading
Lab Summaries
The first lab has you do the layout for a social media sign up page. Code is here: CM3050 Lab 3005: Positioning UI
The second has you create a light switch. Code is here: CM3050 Lab 3109: Light Switch
Lecture Summaries
3.0 Programming UI Elements
Sometimes we want complete control over our UI Components look and feel. Other times though it is good to use built in OS components, since it makes our app more harmonious with the user experience across their device. We can use a bunch of built in UI components in RN that will render differently on Android and iOS. This highlights that we need to test well though! Here are some common ones:
<ActivityIndicator size="large" color="#000" />
A loading spinner. Docs
<Button title="button" color="blue" />
The default button. We use TouchableOpacity
a lot, but the default OS button is Button
and this styles very differently on different OSs. Docs
<Image source={require('filepath')} />
Source can be a URL source={{uri: 'https://...'}}
or a filepath on the system (with require), or a data uri. For network and data you need to specify the dimensions. Docs
<ImageBackground source={require('filepath')} resizeMode="cover" style={styles.image}>...</ImageBackground>
Used to create a background image, layers children on top. Docs
<Switch trackColor={{false: "red", true: "green"}} value={true} onValueChange={toggleSwitch} />
Needs an onValueChange
callback to update the value. A lot of variance across iOS and Android on this one. Docs
<Text>
Suports nesting, styling, and touch handling. Note that unlike html, text nodes have to be contained in a
<TextInput value="Test" />
lets the user input text. Props allow configuration for auto-correct, auto-capitalize, placeholder text and different keyboards. onChangeText
event reads the user input. Docs
<TouchableOpacity>
a View that can respond to touch events. Used as an alternative to button as it can be more heavily customised. Docs. Also check out the new Pressable component.
<View>
The most fundamental component in UI, a container that supports layout, accessibility and some touch handling. Can have 0 to many children of any type. Maps directly to the native UI view of the target platform. Docs. You can layer views on views, and embed views within views. Think of a view like a sheet of paper (Joe).
3.1 Interaction
Walks through the onPress
callback for button interactivity. Notes the different ways you can define the callback:
<Button onPress={myCallback}>
Limitations of this is that you can’t pass a parameter to the callback.
<Button onPress={() => myCallback(param)}>
Now we can pass a parameter.
<Button onPress={() => //do work here}
Just use the arrow syntax to embed the function if it’s a quick one liner.
Hooks
Hooks are functions that let you “hook into” React state and lifecycle features from function components. (React docs)
React has some pre-built hooks that add great functionality, like state and memory. Here’s a quick example of state:
App() {
const [count,setCount = useState(0);
}
It’s returning a pair, the current state value and a function that lets you update the state. We provide 0 to the function so we know the state is 0 to begin with.
Now I can use setState(3)
to update the variable.
So why can’t I just update the variable directly? RN has a state management system. Remember the data is thrown away when we redraw the component. When we change state it causes a refresh of the view.
So for example if I had a text element displaying the count, using setState
will cause the text element to be re-rendered with the new state.
What if we don’t want to cause this re-render? We can use the useRef
function like this:
App() {
const ourRef = useRef(0)
}
This returns a mutable ref object whose current
property is initialized to the argument passed. So ourRef.current
will be 0.
We can change it, so we could update text like this ourRef.current.textContent = "new text"
The object will persist for the whole lifetime of the component.
Some rules on hooks: only call them on the top level of the component, not within loops, conditionals or nested functions.
Only call them within React functional components, not regular JS functions.
Images
Images can be loaded from any folder (assets folder recommended). or via network. Remember that network connections will be slow.
Joe doesn’t like remote assets unless they are unavoidable. You can attach an onLoad
callback that’s fired when the asset is returned. Remember to use https, Apple enforces it.
You can style an image like a view, you can define width/height. If it doesn’t match the original aspect ratio you can define the resizeMode
, eg cover
which crops or stretching and warping. See the docs
Touchable Opacity
Using Touchable Opacities we can create something clickable in exactly the spot we want. Demonstrates with puppies.
3.3 Navigation
Navigation and pages separate our content to easily digestible pieces of information. Expected behaviour. For example in hiding detailed information in ‘details’ or ‘settings’ pages.
You can follow the instructions here to install the packages required for React navigation.
Joe thinks of navigation like a deck of cards. You’re going to have a set of cards and stack them in different orders. Or a bookcase (app) containing books (screens), with the books each defined in a separate function.
We create the basic scaffolding like this:
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
const Stack = createStackNavigator();
export default function App() {
return(
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={MyHomeScreen} />
<Stack.Screen name="Details" component={MyDetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
We can then use the navigation prop in our components to navigate programatically like this:
function HomeScreen({navigation}) {
return (
<Button
title="To the details"
onPress = {() => navigation.navigate("Details")}
/>
);
}
There are more complex things you can do with the navigation stack (push and pop pages onto the nav stack). For example navigation.popToTop()
will go to the first page in the stack (like Home). navigation.goBack()
will go back one screen.
You can pass parameters to routes like this navigation.navigate('RouteName', { /* params go here */ })
Then you can read them in a route.params
prop on the page component. Think of the route
prop like a url in web dev. When deciding what should go in params
think would it be in a url? It should be the minimal data required to show a screen, nothing more. EG don’t pass a whole user data object to params. Just pass the id, the user data should be in global store somewhere.