Обработчик разрешений и доступ к местоположению во Flutter


Введение

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

Как часть потока пользовательского экрана, мы сейчас находимся на этапе, когда нам нужно получить доступ к местоположению пользователя. Поэтому мы будем запрашивать местоположение пользователя, как только он пройдет аутентификацию и попадет на главную страницу. Мы также будем использовать Firebase Cloud Functions для сохранения местоположения пользователя в документе ‘users/userId’ в Firebase Firestore. Найти исходный код для начала этого раздела можно здесь.

Пакеты

В предыдущих работах мы уже устанавливали и настраивали пакеты Firebase. Сейчас нам понадобятся еще три пакета: Location, Google Maps Flutter и Permission Handler. Следуйте инструкциям на домашней странице пакетов или просто используйте версию, которую я использую ниже.

Самого пакета location достаточно, чтобы получить и разрешение, и местоположение. Однако permission_handler может получить разрешение для других задач, таких как камера, локальное хранилище и так далее. Следовательно, мы будем использовать оба пакета: один для получения разрешения, а другой для определения местоположения. Пока что мы будем использовать только пакет google maps для использования типов данных Latitude и Longitude.

Установка

В командной строке Терминал:

# Install location
flutter pub add location

# Install Permission Handler
flutter pub add permission_handler

# Install Google Maps Flutter
flutter pub add google_maps_flutter
Войдите в полноэкранный режим Выйти из полноэкранного режима

Настройка пакетов

Пакет Location

Для пакета Location, чтобы иметь возможность запрашивать разрешение пользователя, нам необходимо добавить некоторые настройки.

Android

Для android в «android/app/src/main/AndroidManifest.xml» перед тегом приложения.

<!--
    Internet permissions do not affect the `permission_handler` plugin but are required if your app needs access to
    the internet.
    -->
    <uses-permission android:name="android.permission.INTERNET" />
   <!-- Permissions options for the `location` group -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />


<!-- Before application tag-->
 <application android:label="astha" android:name="${applicationName}" android:icon="@mipmap/launcher_icon">

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

Для ios в «ios/Runner/Info.plist» в конце тега dict добавьте следующие настройки.

    <!-- Permissions list starts here -->
        <!-- Permission while running on backgroud -->
        <key>UIBackgroundModes</key>
        <string>location</string>
        <!-- Permission options for the `location` group -->
        <key>NSLocationWhenInUseUsageDescription</key>
        <string>Need location when in use</string>
        <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
        <string>Always and when in use!</string>
        <key>NSLocationUsageDescription</key>
        <string>Older devices need location.</string>
        <key>NSLocationAlwaysUsageDescription</key>
        <string>Can I have location always?</string>
        <!-- Permission options for the `appTrackingTransparency` -->
        <key>NSUserTrackingUsageDescription</key>
        <string>appTrackingTransparency</string>
        <!-- Permissions lists ends here -->
Войти в полноэкранный режим Выйти из полноэкранного режима

Обработчик разрешений

Android

Для android в «android/gradle.properties» добавьте эти настройки, если их там еще нет.

android.useAndroidX=true
android.enableJetifier=true
Войти в полноэкранный режим Выйти из полноэкранного режима

В файле «android/app/build.gradle» измените версию скомпилированного SDK на 31, если вы этого еще не сделали.

