App mòbil amb React Native: arbres singulars de Barcelona

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

Introducció

Arbres barcelona.jpg

Es tracta de fer una app Android, com a procés d'aprenentatge de React Native per fer aplicacions natives. La idea és acabar tot el cicle del projecte fins a publicar l'app al Google Play Store.

L'app consta de dues pantalles:

L'aplicació és una gamificació dels Arbres Singulars de Barcelona. Es tracta d'anar col·leccionant aquests arbres. Els arbres descoberts surten en verd, i t'indica el % dels arbres descoberts. Per tant, és un joc per passejar amb família mentre es van coneixent els arbres singulars i es coneixen nous barris de la ciutat. I si es vol anar ràpid o corrents, col·leccionar aquests arbres també és un alicient a la pràctica esportiva.

Github

MapView i react-native-maps (Google Maps)

Hi ha projectes per adaptrar el mòdul react-native-maps a OSM, però això ho faré més avall. De moment em conformo en fer funcionar el projecte amb Google Maps.

Documentació de expo, que és el sistema que estic fent servir per desenvolupar aplicacions reactivenative.

$ expo install react-native-maps
$ npm install react-native-maps --save-exact

Configuration

Apartat: Deploying to a standalone app on Android

Apartat: If you already have not configured Google Sign In

1. Android package name: el que he posat en el app.json. En aquest cas: "package": "org.joanillo.ArbresBarcelona",

2. Open your browser to the Google API Manager and create a project.

Actualizar la cuenta

Estás a un paso de desbloquear Google Cloud Platform íntegramente.

No se te aplicará ningún cargo hasta que tus créditos gratuitos se agoten o caduquen (lo que suceda primero).Más información

Projecte: ArbresBarcelona

3. Once it's created, go to the project and enable the Google Maps SDK for Android

API_KEY: ************************

4. Go back to https://console.developers.google.com/apis/credentials and click Create Credentials, then API Key.

5. In the modal that popped up, click RESTRICT KEY.

6. Choose the Android apps radio button under Key restriction.

7. Click the + Add package name and fingerprint button.

De moment aquesta informació no la necessito:

¿Cómo restrinjo mi clave de API a aplicaciones de Android concretas?
Si quieres restringir una clave de API a determinadas aplicaciones de Android, proporciona una huella digital de certificado de depuración o de publicación.

Huella digital de certificado de depuración
Linux o macOS:

$ keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

Huella digital de certificado de publicación
$ keytool -list -v -keystore your_keystore_name -alias your_alias_name

Sustituye your_keystore_name por la ruta completa y el nombre del almacén de claves, incluida la extensión .keystore. Sustituye your_alias_name por el alias que asignaste al certificado cuando lo creaste.

Restringir el uso a tus aplicaciones de Android Añade el nombre del paquete y la huella digital del certificado de firma SHA‑1 para restringir el uso a tus aplicaciones para Android


8. Add your android.package from app.json (eg: ca.brentvatne.growlerprowler) to the Package name field.

En el nostre cas: org.joanillo.ArbresBarcelona

Huella digital del certificado SHA-1: he de posar la que em diu el punt 9

9. run expo fetch:android:hashes

$ expo fetch:android:hashes

Configuring credentials for joanillo_reactnative in project geo-example
Google Certificate Fingerprint:     ***************
Google Certificate Hash (SHA-1):    ***************
Google Certificate Hash (SHA-256):  ***************
Facebook Key Hash:                  ***************

Note: if you are using Google Play signing (no és el meu cas), this app will be signed with a different key after publishing to the store, and you'll need to use the hashes displayed in the Google Play console.

10. Copy Google Certificate Fingerprint from the output from step 9 and insert it in the "SHA-1 certificate fingerprint" field.

11. Copy the API key (the first text input on the page) into app.json under the android.config.googleMaps.apiKey field. See an example diff.

"android": {
    "package": "org.joanillo.ArbresBarcelona",
    "config": {
      "googleMaps": {
        "apiKey": "**************************"
      }
    }
  }

12. Press Save and then rebuild the app like in step 1.

Exemples

i ara ja puc provar els exemples de:

Concretament puc anar directament a la carpeta example/examples/, i provar els diferents exemples. La única cosa que he de fer és copiar el codi de l'exemple en el meu App.js del projecte. He provat els següents exemples:

Projecte react-native-open-street-map (sense Google Maps)

És un fork de react-native-maps amb la idea d'utilitzar els mapes de OSM amb el component MapView.

al fitxer package.json posem a dependències:

"react-native-open-street-map": "https://github.com/enieber/react-native-open-street-map.git"

de manera que quan fem

$ npm install

s'instal·la aquesta dependència. Un cop instal·lat podem veure dins de node_modules/ la carpeta react-native-open-street-map, que significa que el mòdul ja està instal·lat.

Prove el parell de codis d'exemple que es donen. Ara bé, dóna problemes:

