Search code examples
androidlitho

Litho Radio Group container


I followed the checkbox example on Litho guide and built a similar implementation for a radio button:

@LayoutSpec
object CRadioSpec {

    @OnCreateLayout
    fun onCreateLayout(
        c: ComponentContext,
        @Prop value: String,
        @State isChecked: Boolean
    ): Component? {
        return Column.create(c)
            .alignContent(YogaAlign.CENTER)
            .widthDip(20F)
            .child(
                Image.create(c)
                    .drawableRes(
                        if (isChecked) R.drawable.ic_checkbox_on
                        else R.drawable.ic_checkbox_off)
                    .clickHandler(CRadio.onCheckboxClicked(c))
            )
            .child(
                Text.create(c)
                    .alignment(TextAlignment.CENTER)
                    .alignSelf(YogaAlign.CENTER)
                    .widthDip(20F)
                    .text(value)
                    .build()
            )
            .build()
    }

    @OnCreateInitialState
    fun onCreateInitialState(
        c: ComponentContext?,
        isChecked: StateValue<Boolean?>,
        @Prop initChecked: Boolean
    ) {
        isChecked.set(initChecked)
    }

    @OnUpdateState
    fun updateCheckboxState(isChecked: StateValue<Boolean?>) {
         isChecked.get()?.let {
             isChecked.set(it.not())
         }
    }

    @OnUpdateState
    fun updateCheckbox(isChecked: StateValue<Boolean?>) {
        isChecked.get()?.let {
            isChecked.set(it.not())
        }

    }

    @OnEvent(ClickEvent::class)
    fun onCheckboxClicked(c: ComponentContext?) {
        CRadio.updateCheckbox(c)
    }
}

However I believe this is to be wrapped inside a parent in order to get multiple radio boxes and state has to be managed from parent. As has been mentioned in the docs:

Let’s take the example of a list of radio buttons, where you cannot have multiple items checked at the same time. The state of all the radio buttons depends on whether other items get clicked and checked. Instead of making all the radio buttons stateful, you should keep the state of what item is clicked in the parent and propagate that information top-down to the children through props.

Link

I did quite a bit of searching but I am not able to find anything relevant. I also checked the Implementation of Spinner Component, but cant get to a good implementation. It would be a great help, if I can be guided to a concrete example?


Solution

  • You have to create a Radio group and manage state from there, that is the parent:

    Here is one way of doing it:

    @LayoutSpec
    object CrgSpec {
        @OnCreateLayout
        fun onCreateLayout(
            c: ComponentContext,
            @Prop viewModel: ViewModel,
            @State fields: ArrayList<RadioItem>,
            @Prop parentId: String
        ): Component? {
            val row =
                Row.create(c)
                    .alignItems(YogaAlign.CENTER)
                    .alignContent(YogaAlign.SPACE_AROUND)
                    .flexGrow(1F)
                    .wrap(YogaWrap.WRAP)
            fields.forEach { item ->
                row.child(
                    CRadio.create(c)
                        .value(item.text)
                        .clickHandler(Crg.onClicked(c, item.id, item.text))
                        .isChecked(item.isChecked)
                        .id(item.id)
                        .build()
                )
            }
            val column = Column.create(c)
                .paddingDip(YogaEdge.TOP, 20F)
                .child(row.build())
            return column.child(
                TextInput.create(c)
                    //.visibleHandler()
                    .backgroundRes(R.drawable.edit_text_bg)
                    .build()
            ).build()
        }
    
        @OnCreateInitialState
        fun onCreateInitialState(
            c: ComponentContext?,
            fields: StateValue<ArrayList<RadioItem>>,
            @Prop initChecked: ArrayList<RadioItem>
        ) {
            fields.set(initChecked)
        }
    
        @OnUpdateState
        fun updateCheckboxState(
            fields: StateValue<ArrayList<RadioItem>>,
            @Param id: String
        ) {
            fields.get()?.let { radioItem ->
                radioItem.forEach {
                    it.isChecked = it.id == id
                }
                fields.set(radioItem)
            }
        }
    
    
        @OnUpdateState
        fun updateCheckbox(
            fields: StateValue<ArrayList<RadioItem>>,
            @Param id: String
        ) {
            fields.get()?.let { radioItem ->
                radioItem.forEach {
                    it.isChecked = it.id == id
                }
                fields.set(radioItem)
            }
        }
    
        @OnEvent(ClickEvent::class)
        fun onClicked(
            c: ComponentContext?,
            @Prop viewModel: ViewModel,
            @Param id: String,
            @Prop parentId: String,
            @Param itemValue: String
        ) {
            Timber.d("id is: $id")
            CirclesRadioGroup.updateCheckbox(c, id)
            viewModel.onRadioUpdate(
                id,
                parentId,
                itemValue
            )
        }
    }
    

    Refrences:

    Bonus:- This is also a very nice example of how you would get user action callbacks in your view model and a compact example of a server driven UI on Android.