ConfigFlowHandler and OptionsFlowHandler - managing the same parameter

Pete, you almost saved me from more hours of troubleshooting, I was getting crazy because it was exactly what I needed to implement in my component, I didn’t want to duplicate data from config to options, I wanted to be able to let users edit a subset of config data through the Options Flow.

Now the good news is that I’m able to edit data and saving it in config correctly, the bad news is that the options form is not picking up the labels I configured in en.json, and I can’t understand why. :frowning:

This is the main config form of a first configuration:

This is the options form when I click configure (data is saved correctly, but I don’t see any labels):

image

Any help is greatly appreciated, you can find the source of config_flow.py and en.json in my repo.

Thanks a lot.

It may be that the HASS language is not set to en in your dev environment. Check the .storage/core.config file.

I also have a strings.json in the same directory as the code. I believe when no language is specified it uses those. GitHub - PeteRager/lennoxs30: Home Assistant Lennox S30 / E30 / M30 integration

You can also try copying the .en file to strings.json and see if that fixes it.

But for the config labels it works…I simply duplicated the config lines to options section in the en.json file.

Anyway, I checked core.config: "language": "en"

I also copied en.json to strings.json in source dir, but no luck. :frowning:

Any other idea? I bet it’s some stupid thing in the en.json file…but I can’t see it…it looks good to me.

This morning I restarted HA and it seems to be working fine now, who knows why it didn’t pick-up the labels yesterday. Anyway, seems to be ok now. Thanks a lot @PeteRage, your findings should be in the documentation. It was so hard understanding how to be able to edit a proper working Config Flow, but I guess it’s because I’m not a developer. :slight_smile:

Always thankful for this great community.

image

@PeteRage I forgot to ask one thing: when I save the config, in order to have the integration pick-up the new values, I need to reload it. Is there a way to reload it programmatically when the user clicks submit?

There may be, but i don’t know how to do it.

Thanks anyway Pete.

Reloading it @alexdelprete

1 Like

thank you so much.

questions:

  1. Since I have to reload in the Option Flow, where in reality I’m editing config entries, can I use that function also there or it only works in the context of Config Flow?
  2. I found another example that has a structure more similar to my component, with the difference that it’s used in ConfigFlow. So the idea is to put these two in my option flow, after I update the config items in async_step_init. Do you think it’s ok?
class ABBPowerOnePVISunSpecOptionsFlow(config_entries.OptionsFlow):
    """Config flow options handler"""

    VERSION = 1

    def __init__(self, config_entry: ConfigEntry) -> None:
        """Initialize option flow instance"""
        self.config_entry = config_entry
        self.data_schema=vol.Schema(
            {
                vol.Required(
                    CONF_PORT,
                    default=self.config_entry.data[CONF_PORT],
                ): vol.Coerce(int),
                vol.Required(
                    CONF_SLAVE_ID,
                    default=self.config_entry.data[CONF_SLAVE_ID],
                ): vol.Coerce(int),
                vol.Required(
                    CONF_BASE_ADDR,
                    default=self.config_entry.data[CONF_BASE_ADDR],
                ): vol.Coerce(int),
                vol.Required(
                    CONF_SCAN_INTERVAL,
                    default=self.config_entry.data[CONF_SCAN_INTERVAL],
                ): vol.All(vol.Coerce(int), vol.Range(min=5, max=900)),
            }
        )
    async def async_step_init(self, user_input=None):  # pylint: disable=unused-argument
        """Manage the options"""

        if user_input is not None:
            # complete non-edited entries before update (ht @PeteRage)
            if CONF_NAME in self.config_entry.data:
                user_input[CONF_NAME] = self.config_entry.data[CONF_NAME]
            if CONF_HOST in self.config_entry.data:
                user_input[CONF_HOST] = self.config_entry.data[CONF_HOST]

            # write updated config entries (ht @PeteRage / @anon63427907)
            self.hass.config_entries.async_update_entry(
                self.config_entry, data=user_input, options=self.config_entry.options
            )
            # reload updated config entries (ht @anon63427907)
            await self.hass.config_entries.async_reload(self.config_entry.entry_id)
            self.async_abort(reason="configuration updated")

            # write empty options entries (ht @PeteRage / @anon63427907)
            return self.async_create_entry(title="", data={})

        return self.async_show_form(step_id="init", data_schema=self.data_schema)

I might be wrong, but ConfigFlow is supposed to create the config entry (async_create_entry) and OptionFlow is supposed to update it. So, what would be the point of reloding the entry under ConfigFlow before creating it?

Can you try your code with a fresh new addition of the integration, rather than editing the existing instance?

I agree with what you say, that’s why I didn’t understand why in the EufySecurity code, the async_reload is in the config_flow. That’s why I asked if it should be in the OptionFlow, after the user updates the values.

I tried my code (check previous post) now and it seems to be working fine. I tested it on the polling period, need to do further tests, but thanks a lot for now. :slight_smile:

i have it because of re-authentication flow, but you are right, it is far from clear

That leads me to think that you can use AsyncReload in both ConfigFlow and OptionsFlow.

Everything related to ConfigFlow/OptionsFlow is very far from clear, if you ask me. If it weren’t for people like you and a couple of other guys I would never managed to implement what I needed.

Thanks again.

You are right, we can call async_reload anytime, as long as it is already configured once. Would it fail, in case it was not initialized once?

I think it’s available, as long as you pass it a valid ConfigEntry. But I’m just speculating.

You could decouple it by firing an event (or figure out if one is already being fired) and have that hit a callback that then reloads. This way if the integration is not loaded or already disabled by then user, it won’t start up prematurely.

You don’t see any labels until you refresh the page, it’s a browser caching issue :wink:

I had already tried the obvious hard-refresh, it didn’t work. Restarting HA the next morning did seem to work. Anyway, all set now.

You can set a callback to be called when options are updated, but anecdotally I believe this fires only if the options are actually updated as a result of the config flow. If nothing is changed, then it doesn’t seem to fire. It may also not be fired if you use ConfigEntry.async_update_entry to update the config entry because you store your config in data rather than in options.

# __init__.py

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    [...]
    entry.async_on_unload(entry.add_update_listener(_async_update_listener))

async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
    """Handle options update."""
    await hass.config_entries.async_reload(entry.entry_id)

Also, I don’t think it’s been mentioned previously in this thread, but you could also just store all of your config in options, and leave data empty. ConfigFlow.async_create_entry takes data and options as parameters:

    async def async_step_user(
        self, user_input: dict[str, Any] | None = None
    ) -> FlowResult:
        """Handle the initial step."""
        errors: dict[str, str] = {}
        description_placeholders: dict[str, str] = {}

        if user_input is not None:
            (data, options, errors, description_placeholders) = validate_input(
                user_input
            )
            if not errors:
                return self.async_create_entry(
                    title=data["title"], data=data, options=options
                ) 
        [...]

However, note that OptionsFlow.async_create_entry takes a parameter data but somewhat confusingly stores the value passed in options in the ConfigEntry. So as mentioned earlier, you need instead to use ConfigEntry.async_update_entry to update data, or store all config in options.

I just had that same issue except I was getting two of seven labels (I did a major rewrite of the integration). I searched /config with find . -exec grep "<label>" {} \; -print and found and edited all occurrences in strings.json, translations/en.json & ar.json but I also found a .translations folder (reason = ???) and also edited those but the problem persisted even though find could no longer find the label. I then cleared my browser cache and that solved my issue.