App mòbil amb React Native: arbres singulars de Barcelona
Contingut
- 1 Introducció
- 2 MapView i react-native-maps (Google Maps)
- 3 Projecte react-native-open-street-map (sense Google Maps). Primavera 2020
- 4 Mapes sense Google Maps i amb Expo (solució final). Primavera 2020
- 5 Desenvolupament
- 6 Publicar a la Google Store
- 7 React-Native i mapes (primavera 2022)
- 8 Reduir el tamany de l'app generada per Expo
Introducció
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:
- la primera és un llistat dels arbres, on s'indica la distància dels arbres fins la nostra posició actual. Quan cliquem sobre un arbre es posa de color verd, que significa que l'hem visitat.
- La segona pantalla ha de ser el mapa fet amb Openlayers/Leaflet. Però aquesta part de posar mapes amb Reactnative encara no ho tinc controlat. (mirar [1])
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
- Go back to https://console.developers.google.com/apis/credentials and click Create Credentials, then API Key.
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:
- https://github.com/react-native-community/react-native-maps/blob/master/example/examples/DefaultMarkers.js
- EventListener.js
- FitToSuppliedMarkers.js -> aquest codi m'interessa. Donat un conjunt de markers, posicionar-los en el mapa de manera que hi càpiguen tots.
- MarkerTypes.js -> interessa com a exemple de onPress sobre el Marker
- MassiveCustomMarkers.js
- MyLocationMapMarker.js ?
- OnPoiClick.js
- TestIdMarkers.js
- ViewsAsMarkers.js
Projecte react-native-open-street-map (sense Google Maps). Primavera 2020
É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:
- https://expo.canny.io/feature-requests/p/open-street-maps-integration
- https://expo.canny.io/feature-requests/p/add-mapbox-gl-support
- https://stackoverflow.com/questions/53144067/creating-an-osm-map-app-with-react-native-and-expo
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). Primavera 2020
É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:
- https://tile.thunderforest.com/
- Hobby Project: Free (Tile requests per month 150,000)
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
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
- https://themanifest.com/mobile-apps/how-publish-app-google-play-step-step-guide
- https://reactnative.dev/docs/signed-apk-android
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
- idioma: català - ca
- títol: Arbres Singulars de Barcelona
- descripció breu: Visita i col·leccionar tots els arbres monumentals de Barcelona
- descripció completa: Llistat de tots els arbres catalogats de Barcelona. Es mostra distància i direcció per arribar finns l'arbre, i pots marcar els arbres visitats. A veure si pots completar tota la col·lecció. Són 146 arbres distribuïts per tots els barris.
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.
React-Native i mapes (primavera 2022)
Resum
Després de dos anys, reprenem el projecte i miro si hi ha hagut algun progrés amb la possibilitat d'utiltizar la llibreria openlayers a react-native. La conclusió és que no especialment. Si vull utilitzar Expo, puc utilitzar react-native-maps amb Google Maps. Si vull utilitzar els mapes de OSM la única possibilitat és utilitzar el projecte hiker i modificar-lo (tal com s'explica). hiker ja m'havia funcionat el 2020, i ara el 2022 també em funciona i he aconseguit posar els mapes de OSM i de thunderforest.
Aquí va un resum de les proves que he fet, del que ha funcionat i del que no. Cal dir que hi ha coses que no han funcionat perquè són projectes pensats per a Android Studio i Gradle, i jo de moment vull continuar amb Expo com a entorn de desenvolupament. Si instal·lo i utilitzo Android Studio podria fer funcionar alguns dels projectes que es comenten.
A. react-native-maps
- https://github.com/react-native-maps/react-native-maps -> utilitza els mapes de Google Maps (es necessita una key)
conclusió: funciona amb Expo i Google Maps. No he aconseguit fer funcionar els mapes de OSM.
Però sí que hi ha una possibilitat d'utilitzar aquest enllaç, com s'explica a:
You can use a custom Tile Overlay (OpenStreetMap too) in react-native-maps package: https://github.com/react-native-maps/react-native-maps
(scroll to Using a custom Tile Overlay section)
You need to add api key. You don't need to use Maps API at all and the key won't be used in that case.
Mans a l'obra:
Primer de tot creo una API key de Google Maps, doncs la necessitaré, encara que no es faria servir si vull els mapes de OSM:
key=API_KEY AIzaSyDNocxQJ6Dt9CnT001Zfrk0qVF1pUa2kiw
El problema està en què aquest projecte està pensat per a Android Studio. Per exemple, la API_KEY està dins del Manifest.xml.
I react-native-maps amb Expo? Sí que és possible:
Dins del package.json puc posar la API KEY:
"android": {
"config": {
"googleMaps": {
"apiKey": "AIzaSyDNocxQJ6Dt9CnT001Zfrk0qVF1pUa2kiw"
}
}
}
Creo el projecte:
$ expo init react-native-maps-exemple $ expo install react-native-maps
copio a Apps.js el codi que se'ns proposa a l'enllaç, i fem npm start i ja està, ja tenim un mapa (de moment amb Google Maps)
$ npm start
El MapView bàsic de l'exemple, que representa un mapa de tot el món:
<MapView style={styles.map} />
I ara ja puc fer coses més interessants:
<MapView
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}} style={styles.map}
/>
Per tant, funciona bé amb Google Maps, i a partir d'aquí no hauria de ser difícil crear els markers i fer coses més interessants, com es mostra a la documentació.
I ara puc fer una cosa que no queda gens clara, en el MapView puc posar:
mapType={Platform.OS == "android" ? "none" : "standard"}
i efectivament em desapareix el mapa de Google Maps (ja no es renderitza), però no queda clar com s'ha de fer per renderitzar els mapes de OSM.
La solució vindria per react-native-maps-osmdroid, que és un clon de react-native-maps pensat per a OSM. Però és Android Studio
Si vull fer servir Google Maps aquesta sí que és una solució, que a partir de l'exemple bàsic el puc fer créixer i fer el mapa amb les meves necessitats.
B. Leaflet (per explorar)
Una possibilitat és utilitzar Leaflet:
C. (per explorar)
Anem a provar:
$ expo init react-native-map-exemple
No l'he pogut fer funcionar, no recordo per què.
D.react-native-open-street-map (no ha funcionat, Android Studio)
Una altra prova:
Hauria de fer primer crear un nou projecte i també fer el git clone, i barrejar-ho, doncs m'interessa el codi del clone, però jo desenvolupo amb expo i necessito l'esquelet del Expo.
$ expo init react-native-open-street-map-prova
$ git clone https://github.com/Multiware-Tecnologia/react-native-open-street-map $ cd react-native-open-street-map/ $ npm install -g expo-cli (cal si ja ho havia fet? crec que no...)
Instal·lo totes les dependències
$ npm install
No funciona...
E. react-native-maps-osmdroid
El problema està en què també està pensat per a Andoid Studio. A veure si funciona...
$ expo init react-native-maps-osmdroid-exemple $ cd react-native-maps-osmdroid-exemple $ expo install react-native-maps $ expo install react-native-maps-osmdroid
copio a Apps.js el codi que se'ns proposa a l'enllaç, i fem npm start i ja està, ja tenim un mapa (de moment amb Google Maps), igual que funcionava el react-native-maps. Però no funciona amb OSM
$ npm start
F. Mapbox, per explorar
Es recomana aquest enllaç, i s'hauria d'explorar
A mi en principi Mapbox no m'interessa, però per aquesta via tindria els tiles de OSM.
G.
Reduir el tamany de l'app generada per Expo
(TBD)
creat per Joan Quintana Compte, maig 2020, maig 2022