Skip to main content

Social Report

Last updated: June 18, 2026 | 6 minutes read

This example demonstrates how to report social events (community road events such as police, hazards or accidents) from within an app, and how to view and delete the reports you have submitted. The user picks an event type (and, where applicable, the side the event is on), the report is sent at the current position, the map centers on the new report, and tapping one of their own reports lets them delete it.

Press the button to report a social event
Choose a social event category
Pick the side the event is on
Map centers on the event after reporting
Tap a reported event to delete it

Map Setup and Permissions

MainActivity overrides onCreate, which inflates the view binding, registers the SDK listeners, requests the location permissions needed to report at the current position, and checks for an internet connection. The Report button opens ReportCategoriesActivity, where the user chooses what to report.

MainActivity.ktView on Github
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = false

registerSdkListeners()

requestPermissions(this)

if (!Util.isInternetConnected(this)) {
showDialog(getString(R.string.internet_required))
}

binding.reportEventButton.setOnClickListener {
startActivity(Intent(this, ReportCategoriesActivity::class.java))
}
}

Choosing a Category

ReportCategoriesActivity shows the available report categories in a grid. The categories come from SocialOverlay.reportsOverlayInfo: the top-level list is obtained with getCategories(countryISOCode) for the current country, and a category's children with getCategory(id)?.subcategories. Each category is rendered as a card (icon + name) by the CategoryAdapter.

ReportCategoriesActivity.ktView on Github
private fun loadCategories(categoryId: Int) = SdkCall.execute {
val overlayInfo = SocialOverlay.reportsOverlayInfo ?: return@execute
val iconSize = resources.getDimension(R.dimen.event_image_size).toInt()
val items = mutableListOf<CategoryItem>()

if (categoryId == INVALID_ID) {
val countryISOCode = MapDetails().isoCodeForCurrentPosition ?: return@execute
val categories = overlayInfo.getCategories(countryISOCode) ?: return@execute
for (category in categories) {
val name = category.name ?: continue
items.add(CategoryItem(
uid = category.uid,
name = name,
icon = category.image?.asBitmap(iconSize, iconSize),
hasSubcategories = category.hasSubcategories(),
))
}
} else {
val categories = overlayInfo.getCategory(categoryId)?.subcategories ?: return@execute
for (category in categories) {
val name = category.name ?: continue
items.add(CategoryItem(
uid = category.uid,
name = name,
icon = category.image?.asBitmap(iconSize, iconSize),
hasSubcategories = category.hasSubcategories(),
))
}
}

runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
binding.categoriesRecyclerView.layoutManager = GridLayoutManager(this, GRID_SPAN_COUNT)
binding.categoriesRecyclerView.adapter = CategoryAdapter(items) { item -> onCategorySelected(item) }
}
}

Tapping a category either drills into its subcategories (opening another ReportCategoriesActivity) or, if it is a leaf category, submits the report directly.

ReportCategoriesActivity.ktView on Github
private fun onCategorySelected(item: CategoryItem) {
if (item.hasSubcategories) {
startActivity(
Intent(this, ReportCategoriesActivity::class.java).apply {
putExtra(EXTRA_CATEGORY_ID, item.uid)
putExtra(EXTRA_CATEGORY_NAME, item.name)
}
)
} else {
submitReport(item.uid)
}
}

Sending the Report

submitReport() first calls SocialOverlay.prepareReporting(), which returns a positive preparation id on success or a non-positive error code (most commonly when the GPS fix is not accurate enough). The report is then sent with SocialOverlay.report(prepareId, categoryUid, listener). On success, the activity returns to MainActivity, passing the report's coordinates so the map can center on it.

ReportCategoriesActivity.ktView on Github
private fun submitReport(categoryUid: Int) = SdkCall.execute {
val prepareIdOrError = SocialOverlay.prepareReporting()
if (prepareIdOrError <= 0) {
// A negative/zero id means reporting could not be prepared (commonly a poor GPS fix).
val errorMsg = if (prepareIdOrError == GemError.NotFound || prepareIdOrError == GemError.Required) {
getString(R.string.gps_accuracy_not_good)
} else {
GemError.getMessage(prepareIdOrError, this)
}
runOnUiThread { showDialog(errorMsg) }
return@execute
}

val position = PositionService.improvedPosition?.takeIf { it.isValid() }
val error = SocialOverlay.report(prepareIdOrError, categoryUid, socialReportListener)

runOnUiThread {
if (isFinishing || isDestroyed) return@runOnUiThread
if (GemError.isError(error)) {
showDialog(SdkCall.runSynced { GemError.getMessage(error, this) } ?: "")
} else {
val coords = position?.coordinates
startActivity(
Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
if (coords != null) {
putExtra(EXTRA_REPORT_LAT, coords.latitude)
putExtra(EXTRA_REPORT_LON, coords.longitude)
}
}
)
finish()
}
}
}