android {
    compileSdkVersion 31
...
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Что касается API разрешений, мы уже добавили их в файл AndroidManifest.XML.

IOS

Мы уже добавили разрешения в info.plist. К сожалению, я использую VS Code и не смог найти файл POD в каталоге ios.

Google Maps Flutter

Чтобы использовать карты Google, вам понадобится API-ключ для них. Получите его от Google Maps Platform. Следуйте инструкциям из readme пакета о том, как создать API-ключ. Создайте по два учетных данных для android и ios. После этого нам нужно будет добавить их в приложения для android и ios.

Android

Снова перейдите к файлу AndroidManifest.xml.

<manifest ...
  <application ...
    <meta-data android:name="com.google.android.geo.API_KEY"
               android:value="YOUR KEY HERE"/>
<activity ...
Войдите в полноэкранный режим Выйдите из полноэкранного режима

В файле » android/app/build.gradle» измените минимальную версию SDK на 21, если вы этого еще не сделали.

...
 defaultConfig {
     ...
        minSdkVersion 21
...
Войдите в полноэкранный режим Выход из полноэкранного режима
IOS

В файле ios/Runner/AppDelegate.swift добавьте api ключ для ios.

// import gmap
import GoogleMaps

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  ...
-> Bool {
// Add api key don't remove anything else
    GMSServices.provideAPIKey("API KEY Here")
    ...
}

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

НЕ ДЕЛИТЕСЬ СВОИМ API КЛЮЧОМ, ДОБАВЬТЕ МАНИФЕСТ ANDROID И ФАЙЛ APPDELEGATE В GITIGNORE ПЕРЕД ПУБЛИКАЦИЕЙ.

Напоминание: Если что-то не работает, проверьте страницы read me in packages.

Местоположение пользователя в доступе

Давайте рассмотрим ряд событий, которые произойдут в тот крошечный момент, когда пользователь перейдет с экрана аутентификации на домашний экран.

  1. У пользователя будет запрошено разрешение на предоставление доступа к местоположению.
  2. Если разрешение положительное, местоположение пользователя будет получено и передано облачной функции HTTPS callable.
  3. Затем вызываемая функция получит идентификатор текущего пользователя. С этим идентификатором callable прочитает нужный документ из коллекции «users».
  4. Она проверит, пустое ли поле местоположения или нет.
  5. Если оно пустое, то будет записано новое слияние документа для добавления местоположения.
  6. Если поле не пустое, то функция просто ничего не напишет и вернется.

Поскольку по мере роста приложения количество необходимых разрешений может увеличиваться, а разрешение также является глобальным фактором, давайте создадим класс провайдера, который будет обрабатывать разрешения в папках «globals/providers».

На вашем терминале

# Make folder
mkdir lib/globals/providers/permissions
// make file
touch lib/globals/providers/permissions/app_permission_provider.dart

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

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

app_permission_provider.dart

enum AppPermissions {
  granted,
  denied,
  restricted,
  permanentlyDenied,
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Создадим класс провайдера прямо под перечислением. Как упоминалось ранее, мы будем использовать permission_handler для получения разрешения и пакет location для получения местоположения.

import 'package:cloud_functions/cloud_functions.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:location/location.dart'
    as location_package; // to avoid confusion with google_maps_flutter package


class AppPermissionProvider with ChangeNotifier {
  // Start with default permission status i.e denied
// #1
  PermissionStatus _locationStatus = PermissionStatus.denied;

  // Getter
// #2
  get locationStatus => _locationStatus;

// # 3
  Future<PermissionStatus> getLocationStatus() async {
    // Request for permission
    // #4
     final status = await Permission.location.request();
    // change the location status
   // #5
    _locationStatus = status;
    print(_locationStatus);
    // notify listeners
    notifyListeners();
    return status;

  }
}



Вход в полноэкранный режим Выход из полноэкранного режима
  1. Мы начнем с разрешения по умолчанию, которое является запрещенным.
  2. Геттер статуса разрешения.
  3. Метод, возвращающий будущее типа Permission Status. Он понадобится нам позже.
  4. Метод из Permission Handler(request), который запрашивает разрешение пользователя.
  5. Присвоение нового статуса и последующее уведомление слушателей.

Теперь перейдем к следующему шагу задания, который заключается в получении местоположения и сохранении его в Firestore. Мы добавим несколько новых переменных и экземпляров, которые помогут нам достичь этого. Добавьте следующий код перед методом getLocationStatus.

 // Instantiate FIrebase functions
// #1
  FirebaseFunctions functions = FirebaseFunctions.instance;
  // Create a LatLng type that'll be user location
 // # 2
  LatLng? _locationCenter;
  // Initiate location from location package
 // # 3
  final location_package.Location _location = location_package.Location();
 // # 4
  location_package.LocationData? _locationData;

  // Getter
 // # 5
  get location => _location;
  get locationStatus => _locationStatus;
  get locationCenter => _locationCenter as LatLng;

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

Давайте объясним коды, хорошо?

  1. Экземпляр Firebase functions необходим, потому что после этого мы создадим вызываемую функцию https для обработки отправки местоположения.
  2. Местоположение пользователя, которое будет возвращено вызываемой функцией HTTPS.
  3. Инстанцируйте пакет location.
  4. Местоположение, которое будет передано пакетом Location.
  5. Геттеры для получения частных значений из этого класса.

Firebase Function: HTTPS Callable

Наш метод getLocation для AppPermissionProvider, который мы создадим позже, будет вызывать HTTPS callable внутри него. Итак, давайте перейдем в index.js, чтобы создать метод onCall из функции firebase.

index.js

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

    try {
       // Fetch correct user document with user id.
       // #2
        let snapshot = await db.collection('users').doc((context.auth.uid)).get();
       // functions.logger.log(snapshot['_fieldsProto']['userLocation']["valueType"] === "nullValue");
        // Get Location Value Type
        // #3
        let locationValueType = snapshot['_fieldsProto']['userLocation']["valueType"];
        // Check if field value for location is null
       // # 4
        if (locationValueType == 'nullValue') {
    // # 5
            await db.collection('users').doc((context.auth.uid)).set({ 'userLocation': data.userLocation }, { merge: true });

            functions.logger.log(`User location added ${data.userLocation}`);
        }
        else {
    // # 6
            functions.logger.log(`User location not changed`);

        }

    }
    catch (e) {
    // # 7
        functions.logger.log(e);
        throw new functions.https.HttpsError('internal', e);
    }
    // #7
    return data.userLocation;
});
Вход в полноэкранный режим Выход из полноэкранного режима

В вызываемой функции addUserLocation выше мы:

  1. Обеспечиваем выделение памяти для функций с методом runWith().
  2. Получить документ пользователя на основе идентификатора пользователя, предоставленного EventContext.
  3. Получить тип значения поля Location. Если вы помните, во время процесса регистрации в файле auth_state_provider.dart мы сохранили заполненное поле userLocation как null. Это длинное snapshot[‘_fieldsProto’][‘userLocation’][«valueType»] я получил в результате экспериментов и печати значений. Вот почему лучше всего использовать эмуляторы.
  4. Если locationValueType равно null, это означает, что местоположение пользователя никогда не сохранялось ранее. Следовательно, переходим к созданию нового документа.
  5. Обновим документ пользователя с помощью userLocation из свойства data метода onCall. Это то же самое местоположение, которое будет передано из класса провайдера в эту функцию. Да, то же самое, которое было получено из пакета location.
  6. Если locationValueType не равно null, то мы не будем записывать новый документ.
  7. Возвращаем местоположение пользователя. Очень важно завершать вызываемые функции возвратом, в противном случае функция может работать дольше, что приведет к потреблению памяти, что может вызвать дополнительные счета от Firebase.

Использование HTTPS Callable с пакетом Location во Flutter

Когда наша вызываемая функция готова, давайте создадим метод Future, который будет использоваться приложением. В файле app_permission_provider.dart после метода getLocationStatus создайте метод getLocation.

Future<void> getLocation() async {
    // Call Location status function here
    // #1
    final status = await getLocationStatus();
    // if permission is granted or limited call function
    // #2
    if (status == PermissionStatus.granted ||
        status == PermissionStatus.limited) {
      try {        
        // assign location data that's returned by Location package
        // #3
        _locationData = await _location.getLocation();
        // Check for null values
       // # 4
        final lat = _locationData != null
            ? _locationData!.latitude as double
            : "Not available";
        final lon = _locationData != null
            ? _locationData!.longitude as double
            : "Not available";

        // Instantiate a callable function
       // # 5
        HttpsCallable addUserLocation =
            functions.httpsCallable('addUserLocation');

        // finally call the callable function with user location
         // #6
        final response = await addUserLocation.call(
          <String, dynamic>{
            'userLocation': {
              'lat': lat,
              'lon': lon,
            }
          },
        );
        // get the response from callable function
        // # 7
        _locationCenter = LatLng(response.data['lat'], response.data['lon']);
      } catch (e) {
        // incase of error location witll be null
          // #8
        _locationCenter = null;
        rethrow;
      }
    }
// Notify listeners
    notifyListeners();
  }
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Что мы сделали здесь:

  1. Запрашиваем разрешение пользователя на доступ к местоположению.
  2. Если разрешение разрешено/ограничено, т.е. всегда разрешено/запрещено при использовании приложения, то мы попытаемся получить доступ к местоположению.
  3. Для доступа к данным о местоположении пользовательские пакеты getLocation. Он возвращает тип объекта LatLng.
  4. Проверьте, являются ли возвращаемые данные null или нет, если да, то обработайте их соответствующим образом.
  5. (5&6)Внутри блока try мы инстанцируем вызываемую функцию HTTPS, как описано в пакете FlutterFire. Наша вызываемая функция принимает параметр «userLocation» в виде словаря с lat и lon в качестве ключей. После вызова этой функции в фоновом режиме она возвращает объект LatLng, к которому можно получить доступ из объекта данных ответа.
  6. В случае ошибки местоположение пользователя определяется как нулевое.

Теперь, когда местоположение пользователя обновляется, соответствующие виджеты, слушающие метод, будут уведомлены. Но чтобы виджеты могли обращаться к провайдеру, нам нужно будет добавить провайдер в список MultiProvider в нашем файле app.dart.

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

FutureBuilder на помощь

Наша операция для получения местоположения пользователя является асинхронной и возвращает Future. Future может занять время для возврата результата, поэтому обычный виджет не будет работать. Для этой задачи предназначен класс FutureBuilder из flutter.

Мы вызовем метод getLocation из виджета Home в файле home.dart как свойство future класса FutureBuilder. В ожидании сохранения местоположения мы можем просто отобразить индикатор выполнения.

// Import the provider Package
import 'package:temple/globals/providers/permissions/app_permission_provider.dart';

// Inside Scaffold body 
...
body: SafeArea(
        child: FutureBuilder(
            // Call getLocation function as future
            // its very very important to set listen to false
           // #1
            future: Provider.of<AppPermissionProvider>(context, listen: false)
                .getLocation(),
            // don't need context in builder for now
            builder: ((_, snapshot) {
              // if snapshot connectinState is none or waiting
             // # 2
              if (snapshot.connectionState == ConnectionState.waiting ||
                  snapshot.connectionState == ConnectionState.none) {
                return const Center(child: CircularProgressIndicator());
              } else {
                // if snapshot connectinState is active
                // # 3
                if (snapshot.connectionState == ConnectionState.active) {
                  return const Center(
                    child: Text("Loading..."),
                  );
                }
                // if snapshot connectinState is done
              // #4
                return const Center(
                  child: Directionality(
                      textDirection: TextDirection.ltr,
                      child: Text("This Is home")),
                );
              }
            })),
      ),
...
Вход в полноэкранный режим Выход из полноэкранного режима

В виджете home после импорта класса AppPermissionProvider мы вернули FutureBuilder как дочернее свойство виджета Safe Area. В нем мы:

  1. User getLocation класса AppPermissionProvider как future. Очень важно не забыть установить значение listen на false. Иначе билд будет постоянно перезагружаться и функции будут выполняться снова и снова.

  2. Мы возвращаем CircularProgressIndicator, ожидая завершения результата в фоновом режиме. Пока кажется, что в этом нет смысла, потому что мы не используем местоположение пользователя в нашем приложении. Итак, зачем нужен индикатор прогресса? Это для будущего, где мы снова используем этот момент для получения других данных из firebase, которые также будут асинхронными.

  3. Когда будущее активно, мы отображаем текст с надписью loading.

  4. После завершения работы будущего мы загружаем простую домашнюю страницу.

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

app_permission_provider.dart

import 'package:cloud_functions/cloud_functions.dart';
import 'package:flutter/foundation.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:location/location.dart'
    as location_package; // to avoid confusion with google_maps_flutter package

enum AppPermissions {
  granted,
  denied,
  restricted,
  permanentlyDenied,
}

class AppPermissionProvider with ChangeNotifier {
  // Start with default permission status i.e denied
  PermissionStatus _locationStatus = PermissionStatus.denied;

  // Instantiate FIrebase functions
  FirebaseFunctions functions = FirebaseFunctions.instance;

  // Create a LatLng type that'll be user location
  LatLng? _locationCenter;
  // Initiate location from location package
  final location_package.Location _location = location_package.Location();
  location_package.LocationData? _locationData;

  // Getter
  get location => _location;
  get locationStatus => _locationStatus;
  get locationCenter => _locationCenter as LatLng;

  Future<PermissionStatus> getLocationStatus() async {
    // Request for permission
    final status = await Permission.location.request();
    // change the location status
    _locationStatus = status;
    // notiy listeners
    notifyListeners();
    print(_locationStatus);
    return status;
  }

  Future<void> getLocation() async {
    // Call Location status function here
    final status = await getLocationStatus();
    print("I am insdie get location");
    // if permission is granted or limited call function
    if (status == PermissionStatus.granted ||
        status == PermissionStatus.limited) {
      try {
        // assign location data that's returned by Location package
        _locationData = await _location.getLocation();
        // Check for null values
        final lat = _locationData != null
            ? _locationData!.latitude as double
            : "Not available";
        final lon = _locationData != null
            ? _locationData!.longitude as double
            : "Not available";

        // Instantiate a callable function
        HttpsCallable addUserLocation =
            functions.httpsCallable('addUserLocation');

        // finally call the callable function with user location
        final response = await addUserLocation.call(
          <String, dynamic>{
            'userLocation': {
              'lat': lat,
              'lon': lon,
            }
          },
        );
        // get the response from callable function
        _locationCenter = LatLng(response.data['lat'], response.data['lon']);
      } catch (e) {
        // incase of error location witll be null
        _locationCenter = null;
        rethrow;
      }
    }
// Notify 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;

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

home.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Custom
import 'package:temple/globals/providers/permissions/app_permission_provider.dart';
import 'package:temple/globals/widgets/app_bar/app_bar.dart';
import 'package:temple/globals/settings/router/utils/router_utils.dart';
import 'package:temple/globals/widgets/bottom_nav_bar/bottom_nav_bar.dart';
import 'package:temple/globals/widgets/user_drawer/user_drawer.dart';

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  // create a global key for scafoldstate
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // Provide key to scaffold
      key: _scaffoldKey,
      // Changed to custom appbar
      appBar: CustomAppBar(
        title: APP_PAGE.home.routePageTitle,
        // pass the scaffold key to custom app bar
        // #3
        scaffoldKey: _scaffoldKey,
      ),
      // Pass our drawer to drawer property
      // if you want to slide right to left use
      endDrawer: const UserDrawer(),
      bottomNavigationBar: const CustomBottomNavBar(
        navItemIndex: 0,
      ),
      primary: true,
      body: SafeArea(
        child: FutureBuilder(
            // Call getLocation function as future
            // its very very important to set listen to false
            future: Provider.of<AppPermissionProvider>(context, listen: false)
                .getLocation(),
            // don't need context in builder for now
            builder: ((_, snapshot) {
              // if snapshot connectinState is none or waiting
              if (snapshot.connectionState == ConnectionState.waiting ||
                  snapshot.connectionState == ConnectionState.none) {
                return const Center(child: CircularProgressIndicator());
              } else {
                // if snapshot connectinState is active

                if (snapshot.connectionState == ConnectionState.active) {
                  return const Center(
                    child: Text("Loading..."),
                  );
                }
                // if snapshot connectinState is done
                return const Center(
                  child: Directionality(
                      textDirection: TextDirection.ltr,
                      child: Text("This Is home")),
                );
              }
            })),
      ),
    );
  }
}

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

Резюме

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

  1. Установил три пакета: Location, Permission Handler и Google Maps Flutter.
  2. Потратили немного времени на обновление настроек, необходимых для использования этих пакетов.
  3. Создал класс провайдера, который будет запрашивать у пользователя разрешение на доступ к местоположению.
  4. В этом же классе есть метод, который будет получать доступ к местоположению и вызывать вызываемую функцию HTTPS.
  5. Создана HTTPS-функция, которая будет обновлять местоположение пользователя в Firebase Firestore.
  6. Реализовали класс провайдера с помощью FutureBuilder в нашем приложении.

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

Хорошо, на этом мы закончили. Эта серия еще не закончена, в следующем выпуске мы более подробно рассмотрим Google Places API, Firebase Firestore и Firebase Cloud Functions.

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

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