XMLHttpRequest error with Flutter-Web using Google Places API (firebase hosting)

Viewed 6252

Inside my Flutter-Web application I'm trying to get address using flutter_google_places package. I'm trying simple code to get autocomplete address field (MyTextField is just a customized Text Field):

final addressField = MyTextField(
  controller: _addressController,
  labelText: 'Indirizzo',
  readOnly: true,
  onTap: () async {
    await PlacesAutocomplete.show(
      context: context,
      apiKey: kGoogleApiKey,
      mode: Mode.overlay,
      onError: (error){print('ERROR: $error');},
    );
  },
);

When I run the app and insert something to the field I don't get any result. But I get this error (captured from inspect console on hosting, and I get the same error locally also):

Access to XMLHttpRequest at 'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=h&key=**MY-API-KEY**' from origin 'https://**MY-HOSTING**.firebaseapp.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I read that it's a server-side issue and I tried to modify firebase.json like this:

{
  "hosting": {
    "public": "build/web",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "headers": [ {
      "source" : "**",
      "headers" : [ {
        "key" : "Access-Control-Allow-Origin",
        "value" : "*"
      } ]
    }]
  }
}

Deployed that but also got the same error.

Any tips about solving that (locally and on hosting) are appreciated.

6 Answers

So as there was no answers I'm sharing what solved my situation, hope getting better answers from experts soon.

The Problem:

Google places API prevents CORS. So we can't make a request from client-side. And As the PlacesAutocomplete widget makes http request to the Google places API like this:

https://maps.googleapis.com/maps/api/place/autocomplete/json?input={queryString}&key={API-Key}

This client-side request will be prevented.

My Solution:

First I'm not using PlacesAutocomplete widget anymore.

I wrote a simple cloud function that takes Url as parameter then makes http request for the same Url and returns data. But this time the request will be from server-side so I can bypass cors. So Inside index.ts I wrote the following function:

const functions = require('firebase-functions');
const axios = require('axios');

exports.getDataFromUrl = functions.https.onCall(async (data, context) => {
  const url = data.url;
  try {
    const info = await axios.get(url);
    return info.data;
  } catch (error) {
    return (error);
  }
});

Then from client-side I wrote this function (in dart) which calls the cloud function for any url:

  Future httpRequestViaServer({url}) async {
    HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
      functionName: 'getDataFromUrl',
    );
    try {
      final HttpsCallableResult result = await callable.call(
        <String, dynamic>{
          'url': url,
        },
      );
      return (result.data);
    } on CloudFunctionsException catch (e) {
        print('caught firebase functions exception');
        print(e.code);
        print(e.message);
        print(e.details);
        return null;
    } catch (e) {
        print('caught generic exception');
        print(e);
        return null;
    }

Now I can call this function to get data for the API url (or any url) from anywhere in my project.

I ended building my own autoComplete widget which calls the API using this function.

Hope this answer helps someone facing similar problem, and looking forward for better answers from experts.

I found a solution that lets you keep using PlacesAutocomplete in this github comment: https://github.com/lejard-h/google_maps_webservice/issues/70#issuecomment-636919093

GoogleMapsPlaces(
  apiKey: 'YOUR_API_KEY',
  baseUrl: kIsWeb
      ? 'https://cors-anywhere.herokuapp.com/https://maps.googleapis.com/maps/api'
      : null,
);

This other StackOverflow answer talks about how the solution works a little more, as well as give more info about cors-anywhere (including how to host your own): https://stackoverflow.com/a/43881141/3001277

if you want to test this locally you can use the "Allow CORS: Access-Control-Allow-Origin" for Chrome to disable it

I Reference to the answer of Feras Senjab:

The MEthods should have slightly changed by now in order to still work .

For the index.js no changes

// Forwards a request call to get Google Places AUtocomplete Running
exports.getGooglePlacesAutocomplete = functions.https.onCall(async (data, context) => {
  const url = data.url;
  try {
    console.log(url);
    const info = await axios.get(url);
    console.log(info);
    return info.data;
  } catch (error) {
    return (error);
  }
});

For the CLient side, this is the update

import 'package:cloud_functions/cloud_functions.dart';

Future googlePlacesAutocompleteRequestViaServer({url}) async {
  HttpsCallable callable =
      FirebaseFunctions.instance.httpsCallable('getGooglePlacesAutocomplete');
  try {
    final HttpsCallableResult result = await callable.call(
      <String, dynamic>{
        'url': url,
      },
    );
    return (result.data);
  } on FirebaseFunctionsException catch (e) {
    print('caught firebase functions exception');
    print(e.code);
    print(e.message);
    print(e.details);
    return null;
  } catch (e) {
    print('caught generic exception');
    print(e);
    return null;
  }
}
Related