requireNativeComponent: OpenAirMap was not found in the UIManager

$ expo --version
$ expo-cli --version
3.20.1

i informant-nos una mica veiem que aquesta solució seria vàlida per al Android Studio i la manera com té de fer el build de l'aplicació (gradle), però jo estic utilitzant Expo que és una manera no-nativa, és una porta fàcil d'entrada al desenvolupament react-native, però té les seves limitacions.

Having React Native Maps is a huge plus in Expo. It would be great to have Open Street Maps integrated too in order to have a choice to Google Maps. Veure el que es comenta en el següents fils:

De moment (abril 2020) no hi ha suport de Expo als react-native-maps'. La informació que he trobat de react-native-open-street-map no és per expo, sinó per l'Android Studio directament (gradle).

Però això hauria de canviar en el futur, doncs hi ha interès per no dependre de Google Maps. Però la plataforma Expo s'hi han de posar en el tema. Com es comenta en el fil, aquí hi ha una solució que exploro en el següent apartat:

Mapes sense Google Maps i amb Expo (solució final)

És una solució feta a mida que no depèn de MapView.

$ git clone https://github.com/wende60/hiker.git
$ cd hiker
$ npm install
$ npm start

Right now I am using the tiles from:

An API key is needed and has to be added in settings/mapConfig.js. For small tile usage you can get it for free.

Servidor de tiles:

Ja m'havia registrat anteriorment, i la API Key era: f695b42************

Ara bé, el projecte d'entrada no se m'instal·la perquè, encara que és recent (octubre 2019), les versions de expo-cli del desenvolupador devien estar una mica desfasades.

Quan compilo el projecte amb expo:

32.0.0 is not a valid SDK version

$ expo --version
$ expo-cli --version
3.20.1

$ expo diagnostics

  Expo CLI 3.20.1 environment info:
    System:
      OS: Linux 4.15 Linux Mint 19.2 (Tina)
      Shell: 4.4.20 - /bin/bash
    Binaries:
      Node: 12.16.1 - ~/.nvm/versions/node/v12.16.1/bin/node
      npm: 6.13.4 - ~/.nvm/versions/node/v12.16.1/bin/npm
    npmPackages:
      expo: ^32.0.0 => 32.0.6 
      react: 16.5.0 => 16.5.0 
      react-native: https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz => 0.57.1 
    npmGlobalPackages:
      expo-cli: 3.20.1

Torno a reinstal·lar el expo-cli---

npm -g uninstall expo-cli
npm install --global expo-cli

però s'ha quedat tot amb les mateixes versions.

