Come creare un'app di cose da fare in tempo reale con React Native

Un'app todo tocca tutte le parti importanti della creazione di qualsiasi app data-driven, comprese le operazioni C reate, R ead, U pdate e D elete (CRUD). In questa storia costruirò un'app todo con uno dei framework mobili più popolari, React Native .

Sarò con ReactiveSearch nativo , una libreria open-source che fornisce Reagire componenti dell'interfaccia utente nativi e semplifica la creazione di applicazioni basate sui dati.

Ecco cosa costruirò in questa storia:

Dai un'occhiata all'app su snack o in expo.

Cos'è React Native?

Ecco cosa dicono i documenti:

React Native ti consente di creare app mobili utilizzando solo JavaScript. Utilizza lo stesso design di React, permettendoti di comporre un'interfaccia utente mobile ricca da componenti dichiarativi.

Anche se hai appena iniziato con React o React Native, dovresti essere in grado di seguire questa storia e creare la tua app di cose da fare in tempo reale.

Perché utilizzare ReactiveSearch? ⚛

ReactiveSearch è una libreria di componenti UI React e React Native open source per Elasticsearch di cui ho collaborato con alcune persone fantastiche. Fornisce una varietà di componenti React Native che possono connettersi a qualsiasi cluster Elasticsearch.

Ho scritto un'altra storia su Building a GitHub Repo Explorer con React ed Elasticsearch che puoi consultare per una breve panoramica di Elasticsearch. Anche se non hai avuto esperienza con Elasticsearch dovresti essere in grado di seguire bene questa storia.

Impostazione delle cose ⚒

Useremo qui la versione React Native della libreria.

Prima di iniziare a costruire l'interfaccia utente, dobbiamo creare un datastore in Elasticsearch. ReactiveSearch funziona con qualsiasi indice Elasticsearch e puoi usarlo facilmente con il tuo set di dati.

Per brevità, puoi utilizzare direttamente il mio set di dati o crearne uno per te stesso utilizzando appbase.io che ti consente di creare un indice Elasticsearch ospitato (noto anche come app).

Tutti i todos sono strutturati nel seguente formato:

{ "title": "react-native", "completed": true, "createdAt": 1518449005768 }

Il progetto iniziale

Prima di iniziare, consiglierei di installare il filato. Su Linux può essere fatto semplicemente aggiungendo il repository di filato ed eseguendo il comando di installazione tramite il gestore dei pacchetti. Su Mac, dovresti prima installare Homebrew per rendere le cose più semplici. Ecco i documenti di installazione del filato per maggiori dettagli. La prossima cosa che puoi installare è watchman. È un servizio di controllo file che aiuterà il packager nativo di reazione a funzionare senza problemi.

Ho impostato il progetto di avviamento con l'app nativa per la reazione e la creazione in un ramo GitHub qui. Puoi scaricare un file zip o clonare il ramo di base eseguendo il seguente comando:?

git clone -b base //github.com/appbaseio-apps/todos-native
  • Quindi installa le dipendenze e avvia il packager:
cd todos-native && yarn && yarn start
  • Dopo l'avvio del packager, è possibile eseguire l'app sul telefono utilizzando l'app Expo o utilizzando un emulatore Android o IOS:

Immergersi nel codice?

Dopo aver clonato il codice dal ramo di base, dovresti vedere una struttura di directory come di seguito:

navigation ├── RootComponent.js // Root component for our app ├── MainTabNavigator.js // Tab navigation component screens ├── TodosScreen.js // Renders the TodosContainer components ├── Header.js // Header component ├── AddTodo.js // Add todo input ├── AddTodoButton.js // Add todo floating button ├── TodoItem.js // The todo item ├── TodosContainer.js // Todos main container api ├── todos.js // APIs for performing writes constants // All types of constants used in app types // Todo type to be used with prop-types utils // Streaming logic goes here

Analizziamo cosa viene fornito con la configurazione di base:

