Как получить информацию о близлежащих местах из Google Maps с помощью Flutter?

Здравствуйте и добро пожаловать в серию уроков Flutter App Dev. До этого мы уже сделали заставку, написали тему, сделали пользовательскую панель приложений, сделали экран аутентификации, настроили соединение с облаком firebase и эмуляторами, аутентифицировали пользователей и получили доступ к местоположению пользователя в проектах firebase.

О

Как часть потока пользовательского экрана, у нас уже есть доступ к местоположению пользователя. На этой 1010-м части мы будем использовать местоположение пользователя для поиска ближайших двадцати храмов с помощью Google Places API. Мы получим места с помощью пакета HTTP, а затем напишем еще один HTTPS Callable с Firebase Functions для хранения этих храмов в Firestore. Поскольку мы используем ключ API, мы воспользуемся пакетом flutter_dotenv, чтобы сохранить его в тайне. Найти исходный код для начала этого раздела можно здесь.

Пакеты

DotEnv

Итак, сначала установим пакет flutter_dotenv.

flutter pub add flutter_dotenv
Вход в полноэкранный режим Выход из полноэкранного режима

Создайте файл .env в корне вашего проекта.

touch .env
Войдите в полноэкранный режим Выйти из полноэкранного режима

Добавьте файл .env в файл .gitignore.

#DOT ENV
*.env
Войдите в полноэкранный режим Выйти из полноэкранного режима

Инициализируйте файл .env в методе main() нашего файла main.dart.

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));
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Нам также нужно добавить файл .env в раздел assets файла pubspec.yaml.

#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
Вход в полноэкранный режим Выход из полноэкранного режима

API мест Google Maps

Для использования API google places нам понадобится ключ API places. Для этого перейдите в облачную консоль google, настройте биллинговый аккаунт и создайте новый API-ключ для Goog Places API. Затем добавьте этот API-ключ в файл .env.

.env

#Without quotes
GMAP_PLACES_API_KEY = Your_API_KEY

Вход в полноэкранный режим Выход из полноэкранного режима

HTTP

Установите пакет HTTP.

flutter pub add http
Войти в полноэкранный режим Выход из полноэкранного режима

При использовании пакета HTTP нет необходимости в дополнительной настройке.

Структуры папок

Как и папки Home и Auth, каталог temples будет содержать все файлы, связанные с 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

Вход в полноэкранный режим Выход из полноэкранного режима

Как и в предыдущих блогах, мы будем хранить всю логику приложений в файле провайдера. Кроме того, нам также понадобится файл utils для хранения нескольких функций, которые мы будем использовать в классе провайдера. Итак, сначала создадим две простые функции в temple_utils.dart

Утилиты

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;
  }
}

Вход в полноэкранный режим Выйти из полноэкранного режима
  1. Google Maps NearbySearch принимает несколько параметров, среди которых мы будем использовать location (обязательный параметр), rankby и type.
  2. Импортируйте ключ API из файла .env.
  3. Search Url – это функция, которая принимает местоположение пользователя, затем объединяет базовый URL с приемлемыми параметрами API поиска и возвращает разобранный URI.
  4. Ключ API является обязательным условием для каждого URL, чтобы получить результат от API.
  5. rankby=”distance” сортирует результаты поиска на основе расстояния. Когда мы используем rankby, параметр type обязателен.
  6. Конечный URL, который будет использоваться HTTP-пакетом для поиска храмов.

*Примечание: Если вы из места, где нет храмов (некоторых или вообще нет), вы, вероятно, не увидите никаких результатов. Поэтому используйте что-то другое для типа заведения. *

Другой метод будет простым маппером, его единственная цель – преобразовать входящий список в список TempleModels (который мы создадим следующим) и вернуть его в таком виде. Это сделает наш последующий код намного чище.

 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;
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Модель храма

Класс Temple model определит рамки для хранения информации о храме. В файле temple.dart внутри models создадим модель храма.

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});
}

Вход в полноэкранный режим Выход из полноэкранного режима