Centering on the New Report

Back in MainActivity, onNewIntent receives the report's coordinates and shows a confirmation sheet. Once the sheet is laid out, the map centers on the new report with centerOnCoordinates, placing it in the part of the map left visible above the sheet.

MainActivity.ktView on Github
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
setIntent(intent)
val lat = intent.getDoubleExtra(ReportCategoriesActivity.EXTRA_REPORT_LAT, Double.NaN)
val lon = intent.getDoubleExtra(ReportCategoriesActivity.EXTRA_REPORT_LON, Double.NaN)
if (!lat.isNaN() && !lon.isNaN()) {
showReportSentDialog(lat, lon)
}
}
MainActivity.ktView on Github
private fun showReportSentDialog(lat: Double, lon: Double) {
showBottomSheet(
title = getString(R.string.info),
message = getString(R.string.report_sent_message),
onShow = { root ->
// Wait for the sheet to be laid out before reading its height.
root.post {
val freeRect = getFreeSpaceRect(bottomOffset = root.height)
SdkCall.execute {
binding.gemSurfaceView.mapView?.centerOnCoordinates(
Coordinates(lat, lon),
-1,
freeRect.center,
Animation(EAnimation.Linear, 900),
0.0,
0.0,
)
}
}
}
)
}

Detecting and Deleting Your Reports

Once the worldwide road map is available, registerSdkListeners() installs a mapView.onTouch handler. Each tap sets the cursor position and inspects the overlay items under it. fetchSocialEventInfo keeps only social-report overlays - and only those the current user may delete (their allow_delete preview flag is true) - and the event panel is shown for those.

MainActivity.ktView on Github
SdkSettings.onWorldwideRoadMapSupportStatus = { status ->
if (status == EOffboardListenerStatus.UpToDate) {
SdkSettings.onWorldwideRoadMapSupportStatus = {}
SdkCall.execute {
val mapView = binding.gemSurfaceView.mapView ?: return@execute

mapView.onTouch = { xy ->
SdkCall.execute {
mapView.cursorScreenPosition = xy
val eventInfo = fetchSocialEventInfo(mapView.cursorSelectionOverlayItems)
if (eventInfo != null) {
Util.postOnMain { showSocialEventPanel(eventInfo) }
} else {
Util.postOnMain { hideSocialEventPanel() }
}
}
}
}
}
}
MainActivity.ktView on Github
// Must be called on the SDK thread. Returns null if the tapped overlay is not
// a self-reported social event (i.e., one the current user can delete).
private fun fetchSocialEventInfo(overlays: List<OverlayItem>?): SocialEventInfo? {
val overlay = overlays?.firstOrNull {
it.overlayInfo?.uid == ECommonOverlayId.SocialReports.value
} ?: return null

val previewData = overlay.getPreviewData() ?: return null

// "allow_delete" is true only on reports submitted by the current user.
if (previewData.find { it.key == "allow_delete" }?.valueBoolean != true) return null

val iconSize = resources.getDimension(R.dimen.event_image_size).toInt()
return SocialEventInfo(
overlay = overlay,
bitmap = overlay.image?.asBitmap(iconSize, iconSize),
name = overlay.name.toString(),
time = formatEventTimestamp(
previewData.find { it.key == ESocialOverlayParamsKeys.ReportCreateTimeUTC.value }?.valueLong ?: 0
),
)
}

The event panel shows the report's icon, name and time, and its delete button removes the report with SocialOverlay.deleteReport(item, deleteReportListener). On success the report simply disappears from the map; the listener only reports a failure.

MainActivity.ktView on Github
private fun showSocialEventPanel(info: SocialEventInfo) {
currentOverlayItem = info.overlay
binding.icon.setImageBitmap(info.bitmap)
binding.text.text = info.name
binding.time.text = info.time
binding.eventPanel.visibility = View.VISIBLE
binding.deleteButton.setOnClickListener {
val item = currentOverlayItem ?: return@setOnClickListener
SdkCall.execute { SocialOverlay.deleteReport(item, deleteReportListener) }
hideSocialEventPanel()
}
}