Fork us on GitHub

New Improved Native Google Maps

Render CN1 Components over top of your Native Maps, and more....
New Improved Native Google Maps

New Improved Native Google Maps

One of the primary use-cases that benefits from our recent improvements for native peer integration, is "map apps". That is, apps that use native maps in some shape or form. This is an extremely common uses case for mobile apps these days. Codename One has supported native maps for quite some time, but (up until recently), they were limited by a couple of factors:

  1. Native Widgets Were Always In Front - Since Google Maps were "native" widgets Codename One couldn’t paint over top of the map. Native widgets were always placed in front of the Codename One UI. We could place markers on the map, and draw paths using MapContainer APIs (which were backed by native code on each platform), but we couldn’t, for example, place a Button over top of the map.

  2. The Simulator Still Used the Old MapComponent - The simulator didn’t have support for native maps. It would just use the light-weight Codename One MapComponent, which uses tiles (rather than vector graphics like the native maps), and didn’t behave the same as native maps in some cases. E.g. you could draw over top of the MapComponent, which would cause a bit of a surprise if you were counting on that, only to find out after building for iOS that your beautiful buttons were rendered behind the map.

I am happy to announce that on Friday we released an update for the Google maps library the resolves both of these issues.

  1. You Can Place CN1 Widgets In Front of the Map Now - Now, you can integrate your native maps into your UI seamlessly with the rest of your Codename One UI. Place your codename one widgets under, over, beside, and around your maps…​ but especially over your maps. You don’t need to do anything special for this to happen. The recent native peer improvements cause native peers to just work.

  2. The Simulator Now Behaves More Like Actual Devices - The simulator now uses an internal BrowserComponent with the GoogleMaps Javascript API, which behaves much more like then native maps on device. (Don’t worry, you don’t have to use any Javascript…​ the Java <→ Javascript interop is all hidden. You just use the MapContainer API, and it will take care of the rest.

Configuration

Adding the native maps library is easy. Just open Codename One Settings, click on "Extensions", and install the "Google Maps" library.

Install Google Maps library in Codename One Settings

A little bit has changed since the last time we blogged about native maps. Configuration has gotten a little bit easier. You need to provide separate keys for Android, iOS, and Javascript (If you plan to use the Javascript port). The build hints to provide these values are as follows:

javascript.googlemaps.key=YOUR_JAVASCRIPT_API_KEY
android.xapplication=<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="YOUR_ANDROID_API_KEY"/>
ios.afterFinishLaunching=[GMSServices provideAPIKey:@"YOUR_IOS_API_KEY"];

Make sure to replace the values YOUR_ANDROID_API_KEY, YOUR_IOS_API_KEY, and YOUR_JAVASCRIPT_API_KEY with the values you obtained from the Google Cloud console by following the instructions for Android , for iOS, and for Javascript.

Additionally, if you want to use the Javascript maps in the simulator (highly recommended), you’ll need to provide your JAVASCRIPT_API_KEY as a parameter to the MapContainer constructor:

MapContainer map = new MapContainer(JAVASCRIPT_API_KEY);

Now you’re ready to use native maps. Here is a sample app that demonstrates adding markers and paths, as well as using LayeredLayout to layer Codename One widgets over top of a native map.

public class GoogleMapsTestApp {

    private static final String HTML_API_KEY = "*********************************";
    private Form current;

    public void init(Object context) {
        try {
            Resources theme = Resources.openLayered("/theme");
            UIManager.getInstance().setThemeProps(theme.getTheme(theme.getThemeResourceNames()[0]));
            Display.getInstance().setCommandBehavior(Display.COMMAND_BEHAVIOR_SIDE_NAVIGATION);
            UIManager.getInstance().getLookAndFeel().setMenuBarClass(SideMenuBar.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        if (current != null) {
            current.show();
            return;
        }
        Form hi = new Form("Native Maps Test");
        hi.setLayout(new BorderLayout());
        final MapContainer cnt = new MapContainer(HTML_API_KEY);

        Button btnMoveCamera = new Button("Move Camera");
        btnMoveCamera.addActionListener(e->{
            cnt.setCameraPosition(new Coord(-33.867, 151.206));
        });
        Style s = new Style();
        s.setFgColor(0xff0000);
        s.setBgTransparency(0);
        FontImage markerImg = FontImage.createMaterial(FontImage.MATERIAL_PLACE, s, Display.getInstance().convertToPixels(3));

        Button btnAddMarker = new Button("Add Marker");
        btnAddMarker.addActionListener(e->{

            cnt.setCameraPosition(new Coord(41.889, -87.622));
            cnt.addMarker(
                    EncodedImage.createFromImage(markerImg, false),
                    cnt.getCameraPosition(),
                    "Hi marker",
                    "Optional long description",
                     evt -> {
                             ToastBar.showMessage("You clicked the marker", FontImage.MATERIAL_PLACE);
                     }
            );

        });

        Button btnAddPath = new Button("Add Path");
        btnAddPath.addActionListener(e->{

            cnt.addPath(
                    cnt.getCameraPosition(),
                    new Coord(-33.866, 151.195), // Sydney
                    new Coord(-18.142, 178.431),  // Fiji
                    new Coord(21.291, -157.821),  // Hawaii
                    new Coord(37.423, -122.091)  // Mountain View
            );
        });

        Button btnClearAll = new Button("Clear All");
        btnClearAll.addActionListener(e->{
            cnt.clearMapLayers();
        });

        cnt.addTapListener(e->{
            TextField enterName = new TextField();
            Container wrapper = BoxLayout.encloseY(new Label("Name:"), enterName);
            InteractionDialog dlg = new InteractionDialog("Add Marker");
            dlg.getContentPane().add(wrapper);
            enterName.setDoneListener(e2->{
                String txt = enterName.getText();
                cnt.addMarker(
                        EncodedImage.createFromImage(markerImg, false),
                        cnt.getCoordAtPosition(e.getX(), e.getY()),
                        enterName.getText(),
                        "",
                        e3->{
                                ToastBar.showMessage("You clicked "+txt, FontImage.MATERIAL_PLACE);
                        }
                );
                dlg.dispose();
            });
            dlg.showPopupDialog(new Rectangle(e.getX(), e.getY(), 10, 10));
            enterName.startEditingAsync();
        });

        Container root = LayeredLayout.encloseIn(
                BorderLayout.center(cnt),
                BorderLayout.south(
                        FlowLayout.encloseBottom(btnMoveCamera, btnAddMarker, btnAddPath, btnClearAll)
                )
        );

        hi.add(BorderLayout.CENTER, root);
        hi.show();

    }

    public void stop() {
        current = Display.getInstance().getCurrent();
    }

    public void destroy() {
    }
}
Native Maps Demo App Screensshot
Figure 1. Native Maps Demo App Screensshot

You can view the Javascript version of this app here.

Read more about the Google Maps library in its Github repository.

Interaction Dialog vs Dialog

When using native peers, you’ll find it preferable to use InteractionDialog rather than Dialog whenever possible. This is because Dialog is actually a Form, and Codename One uses a "trick" (displaying a screen shot) to be able to display the existing form underneath it. InteractionDialog, on the other hand, is a light-weight component that acts like a dialog, but is rendered in the LayeredPane in front of the elements of the current form. This plays with the new native peers quite nicely.

Share this Post:

Posted by Steve Hannah

Steve writes software for Codename One. He is an open source enthusiast who loves to tinker with new technologies.