Skip to content

Add configurable character limits and feature toggles for polls#6435

Open
ryanhurststrava wants to merge 1 commit into
GetStream:v6from
ryanhurststrava:feature/poll_character_limits_v2
Open

Add configurable character limits and feature toggles for polls#6435
ryanhurststrava wants to merge 1 commit into
GetStream:v6from
ryanhurststrava:feature/poll_character_limits_v2

Conversation

@ryanhurststrava
Copy link
Copy Markdown

@ryanhurststrava ryanhurststrava commented May 11, 2026

🎯 Goal

Introduce comprehensive configuration for poll creation features, allowing developers to control which features are available to users, set default values, and enforce character limits on questions and options. This provides flexibility for different use cases where certain poll features may need to be restricted, pre-configured, or character-limited based on UI constraints, business rules, or localization requirements. This loosely follows the iOS implementation.

🛠 Implementation

New Files Created

PollsConfig.kt

  • New PollsConfig data class for configuring all poll creation features
  • New PollsEntryConfig data class for individual feature configuration (configurable flag + default value)
  • Configurable features:
    • multipleVotes - Allow users to select multiple options
    • anonymousPoll - Hide voter identities
    • suggestAnOption - Allow users to add their own options
    • addComments - Allow users to add comments to polls
  • Character limits:
    • questionTextLimit - Optional character limit for poll questions
    • optionTextLimit - Optional character limit for poll options
  • Both classes implement Parcelable for passing via Bundle arguments
  • Includes Default and NotConfigurable presets

Modified Files

ChatUI.kt

  • Added pollsConfig property to provide global default configuration
  • Defaults to PollsConfig.Default (all features configurable and disabled by default)

CreatePollDialogFragment.kt

  • Updated newInstance() to accept optional PollsConfig parameter
  • Falls back to ChatUI.pollsConfig when not provided
  • New configurePollFeatures() method to show/hide UI elements based on config
  • Applies character limits using InputFilter.LengthFilter
  • Wires up "add a comment" feature that was previously in UI but not functional
  • Sets initial switch states based on config default values

CreatePollViewModel.kt

  • Added allowAnswers field and setAllowAnswers() method
  • Passes allowAnswers to the poll configuration when creating polls

OptionsAdapter.kt

  • Added optionTextLimit parameter to constructor
  • Applies InputFilter.LengthFilter to option EditText fields when limit is specified
  • Maintains backward compatibility with overloaded constructor

Key Design Decisions

  • Backward Compatibility: All features remain configurable by default; existing code continues to work without changes
  • Two-Level Configuration: Features can be controlled via global ChatUI.pollsConfig or per-dialog via newInstance()
  • Visibility Control: When a feature is not configurable, its UI elements are completely hidden
  • Default Values: Features can be pre-configured with default on/off states
  • Optional Limits: Character limits default to null (unlimited) unless explicitly specified

Usage Examples

// Global configuration
ChatUI.pollsConfig = PollsConfig(
    multipleVotes = PollsEntryConfig(configurable = false, defaultValue = false),
    anonymousPoll = PollsEntryConfig(configurable = true, defaultValue = true),
    questionTextLimit = 200,
    optionTextLimit = 100
)

// Per-dialog configuration
CreatePollDialogFragment.newInstance(
    createPollDialogListener = myListener,
    pollsConfig = PollsConfig(
        suggestAnOption = PollsEntryConfig.NotConfigurable,
        addComments = PollsEntryConfig(configurable = true, defaultValue = false),
        questionTextLimit = 150,
        optionTextLimit = 75
    )
)

📱 UI Changes

  • Poll features can now be hidden when not configurable
  • Features can be pre-enabled with default values
  • Character limits are enforced at the input level using InputFilter
  • "Add a comment" switch is now functional

✅ Testing