1. Navigazione

  • Tutte le configurazioni necessarie per connettersi a Elasticsearch si trovano su constants/Config.js.
  • Stiamo usando TabNavigator dalla navigazione reattiva per mostrare la schermata Tutto , Attivo e Completato . Questo è reso da navigation/RootComponent.js. Noterai che RootComponentavvolge tutto all'interno del ReactiveBasecomponente da ReactiveSearch. Questo componente fornisce tutti i dati necessari ai componenti figlio di ReactiveSearch. Puoi collegare il tuo indice Elasticsearch qui semplicemente aggiornando le configurazioni in constants/Config.js.

La logica di navigazione è presente in navigation/MainNavigator.js. Vediamo come funziona. Di seguito sono riportati i documenti per la navigazione tramite schede se desideri fare riferimento a qualcosa.

import React from 'react'; import { MaterialIcons } from '@expo/vector-icons'; import { TabNavigator, TabBarBottom } from 'react-navigation'; import Colors from '../constants/Colors'; import CONSTANTS from '../constants'; import TodosScreen from '../screens/TodosScreen'; const commonNavigationOptions = ({ navigation }) => ({ header: null, title: navigation.state.routeName, }); // we just pass these to render different routes const routeOptions = { screen: TodosScreen, navigationOptions: commonNavigationOptions, }; // different routes for all, active and completed todos const TabNav = TabNavigator( { [CONSTANTS.ALL]: routeOptions, [CONSTANTS.ACTIVE]: routeOptions, [CONSTANTS.COMPLETED]: routeOptions, }, { navigationOptions: ({ navigation }) => ({ // this tells us which icon to render on the tabs tabBarIcon: ({ focused }) => { const { routeName } = navigation.state; let iconName; switch (routeName) { case CONSTANTS.ALL: iconName = 'format-list-bulleted'; break; case CONSTANTS.ACTIVE: iconName = 'filter-center-focus'; break; case CONSTANTS.COMPLETED: iconName = 'playlist-add-check'; } return (  ); }, }), // for rendering the tabs at bottom tabBarComponent: TabBarBottom, tabBarPosition: 'bottom', animationEnabled: true, swipeEnabled: true, }, ); export default TabNav;
  • La TabNavigatorfunzione accetta due argomenti, il primo sono le configurazioni del percorso e il secondo sono le TabNavigatorconfigurazioni. Nello snippet sopra, stiamo passando le configurazioni per mostrare una barra di navigazione delle schede nella parte inferiore e impostare icone diverse per ciascuna scheda.

2. TodosScreen e TodosContainer

Il TodosScreencomponente in screens/TodosScreen.jsavvolge il nostro TodosContainercomponente principale in components/TodosContainer.jscui aggiungeremo vari componenti per l'app. Il TodosContainermostrerà dati filtrati, in base al fatto che siamo in tutto , attivo, o Completato scheda.

3. API per la creazione, l'aggiornamento e l'eliminazione degli oggetti

Le API per le operazioni CUD su Elasticsearch sono presenti in api/todos.js. Esso contiene tre metodi semplici add, updatee destroyche funzionano con qualsiasi indice elasticsearch come specificato constants/Config.js. Un punto importante da tenere a mente è che ogni elemento todo che creiamo avrà un _idcampo unico . Possiamo utilizzare questo _idcampo per aggiornare o eliminare una cosa da fare esistente.

Per la nostra app, avremo solo bisogno di tre metodi per aggiungere, creare o eliminare todos. Tuttavia, è possibile trovare una spiegazione dettagliata sui metodi API nella documentazione.

Costruire i componenti e l'interfaccia utente?

Iniziamo ad aggiungere alcuni componenti per completare la funzionalità dell'app.

1. Aggiunta di Todos

Useremo Fabda native-baseper eseguire il rendering di un pulsante mobile per l'aggiunta di cose da fare.

const AddTodoButton = ({ onPress }) => (    );

Ora puoi usare questo componente in components/TodosContainer.js.

import AddTodoButton from './AddTodoButton'; ... export default class TodosContainer extends React.Component { render() { return (  ...   ); } }

Una volta aggiunto il pulsante, vedremo qualcosa del genere:

