Android SharedPreferences Tutorial and Example

Build an Android map with custom markers

Creating a Map View with Markers Using Google Maps SDK gives an Android app a familiar way to display places, routes, and points of interest. The core pieces are a map container, a key-enabled app, and a callback that runs once the Google map is ready. From there, you can add pins, move the camera, and reveal the user’s position.

For an Australian app, those pins might represent Sydney Harbour, Melbourne’s laneway cafés, or a depot near Uluru. The screen should use kilometres, respect left-hand driving, and remain useful across metro and regional connections, where mobile coverage can be patchy. Labels can use informal Australian English, such as “arvo” for the afternoon, where it fits the audience.

Begin in Android Studio with a Kotlin or Java project, an emulator image that includes Google Play services, and a Google Cloud project. A Google Maps API key is required; without it, the map usually stays blank and the SDK logs an authorisation error. Register the app’s package name and signing certificate before testing.

The implementation below uses a MapView because its lifecycle is explicit, then shows where a fragment can reduce boilerplate. Every marker is positioned with latitude and longitude, while icons, titles, snippets, and click actions provide context beyond a coloured pin. The same foundations suit events, property, delivery, and tourism apps.

Prepare the Android project

Add the Maps SDK dependency from Google’s Maven repository. In the app module’s build.gradle.kts file, use a current stable release; this example uses 19.2.0:

dependencies {
    implementation("com.google.android.gms:play-services-maps:19.2.0")
}

Use an emulator created with Google APIs or Google Play, because the base Android image may not contain the services required by the map. A physical device with current Google Play services is useful for testing the blue location dot, rotation, and performance.

Configure the Maps API key

In Google Cloud Console, select or create a project, attach billing if required, and enable Maps SDK for Android. Create an Android API key, then restrict it by the app’s package name and SHA-1 signing certificate. A debug certificate differs from the certificate used for a Google Play release.

Store the key in local.properties, which should remain outside version control:

MAPS_API_KEY=your_restricted_key

Reference that value as a manifest placeholder rather than repeating it in several source files:

<meta-data
    android:name="com.google.android.gms.maps.API_KEY"
    android:value="${MAPS_API_KEY}" />

Load the property in app/build.gradle.kts. Because an API key is not a secret once packaged in an APK, application restrictions are the main protection. Register the release signing certificate’s SHA-1 as well; otherwise, the production build may show a blank map.

import java.util.Properties

val localProperties = Properties().apply {
    val file = rootProject.file("local.properties")
    if (file.exists()) file.inputStream().use { load(it) }
}

android {
    defaultConfig {
        manifestPlaceholders["MAPS_API_KEY"] =
            localProperties.getProperty("MAPS_API_KEY", "")
    }
}

Add the map to the screen

A MapView can fill the activity layout directly:

<com.google.android.gms.maps.MapView
    android:id="@+id/mapView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Initialise it in the activity and request the asynchronous map callback. Forward the matching lifecycle calls so Google can manage rendering, sensors, and saved state correctly.

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {
    private lateinit var mapView: MapView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        mapView = findViewById(R.id.mapView)
        mapView.onCreate(savedInstanceState)
        mapView.getMapAsync(this)
    }

    override fun onMapReady(googleMap: GoogleMap) {
        googleMap.uiSettings.isZoomControlsEnabled = true
    }

    override fun onStart() {
        super.onStart()
        mapView.onStart()
    }

    override fun onResume() {
        super.onResume()
        mapView.onResume()
    }

    override fun onPause() {
        mapView.onPause()
        super.onPause()
    }

    override fun onDestroy() {
        mapView.onDestroy()
        super.onDestroy()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        mapView.onSaveInstanceState(outState)
    }
}

Choose a map container

The right container depends on the screen architecture. Fragment-based activities usually need less manual work, while a MapView offers direct control inside a custom layout.

Container Lifecycle handling Main benefit Best fit
SupportMapFragment Managed by the fragment Minimal host code Standard Android screens
MapView Forwarded by the host activity Precise layout control Existing views or embedded maps
GoogleMap in Compose Observed by Compose Declarative state New Compose interfaces

For the direct implementation above, MapView makes every lifecycle dependency visible. In a typical single-map activity, SupportMapFragment is often shorter and easier to maintain.

Place and customise markers

MarkerOptions defines each pin’s coordinates and display text. This example marks central Sydney and Melbourne with different built-in colours:

val sydney = LatLng(-33.8688, 151.2093)
val melbourne = LatLng(-37.8136, 144.9631)

val sydneyMarker = googleMap.addMarker(
    MarkerOptions()
        .position(sydney)
        .title("Sydney Harbour")
        .snippet("Start the city walk at Circular Quay")
        .icon(
            BitmapDescriptorFactory.defaultMarker(
                BitmapDescriptorFactory.HUE_AZURE
            )
        )
)
sydneyMarker.tag = "sydney"

val melbourneMarker = googleMap.addMarker(
    MarkerOptions()
        .position(melbourne)
        .title("Federation Square")
        .snippet("Meet beside the Flinders Street entrance")
)
melbourneMarker.tag = "melbourne"

For a branded marker, add a small PNG to res/drawable-nodpi and load it with BitmapDescriptorFactory.fromResource. Set a tag on each marker to identify its location, then handle selection with setOnMarkerClickListener; this keeps business logic separate from the visual pin.

Add location and camera behaviour

Showing the user’s position requires both manifest declarations and a runtime permission request. Add ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION, request them when the feature is used, and set googleMap.isMyLocationEnabled = true only after permission is granted. If the app does not need a blue dot, leave those permissions out.

Control the initial camera with a LatLng and zoom level:

googleMap.moveCamera(
    CameraUpdateFactory.newLatLngZoom(
        LatLng(-25.2744, 133.7751),
        4f
    )
)

That view centres on Australia. Animate to a selected place with animateCamera, or use LatLngBounds to fit several markers. Use the fullscreen map for easier pin selection and ensure its controls remain large enough for reliable touch input.

Release checklist for Australian users

Before publishing through Google Play in Australia, verify both debug and release builds because their signing certificates produce different key restrictions. Upload an Android App Bundle, and register the SHA-1 from the certificate Google uses for app signing; the local upload-key SHA-1 may not match. Test marker taps, rotation, process recreation, and camera restoration on more than one physical device.

A practical release checklist includes:

  • Restrict the production key by the exact package name and release SHA-1 certificate.
  • Request location permission only when the current-location feature requires it.
  • Test on an emulator with Google Play services and on physical Android phones.
  • Provide loading and network-error states for unreliable regional connections.
  • Format distances in kilometres and check guidance for left-hand driving.
  • Keep marker labels readable, support accessibility, and preserve Google attribution.

The finished release then shares the same verified map behaviour across the Australian metro, regional, and rotation scenarios exercised during testing.