React-native

React Native

Nei miei ultimi post mi sono occupato di React: framework frontend creato da Facebook che è ormai l’ultima moda in fatto di sviluppo web. React-native è un progetto parallelo il cui scopo è quello di creare applicazioni native iOS e Android. Il risultato non sarà infatti un’applicazione ibrida in stile Apache Cordova ma una vera e propria applicazione nativa, scritta però nello stile React. Il framework ad oggi include una serie di componenti e delle API che ci permettono di accedere alle features del dispositivo quali vibrazione, fotocamera, etc.

Setup

Alla fine di questo post saremo in grado di scrivere la solita todo-list in React Native. Come prima cosa dobbiamo installare React-native tramite il comando:

npm install -g react-native-cli

Dopodichè possiamo inizializzare il nostro progetto, tramite il comando:

react-native init TodoList

La situazione della cartella TodoList una volta terminata la procedura sarà la seguente:

Filesystem di un progetto "blank"

Filesystem di un progetto “blank”

la cosa che salta più all’occhio è la presenza di due files: index.ios.js e index.android.js. Il punto di ingresso dell’applicazione sarà differente a seconda della piattaforma scelta. Questa feature può sembrare poco intuitiva per un tool che si professa multipiattaforma, ma in realtà ha una sua logica. Il motto di react-native è “learn once, run anywhere” a differenza del solito “write once, run anywhere”. Lo scopo di react-native è quello di poter fornire la maggiore aderenza possibile al look and feel della piattaforma scelta anche a costo di avere codice che (almeno in parte) diverge.

Let’s code!

Passiamo ora al codice dell’applicazione. Implementeremo una todo-list il cui codice sarà identico in entrambe le piattaforme supportate. Per questo esempio la parte di business logic è presa integralmente dal mio ultimo post su Flux. Quindi in questo caso ci concentreremo solo sulla parte View del progetto. Iniziamo con il codice della root dell’applicazione:

var React = require('react-native');
var Form = require('./Form');
var List = require('./List');

var {
  StyleSheet,
  Text,
  View
} = React;

var styles = StyleSheet.create({
  title: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  }
});

module.exports = class extends React.Component{
	render() {
	    return (
	    	<View>
	    		<Text style={styles.title}>
      			     React Native Todo-List
      		        </Text>
                        <Form/>
                        <List/>
    		</View>
	    );
	}
}

La struttura di un’applicazione React-native è perfettamente identica a quella di un’applicazione React classica. Unica differenza sta nell’utilizzo di tag forniti dal framework (come View e Text) al posto dei classici tag HTML. I tag Form e List rappresentano invece dei componenti custom di cui analizziamo subito il codice. Iniziamo dal componente Form:

var React = require('react-native');
var Actions = require('../Actions');

var {
  StyleSheet,
  TextInput,
  View,
  TouchableHighlight,
  Text
} = React;

var styles = StyleSheet.create({
  row: {
    flexDirection: 'row',
    margin: 30
  },
  input: {
    flex: 9,
    borderWidth: 1,
    borderColor: 'black'
  },
  button: {
    flex: 3,
    padding: 5,
    borderWidth: 1,
    marginLeft: 10,
    borderColor: 'black',
    borderRadius: 5
  }
});

module.exports = class extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      todoText: ""
    };

    this.onSavePress = this._onSavePress.bind(this);
    this.onChangeInput = this._onChangeInput.bind(this);
  }

  _onSavePress() {
    if (this.state.todoText) {
      Actions.add(this.state.todoText);
      this.setState({
        todoText: ""
      });
    }
  }

  _onChangeInput(event) {
    this.setState({
      todoText: event.nativeEvent.text
    });
  }

  render() {
      return (
        <View style={styles.row}>
          <TextInput
            placeholder="Add a ToDo..."
            onChange={this.onChangeInput}
            style={styles.input}
            value={this.state.todoText}/>
          <TouchableHighlight
            onPress={this.onSavePress}
            style={styles.button}>
              <Text style={{textAlign:'center'}}>Save</Text>
          </TouchableHighlight>        
        </View>
      );
  }
}

Come vediamo questo componente non presenta caratteristiche particolari, rispetto ad una canonica applicazione React. Passiamo ora all’elemento List.

var React = require('react-native');
var Store = require('../Store');
var Actions = require('../Actions');

