Search code examples
google-mapswebassemblyuno-platform

How do you implement Google Maps in Uno WASM


I'm new to Uno Platform and I need help adding Google Maps to my app. Since MapControl doesn't provide WASM support, it seems like I need to embedd a javascript component. This should be possible according to https://platform.uno/blog/how-to-embed-javascript-components-in-c-built-uno-webassembly-web-applications/ . But I can't seem to make it work.

I have a javascript file containing the following, taken directly from the official google maps site. Also not sure where to put the map API key.

// Initialize and add the map
function initMap() {
    // The location of Uluru
    const uluru = { lat: -25.344, lng: 131.036 };
    // The map, centered at Uluru
    const map = new google.maps.Map(document.getElementById("map"), {
        zoom: 4,
        center: uluru,
    });
    // The marker, positioned at Uluru
    const marker = new google.maps.Marker({
        position: uluru,
        map: map,
    });
}

I also have this class, tried doing it similar to part 3 in the official Uno Platform Embedd Javascript Components guide.

    public class GoogleMapsController : FrameworkElement
    {
        public GoogleMapsController()
        {
            LoadJavaScript();
        }

        private async void LoadJavaScript()
        {
            await this.ExecuteJavascriptAsync("initMap()");
        }

    }

To display the map in the xaml page:

<local:GoogleMapsController x:Name="googlemaps" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" />

When I run the app the map doesn't show up. Does anyone know what I am doing wrong?

The following shows up in the web console

fail: Windows.UI.Core.CoreDispatcher[0]
dotnet.js:1       Dispatcher unhandled exception
dotnet.js:1 System.ApplicationException
dotnet.js:1   at TestMaps.GoogleMapsController.LoadJavaScript () <0x2f90540 + 0x000e6> in <filename unknown>:0 
dotnet.js:1   at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__7_0 (System.Object state) <0x2f9ba10 + 0x00010> in <filename unknown>:0 
dotnet.js:1   at Windows.UI.Core.CoreDispatcherSynchronizationContext+<>c__DisplayClass3_0.<Post>b__0 () <0x2f9b9d0 + 0x00014> in <filename unknown>:0 
dotnet.js:1   at Windows.UI.Core.CoreDispatcher.DispatchItems () <0x2a8a0a0 + 0x001e6> in <filename unknown>:0 

If I change the LoadJavaScript() to synchronous in GoogleMapsController, the following shows up in the web console.

dotnet.js:1 Error #1 "ReferenceError: google is not defined" executing javascript: "
put_char @ dotnet.js:1
dotnet.js:1 (function(element) {
put_char @ dotnet.js:1
dotnet.js:1 initMap()
put_char @ dotnet.js:1
dotnet.js:1 })(Uno.UI.WindowManager.current.getView(38002));
put_char @ dotnet.js:1
dotnet.js:1 "

Solution

  • Got it working, I will probably write a blogpost on it later.

    Get Google Maps API key

    You can follow any tutorial available on the web on how to get an API key via the Google Developer Console. It involves creating a project, adding required billing information, creating API key and then enabling Google Maps API for it. Make sure to restrict the API key when you plan to go public so that someone does not misuse your key for their purposes and use up your billing this way.

    Add a custom index.html to your WASM project

    Because Google Maps requires loading an external JavaScript library, it is useful to load it ahead of time when your app first loads. to do that, add a index.html file to the root of your WASM project with the following content:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
    
        <script type="text/javascript" src="./require.js"></script>
        <script type="text/javascript" src="./mono-config.js"></script>
        <script type="text/javascript" src="./uno-config.js"></script>
        <script type="text/javascript" src="./uno-bootstrap.js"></script>
        <script async type="text/javascript" src="./dotnet.js"></script>
        <!-- Google Maps libs here: -->
        <script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
        <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=MYAPIKEY&libraries=&v=weekly"></script>
    
        $(ADDITIONAL_CSS)
        $(ADDITIONAL_HEAD)
    </head>
    <body>
        <div id="uno-body" class="container-fluid uno-body">
            <div class="uno-loader"
                 loading-position="bottom"
                 loading-alert="none">
    
                <!-- Logo: change src to customize the logo -->
                <img class="logo"
                     src=""
                     title="Uno is loading your application" />
    
                <progress></progress>
                <span class="alert"></span>
            </div>
        </div>
        <noscript>
            <p>This application requires Javascript and WebAssembly to be enabled.</p>
        </noscript>
    </body>
    </html>
    

    Note that most of the code is boilerplate that is the default in Uno WASM projects (see here). Replace MYAPIKEY with your API key from developer console.

    Now you need to instruct the WASM bootstrapper to use this custom index.html. Double-click the .csproj in the Solution Explorer and add the following:

    <PropertyGroup>
       <WasmShellIndexHtmlPath>index.html</WasmShellIndexHtmlPath>
    </PropertyGroup>
    

    Create custom Maps element

    Similarly to the tutorial in docs, I created the following element:

    #if __WASM__
    using System;
    using Uno.UI.Runtime.WebAssembly;
    using Windows.UI;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Media;
    
    namespace UnoWasmGoogleMaps
    {
        [HtmlElement("div")]
        public class GoogleMapView : FrameworkElement
        {
            private readonly string _mapObjectName = "map_" + Guid.NewGuid().ToString().Replace("-", "");
    
            public GoogleMapView()
            {            
                Background = new SolidColorBrush(Colors.Transparent);
                LoadMap();
            }
    
            private void LoadMap()
            {
                var javascript = $@"var {_mapObjectName} = new google.maps.Map(element, {{ center: {{lat: -34.397, lng: 150.644}}, zoom: 8 }});";
    
                this.ExecuteJavascript(javascript);
            }
        }
    }
    #endif
    

    Note I am using a custom unique variable name to store the map object created by Google Maps API. You can use this variable name to manipulate the map and call some functions on it.

    Use the element

    Finally, you can use the element in your app like this:

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <local:GoogleMapView />
    </Grid>
    

    Result

    Hello Google Maps in Uno Platform!