You don't have to make your complex object implement MutableState. You can create a complex object that contains mutableState
s and other objects that doesn't need to trigger recomposition on change. It's a common pattern as in JetSnack app.
@Stable
class SearchState(
query: TextFieldValue,
focused: Boolean,
searching: Boolean,
categories: List<SearchCategoryCollection>,
suggestions: List<SearchSuggestionGroup>,
filters: List<Filter>,
searchResults: List<Snack>
) {
var query by mutableStateOf(query)
var focused by mutableStateOf(focused)
var searching by mutableStateOf(searching)
var categories by mutableStateOf(categories)
var suggestions by mutableStateOf(suggestions)
var filters by mutableStateOf(filters)
var searchResults by mutableStateOf(searchResults)
val searchDisplay: SearchDisplay
get() = when {
!focused && query.text.isEmpty() -> SearchDisplay.Categories
focused && query.text.isEmpty() -> SearchDisplay.Suggestions
searchResults.isEmpty() -> SearchDisplay.NoResults
else -> SearchDisplay.Results
}
}
And you wrap this object with remember to prevent to not instantiate on each recomposition
@Composable
private fun rememberSearchState(
query: TextFieldValue = TextFieldValue(""),
focused: Boolean = false,
searching: Boolean = false,
categories: List<SearchCategoryCollection> = SearchRepo.getCategories(),
suggestions: List<SearchSuggestionGroup> = SearchRepo.getSuggestions(),
filters: List<Filter> = SnackRepo.getFilters(),
searchResults: List<Snack> = emptyList()
): SearchState {
return remember {
SearchState(
query = query,
focused = focused,
searching = searching,
categories = categories,
suggestions = suggestions,
filters = filters,
searchResults = searchResults
)
}
}
Any changes in any of the MutableState
s will trigger recomposition in Composable scopes these values are read.
remember functions that are commonly used such as rememberScrollState and some others also use this approach either.
@Stable
class ScrollState(initial: Int) : ScrollableState {
/**
* current scroll position value in pixels
*/
var value: Int by mutableStateOf(initial, structuralEqualityPolicy())
private set
/**
* maximum bound for [value], or [Int.MAX_VALUE] if still unknown
*/
var maxValue: Int
get() = _maxValueState.value
internal set(newMax) {
_maxValueState.value = newMax
if (value > newMax) {
value = newMax
}
}
/**
* [InteractionSource] that will be used to dispatch drag events when this
* list is being dragged. If you want to know whether the fling (or smooth scroll) is in
* progress, use [isScrollInProgress].
*/
val interactionSource: InteractionSource get() = internalInteractionSource
internal val internalInteractionSource: MutableInteractionSource = MutableInteractionSource()
private var _maxValueState = mutableStateOf(Int.MAX_VALUE, structuralEqualityPolicy())
// Rest of the code
}