didi/DoKit · error · IllegalArgumentException

The key is null.

Error message

The key is null.

What it means

DebouncingUtils.isValid(key, duration) implements click-debouncing keyed on a string (typically a View hash) stored in a static map with an expiry timestamp. An empty or null key cannot identify a debounce entry, so it fails fast with IllegalArgumentException('The key is null.') despite the @NonNull annotation — the annotation is not enforced at runtime in Android unless Kotlin null-checks are in play.

Source

Thrown at Android/dokit-util/src/main/java/com/didichuxing/doraemonkit/util/DebouncingUtils.java:62

     *
     * @param view     The view.
     * @param duration The duration.
     * @return {@code true}: yes<br>{@code false}: no
     */
    public static boolean isValid(@NonNull final View view, final long duration) {
        return isValid(String.valueOf(view.hashCode()), duration);
    }

    /**
     * Return whether the key is not in a jitter state.
     *
     * @param key      The key.
     * @param duration The duration.
     * @return {@code true}: yes<br>{@code false}: no
     */
    public static boolean isValid(@NonNull String key, final long duration) {
        if (TextUtils.isEmpty(key)) {
            throw new IllegalArgumentException("The key is null.");
        }
        if (duration < 0) {
            throw new IllegalArgumentException("The duration is less than 0.");
        }
        long curTime = SystemClock.elapsedRealtime();
        clearIfNecessary(curTime);
        Long validTime = KEY_MILLIS_MAP.get(key);
        if (validTime == null || curTime >= validTime) {
            KEY_MILLIS_MAP.put(key, curTime + duration);
            return true;
        }
        return false;
    }

    private static void clearIfNecessary(long curTime) {
        if (KEY_MILLIS_MAP.size() < CACHE_SIZE) return;
        for (Iterator<Map.Entry<String, Long>> it = KEY_MILLIS_MAP.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<String, Long> entry = it.next();

View on GitHub (pinned to 626827cddb)

Solutions

  1. Always pass a non-empty key; for views use the provided isValid(view, duration) overload which derives the key from hashCode().
  2. If the key is dynamic, fall back to a constant: key = TextUtils.isEmpty(key) ? "default" : key.
  3. Add Objects.requireNonNull(key) at your call site to fail with a clearer stack trace.

Example fix

// before
DebouncingUtils.isValid(tag, 500); // tag == "" -> throws

// after
String safeTag = TextUtils.isEmpty(tag) ? "global" : tag;
DebouncingUtils.isValid(safeTag, 500);
Defensive patterns

Strategy: validation

Validate before calling

String safeKey = TextUtils.isEmpty(key) ? "fallback_key" : key;
boolean valid = DebouncingUtils.isValid(safeKey, duration);

Prevention

When it happens

Trigger: Calling isValid with an empty String key: isValid("", 500); or from Java with a null key that slips past @NonNull; building the key from a nullable field via String.valueOf(null) producing 'null' is fine, but concatenation of nulls or trimming can produce empty strings.

Common situations: Debouncing helper wrappers that accept an optional tag and default it to null/empty; dynamic keys built from view identifiers that are unset in some layout variants; Java callers ignoring the @NonNull contract.

Related errors


AI-assisted analysis of didi/DoKit@626827cddb (2026-08-14). Data as JSON: /api/errors/e2bab2bff3a21410. Report an issue: GitHub.