dokar3 / ChipTextField

Editable chip layout for Compose Multiplatform
Apache License 2.0
80 stars 3 forks source link

Support MVVM approch #120

Open hlayan opened 5 months ago

hlayan commented 5 months ago

Currently, chips are handled by ChipTextFieldState internally. Chips should come from outside like ViewModel. Also there should be Events or Listeners while removing chips that is currently handled internally.

For example, LIKE TextField's value and onValueChanged design (unidirectional)

dokar3 commented 5 months ago

Thanks for the feedback. That's right, currently, there is no onValueChange callback.

The main reason is that the Chip class also holds a TextFieldValue and a FocusInteraction.Focus, so it may not be a good idea to maintain these ui states in ViewModel in my opinion.

But if you need the UDF pattern or TextField like API, you can create a wrapper to sync chips between the view model and the UI:

@Composable
fun ChipTextField(
    state: ChipTextFieldState<Chip>,
    chips: List<Chip>,
    onValueChange: (chips: List<Chip>) -> Unit,
    modifier: Modifier = Modifier,
) {
    LaunchedEffect(chips) {
        state.chips = chips
    }

    LaunchedEffect(state, onValueChange) {
        snapshotFlow { state.chips.map { it.text } }
            .collect { onValueChange(state.chips) }
    }

    ChipTextField(
        state = state,
        modifier = modifier,
        onSubmit = ::Chip,
    )
}

This will make your view model dependent on the library class. For List<String> or your types, you can do a 'half-sync':

data class UiState(
    val tags: List<String> = emptyList(),
)

@Composable
fun SomeScreen(
    modifier: Modifier = Modifier,
    viewModel: ViewModel = remember { ViewModel() },
) {
    val uiState by viewModel.uiState.collectAsState()

    val state = rememberChipTextFieldState<Chip>()

    LaunchedEffect(state, uiState.tags) {
        if (state.chips.isEmpty() && uiState.tags.isNotEmpty()) {
            // Set the initial chips from the view model
            state.chips = uiState.tags.map(::Chip)
        }
    }

    LaunchedEffect(state) {
        snapshotFlow { state.chips.map { it.text } }
            .collect { viewModel.updateTags(it) } // Update the view model chips
    }

    ChipTextField(
        state = state,
        onSubmit = ::Chip,
    )
}