Parte 2 – Agregar dependencias de Firebase Messaging

Este post es parte del tutorial Flutter agregar push notificaciones para IOS y Android

Abrir el proyecto flutter en Visual studio code

Ahora buscamos los paquetes que necesitaremos en https://pub.dev y buscar firebase messaging

Click en “Installing” y seguir las instrucciones, para esta versión indica, agregar firebase_messaging: ^7.0.3 en el archivo pubspec.yaml las dependencias

...
dependencies:
  flutter:
    sdk: flutter
  firebase_messaging: ^7.0.3
...

Guardar el archivo y Visual Studio comenzará a obtener las dependencias, esperar que termine.

Si todo va bien debe mostrar el siguiente mensaje

Listo.

Flutter agregar push notificaciones para IOS y Android

Nota: Revisar la fecha y las dependencias usadas, ya que por políticas de las plataformas o nuevas versiones de estas librerías las indicaciones pueden variar

Para los que pueden guiarse de la documentación oficial, solo necesitan:

Sino pueden ver paso a paso.

Parte 1 – Creamos un proyecto Flutter

Este post es parte del tutorial Flutter agregar push notificaciones para IOS y Android

Para este ejemplo creo una aplicación por defecto de flutter

flutter create demo_notifications

Ejecutar proyecto Flutter en emulador IOS desde Visual Studio Code – VSCode

Primero abrir el proyecto en XCode, y correrlo, si tiene problemas puede revisar este post, donde se describe como ejecutarlo en un dispositivo real:

Abrir el proyecto en Visual studio Code, luego en la abrir la terminal, por comodidad lo abriré desde visual studio code, pero tiene el mismo efecto si lo abre aparte.

Luego en la termina ejecutar: flutter devices

Si aparece que detecto el emulador puede correrlo directamente, sino continue leyendo, aparece

No devices detected.

Luego ejecutar : Flutter doctor

Es posible que obtenga el mensaje de advertencias para XCode

[!] Xcode – develop for iOS and macOS
✗ Xcode installation is incomplete; a full installation is necessary for iOS development.
Download at: https://developer.apple.com/xcode/download/
Or install Xcode via the App Store.
Once installed, run:
sudo xcode-select –switch /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -runFirstLaunch

Luego, siga las instrucciones , correr los dos comando que indica:

sudo xcode-select –switch /Applications/Xcode.app/Contents/Developer

sudo xcodebuild -runFirstLaunch

Luego correr : flutter doctor

Deberá aparecer con un check, ademas de mostrar los dispositivos conectados:

[✓] Xcode – develop for iOS and macOS (Xcode 11.3.1)

Ahora puede correr el proyecto, y deberá mostrarse.

Para probar puede cambiar un texto y probar el hot reload.

Ejecutar Flutter en Dispositivo IOS real

Desde el proyecto flutter buscar la carpeta de IOS y el archivo .xcworkspace, doble click y debe abrir XCode

Al terminar de abrir, si lo corre directamente obtendrá este error.

Showing All Messages

Build target Runner of project Runner with configuration Debug

error: Signing for "Runner" requires a development team. Select a development team in the Signing & Capabilities editor. (in target 'Runner' from project 'Runner')<

ir a al archivo raíz, después a “target” y Signing & Capatibilities > Signing

Seleccionar un Team, es posible que tengas que loguearte por expiración

Luego correr nuevamente

Googleando, basicamente es porque iphone no confia en la computadora, puede ir a general>restblecer > restablecer localizacion y provacidad

Fuente: https://developer.apple.com/forums/thread/52048

Una ves hecho eso, no debe salir esa alerta, después , es posible que el teléfono no reaccione, pero revisando , la aplicación fue instalada, presiona sobre ella y obtendrá un mensaje “Desarrollador no fiable”, en XCode también mostrará un mensaje : Could not launch “Runner”, click en ok

Seguir las instrucciones que indica ir a General>Gestion de dispositivo> aparecerá certificado> click en “Confiar…”> en el dialogo click en “Confiar”

Luego puede seleccionar al aplicación o correrlo nuevamente desde XCode y no debería mostrar más mensajes.