Каждый храм, который будет сохранен в Firestore, будет иметь название, адрес, географические координаты, imageUrl и идентификатор, задаваемый API Google Place.

Google Place API с HTTP и провайдером

Теперь пришло время написать класс провайдера, который будет заботиться о получении списка ближайших храмов. Это будет длинный файл с множеством вещей, которые нужно объяснить. Поэтому мы будем писать код по частям, сверху вниз.

Импортируйте модули и создайте класс провайдера.


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 {}
Вход в полноэкранный режим Выйти из полноэкранного режима

Внутри класса инстанцируйте Firebase Products, которые мы будем использовать.

// 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');
Войти в полноэкранный режим Выйти из полноэкранного режима

С Firebase Storage мы не будем использовать ведро по умолчанию для хранения изображений, URL которых будет получен для сохранения в Firestore. Итак, что происходит с этой логикой? API google places не предоставляет изображения, их предоставляет details api. Мы не будем его использовать. Но вместо этого у меня есть несколько случайных (4 штуки) изображений индусов, которые я скачал с Unsplash, которые я сохраню в хранилище и получу случайный URL среди изображений и присвою его модели храма. Вам не обязательно делать это и предоставлять жестко закодированный URL изображения для imageRef, но это нужно для тренировки чтения хранилища.

Другие поля и геттеры

// 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',
  ];
Вход в полноэкранный режим Выйти из полноэкранного режима

imagePaths – это список буквально названий изображений, которые я загрузил в папку с именем “TempleImages” внутри нашего ведра, на которое мы ссылались ранее в Хранилище эмулятора.

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;
  }
Вход в полноэкранный режим Выйти из полноэкранного режима

Итак, теперь создан основной метод, который будет делать все, над чем мы работали до сих пор в этом блоге “getNearyByTemples”. Давайте пройдемся по номерам:

  1. Используйте утилиты храмов, которые мы создали ранее, чтобы получить URL, готовый для использования HTTP-пакетом.

  2. Ссылка на HTTP callable getNearyByTemples, которую мы создадим после этой сессии провайдера. Она отвечает за сохранение списка всех храмов, которые мы получили во время поиска.

  3. Ссылка на коллекцию храмов.

  4. Ссылка на храм будет использоваться для чтения одного документа из коллекции.

  5. Ссылка на папку с именем “TempleImages” внутри ведра хранения.

  6. Мы проверяем, пуст ли документ temple doc, который мы получили ранее. Логика заключается в том, что мы не хотим обращаться к Place Api, Firestores и Functions каждый раз, когда пользователь использует наше приложение. Мы будем получать храмы и сохранять их в FireStore, только если коллекция храмов пуста или не существует.

  7. Для получения результатов можно использовать метод HTTP get(). Вы можете использовать такое программное обеспечение, как postman или просто браузер chrome, чтобы увидеть результаты запроса get.

  8. Функция декодирования Json разбирает строки на типы данных json.

  9. API Places предоставляет ответ в виде списка хранилищ в виде списка с результатами в качестве ключа. Мы извлечем его как тип List.

  10. Firebase Storage предоставил средства для загрузки URL из ссылки. Мы произвольно загружаем URL и присваиваем его свойству imageRef, необходимому в нашей вызываемой программе HTTPS.

  11. Теперь мы вызываем наш HTTPS callable, предоставляем список хранилищ и Url изображения. Он сохранит список в коллекции Temples Firesotre и вернет этот список.

  12. Возвращенный список теперь будет использоваться для обновления нашего списка моделей храмов с помощью метода mapper в Temple Utils.

  13. Этот же список будет использоваться нашим приложением для отображения красивого списка карт храмов на экране храмов.

  14. Блок else выполняется только в том случае, если в коллекции храма уже есть список храмов. В этом случае, в отличие от блока if, мы не получаем список храмов из API, а просто читаем все документы, сохраненные в коллекции храма. После этого процесс аналогичен описанному выше.

  15. В случае ошибки список храмов будет пуст.

  16. Очень важно вернуть этот новый список. Этот список понадобится нам в качестве данных QurerySnapshot, получаемых FutureBuilder для отображения в нашем приложении.

