Project: Timer

You’ve seen bits and pieces from the Timer app (Figure 8), which is in timer. I think it will be beneficial if we go through the implementation at once. The main file is index.ios.js. It has three components, not unlike my browser/web React Timer from React Quickly (Manning, 2016), (GitHub):

Figure 8: Timer with control elements to set the time
Figure 8: Timer with control elements to set the time

We start the index.ios.js file with importations of React Native, its objects, and Audio Player:

 1 'use strict'
 2 
 3 var React = require('react-native'),
 4   AudioPlayer = require('react-native-audioplayer')
 5 
 6 var {
 7   AppRegistry,
 8   StyleSheet,
 9   Text,
10   View,
11   ScrollView,
12   TouchableOpacity,
13   Switch
14 } = React

The next statement declares the array of options for the Timer buttons, which we will turn into either number of seconds or number of minutes by using Switch:

1 const timerOptions = [5, 7, 10, 12, 15, 20]

I enhanced TimerWrapper from the chapter 5 project with the dynamic generation of buttons and the seconds to minutes switch. The switch is using the isMinutes state, so let’s set it to false at the beginning. Just to remind you, this example uses some ES6+/ES2015+ syntax. If you are not familiar with it or are not sure whether you’re familiar with it, check out chapter 10 and appendix I.

1 var TimerWrapper = React.createClass({
2   getInitialState () {
3     return {time: null, int: null, isMinutes: false}
4   },

The initial value of isMinutes is false. toggleTime is the handler for the Switch. We flip the value of isMinutes with the logical not (!). It’s important to set the time to null, as otherwise the sound will be triggered each time we flip the switch. The sound play is conditioned on time being 0, so if we set it to null, it won’t play. The sound logic is in the Timer component. The React algorithm decides to re-render it when we change the state of isMinutes:

1   toggleTime(){
2     let time = this.state.time
3     if (time == 0 ) time = null
4     this.setState({isMinutes: !this.state.isMinutes, time: time})
5   },

The next method starts the timers. If you followed the project in chapter 5, you know how it works. React Native provides an API for timers, i.e., clearInterval() and setInterval() as global objects. The number in the time state is always in seconds, even if we see minutes on the buttons and the switch is turned on:

 1   startTimer(time) {
 2     clearInterval(this.state.int)
 3     var _this= this
 4     var int = setInterval(function() {
 5       console.log('2: Inside of setInterval')
 6       var tl = _this.state.time - 1
 7       if (tl == 0) clearInterval(int)
 8       _this.setState({time: tl})
 9     }, 1000)
10     console.log('1: After setInterval')
11     return this.setState({time: time, int: int})
12   },

In the render method, we are using a simple map() iterator to generate a column of buttons. It’s wrapped in a ScrollView, so you can really go crazy with the timerOptions array by adding more elements, and see what has happened:

 1   render() {
 2     return (
 3       <ScrollView>
 4         <View style={styles.container}>
 5           <Text style={styles.heading}>Timer</Text>
 6           <Text style={styles.instructions}>Press a button</Text>
 7           <View style={styles.buttons}>
 8             {timerOptions.map((item, index, list)=>{
 9               return <Button key={index} time={item} startTimer={this.startTimer\
10 } isMinutes={this.state.isMinutes}/>
11             })}
12           </View>

After the buttons, we have a text label that says Minutes and the Switch controlled component:

1           <Text>Minutes</Text>
2           <Switch onValueChange={this.toggleTime} value={this.state.isMinutes}><\
3 /Switch>
4           <Timer time={this.state.time}/>
5         </View>
6       </ScrollView>
7     )
8   }
9 })

The buttons we render in TimerWrapper come from this component. It has a ternary condition (a.k.a. the Elvis operator) to set either minutes, by multiplying them by 60 (60 seconds in a minute), or seconds:

1 var Button = React.createClass({
2   startTimer(event) {
3     let time = (this.props.isMinutes) ? this.props.time*60 : this.props.time
4     return this.props.startTimer(time)
5   },

When rendering, we use TouchableOpacity, which is functionally similar to TouchableHighlight but differs in visual representation (it’s transparent when touched). There is a ternary condition to output the word “minutes” or “seconds” based on the value of the isMinutes property:

1   render() {
2     return (
3       <TouchableOpacity onPress={this.startTimer}>
4         <Text style={styles.button}>{this.props.time} {(this.props.isMinutes) ? \
5 'minutes' : 'seconds'}</Text>
6       </TouchableOpacity>
7     )
8   }
9 })

The Timer component renders the number of seconds left as well as playing the sound when this number is 0:

 1 var Timer = React.createClass({
 2    render() {
 3      if (this.props.time == 0) {
 4       AudioPlayer.play('flute_c_long_01.wav')
 5      }
 6      if (this.props.time == null || this.props.time == 0) return <View><Text  st\
 7 yle={styles.heading}> </Text></View>
 8      return (
 9        <View>
10          <Text style={styles.heading}>{this.props.time}</Text>
11          <Text>Seconds left</Text>
12        </View>
13      )
14     }
15 })

The styles object uses Flex. In container, there’s flexDirection, set to column. It positions elements vertically, as in a column. Another value is row, which will position them horizontally.

 1 var styles = StyleSheet.create({
 2   container: {
 3     flex: 1,
 4     flexDirection: 'column',
 5     alignItems: 'center'
 6   },
 7   heading: {
 8     flex: 1,
 9     fontSize: 36,
10     paddingTop: 40,
11     margin: 10
12   },
13   instructions: {
14     color: '#333333',
15     marginBottom: 15,
16   },
17   button: {
18     color: '#111',
19     marginBottom: 15,
20     borderWidth: 1,
21     borderColor: 'blue',
22     padding: 10,
23   	borderRadius: 20,
24     fontWeight: '600'
25   },
26   buttons: {
27     flex: 1,
28     alignItems: 'center',
29     justifyContent: 'flex-start'
30   }
31 })

Lastly, there is the register statement:

1 AppRegistry.registerComponent('timer', () => TimerWrapper)

Now, we can install and import the Audio Player into the Xcode project following the steps in the previous section. Don’t forget to include the sound file as well. When you’re done, navigate to the ch9/timer folder and start the local server with $ react-native start. You should see:

1 React packager ready.

Go to your Simulator and refresh it. You should see buttons with seconds on them and the switch in the off position. Turn it on to use minutes and the buttons will change. Pressing on 5 minutes will start the countdown showing seconds left, as shown in Figure 9.

Figure 9: Timer using minutes
Figure 9: Timer using minutes

I dare you to redesign this little app (make it prettier!), publish it to the App Store, and send me the link. Maybe you can get to the top charts. Flappy Bird did.