DEV Community

Cover image for How To Fetch Nearby Places From Google Maps with Flutter?
Nibesh Khadka
Nibesh Khadka

Posted on • Updated on

How To Fetch Nearby Places From Google Maps with Flutter?

Hello and welcome back to Flutter App Dev Tutorial Series. Before this, we have already made a splash screen, wrote a theme, made a custom app bar, made authentication screen, set up a connection with firebase cloud and emulators, authenticated users and accessed user's location on firebase projects.

About

As a part of the user-screen flow, we already have access to the user location. On this 10th installment, we'll use the user's location to fetch the nearest twenty temples using google's Places API. We'll fetch places with the HTTP package, then we'll write another HTTPS Callable with Firebase Functions to store those temples in Firestore. Since we're using an API key, we'll use the flutter_dotenv package to keep it secret. Find the source code to start this section from here.

Packages

DotEnv

So, first lets install flutter_dotenv package.

flutter pub add flutter_dotenv
Enter fullscreen mode Exit fullscreen mode

Create a .env file at the root of your project.

touch .env
Enter fullscreen mode Exit fullscreen mode

Add the .env file in .gitignore file.

#DOT ENV
*.env
Enter fullscreen mode Exit fullscreen mode

Initialize .env file in main() method of our main.dart file.

import 'package:flutter_dotenv/flutter_dotenv.dart';

void main() async {
....

 // Initialize dot env
  await dotenv.load(fileName: ".env");
// Pass prefs as value in MyApp
  runApp(MyApp(prefs: prefs));
}
Enter fullscreen mode Exit fullscreen mode

We also need to add the .env file to the assets section of the pubspec.yaml file.

#find assets section 
assets:
    # Splash Screens
    - assets/splash/om_splash.png
    - assets/splash/om_lotus_splash.png
    - assets/splash/om_lotus_splash_1152x1152.png
    # Onboard Screens
    - assets/onboard/FindTemples.png
    - assets/onboard/FindVenues.png
    # Auth Screens
    - assets/AuthScreen/WelcomeScreenImage_landscape_2.png  
// add this here
   # Dotenv flie
    - .env
Enter fullscreen mode Exit fullscreen mode

Google Maps Places API

To use the google places API we'll need the places API key. For that please go to google cloud console, set up a billing account, and create a new API key for Goog Places API. Then add that API key in the .env file.

.env

#Without quotes
GMAP_PLACES_API_KEY = Your_API_KEY

Enter fullscreen mode Exit fullscreen mode

HTTP

Install the HTTP package.

flutter pub add http
Enter fullscreen mode Exit fullscreen mode

There's no need for extra setup with the HTTP package.

Folder Structures

Like Home and Auth Folders, the temples directory will have all the files associated with temples. So, let's create various files and folders we'll use to fetch & display temples.

# Make folders
mkdir  lib/screens/temples lib/screens/temples/providers lib/screens/temples/screens lib/screens/temples/widgets lib/screens/temples/models lib/screens/temples/utils

# Make files
touch lib/screens/temples/providers/temples_provider.dart lib/screens/temples/screens/temples_screen.dart  lib/screens/temples/widgets/temples_item_widget.dart lib/screens/temples/models/temple.dart lib/screens/temples/utils/temple_utils.dart

Enter fullscreen mode Exit fullscreen mode

Like the blogs before it, we'll keep our all apps logic inside the provider file. On top of that, we'll also need a utils file to store a few functions that we'll use on the provider class. So, first, let's create two simple functions of temple_utils.dart

Utilities

temple_utils.dart

import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';

class TemplesUtils {
  // Base url for google maps nearbysearch
  // #1
  static const String _baseUrlNearBySearch =
      "https://maps.googleapis.com/maps/api/place/nearbysearch/json?";
  // get api key
  // #2
  final String _placesApi = dotenv.env['GMAP_PLACES_API_KEY'] as String;