Ora, quando qualcuno fa clic su questo pulsante, dovremo mostrare l'input per aggiungere una cosa da fare. Aggiungiamo il codice per questo in components/AddTodo.js.

class AddTodo extends Component { constructor(props) { super(props); const { title, completed, createdAt } = this.props.todo; this.state = { title, completed, createdAt, }; } onSubmit = () => { if (this.state.title.length > 0) this.props.onAdd(this.state); return null; }; setStateUtil = (property, value = undefined) => { this.setState({ [property]: value, }); }; render() { const { title, completed } = this.state; const { onBlur } = this.props; return (   this.setStateUtil('completed', !completed)} />   this.setStateUtil('title', changedTitle)} value={title} autoCorrect={false} autoCapitalize="none" onBlur={onBlur} />   this.props.onCancelDelete} style={{ paddingLeft: 25, paddingRight: 15 }} >  0 ? 'black' : 'grey'}`} size={23} />   ); } }

I principali componenti utilizzati sono qui TextInput, Checkboxe Ioniconscon i puntelli semplici. Stiamo usando titlee completeddal state. Saremo passando gli oggetti di scena todo, onAdd, onCancelDeletee onBlurdal components/TodosContainer.js. Questi ci aiuteranno ad aggiungere nuove cose da fare o a ripristinare la vista se desideri annullare l'aggiunta di cose da fare.

Ora possiamo aggiornare components/TodosContainer.jscon le modifiche richieste per il AddTodocomponente di rendering :

... import AddTodoButton from './AddTodoButton'; import AddTodo from './AddTodo'; import TodoModel from '../api/todos'; ... // will render todos based on the active screen: all, active or completed export default class TodosContainer extends React.Component { state = { addingTodo: false, }; componentDidMount() { // includes the methods for creation, updation and deletion this.api = new TodoModel('react-todos'); } render() { return (     {this.state.addingTodo ? (   { this.setState({ addingTodo: false }); this.api.add(todo); }} onCancelDelete={() => this.setState({ addingTodo: false })} onBlur={() => this.setState({ addingTodo: false })} />  ) : null}   this.setState({ addingTodo: true })} />  ); } }

The AddTodo component is rendered inside a ScrollView component. We also pass an onPress prop to the AddTodoButton to toggle the state and conditionally display the AddTodo component based on this.state.addingTodo. The onAdd prop passed to AddTodo also creates a new todo using the add API at api/todos.js.

After clicking the add button, we’ll see the input for adding a todo like this:

2. Displaying Todos

After you finish adding a todo, it’s added into Elasticsearch (which we configured in constants/Config.js). All this data can be viewed in realtime by using ReactiveSearch Native components.

There are over 10 native UI components that the library provides. For our todo app, we will primarily utilize the ReactiveList component to show the state of todos.

Lets add the ReactiveList component and get our todos displaying. We’ll add this component in components/TodosContainer.js and the necessary methods for it to work. Here’s how the ReactiveList will be used:

 ... import { ReactiveList } from '@appbaseio/reactivesearch-native'; ... export default class TodosContainer extends React.Component { render() { return (      ({ query: { match_all: {}, }, })} stream onAllData={this.onAllData} dataField="title" showResultStats={false} pagination={false} /> ...   this.setState({ addingTodo: true })} />  ); } }

We haven’t added the onAllData method yet, but let’s understand a bit about the props that we have used here:

  • componentId — unique identifier for the component.
  • defaultQuery: the query to be applied initially for the list. We’ll use match_all to show all the todos in default case.
  • stream: whether to stream new result updates or just show historical results. By setting this to true, we now also listen for the live Todo updates. We’ll add the streaming related logic later.
  • onAllData — a callback function which receives the list of current todo items and the streaming (new todos and any updates) and returns a React component or JSX to render. Here’s how the syntax looks like:

You can read more about all of these props in detail on the ReactiveList’s docs page.

To see something, we’ll need to return a JSX or React component from onAllData callback. For this, we will use React Native’s FlatList which is composed of Text components. In the next step we’ll add our custom TodoItem component.

... import { ScrollView, StyleSheet, StatusBar, FlatList, Text } from 'react-native'; import CONSTANTS from '../constants'; ... export default class TodosContainer extends React.Component { ... onAllData = (todos, streamData) =>  Completed] const filteredData = this.filterTodosData(todos); return (  item._id renderItem={({ item: todo }) => ( {todo.title} )} /> ); }; filterTodosData = (todosData) => { const { screen } = this.props; switch (screen) { case CONSTANTS.ALL: return todosData; case CONSTANTS.ACTIVE: return todosData.filter(todo => !todo.completed); case CONSTANTS.COMPLETED: return todosData.filter(todo => todo.completed); } return todosData; }; render() { ... } }

3. Adding TodoItem(s)

Next, we’ll create a separate component TodoItem for showing each todo which will contain all necessary markups for a todo item like the CheckBox, Text, and a delete Icon. This goes in components/TodoItem.js:

class TodoItem extends Component { onTodoItemToggle = (todo, propAction) => { propAction({ ...todo, completed: !todo.completed, }); }; render() { const { todo, onUpdate, onDelete } = this.props; return (    this.onTodoItemToggle(todo, onUpdate)} style={{ flex: 1, width: '100%', flexDirection: 'row', }} >  this.onTodoItemToggle(todo, onUpdate)} />   {todo.title}     onDelete(todo)} style={{ paddingLeft: 25, paddingRight: 15 }} >  0 ? 'black' : 'grey'}`} size={23} />    ); } }