Subir proyecto Django a Heroku

Primero crear un proyecto básico, pueden usar la guía de la página oficial:

https://docs.djangoproject.com/es/3.1/intro/tutorial01/

Y después para subirlo pueden seguir este tutorial:

https://www.codementor.io/@jamesezechukwu/how-to-deploy-django-app-on-heroku-dtsee04d4

Básicamente se configura para subirlo a heroku y se hace push a Heroku

Problemas:

Crear el proyecto y aplicaciones con minuscula, tenerlo en mayucula puede traer problemas al subirlo a heroku

Seguir primero el tutorial de django para tener un proyecto, y saltarse es parte en el segundo

En heroku se debe correr las migraciones y el createsupersuser

En caso de errores, se pueden ayudar de la documentación de Heroku:
https://devcenter.heroku.com/articles/django-app-configuration

Para ver los errores tanto de consola y desde la web
https://devcenter.heroku.com/articles/logging#view-logs

Si pip no funciona, probar esta solución: https://askubuntu.com/a/1026848

Para mac por el error pg_config
https://stackoverflow.com/a/24645416

Para whitenoise, hay un error con el static que lo toma como subcarpeta de settings.js, para que tome el directorio correcto corregirlo :

PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

https://stackoverflow.com/a/59895337

PhpStorm reindex – no suggestions

A veces phpstorm no autocompleta automáticamente el código,

si no es por la ram puede probar forzar la reindexacion de los archivos, en File > Invalidate Caches / Restart..

PhpStorm reiniciara y comenzara a indexar

Terminado de reindexar, probando nuevamente muestra las sugerencias

Mas sugerencias aquí https://intellij-support.jetbrains.com/hc/en-us/community/posts/206258359-Is-there-a-way-to-force-indexing-

Ver un mapa en tiempo real las posiciones de conductores

En este ejemplo integro Firebase Realtime y google maps para poder ver en tiempo real las posiciones, de conductores para este ejemplo, y ver algunos datos básicos en un popup al clicar sobre sus pines.

Mejoras pendientes:
– Centrar el mapa por click
– Mostrar el icono del pin en la dirección del movimiento del conductor
– Cambiar el icono del conductor dependiendo de su tipo
– …

Primero mostraremos un mapa.

Ir a la página de google para una guía rápida https://developers.google.com/maps/documentation/javascript/overview

Para agregarlo a mi página copie los scripts

<script
  src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBIwzALxUPNbatRBj3Xi1Uhp0fFzwWNBkE&callback=initMap&libraries=&v=weekly"
  defer
></script>

Agregr el div para el mapa

<div id="map"></div>

Y colocar al final el llamado a la carga del mapa

let map;

function initMap() {
  map = new google.maps.Map(document.getElementById("map"), {
    center: { lat: -34.397, lng: 150.644 },
    zoom: 8,
  });
}

Errores:

Error RefererNotAllowedMapError:
Al colocarlo como está, obtengo el error RefererNotAllowedMapError en consola, parece que se tiene que autorizar la url desde donde se llamara al mapa.