  // Create a method that'll parse complete url and return it using http package
  // #3
  Uri searchUrl(LatLng userLocation) {
    // Create variables that'll pass maps API parmas as string
  // # 4
//===================//
    final api = "&key=$_placesApi";

    final location =
        "location=${userLocation.latitude},${userLocation.longitude}";

    const type = "&type=hindu_temple";
    // Closest first
  // #5
    const rankBy = "&rankby=distance";
//=====================//
    // Parse URL to get a new uri object
  // #6
    final url =
        Uri.parse(_baseUrlNearBySearch + location + rankBy + type + api);

    return URL;
  }
}

Enter fullscreen mode Exit fullscreen mode
  1. Google Maps NearbySearch takes several parameters, among them we'll use location(required parameter), rankby and type .
  2. Import API key from .env file.
  3. Search Url is a function that'll take user location then, combine the base URL acceptable parameter by search API and return parsed URI.
  4. The API key is a must, on every URL to get a result back from API.
  5. The rankby="distance" sorts the search results based on distance. When we're using rankby the type parameter is required.
  6. The final URL to be used by the HTTP package to search for temples.

*Note: If you're from a place that doesn't have temples(some or not at all) you probably won't see any results. So, use something else for the establishment type. *

Another method will be a simple mapper, its sole purpose is to map the incoming list into a list of TempleModels(which we'll create next) and return it as such. This will make our code later much cleaner.

 List<TempleModel> mapper(List results) {
    final newList = results
        .map(
          (temple) => TempleModel(
            name: temple['name'],
            address: temple['address'],
            latLng: LatLng(
              temple['latLng']['lat'],
              temple['latLng']['lon'],
            ),
            imageUrl: temple['imageRef'],
            placesId: temple['place_id'],
          ),
        )
        .toList();

    return newList;
  }
Enter fullscreen mode Exit fullscreen mode

Temple Model

The Temple model class will define a framework for the information to be stored about the temple. On the temple.dart file inside models let's create a temple model.

import 'package:google_maps_flutter/google_maps_flutter.dart';

class TempleModel {
  // name of temple
  final String name;
  // the address
  final String address;
  // geo location
  final LatLng latLng;
  // ImageUrls
  final String imageUrl;
  // id given to each item by places api
  final String placesId;

  const TempleModel(
      {required this.name,
      required this.address,
      required this.latLng,
      required this.imageUrl,
      required this.placesId});
}

Enter fullscreen mode Exit fullscreen mode

Each temple that'll be saved in Firestore will have a name, address, geographical coordinates, imageUrl, and an ID given by google's place API.

Google Place API with HTTP and Provider

Now, it's time to write a provider class that'll take care of fetching the nearby temples list. This will be a long file with many things to explain. So, we'll go piece by piece codes from top to bottom.

Import Modules and Create a provider class.


import 'dart:convert';
import 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart' as firbase_storage;
import 'package:flutter/foundation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;

//custom modules
import 'package:temple/screens/temples/models/temple.dart';
import 'package:temple/screens/temples/utils/temple_utils.dart';

class TempleProvider with ChangeNotifier {}
Enter fullscreen mode Exit fullscreen mode

Inside the class, instantiate Firebase Products we will be using.

// Instantiate FIbrebase products
  final FirebaseAuth auth = FirebaseAuth.instance;
  final FirebaseFunctions functions = FirebaseFunctions.instance;
  final FirebaseFirestore firestore = FirebaseFirestore.instance;
  // Estabish sotrage instance for bucket of our choice
  // once e mulator runs you can find the bucket name at storage tab
  final firbase_storage.FirebaseStorage storage =
      firbase_storage.FirebaseStorage.instanceFor(
          bucket: 'astha-being-hindu-tutorial.appspot.com');
Enter fullscreen mode Exit fullscreen mode

With the Firebase Storage we will not use a default bucket to store images whose URL will be fetched to save on Firestore. So, what's up with this logic? You see google places API doesn't provide images, it's provided by details api. We'll not be using it. But instead, I have some random(4 in numbers) Hindu images that I downloaded from Unsplash, which I'll store in storage and fetch a random URL among images and assign it to the temple model. You don't have to do it and provide the hardcoded image URL for imageRef, but it's to practice reading storage.

Other Fields and Getters

// Instantiate Temple Utils
final TemplesUtils templesUtils = TemplesUtils();
  // Create the fake list of temples
  List<TempleModel>? _temples = [];
  // User location from db
  LatLng? _userLocation;

// Getters
  List<TempleModel> get temples => [..._temples as List];
  LatLng get userLocation => _userLocation as LatLng;

// List of Images

  static const List<String> imagePaths = [
    'image_1.jpg',
    'image_2.jpg',
    'image_3.jpg',
    'image_4.jpg',
  ];
Enter fullscreen mode Exit fullscreen mode

The imagePaths is a list of literally the name of images that I've uploaded in a folder named "TempleImages" inside our bucket we referenced earlier in Emulator's Storage.

Future To Return Places API Result


// Future method to get temples
   Future<List<TempleModel>?> getNearyByTemples(LatLng userLocation) async {
    // Get urls from the temple utils
    // #1
    Uri url = templesUtils.searchUrl(userLocation);

    try {
      // Set up references for firebase products.
      // Callable getNearbyTemples
       // #2
      HttpsCallable getNearbyTemples =
          functions.httpsCallable('getNearbyTemples');
      // Collection reference for temples
      // # 3
      CollectionReference templeDocRef = firestore.collection('temples');
      // Get one doc from temples collection
      // #4
      QuerySnapshot querySnapshot = await templeDocRef.limit(1).get();
      // A reference to a folder in storage that has images.
      // #5
      firbase_storage.Reference storageRef = storage.ref('TempleImages');

      // We'll only get nearby temples if the temple's collection empty
      // #6
      if (querySnapshot.docs.isEmpty) {
        print("Temple collection is empty");
        // get the result from api search
       // #7
        final res = await http.get(url);
        // decode to json result
       // #8
        final decodedRes = await jsonDecode(res.body) as Map;
        // get result as list
       // #9
        final results = await decodedRes['results'] as List;
        // Get random image url from available ones to put as images
        // Since we have 4 images we'll get 0-3 values from Random()
       // #10
        final imgUrl = await storageRef
            .child(imagePaths[Random().nextInt(4)])
            .getDownloadURL();
        // Call the function
       // #11
        final templesListCall = await getNearbyTemples.call(<String, dynamic>{
          'templeList': [...results],
          'imageRef': imgUrl,
        });

        // map the templesList returned by https callable
       // we'll use utils mapper here
       // #12
        final newTempleLists = templesUtils.mapper(templesListCall.data['temples']);
        // update the new temples list
       // #13
        _temples = [...newTempleLists];
      } else {
        // If the temples collection already has temples then we won't write
        // but just fetch temples collection
       // #14
        print("Temple collection is not empty");
        try {
          // get all temples documents
          final tempSnapShot = await templeDocRef.get();
          // fetch the values as list.
          final tempList = tempSnapShot.docs[0]['temples'] as List;
          // map the results into a list
          final templesList = templesUtils.mapper(tempList);
          // update temples
          _temples = [...templesList];
        } catch (e) {
          // incase of error temples list in empty
       // # 15
          _temples = [];
        }
      }
    } catch (e) {
      // incase of error temples list in empty
      _temples = [];
    }
// notify all the listeners
    notifyListeners();
 // #16
  return _temples;
  }
Enter fullscreen mode Exit fullscreen mode

Alright, now the main method that'll do everything we've worked on so far in this blog "getNearyByTemples" has been created. Let's go by numbers:

  1. User the temple utils we created earlier to get the URL ready to be used by the HTTP package.

  2. Reference to the HTTP callable getNearyByTemples, which we'll create after this provider session. It's responsible to save the list of all the temples we fetch during this search.

  3. Reference to temple collection.

  4. Temple reference be used to read a single document from the collection.

  5. References to a folder named "TempleImages" inside the bucket of storage.

  6. We're checking if the temple doc we fetched earlier is empty. The logic is that we don't want to call for Place Api, Firestores, and Functions every time user uses our app. We'll only fetch temples and save them on FireStore if the temple's collection is empty or doesn't exist.

  7. HTTP get() method can be used to fetch the results. You can use software like postman or just a chrome browser to see the results of the get request.

  8. Json Decode Function parses strings to json data types.

  9. Places API provides the response of a list of temples as a list with results as its key. We'll extract that as the List type.

  10. Firebase Storage provided the means to download URL from the reference. We're randomly downloading a URL and assigning it to imageRef property needed in our HTTPS callable.

  11. We call our HTTPS callable now, provide with temples list and image's Url. It'll save the list in Firesotre's Temples collection and return that list.

  12. The returned list will be now used to update our List of Temple Models using the mapper method of Temple Utils.

  13. This same list will be used by our app to display a beautiful list of temple cards on the temples screen.

  14. The else block only executes if the temple's collection already has a list of temples. In that case unlike in if block we do not fetch the temples list from API, we just read all the documents that are saved in the temple's collection. After this process is the same as above.

  15. In case of errors, the temples list will be empty.

  16. It is very important to return this new list. We will need this List as QurerySnapshot data fetched by FutureBuilder to display it on our app.

Write Google Places API Search Results On FireStore With Firebase Functions

Inside the index.js file we'll now create another HTTPS callable function "getNearbyTemples". This method will create an array with the list of temple objects and then save it to the temples collection.

exports.getNearbyTemples = functions.https.onCall(async (data, _) => {

    try {
        // Notify function's been called
        functions.logger.log("Add nearby temples function was called");
        // Create array of temple objects.
        let temples = data.templeList.map((temple) => {
            return {
                'place_id': temple['place_id'],
                'address': temple['vicinity'] ? temple['vicinity'] : 'Not Available',
                'name': temple['name'] ? temple['name'] : 'Not Available',
                'latLng': {
                    'lat': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lat'] : 'Not Available', 'lon': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lng'] : 'Not Available',
                    'dateAdded': admin.firestore.Timestamp.now()
                },
                'imageRef': data.imageRef
            }
        }

        );

        // save the temples array to temples collection as one document named temples 
        await db.collection('temples').add({ temples: temples });

    } catch (e) {
        // if error return errormsg
        return { 'Error Msg': e };
    }
    // If everything's fine return the temples array.
    return temples;
});