En el package.json tinc:
  "dependencies": {
    "expo": "^32.0.0",

  "dependencies": {
    "expo": "^32.0.0",
    "react": "16.5.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz"

"react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz"
que és la versió que tinc en d'altres projectes

i en el app.json poso:
        "sdkVersion": "32.0.0",

torno a fer 
npm install

Your project is in SDK version >= 33.0.0, but the expo package version seems to be older.

i tot i aquest missatge a la consola, avança en la instal·lació, però encara no funciona. Ara diu:
Unable to resolve "react-native/Libraries/Components/View/ViewStylePropTypes" from "node_modules/react-native-reanimated/src/createAnimatedComponent.js"
Failed building JavaScript bundle.

"react": "~16.9.0",

Solució: en comptes de fer un git clone, començar un projecte des de zero, i copiar els fitxers:

$ expo init hiker2

és un codi desactualitzat i he hagut de fer uns quants canvis: rutes relatives dels fitxers i maneres de fer imports als mòduls que són d'una versió més antiga.

I finalment ja funciona. Almenys ja tinc un codi que funciona amb react-native i expo, i que no depèn de Google Maps.

Depèn dels tiles de thunderforest.com (no confondre amb OSM, OSM de fet no és un servidor de Tiles) i té la restricció de 150000 peticions/mes.

És un bon punt de partida per començar a pintar els meus arbres sobre un mapa.

Desenvolupament

Solucionat el tema de com programar un mapa que no depengui de Google Maps, en el projecte (que de fet és un procés d'aprenentatge perquè no tenia experiència amb React) he hagut de resoldre aquestes qüestions.

1. componentDidMount() i render(). S'ha de vigilar, i és motiu d'errors. El primer render() es fa abans del componentDidMount(), que és on faig el setState del contingut del fitxer JSON. componentWillMount() funciona, però està deprecated, no s'ha de fer servir. S'ha de tenir molt clar el cicle de vida de l'aplicació, i què es pot fer o no es pot fer en el render(). Concretament, dins el render() no podem canviar l'estat de cap variable.

2. event onStartShouldSetResponder: Tinc el problema de clicar sobre la icona de l'arbre per canviar de vermell a verd. Però no funciona el onPress sobre el component Image (ni sobre View). El onPress està pensat per als Touchables. Però si encapsulo la Image sobre un TouchableOpacity tampoc funciona (a més gent li passa, és una mica tricky). Per sort existeix l'event onStartShouldSetResponder que resol bé el meu problema.

3. Persistència de les dades: AsyncStorage: el component AsyncStorage em permet guardar els settings d'una aplicació. Concretament a mi m'interessa guardar un array dels arbres que ja s'han visitat (pintats de color verd), i aquesta informació s'ha de guardar entre ús i ús de l'aplicació, i també s'ha de carregar al principi. Com es comenta a [2], per llegir/escriure fitxers hi ha el mòdul react-native-fs, però si el que es vol es guardar els settings d'una aplicació amb persistència, amb AsyncStorage n'hi ha prou.

4. How to update a component’s prop:

Tinc el component Grid, on es visualitza el % d'arbres verds, i per això he creat la variable d'estat discoveredPercent. En el Grid es visualitza la capa de Tree, i per tant li passo com a prop aquest valor. Ara bé, és en el component Tree on descubreixo nous arbres, i aquí és on es fa la modificació del valor del percentatge. Per tant, hauria de fer una modificació del props.discoveredPercent i que se n'enteri el Grid, però això a ReactJS no es pot fer.

Whether you declare a component as a function or a class, it must never modify its own props.
React is pretty flexible but it has a single strict rule:
All React components must act like pure functions with respect to their props

Però en aquest enllaç hi ha una pista de com això es pot aconseguir.

Ha funcionat bé, però com es comenta en l'enllaç, aquesta manera de fer no és molt ortodoxa. Els canvis que s'han fet són:

a Grid.js


en el constructor (important):
        this.onDiscoveredPercentChange = this.onDiscoveredPercentChange.bind(this);

en el render():
                <Trees
                    gridData={this.state.gridData}
                    location={this.props.location}
                    zoom={this.props.zoom}
                    discoveredPercent={this.state.discoveredPercent}
                    onDiscoveredPercentChange={this.onDiscoveredPercentChange}
                />
dins del component, definim la funció:
    onDiscoveredPercentChange(newValue) {   
        this.setState({ discoveredPercent: newValue });
        //console.log(newValue)
    }

A Tree.js, en un parell de llocs he de posar:

   this.props.onDiscoveredPercentChange(percent.toFixed(2)) 

i d'aquesta manera el component Grid se n'entera del canvi de percentatge que he fet en el component Tree.

Versions

ArbresBarcelona v1.png

La versió 1.0 és sense mapes, només el FlatList amb el llistat de tots els arbres. Es pot activar el posicionament, i per tant sé quina és la direcció des de la meva posició actual fins a l'arbre. A més a més he calculat el heading, que és la direcció a la que em moc. I per tant, amb aquestes dues dades, ja puc saber en quina direcció m'he de moure/desviar, i això ho mostro amb unes brúixoles.

Tot això funciona, sembla ser que la FlatList està una mica carregada d'informació, i en fase de desenvolupament amb Expo no és molt fluïd i surt el missatge:

VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. Object {

Ara bé, amb el APK va bé, fluid. S'ha de mirar a veure si es pot millorar.

Tot el desenvolupament l'he fet a la carpeta geo-example/, però ara ja he migrat a ArbresBarcelona/.

D'altra banda, he estat desenvolupant a la carpeta thunderforest/ la versió del mapa (finalment mapes sense google Maps, una solució nativa on no cal instal·lar cap llibreria). I ja funciona, i en la versió 2.0 hauré d'integrar la v1.0 amb aquesta funcionalitat (es podrà intercanviar entre les dues pantalles, el llistat i el mapa).

Publicar a la Google Store

Step 1: Create a Developer Account

Nom del desenvolupador: joanillo
joanqc@gmail.com
www.joanillo.org
+34-636517785
identificador del compte de desenvolupador: *******************

Step 2: Plan to Sell? Link Your Merchant Account De moment res.

Step 3: Create an App

Ara falta acabar d'omplir tota la fitxa, crear la icona, captures de pantalla, etiquetes i més coses.

També enllaç a una política de privacitat, dir que l'aplicació és gratuïta, posar una categoria i etiquetes, i bastantes coses més. Després s'ha de pujar el APK que s'ha generat des de Expo. I un cop fet tot, ha de sortir el missatge llest per publicar. Amb aquest missatge no s'ha acabat, he d'aconseguir que surti el missage pendent de publicació. I aleshores esperar (degut al COVID-19, sembla ser que pot trigar una setmana).

Una de les coses que li passa al meu APK i que detecta el Google Store és que és massa gran (pesat). Això ja en sóc conscient, i ve de que s'ha desenvolupat amb el Expo. En el següent apartat es comenta de com es pot reduir el tamany.

Reduir el tamany de l'app generada per Expo

(TBD)


creat per Joan Quintana Compte, maig 2020

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
Institut Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines