When Jetpack Compose was introduced, it came with the ability to define functions annotated with "@Composable" that describe how we want our UI to look. When defining these functions we call composables, they should be fast, idempotent, and free of side-effects.
There are times when it is absolutely necessary to include side-effects in our composables because we might want to make certain changes to the state of our app, when doing so, it is advised to implement such in a "controlled environment".
Quoting from the docs:
"This function is fast, idempotent, and free of side-effects.
The function behaves the same way when called multiple times with the same argument, and it does not use other values such as global variables or calls to random().
The function describes the UI without any side-effects, such as modifying properties or global variables."
The usage of side-effects here is referring to any change that composable functions make directly on entities that exist outside its local environment. This includes writing to or updating any object that exists externally. The objects are visible to other parts of our app and as such can be accessed at any point in time and changed. The internal workings of Composables does not favor this (except when using Effects API) and additionally the declarative approach of Compose insists that data flow down and events go up. If you want to implement a side-effect do it through a callback. A very common and basic example of what this means can be seen is a user clicking a button. In Compose, once the user clicks said button, you should create your composables in a way that allows the events to notify the app logic. You can do that normally by passing a lambda to your composable as an argument, this lets the app logic make the necessary changes and then the composable is recalled with the new data that will determine how it will be drawn on screen. In this manner, data climbs down the compose hierarchy and events go up.
In the button clicking example, I pointed out that the composable would be redrawn, the android docs calls this recomposition.
Idempotence describes an object's ability to not change its behavior beyond how its state was the first time it was called, even if the same operation was applied on it many times. Note that this definition is based on how it was used in the android docs. In the case of our composable function, it should always have the same effect in our environment no matter how many times we call it as long as we pass it the same input. Additionally, it should be free from side effects like using global variables which are available to other parts of our app. Using random values in our composable function also means that if we were to run our composable function several times with the same input, it would give us different results. According to the android docs, this destroys the idempotence of our composable.
When creating your composables most at times, think of a way to make them reusable top-level functions. If you can do this, then you are most likely following the android docs' advice to make the majority of your composable functions idempotent and free of side-effects.