Enter fullscreen mode Exit fullscreen mode

I have not allocated memory for this operation. It was very tricky and time-consuming. If you want you can experiment. A firebase document can store up to 1MB in size. So, our list at least for this app will never grow beyond 20. So, inside the temple's collection, we are not saving 20 documents but one document with 20 items as a field "temples", db.collection('temples').add({ temples: temples }).

Handle Updates With Firestore Triggers

Let's say the user changed location or a new temple has been added to the google database. It should be reflected in the Firestore Temples collection. But we should handle updates carefully and only write new documents if there are any changes to the old ones. For our temples collection, we can just match the places_id, and only take action accordingly. Firebase provides onUpdate() trigger to handle this type of work. Now, let's write some code on the index.js.


// When the temple List Updates
exports.updateNearbyTemples = functions.runWith({
    timeoutSeconds: 120,
    memory: "256MB"
}).firestore.document('temples/{id}').onUpdate(async (change, context) => {
    // If theres both new and old value
    if (change.before.exists && change.after.exists) {
        // temples list both new and old
        let newTemplesList = change.after.data()['temples'];
        let oldTemplesList = change.before.data()['temples'];

        // Places Id list from both new and old list
        let oldTemplesIdList = oldTemplesList.map(temple => temple['place_id']);
        let newTemplesIdList = newTemplesList.map(temple => temple['place_id']);

        // Lets find out if theres new temples id by filtering with old one
        let filteredList = newTemplesIdList.filter(x => !oldTemplesIdList.includes(x));

        // if the length are not same of fileted list has 
        //length of 0 then nothing new is there so just return
        if (oldTemplesIdList.length != newTemplesIdList.length || filteredList.length == 0) {
            functions.logger.log("Nothing is changed so onUpdate returned");
            return;
        }
        // If somethings changed then 
        try {
            functions.logger.log("On Update was called ");
            // Make new list of temples
            let temples = newTemplesList.map((temple) => {
                return {
                    'place_id': temple['place_id'],
                    'address': temple['vicinity'] ? temple['vicinity'] : 'Not Available',
                    'name': temple['name'] ? temple['name'] : 'Not Available',
                    'latLng': {
                        'lat': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lat'] : 'Not Available', 'lon': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lng'] : 'Not Available',
                        'dateAdded': admin.firestore.Timestamp.now()
                    }
                }
            }
            );
            // use the current context id to update temples, no need to merge
            await db.collection('temples').doc(context.params.id).set({ 'palces_id_list': newTemplesIdList, temples: temples });
        }
        catch (e) { throw e; }
        return { 'status': 200 };
    }
    // return nothing
    return null;
});
Enter fullscreen mode Exit fullscreen mode

Changes.before.data gives values already in the Firestore and changes.after.data gives value newly gotten from the API. With this update, the function will not run every time the user loads the temples screen. It will save us lots of money on production mode.

Making Provider Class available for Widgets

Now, our classes are ready for work. So, let's make them available by updating the MultiProviders list in the app.dart file.

 MultiProvider(
      providers: [
       ...
        ChangeNotifierProvider(create: (context) => TempleProvider()),
        ...
      ],
...
Enter fullscreen mode Exit fullscreen mode

Now, the GetNearbyTemples method is accessible for all the descendants of MultiProviders. So, where exactly are we going to call this method? Well in the next blog, We'll make our home page a little bit better looking. On that homepage, there will be a link to Temple List Screen. In method will be executed when the link is clicked. For now, let's end this blog before we derail from the main theme of the blog.

KCl

Summary

Before we leave let's summarize what we did so far.

  1. We created google maps places API.
  2. By installing the Flutter_DotEnv package, we secured the API from being public.
  3. HTTP package was also added which played a vital role in fetching temples lists from API.
  4. We created a utility file though with just one util. Later on, if you want, you can write a distance calculator method from user to temple to represent in google Maps.
  5. We then wrote a method in our provider class, that fetched a search list, and passes it to firebase cloud functions.
  6. The firebase function saves the temples lists to Firestore if the collection is empty.
  7. We then wrote an update trigger, that'll only run when the value is changed.

Final Code

temple_provider.dart

import 'dart:convert';
import 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_storage/firebase_storage.dart' as firbase_storage;
import 'package:flutter/foundation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:http/http.dart' as http;

//custom modules
import 'package:temple/screens/temples/models/temple.dart';
import 'package:temple/screens/temples/utils/temple_utils.dart';

class TempleProvider with ChangeNotifier {
  // Instantiate FIbrebase products
  final FirebaseAuth auth = FirebaseAuth.instance;
  final FirebaseFunctions functions = FirebaseFunctions.instance;
  final FirebaseFirestore firestore = FirebaseFirestore.instance;
  // Estabish sotrage instance for bucket of our choice
  // once e mulator runs you can find the bucket name at storage tab
  final firbase_storage.FirebaseStorage storage =
      firbase_storage.FirebaseStorage.instanceFor(
          bucket: 'astha-being-hindu-tutorial.appspot.com');

// Instantiate Temple Utils
  final TemplesUtils templesUtils = TemplesUtils();
  // Create the fake list of temples
  List<TempleModel>? _temples = [];
  // User location from db
  LatLng? _userLocation;

// Getters
  List<TempleModel> get temples => [..._temples as List];
  LatLng get userLocation => _userLocation as LatLng;

// List of Images
  static const List<String> imagePaths = [
    'image_1.jpg',
    'image_2.jpg',
    'image_3.jpg',
    'image_4.jpg',
  ];

// Future method to get temples
  Future<void> getNearyByTemples(LatLng userLocation) async {
    // Get urls from the temple utils
    Uri url = templesUtils.searchUrl(userLocation);

    try {
      // Set up references for firebase products.
      // Callable getNearbyTemples
      HttpsCallable getNearbyTemples =
          functions.httpsCallable('getNearbyTemples');
      // COllection reference for temples
      CollectionReference templeDocRef = firestore.collection('temples');
      // Get one doc from temples collection
      QuerySnapshot querySnapshot = await templeDocRef.limit(1).get();
      // A reference to a folder in storage that has images.
      firbase_storage.Reference storageRef = storage.ref('TempleImages');

      // We'll only get nearby temples if the temples collection empty
      if (querySnapshot.docs.isEmpty) {
        print("Temple collection is empty");
        // get the result from api search
        final res = await http.get(url);
        // decode the json result
        final decodedRes = await jsonDecode(res.body) as Map;
        // get result as list
        final results = await decodedRes['results'] as List;
        // Get random image url from available ones to put as images
        // Since we have 4 images we'll get 0-3 values from Random()
        final imgUrl = await storageRef
            .child(imagePaths[Random().nextInt(4)])
            .getDownloadURL();
        // Call the function
        final templesListCall = await getNearbyTemples.call(<String, dynamic>{
          'templeList': [...results],
          'imageRef': imgUrl,
        });

        // map the templesList restured by https callable
        final newTempleLists = templesListCall.data['temples']
            .map(
              (temple) => TempleModel(
                name: temple['name'],
                address: temple['address'],
                latLng: LatLng(
                  temple['latLng']['lat'],
                  temple['latLng']['lon'],
                ),
                imageUrl: temple['imageRef'],
                placesId: temple['place_id'],
              ),
            )
            .toList();
        // update the new temples list
        _temples = [...newTempleLists];
      } else {
        // If the temples collection already has temples then we won't write
        // but just fetch temples collection
        print("Temple collection is not empty");

        try {
          // get all temples documents
          final tempSnapShot = await templeDocRef.get();
          // fetch the values as list.
          final tempList = tempSnapShot.docs[0]['temples'] as List;
          // map the results into a list
          final templesList = tempList
              .map(
                (temple) => TempleModel(
                  name: temple['name'],
                  address: temple['address'],
                  latLng: LatLng(
                    temple['latLng']['lat'],
                    temple['latLng']['lon'],
                  ),
                  imageUrl: temple['imageRef'],
                  placesId: temple['place_id'],
                ),
              )
              .toList();
          // update temples
          _temples = [...templesList];
        } catch (e) {
          // incase of error temples list in empty
          _temples = [];
        }
      }
    } catch (e) {
      // incase of error temples list in empty

      _temples = [];
    }
// notify all the listeners
    notifyListeners();
  }
}


Enter fullscreen mode Exit fullscreen mode

index.js

// Import modiules
const functions = require("firebase-functions"),
    admin = require('firebase-admin');

// always initialize admin 
admin.initializeApp();

// create a const to represent firestore
const db = admin.firestore();


// Create a new background trigger function 
exports.addTimeStampToUser = functions.runWith({
    timeoutSeconds: 240,  // Give timeout 
    memory: "512MB" // memory allotment 
}).firestore.document('users/{userId}').onCreate(async (_, context) => {
    // Get current timestamp from server
    let curTimeStamp = admin.firestore.Timestamp.now();
    // Print current timestamp on server
    functions.logger.log(`curTimeStamp ${curTimeStamp.seconds}`);

    try {
        // add the new value to new users document i
        await db.collection('users').doc(context.params.userId).set({ 'registeredAt': curTimeStamp, 'favTempleList': [], 'favShopsList': [], 'favEvents': [] }, { merge: true });
        // if its done print in logger
        functions.logger.log(`The current timestamp added to users collection:  ${curTimeStamp.seconds}`);
        // always return something to end the function execution
        return { 'status': 200 };
    } catch (e) {
        // Print error incase of errors
        functions.logger.log(`Something went wrong could not add timestamp to users collectoin ${curTimeStamp.seconds}`);
        // return status 400 for error
        return { 'status': 400 };
    }
});

// Create a function named addUserLocation 
exports.addUserLocation = functions.runWith({
    timeoutSeconds: 60,
    memory: "256MB"
}).https.onCall(async (data, context) => {

    try {
        // Fetch correct user document with user id.
        let snapshot = await db.collection('users').doc((context.auth.uid)).get();
        // Check if field value for location is null
        // functions.logger.log(snapshot['_fieldsProto']['userLocation']["valueType"] === "nullValue");
        let locationValueType = snapshot['_fieldsProto']['userLocation']["valueType"];
        if (locationValueType == 'nullValue') {
            await db.collection('users').doc((context.auth.uid)).set({ 'userLocation': data.userLocation }, { merge: true });
            functions.logger.log(`User location added    ${data.userLocation}`);
            return data.userLocation;

        }
        else {
            functions.logger.log(`User location not changed`);

        }

    }
    catch (e) {
        functions.logger.log(e);
        throw new functions.https.HttpsError('internal', e);
    }
    return data.userLocation;

});


exports.getNearbyTemples = functions.https.onCall(async (data, _) => {

    try {
        // Notify function's been called
        functions.logger.log("Add nearby temples function was called");
        // Create array of temple objects.
        let temples = data.templeList.map((temple) => {
            return {
                'place_id': temple['place_id'],
                'address': temple['vicinity'] ? temple['vicinity'] : 'Not Available',
                'name': temple['name'] ? temple['name'] : 'Not Available',
                'latLng': {
                    'lat': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lat'] : 'Not Available', 'lon': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lng'] : 'Not Available',
                    'dateAdded': admin.firestore.Timestamp.now()
                },
                'imageRef': data.imageRef
            }
        }

        );

        // save the temples array to temples collection as one document named temples 
        await db.collection('temples').add({ temples: temples });

    } catch (e) {
        // if error return errormsg
        return { 'Error Msg': e };
    }
    // If everything's fine return the temples array.
    return temples;
});


// When the temple List Updates
exports.updateNearbyTemples = functions.runWith({
    timeoutSeconds: 120,
    memory: "256MB"
}).firestore.document('temples/{id}').onUpdate(async (change, context) => {
    // If theres both new and old value
    if (change.before.exists && change.after.exists) {
        // temples list both new and old
        let newTemplesList = change.after.data()['temples'];
        let oldTemplesList = change.before.data()['temples'];

        // Places Id list from both new and old list
        let oldTemplesIdList = oldTemplesList.map(temple => temple['place_id']);
        let newTemplesIdList = newTemplesList.map(temple => temple['place_id']);

        // Lets find out if theres new temples id by filtering with old one
        let filteredList = newTemplesIdList.filter(x => !oldTemplesIdList.includes(x));

        // if the length are not same of fileted list has 
        //length of 0 then nothing new is there so just return
        if (oldTemplesIdList.length != newTemplesIdList.length || filteredList.length == 0) {
            functions.logger.log("Nothing is changed so onUpdate returned");
            return;
        }
        // If somethings changed then 
        try {
            functions.logger.log("On Update was called ");
            // Make new list of temples
            let temples = newTemplesList.map((temple) => {
                return {
                    'place_id': temple['place_id'],
                    'address': temple['vicinity'] ? temple['vicinity'] : 'Not Available',
                    'name': temple['name'] ? temple['name'] : 'Not Available',
                    'latLng': {
                        'lat': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lat'] : 'Not Available', 'lon': temple.hasOwnProperty('geometry') ? temple['geometry']['location']['lng'] : 'Not Available',
                        'dateAdded': admin.firestore.Timestamp.now()
                    }
                }
            }
            );
            // use the current context id to update temples, no need to merge
            await db.collection('temples').doc(context.params.id).set({ 'palces_id_list': newTemplesIdList, temples: temples });
        }
        catch (e) { throw e; }
        return { 'status': 200 };
    }
    // return nothing
    return null;
});
Enter fullscreen mode Exit fullscreen mode

Show Support

Alright, this is it for this time. This series, is still not over, on the next upload we'll redesign our ugly-looking mundane homepage, create a Temple List Screen and execute the method we created today

So, please do like, comment, and share the article with your friends. Thank you for your time and for those who are subscribing to the blog's newsletter, we appreciate it. Keep on supporting us. This is Nibesh from Khadka's Coding Lounge, a freelancing agency that makes websites and mobile applications.

Like and Subscribe

Latest comments (0)