Solución:
Para restringirlo encontré en la documentación https://developers.google.com/maps/documentation/javascript/get-api-key, que configurando la api key se puede restringir desde donde se solicita.
Pueden ir directamente a cada paso desde este link: https://developers.google.com/maps/documentation/javascript/get-api-key#restrict_key

  1. Crear o escoger un proyecto en Google Cloud Console ( crear: https://console.cloud.google.com/projectcreate )
  2. Ir a las credenciales ( https://console.cloud.google.com/apis/credentials ) y crear credenciales, escoger “Clave Api”

Luego click en “Restringir Clave”, luego en el panel seleccionar “URL de referencia” y abajo agregar los sitios desde donde se llamara.

Mas abajo en restricciones api, seleccionar map api, que no aparecerá, la documentación dice que se debe habilitar.
Habilitar Map Api, buscandolo en la biblioteca de apis y habilitarlo

Volviendo al paso anterior ya aparece api maps, y seleccionarlo.

Luego copiar el api key en la llamada al script. ( puede demorar en hacer efecto 5 minutos )

<script
            src="https://maps.googleapis.com/maps/api/js?key=COPIAR_AQUI&callback=initMap&libraries=&v=weekly"
            defer
    ></script>

Error: No muestra el mapa
Es posible que este error solo sea por los estilos de mi sitio, el problema es que al cargar carga por defecto el div map con los estilos:

    position: relative;
    overflow: hidden;

Gracias a https://stackoverflow.com/users/2384642/shiv-singh con los estilos , cambiando el script de inicio y estilos logra mostrarse el mapa.

<div id="map" style="width: 100%;
  height: 400px;
  margin-bottom: 15px;
  border: 2px solid #fff;">
....
<script>
let map;

        function initMap() {
            console.log("cargo");

            var mapOptions = {
                center: new google.maps.LatLng(-12.048905, -76.968220),
                zoom: 15,
                mapTypeId: google.maps.MapTypeId.HYBRID,
                scrollwheel: true,
                draggable: true,
                panControl: true,
                zoomControl: true,
                mapTypeControl: true,
                scaleControl: true,
                streetViewControl: true,
                overviewMapControl: true,
                rotateControl: true,
            };


            map = new google.maps.Map(document.getElementById("map"),mapOptions);


        }
</script>

El texto “For development purpose only” según la documentación se quitara una ves se habilite la facturación.

Listo. Ya vemos un mapa.

Lo siguiente es conectar a firebase realtime para escuchar la posición de cada conductor.

La logica es:

  • Escuchar la ruta principal del nodo de posiciones ( esto variará de cómo hizo el esquema no relacional cada uno).
  • Obtener el nodo raíz , que en nuestro caso es el ID y los dos nodos hijos que son las pociones Latitud y Longitud.
  • Agregar estos datos a un array o matriz, o actualizarlo en caso exista.
  • Según el array también se sabe si ya se agrego un marcador en el mapa, el cual se agrega o actualiza.
  • Listo.

Empezamos.

Escuchar la ruta principal del nodo de posiciones ( esto variará de cómo hizo el esquema no relacional cada uno).

Agregamos los script para firebase realtime database

 
  <!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->

  <!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>

  <!-- Add Firebase products that you want to use -->
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-database.js"></script>

Configuramos Firebase

  <script>
    // TODO: Replace the following with your app's Firebase project configuration
    var firebaseConfig = {
      apiKey: "??",
     // authDomain: "??",
      databaseURL: "??",
      projectId: "??",
      storageBucket: "??",
      appID: "??",
    };

    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
</script>

Para obtener apiKey, ir a configuración y luego General>”Clave api de la web”

Para databaseurl , ir a realtime database>datos>click en clip y copiar la url de la base de datos

projectId, es el ID del projecto, que esta en configuración>general>Id Del Proyecto.

Con la configuración ya podemos escuchar los cambios en la base de datos, colocando el siguiente script

var logref = firebase.database().ref('RUTA_DEL NODO');
           logref.on('value', function(snapshot) {
               console.log("Entro");
               console.log(snapshot.val())
           });

obtenemos una referencia al nodo y despues imprimimos los datos,

Para este caso no se obtuvo errores en la consola.

Siguiente:

Obtener el nodo raíz , que en nuestro caso es el ID y los dos nodos hijos que son las pociones Latitud y Longitud.

Ya obtenemos el valor de firebase, obtener los otros datos depende de como lo esquematizaron, en mi caso, tengo que parsear el nodo raíz y dos hijos:

logref.on('value', function(snapshot) {
            console.log("Entro");
            console.log(snapshot.val());

            $driverIds = Object.getOwnPropertyNames(snapshot.val());

            $driverIds.forEach( function(valor, indice, array) {
                $lat = snapshot.val()[valor].position.lat;
                $lon = snapshot.val()[valor].position.lon;
                $idDriver = valor.replace("driver_", "");

                console.log($lat,$lon,$idDriver);
            });
        });

List, ahora.

Agregar estos datos a un array o matriz, o actualizarlo en caso exista.

Analizando esto va depende como referencia los marcadores, con eso implementado decidiré como guardarlos.

Según el array también se sabe si ya se agrego un marcador en el mapa, el cual se agrega o actualiza.

Para agregar un markador, siguiendo el tutorial de google maps https://developers.google.com/maps/documentation/javascript/markers#add , con los datos que obtuvimos de lat lon y iddriver

$myLatLng = { lat: $lat, lng: $lon };

                new google.maps.Marker({
                    position: $myLatLng,
                    map,
                    title: "Conductor "+$idDriver
                });

Si se deja como esta, cuando se actualiza la posición aparece otro marcador

para eso asociamos el marcador a una hashmap, creamos un hashmap y creamos una función que se encarga de validar si existe o no.

var markers = {};
        function addMarker($id,$lat,$lon){
            $idDriver = 'driver_'+$id;

            if($idDriver in markers){
                console.log("existe");
                //existe
                var latlng = new google.maps.LatLng($lat,$lon);
                markers[$idDriver].setPosition(latlng);
            }
            else{
                console.log("no existe");
                //no existe
                $myLatLng = { lat: $lat, lng: $lon };

                markers[$idDriver] = new google.maps.Marker({
                    position: $myLatLng,
                    map,
                    title: "Conductor "+$idDriver
                });
            }
        }

**Como el mapa carga asincronamente es posible que firebase cargue primero, para eso firebase cargara despues de que inicie el mapa, moviendo un poco el orden quedaría asi.

<!--MAPS-->
    <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
    <script
            src="https://maps.googleapis.com/maps/api/js?key=XXXXXX&callback=initMap&libraries=&v=weekly"
            defer
    ></script>


    <!--FIREBASE-->
    <!-- Insert these scripts at the bottom of the HTML, but before you use any Firebase services -->

    <!-- Firebase App (the core Firebase SDK) is always required and must be listed first -->
    <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>

    <!-- Add Firebase products that you want to use -->
    <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-auth.js"></script>
    <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
    <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-database.js"></script>

    


    <!--INI FIREBASE-->
    <script>
        var markers = {};
        function addMarker($id,$lat,$lon){
            $idDriver = 'driver_'+$id;

            if($idDriver in markers){
                console.log("existe");
                //existe
                var latlng = new google.maps.LatLng($lat,$lon);
                markers[$idDriver].setPosition(latlng);
            }
            else{
                console.log("no existe");
                //no existe
                $myLatLng = { lat: $lat, lng: $lon };

                markers[$idDriver] = new google.maps.Marker({
                    position: $myLatLng,
                    map,
                    title: "Conductor "+$idDriver
                });
            }
        }

        function configurarFirebase() {

            var firebaseConfig = {
                apiKey: "XXXXXX",
                databaseURL: "XXXXXX",
                projectId: "XXXXXX"
            };

            // Initialize Firebase
            firebase.initializeApp(firebaseConfig);


            var logref = firebase.database().ref('xxxxxx/position');
            logref.on('value', function(snapshot) {
                console.log("Entro");
                console.log(snapshot.val());

                $driverIds = Object.getOwnPropertyNames(snapshot.val());

                $driverIds.forEach( function(valor, indice, array) {
                    $lat = snapshot.val()[valor].position.lat;
                    $lon = snapshot.val()[valor].position.lon;
                    $idDriver = valor.replace("driver_", "");

                    console.log($lat,$lon,$idDriver);
                    addMarker($idDriver,$lat,$lon);

                });


            });
        }



    </script>
    <!--FIN FIREBASE-->


    <!--INI MAPA-->
    <script>
        let map;

        function initMap() {
            console.log("cargo");

            var mapOptions = {
                center: new google.maps.LatLng(-2.048905, -6.968220),
                zoom: 15,
                mapTypeId: google.maps.MapTypeId.HYBRID,
                scrollwheel: true,
                draggable: true,
                panControl: true,
                zoomControl: true,
                mapTypeControl: true,
                scaleControl: true,
                streetViewControl: true,
                overviewMapControl: true,
                rotateControl: true,
            };
            
            map = new google.maps.Map(document.getElementById("map"),mapOptions);
            
            configurarFirebase();

        }

    </script>
    <!--FIN MAPA-->