Enable on-site search signals in an Android app

The on-site search signal captures the queries users enter in your app's search screen and the number of results returned. It helps identify popular search terms, discover content gaps, and segment users by the terms they searched for.

Availability: Premium and Ultimate

Languages: Kotlin and Java


Implementation considerations

When to fire the signal

In Android apps, search results typically update as the user types. Choose a firing strategy that captures a deliberate search rather than every keystroke:

  • On text change with debounce — fire after the user stops typing for a set interval (for example, 500 ms). Recommended for live-search implementations where results update as the user types and there is no explicit submit action.
  • On IME action — fire when the user taps the search or done key on the keyboard (EditorInfo.IME_ACTION_SEARCH or IME_ACTION_DONE). Better for search screens with an explicit submit step.
  • On fragment pause — fire when the user navigates away from the search screen, capturing the final query state. Useful when neither of the above is practical.

Effect values

Use the effect field to indicate whether the search returned results:

  • "positive" — the search returned one or more results
  • "negative" — the search returned zero results

Contact mapping

Use the audience field to map the signal to a contact in Connect. Commonly used identifiers are email address, phone number, and customer ID.

📘

Note

A customer ID mapped to the Contact key attribute in Connect is the most reliable identifier for known contacts. However, if no matching contact exists and the only attribute is a contact key, the signal is discarded — contact keys alone cannot create new contacts.

Sources of the identifier in an Android app:

  • In-memory session object (for example, UserSession.email)
  • SharedPreferences for persisted login state
  • Room database or other local store

The value must be a JSONObject where each key is a contact attribute name as it appears in Connect (including capitalization and spacing) and each value is the attribute value. For phone number attributes, use the E.164 format: +[country code][area code][phone number] — no spaces, dashes, or special characters. If the + symbol is omitted, Connect adds it automatically.

📘

Note

We recommend attaching identifiers to as many signals as possible. If the user is not authenticated, omit audience — Connect will attempt to identify the visitor using other signals from the same session.


Configuration

Method

Connect.logSignal(data: HashMap<String?, Any?>?): Boolean

Sends the signal to the Acoustic Connect endpoint. The Connect SDK must be initialized via Connect.enable() before calling this method.

Pass all fields as a flat HashMap. The SDK handles the signal structure automatically — you do not need to construct a nested object.

Signal fields

Required

FieldSchema typeDescription
numberOfResultsNumberNumber of results returned for the search term. Pass as a string.
searchTermStringThe word or phrase the user searched for
signalTypeStringSignal type. Value: "onSiteSearch".

Optional

FieldSchema typeDescription
audienceJSONObjectKey-value pairs for contact mapping. Must be a flat JSONObject — not a HashMap. Keys must match contact attribute names exactly as they appear in Connect.
categoryStringSignal category. Value: "Behavior".
descriptionStringA description of the signal
effectStringDescribes the effect of the signal on engagement. Use "positive" when results are found, "negative" when the search returns zero results.
nameStringA label to differentiate this signal from others (for example, "product search"). Max 256 characters

Things to know

  • numberOfResults has schema type Number and must be passed as a string. Use resultCount.toString().

  • Pass audience as a JSONObject, not a HashMap. A HashMap value is silently dropped by the SDK serializer and the contact will not be mapped.

  • If a required field is missing or invalid, the entire signal is discarded.

Required import

import org.json.JSONObject

Basic example

val data = hashMapOf<String?, Any?>(
    "signalType" to "onSiteSearch",
    "searchTerm" to "wireless headphones",
    "numberOfResults" to "24",
    "effect" to "positive"
)
Connect.logSignal(data)

Complete example

This example fires an on-site search signal in a live-search implementation, where results update as the user types. A 500 ms debounce ensures the signal fires after the user pauses, not on every keystroke. It captures the query and result count, sets the effect based on whether results were found, and includes optional contact mapping.

import android.os.Handler
import android.os.Looper
import org.json.JSONObject
import com.acoustic.connect.android.connectmod.Connect

private val searchDebounce = Handler(Looper.getMainLooper())
private val searchRunnable = Runnable {
    val query = binding.searchField.text?.toString().orEmpty().trim()
    if (query.isNotEmpty()) {
        sendOnSiteSearchSignal(query, adapter.itemCount)
    }
}

// In onViewCreated(), after setting up the adapter:
binding.searchField.doAfterTextChanged {
    refreshResults()
    searchDebounce.removeCallbacks(searchRunnable)
    searchDebounce.postDelayed(searchRunnable, 500)
}

// Cancel the debounce when the view is destroyed to avoid memory leaks:
override fun onDestroyView() {
    super.onDestroyView()
    searchDebounce.removeCallbacks(searchRunnable)
}

private fun sendOnSiteSearchSignal(query: String, resultCount: Int) {
    // numberOfResults has schema type Number — pass as string
    val data = hashMapOf<String?, Any?>(
        "signalType" to "onSiteSearch",
        "searchTerm" to query,
        "numberOfResults" to resultCount.toString(),
        "effect" to if (resultCount > 0) "positive" else "negative"
    )

    // audience must be JSONObject — HashMap values are silently dropped
    UserSession.email?.let { email ->
        data["audience"] = JSONObject().put("Email Address", email)
    }

    Connect.logSignal(data)
}

Troubleshooting

Signal not appearing in Connect?

  • Confirm Connect.enable() is called before logSignal.
  • Capture the return value and check that it is true: val accepted = Connect.logSignal(data). A false return indicates the SDK rejected the call — no additional configuration is required to use the return value.
  • Verify the appKey and postMessageUrl match the Connect org you're checking.

Signal marked as invalid in Connect?

  • Confirm all required fields are present: signalType, searchTerm, numberOfResults.
  • Verify numberOfResults is passed as a string — use resultCount.toString().

Signal fires on every keystroke?

  • If using doAfterTextChanged, add debouncing logic to avoid sending a signal on every character. A 500 ms delay is a reasonable starting point.
  • Alternatively, fire the signal only on the IME search or done action.

Contact not created or updated?

  • Confirm audience is a JSONObject, not a HashMap.
  • Verify attribute key names match exactly how they appear in Connect — including capitalization and spacing.

Related pages