Manual Testing Steps

  1. Test default behavior - all features configurable, none enabled by default
  2. Hide features - set configurable = false, verify UI elements are hidden
  3. Set default values - verify switches initialize to correct state
  4. Test character limits on questions and options
  5. Verify "add a comment" feature now works correctly
  6. Test global ChatUI.pollsConfig vs per-dialog config
  7. Verify backward compatibility with existing newInstance() calls

Expected Behavior

  • Non-configurable features are hidden from the UI
  • Configurable features display with correct default switch states
  • Character limits prevent input beyond specified length
  • Configuration can be set globally or per-dialog
  • Existing code without config works unchanged

Contributor Checklist

  • I have signed the Stream CLA (required)
  • I have assigned a reviewer (code owner)
  • This PR targets the develop branch
  • I have added unit tests for new code
  • I have updated documentation (KDocs included in code)

Reviewer Checklist

  • UI Components sample app has been tested
  • Compose sample app has been tested (N/A - UI Components only)
  • Visuals are correct
  • Feature is validated
  • KDocs have been reviewed
  • SDK size impact has been reviewed from CI logs

🎉 GIF

1778192411.mp4
Screen_recording_20260511_173314.webm

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced comprehensive poll configuration system enabling control over which poll creation features are available to users
    • Added configurable text limits for poll questions and options
    • Made poll settings (multiple votes, anonymous polls, suggest-an-option, add comments) individually configurable with customizable default values
    • Added option to control whether poll responses can include answers

Review Change Stack

Introduces PollsConfig to control poll feature availability and enforce character limits on questions and options. Poll features (multiple votes, anonymous voting, suggest options, add comments) can now be
hidden or preset with default values through ChatUI.pollsConfig or passed directly to CreatePollDialogFragment.
@ryanhurststrava ryanhurststrava marked this pull request as ready for review May 11, 2026 21:49
@ryanhurststrava ryanhurststrava requested a review from a team as a code owner May 11, 2026 21:49
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Walkthrough

This PR introduces a centralized poll configuration system allowing apps to control which poll creation features are user-configurable, their default states, and text input limits. New PollsConfig and PollsEntryConfig parcelable models are exposed globally via ChatUI.pollsConfig, read by CreatePollDialogFragment, and applied to UI visibility, feature defaults, view model state, and text constraints.

Changes

Poll Configuration Feature

Layer / File(s) Summary
Configuration Model
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt
PollsEntryConfig parcelize data class with configurable and defaultValue booleans; PollsConfig parcelize data class aggregating multiple feature entries and optional text limits; both include Default companion presets.
Global Configuration
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt
ChatUI.pollsConfig static property initialized to PollsConfig.Default; import for PollsConfig type.
Dialog Factory & Config Read
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt
newInstance(createPollDialogListener, pollsConfig: PollsConfig? = null) factory method with @JvmOverloads; stores config in fragment arguments under ARG_POLLS_CONFIG; defaults to ChatUI.pollsConfig if not provided.
Feature UI Wiring
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt
configurePollFeatures() helper reads pollsConfig and drives feature switch visibility and checked states for multiple votes, anonymous poll, suggest-an-option, and add-comments based on configurable and defaultValue flags; wires add-comment toggle to setAllowAnswers().
View Model State
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollViewModel.kt
Adds allowAnswers private state property; new public setAllowAnswers(Boolean) method; pollConfig construction includes allowAnswers value.
Option Text Input Limiting
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt
OptionsAdapter constructor updated to accept optional optionTextLimit parameter with public overload for backward compatibility; OptionViewHolder applies InputFilter.LengthFilter(optionTextLimit) to option EditText when limit is non-null; onCreateViewHolder passes limit to view holder.

Poem

🐰 A polls config blooms, so neat and tidy,
Features configurable, defaults stringent,
Text limits guard each option's length with pride,
From ChatUI down to each input field descent!

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding configurable character limits and feature toggles for the poll creation system.
Description check ✅ Passed The description is comprehensive and well-structured, covering goal, implementation details, design decisions, usage examples, testing steps, and includes a GIF demonstration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt (1)