Запись результатов поиска Google Places API на FireStore с помощью Firebase Functions

Внутри файла index.js мы создадим еще одну HTTPS-вызываемую функцию “getNearbyTemples”. Этот метод создаст массив со списком объектов храмов и затем сохранит его в коллекции temples.

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;
});

Вход в полноэкранный режим Выход из полноэкранного режима

Я не выделял память для этой операции. Это было очень сложно и долго. Если хотите, можете поэкспериментировать. Документ firebase может хранить до 1 МБ в размере. Так что наш список, по крайней мере для этого приложения, никогда не вырастет больше 20. Поэтому внутри коллекции храмов мы сохраняем не 20 документов, а один документ с 20 элементами в виде поля “temples”, db.collection(‘temples’).add({ temples: temples }).

Обработка обновлений с помощью триггеров Firestore

Допустим, пользователь сменил местоположение или в базу данных google был добавлен новый храм. Это должно быть отражено в коллекции храмов Firestore Temples. Но мы должны аккуратно обращаться с обновлениями и записывать новые документы только в том случае, если есть изменения в старых. Для нашей коллекции храмов мы можем просто сопоставить places_id и предпринять соответствующие действия. Firebase предоставляет триггер onUpdate() для обработки такого типа работы. Теперь давайте напишем немного кода в файле 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;
});
Вход в полноэкранный режим Выход из полноэкранного режима

Changes.before.data дает значения, уже имеющиеся в Firestore, а changes.after.data – значения, полученные из API. Благодаря этому обновлению функция не будет запускаться каждый раз, когда пользователь загружает экран temples. Это сэкономит нам много денег в производственном режиме.

Обеспечение доступности класса Provider для виджетов

Теперь наши классы готовы к работе. Итак, давайте сделаем их доступными, обновив список MultiProviders в файле app.dart.

 MultiProvider(
      providers: [
       ...
        ChangeNotifierProvider(create: (context) => TempleProvider()),
        ...
      ],
...
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь метод GetNearbyTemples доступен для всех потомков MultiProviders. Итак, где именно мы будем вызывать этот метод? В следующем блоге мы сделаем нашу домашнюю страницу немного лучше. На этой домашней странице будет ссылка на Temple List Screen. Метод In будет выполняться при нажатии на ссылку. На этом давайте закончим этот блог, пока мы не отклонились от основной темы блога.

Резюме

Прежде чем мы уйдем, давайте подведем итоги того, что мы сделали до сих пор.

  1. Мы создали google maps places API.
  2. Установив пакет Flutter_DotEnv, мы защитили API от публичного доступа.
  3. Также был добавлен пакет HTTP, который сыграл важную роль в получении списков храмов из API.
  4. Мы создали файл утилиты, но только с одним util. Позже, если вы захотите, вы можете написать метод калькулятора расстояния от пользователя до храма для отображения в Google Maps.
  5. Затем мы написали метод в нашем классе провайдера, который извлекает список поиска и передает его функции облака firebase.
  6. Функция firebase сохраняет списки храмов в Firestore, если коллекция пуста.
  7. Затем мы написали триггер обновления, который будет запускаться только при изменении значения.

Окончательный код

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();
  }
}


Вход в полноэкранный режим Выйти из полноэкранного режима

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;
});
Войти в полноэкранный режим Выйти из полноэкранного режима

Показать поддержку

Итак, на этом мы закончили. Эта серия статей еще не закончена, в следующем выпуске мы изменим дизайн нашей уродливо выглядящей мирской домашней страницы, создадим экран списка храмов и выполним метод, который мы создали сегодня.

Итак, пожалуйста, ставьте лайк, комментируйте и делитесь статьей со своими друзьями. Спасибо за ваше время, а тем, кто подписывается на рассылку блога, мы очень признательны. Продолжайте поддерживать нас. Это Нибеш из Khadka’s Coding Lounge, фрилансерского агентства, которое занимается созданием сайтов и мобильных приложений.

Оцените статью
Procodings.ru
Добавить комментарий