var {
  StyleSheet,
  View,
  ListView,
  Text,
  TouchableHighlight
} = React;

var styles = StyleSheet.create({
	row: {
	    flex: 1,
	    flexDirection: 'row',
	    borderWidth:1,
    	marginLeft:10,
    	borderColor:'black',
    	borderRadius:2,
    	marginTop:5,
    	padding: 5
	},
	todoText:{
		flex:9
	},
	deleteButton:{
	    flex:3,
	    borderWidth:1,
	    borderColor:'black',
	    borderRadius:5,
	    backgroundColor:'red'
  	},	
	listView: {
   		paddingLeft: 30,
   		paddingRight: 30
  	}
});

module.exports = class extends React.Component{

	constructor(props) {
	    super(props);
	    this.state = {
			dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})
	    };

	    this.listener = this._listener.bind(this);
	    this.renderRow = this._renderRow.bind(this);
	}

	_listener(){
		this.setState({
			dataSource: this.state.dataSource.cloneWithRows(Store.get())
	    });
	}

	_onDeletePress(rowId){
		Actions.delete(parseInt(rowId,10));
	}


	componentDidMount() {
	    Store.addChangeListener(this.listener);
	}

	componentWillUnmount() {
		Store.removeChangeListener(this.listener);
	}

	_renderRow(todo,sectionID,rowId) {
		return (
	      <View style={styles.row}>
	        <Text style={styles.todoText}>{todo}</Text>
	        <TouchableHighlight
	            onPress={this._onDeletePress.bind(this,rowId)}
	            style={styles.deleteButton}>
	              <Text style={{textAlign:'center'}}>Delete</Text>
          	</TouchableHighlight>     
	      </View>
	    );
	}
	
	render() {
	    return (
	        <View>
	        	<ListView
	        		style={styles.listView}
	        		automaticallyAdjustContentInsets={false}
  					dataSource={this.state.dataSource}
          			renderRow={this.renderRow}/>
	        </View>
	    );
	}
}

all’interno di List possiamo notare la presenza della ListView il repeater base di React-native. Questo componente ha bisogno di due attributi fondamentali:

  • dataSource: la sorgente dati del repeater, da notare che in fase di inizializzazione gli viene passata la funzione rowHasChanged che indica quando una riga può essere considerata modificata (quando cioè rilanciare il render della ListView)
  • renderRow: metodo che si occupa di eseguire il render della singola riga

Avvio e debug

Per avviare l’applicazione dobbiamo utilizzare XCode nel caso di piattaforma iOS oppure invocare il comando react-native run-android per quanto riguarda Android. Il risultato del codice visto in precedenza è il seguente:

React-native todo-list

React-native todo-list

Premendo ⌘-D per quando l’applicazione gira all’interno del simulatore iOS, oppure premendo il pulsante “menù” su un emulatore Android verrà fuori il menù debug di react-native, che comprende moltissime funzioni utili come il live reload, l’inspect degli elementi e il debug su chrome o safari.

Debug menu

Debug menu

Inspect element

Inspect element

Conclusioni

React-native è un progetto molto giovane, il supporto ad Android è ufficiale da pochissimo tempo. Ma ha già parecchie frecce al suo arco. La cosa più impressionante è l’enorme efficacia del suo debug menù: su android si hanno tempi di build ridicoli e un live reload funzionante. Altra caratteristica interessante è la possibilità (che abbiamo sfruttato nel nostro esempio) di recuperare completamente la business logic di una pre-esistente applicazione React web. Non credo che ad oggi sia un prodotto valido per creare app da mandare in produzione, ma è sicuramente il framework da tenere d’occhio nei prossimi mesi per chi si interessa di sviluppo mobile multipiattaforma.

Il sorgente dell’applicazione è disponibile, come sempre, al repository GitHub di cosenonjaviste.

Francesco Strazzullo

Faccio il Front-end developer per e-xtrategy dove mi occupo di applicazioni AngularJS e mobile. In passato ho lavorato principalmente con applicazioni con stack Spring+Hibernate+JSF 2.X+Primefaces. Sono tra i collaboratori del progetto Primefaces Extensions: suite di componenti aggiuntivi ufficialmente riconosciuta da Primefaces. Sono anche uno dei fondatori del progetto MaterialPrime: una libreria JSF che segue le direttive del Material Design di Google.