material-components/material-components-android · error · IllegalArgumentException

STATE_%s should not be set externally.

Error message

STATE_%s should not be set externally.

What it means

SideSheetBehavior.setState() throws IllegalArgumentException when passed STATE_DRAGGING or STATE_SETTLING. Those two states are transient: they only have meaning while the user is dragging or the sheet is mid-settle, driven internally by ViewDragHelper; externally setting them would corrupt the state machine. The public API accepts only STATE_EXPANDED and STATE_HIDDEN.

Source

Thrown at lib/java/com/google/android/material/sidesheet/SideSheetBehavior.java:635

  /**
   * Removes a previously added callback.
   *
   * @param callback The callback to remove.
   */
  @Override
  public void removeCallback(@NonNull SideSheetCallback callback) {
    callbacks.remove(callback);
  }

  /**
   * Sets the state of the sheet. The sheet will transition to that state with animation.
   *
   * @param state One of {@link #STATE_EXPANDED} or {@link #STATE_HIDDEN}.
   */
  @Override
  public void setState(@StableSheetState int state) {
    if (state == STATE_DRAGGING || state == STATE_SETTLING) {
      throw new IllegalArgumentException(
          "STATE_"
              + (state == STATE_DRAGGING ? "DRAGGING" : "SETTLING")
              + " should not be set externally.");
    }
    final int finalState = state;
    if (viewRef == null || viewRef.get() == null) {
      // The view is not laid out yet; modify state and let onLayoutChild handle it later
      setStateInternal(state);
    } else {
      runAfterLayout(
          viewRef.get(),
          () -> {
            V child = viewRef != null ? viewRef.get() : null;
            if (child != null) {
              startSettling(child, finalState, false);
            }
          });
    }

View on GitHub (pinned to ac7e18efee)

Solutions

  1. Only call setState() with STATE_EXPANDED or STATE_HIDDEN.
  2. In onStateChanged callbacks, never feed the received state back into setState(); treat transient states as read-only signals.
  3. Delete or guard any generic setState(state) forwarding path with a whitelist check.

Example fix

// before
override fun onStateChanged(sheet: View, newState: Int) {
    behavior.setState(newState) // echoes STATE_DRAGGING/STATE_SETTLING -> crash
}

// after
override fun onStateChanged(sheet: View, newState: Int) {
    // read-only: update your own UI based on newState; never call setState with it
}
Defensive patterns

Strategy: type-guard

Validate before calling

static boolean isUserSettableState(int state) {
  return state == BottomSheetBehavior.STATE_EXPANDED
      || state == BottomSheetBehavior.STATE_HIDDEN;
}
// usage
if (isUserSettableState(target)) behavior.setState(target);

Type guard

/** Only STATE_EXPANDED and STATE_HIDDEN may be passed to SideSheetBehavior.setState. */
static boolean isStableSheetState(int state) {
  return state == BottomSheetBehavior.STATE_EXPANDED
      || state == BottomSheetBehavior.STATE_HIDDEN;
}

Prevention

When it happens

Trigger: Calling sideSheetBehavior.setState(BottomSheetBehavior.STATE_DRAGGING) or STATE_SETTLING from app code — often by forwarding a state observed from a callback (e.g. onStateChanged reporting STATE_SETTLING is written back via setState).

Common situations: Echoing callback states back ('state restoration' code that does setState(newState) inside onStateChanged); copy-paste from BottomSheet examples that enumerate all 5 states; switch UIs mapping all states to buttons.

Related errors


AI-assisted analysis of material-components/material-components-android@ac7e18efee (2026-08-14). Data as JSON: /api/errors/c7d5e90a62dea31c. Report an issue: GitHub.