240-240: ⚡ Quick win

Remove unnecessary cast.

The explicit cast to android.os.Parcelable is redundant since PollsConfig already implements Parcelable (defined at line 70 in PollsConfig.kt). Kotlin's type inference handles this automatically.

Simplified code
-                    pollsConfig?.let { config -> putParcelable(ARG_POLLS_CONFIG, config as android.os.Parcelable) }
+                    pollsConfig?.let { putParcelable(ARG_POLLS_CONFIG, it) }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt`
at line 240, Remove the redundant explicit cast in the putParcelable call: where
CreatePollDialogFragment stores pollsConfig with putParcelable(ARG_POLLS_CONFIG,
config as android.os.Parcelable), change it to pass pollsConfig (config)
directly since PollsConfig already implements Parcelable; update the call in the
block that references pollsConfig and ARG_POLLS_CONFIG to remove "as
android.os.Parcelable".
stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt (1)

67-67: ⚡ Quick win

Consider renaming addComments to allowAnswers for consistency.

The config property is named addComments, but the underlying model field in CreatePollViewModel (line 49) and PollConfig (line 106 in CreatePollViewModel.kt) is named allowAnswers. This naming mismatch may confuse developers trying to understand how the config maps to the actual poll behavior.

Suggest renaming to maintain consistent terminology across the configuration and implementation layers.

Proposed fix
-    val addComments: PollsEntryConfig = PollsEntryConfig.Default,
+    val allowAnswers: PollsEntryConfig = PollsEntryConfig.Default,

Update corresponding documentation and usage in CreatePollDialogFragment.kt lines 124-129.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt`
at line 67, Rename the PollsConfig property addComments to allowAnswers to match
the field name used in CreatePollViewModel and PollConfig; update the property
declaration in PollsConfig (currently val addComments: PollsEntryConfig =
PollsEntryConfig.Default) to val allowAnswers: PollsEntryConfig =
PollsEntryConfig.Default, then update all usages and bindings (including where
CreatePollDialogFragment populates the config) and any documentation/comments to
use allowAnswers so terminology is consistent across PollsConfig,
CreatePollViewModel, and PollConfig.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt`:
- Around line 30-40: The public API changed because the primary constructor
parameter order for OptionsAdapter now places optionTextLimit first, breaking
existing positional calls; restore a backward-compatible single-argument
constructor by adding a public constructor matching the old signature
(constructor(onOptionChange: (id: Int, text: String) -> Unit)) that delegates to
the new primary (this(null, onOptionChange)), mark it `@Deprecated` with a message
pointing to the new constructor/parameter and add `@JvmOverloads/`@JvmName if
needed for Java callers so existing binaries still resolve the single-arg
constructor while signaling callers to move to the new API.

---

Nitpick comments:
In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt`:
- Line 240: Remove the redundant explicit cast in the putParcelable call: where
CreatePollDialogFragment stores pollsConfig with putParcelable(ARG_POLLS_CONFIG,
config as android.os.Parcelable), change it to pass pollsConfig (config)
directly since PollsConfig already implements Parcelable; update the call in the
block that references pollsConfig and ARG_POLLS_CONFIG to remove "as
android.os.Parcelable".

In
`@stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt`:
- Line 67: Rename the PollsConfig property addComments to allowAnswers to match
the field name used in CreatePollViewModel and PollConfig; update the property
declaration in PollsConfig (currently val addComments: PollsEntryConfig =
PollsEntryConfig.Default) to val allowAnswers: PollsEntryConfig =
PollsEntryConfig.Default, then update all usages and bindings (including where
CreatePollDialogFragment populates the config) and any documentation/comments to
use allowAnswers so terminology is consistent across PollsConfig,
CreatePollViewModel, and PollConfig.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0ab5b5db-0f77-4ecc-a22d-aa08094ec362

📥 Commits

Reviewing files that changed from the base of the PR and between 7ad0cda and 6116f9c.