This component gets the todo from its props along with onDelete and onUpdate which are used to update and delete the todo item respectively. We’re using these at the necessary places using the onPress prop of the components we’re using.

Next, we can import and use the TodoItem component in our onAllData in components/TodosContainer.js. We’ll pass the todo as a prop along with the API methods for update and destroy which will be used by TodoItem component.

class TodosContainer extends Component { ... onAllData = (todos, streamData) => { ... return (  (  )} /> ); } }

4. Streaming Data Updates

You might have noticed that the todos are displaying fine, except you’re unable to view updated todos without refreshing the app. In this final step, we’re going to fit that missing part of the puzzle.

In the previous section, we added an onAllData method for the ReactiveList component. The second parameter of onAllData receives streaming updates which we’re going to utilize to always keep the todos updated. Here’s how the updated onAllData method will look like in components/TodosContainer.js.

import Utils from '../utils'; ... export default class TodosContainer extends React.Component { ... onAllData = (todos, streamData) =>  // merge streaming todos data along with current todos const todosData = Utils.mergeTodos(todos, streamData); // filter data based on "screen": [All  renderItem={({ item: todo }) => (  )} /> ); }; ... }

The mergeTodos method is present in utils/index.js. Here’s how it works:

class Utils { static mergeTodos(todos, streamData) { // generate an array of ids of streamData const streamDataIds = streamData.map(todo => todo._id); return ( todos // consider streamData as the source of truth // first take existing todos which are not present in stream data .filter(({ _id }) => !streamDataIds.includes(_id)) // then add todos from stream data .concat(streamData) // remove todos which are deleted in stream data .filter(todo => !todo._deleted) // finally sort on the basis of creation timestamp .sort((a, b) => a.createdAt - b.createdAt) ); } } export default Utils;

The streamData receives an array of todo objects when they’re created, deleted, or updated. If an object is updated, it contains a _updated key set to true. Similarly, if an object is deleted, it contains a _deleted key set to true. If an object is created, it contains neither of the two. Using these points, we’ve added the mergeTodos function.

With this, you should be able to see the changes to todo items in realtime! If you have an additional device/emulator running the same app, both will stream new updates too. ?

Useful links

  1. Todos app demo, expo link, starter project and final source code
  2. ReactiveSearch GitHub repo ⭐️
  3. ReactiveSearch docs

Hope you enjoyed this story. If you have any thoughts or suggestions, please let me know and have fun!

You may follow me on twitter for latest updates. I've also started posting more recent posts on my personal blog.

Special thanks to Dhruvdutt Jadhav for helping me with this story and the Todos app.