📒 Files selected for processing (5)
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/ChatUI.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollDialogFragment.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/CreatePollViewModel.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/OptionsAdapter.kt
  • stream-chat-android-ui-components/src/main/kotlin/io/getstream/chat/android/ui/feature/messages/composer/attachment/picker/poll/PollsConfig.kt

/**
* Configures the visibility and default values of poll features based on [pollsConfig].
*/
private fun configurePollFeatures() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function updates the state of some views, i.e. when setting switch.isChecked = defaultValue, which we should only do when the fragment view is created the first time (savedInstanceState == null). Otherwise, we are going to overwrite user selections on configuration changes.

* @param optionTextLimit Optional character limit for poll answer options. Null means no limit.
*/
@Parcelize
public data class PollsConfig(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a similar config class in the Compose SDK for v7 (on the develop branch), could you align the naming to be consistent with that one?

Basically, my suggestion would be:

  • PollsEntryConfig -> PollFeatureConfig
  • addComments -> allowComments

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see there was similar functionality added to v7. I'll see if I can get upgraded to v7.

Also, I'll create a new pr for character limits in v7.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a look, but keep in mind that the changes I mentioned were for Compose only in v7, so we'd still have to port basically what you're doing in this PR over there.

My suggestion for unblocking you is to move forward with this very PR to the v6 branch so you get the functionality you need. Then the same feature can be ported to v7 and you can bump to it at your own pace, given that the v7 major includes breaking changes too.

): CreatePollDialogFragment {
return CreatePollDialogFragment().apply {
arguments = Bundle().apply {
pollsConfig?.let { config -> putParcelable(ARG_POLLS_CONFIG, config as android.os.Parcelable) }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the cast shouldn't be needed

Suggested change
pollsConfig?.let { config -> putParcelable(ARG_POLLS_CONFIG, config as android.os.Parcelable) }
pollsConfig?.let { config -> putParcelable(ARG_POLLS_CONFIG, config) }

Comment on lines +55 to +60
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelable(ARG_POLLS_CONFIG, PollsConfig::class.java)
} else {
@Suppress("DEPRECATION")
arguments?.getParcelable(ARG_POLLS_CONFIG)
} ?: ChatUI.pollsConfig
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we could use BundleCompat to do this for us:

Suggested change
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelable(ARG_POLLS_CONFIG, PollsConfig::class.java)
} else {
@Suppress("DEPRECATION")
arguments?.getParcelable(ARG_POLLS_CONFIG)
} ?: ChatUI.pollsConfig
arguments?.let { BundleCompat.getParcelable(it, ARG_POLLS_CONFIG, PollsConfig::class.java) }
?: ChatUI.pollsConfig

Comment on lines +99 to +102
createPollViewModel.setAllowMultipleVotes(pollsConfig.multipleVotes.defaultValue)
binding.multipleAnswersLabel.isVisible = pollsConfig.multipleVotes.configurable
binding.multipleAnswersSwitch.isVisible = pollsConfig.multipleVotes.configurable
if (pollsConfig.multipleVotes.configurable) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're introducing a new edge case here.

If we have multipleVotes.defaultValue = true + multipleVotes.configurable = false, then we end up in a state where:

  • In the VM, allowMultipleVotes = true since we set it here
  • multipleAnswersCount is hidden because of configurable = false
  • sendMenuItem is disabled because of the above (due to this logic) so the user can never create the poll

In Compose v7 we changed the behavior to allow multipleVotes = true + maxVotes = null which means unlimited votes, so maybe we could do something like that? It's a behavior change, though.

Another option is to disallow the problematic combo, e.g. we could add a check(multipleVotes.configurable == true || multipleVotes.defaultValue == false) call in PollsConfig's constructor.

cc @andremion @VelikovPetar wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am more in favour of disallowing the problematic combination - I wouldn't want to change the default behaviour in a minor release. But once we port this addition to V7, I would say to align it with the compose implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants