MultiPage Streamlit app with persistent widget states
Run the demo app: https://share.streamlit.io/crxi/multipage_streamlit/main/example/demo/app.py
Recently I needed an app that supports multiple pages. However, Streamlit does not yet have such a feature. Not surprisingly, many innovative developers have came up with their own workarounds. Often it involves using the selectbox as a drop-down menu to offer pages. One such implementation that I like can be found here: https://towardsdatascience.com/creating-multipage-applications-using-streamlit-efficiently-b58a58134030. It structure each page as a separate module in a package directory, which leads to a nice modular design.
While that solution works, the pages generated do not remember their states when we switch pages. E.g. if I change a slider from default value of 0 to 10 on Page A and I moved to Page B to do something, then when I switch back to Page A, the slider is again at default 0. I thought saving the values into session_state will help, but frustratingly it does not. The reason is addressed here: streamlit/streamlit#3925. By design, the values of widgets stored in session_state are removed if they no longer appear on the current page.
That same ticket implemented a solution to make the values persist. It does it by intercepting "on_change" so that it can save the value into a persistent dictionary in session_state. I thought it was pretty clever, although it does add some complexity if we want to use "on_change".
Then I came across this: streamlit/streamlit#4338. The purposed solution (in the comment) had a different approach which does not involve using "on_change". While it did not work out-of-the-box for me as my app spanned different modules, it inspired me to come up with a different solution.
I combined the solutions and got something that worked for my purpose. And so I would like to share it with the Streamlit Community.
I added some other features:
- 3 different styles of multipage app: selectbox, expander and radio.
- allow setting of default value when declaring key name
- concept of namespace-prefix when saving states to avoid accidental reuse of key names (especially if the pages span multiple modules)
From PyPI:
python -m pip install multipage_streamlit
From GitHub:
python -m pip install git+https://github.com/crxi/multipage_streamlit
Organize your pages as follows:
app.py
pages/
+- page_a.py
+- page_b.py
Your top level app.py:
import multipage_streamlit as mt
from pages import page_a, page_b
app = mt.MultiPage()
app.add("Page A", page_a.run)
app.add("Page B", page_a.run)
app.run_selectbox()
# alternatives: app.run_expander() or app.run_radio() if you prefer those
In each page.py:
import streamlit as st
from multipage_streamlit import State
def run():
state = State(__name__)
# the above line is required if you want to save states across page switches.
# you can provide your own namespace prefix to make keys unique across pages.
# here we use __name__ for convenience.
st.header("Page A")
x = st.slider("Value X", min_value=0, max_value=100, key=state("x", 50))
# here's the "magic": state(name, default, ...) returns the namespace-prefixed
# key name. if a previously saved state exist, the widget is set to it. if not,
# the widget is set to default if it is specified.
state.save() # MUST CALL THIS TO SAVE THE STATE!
See the demo for more examples.
Your feedback is most welcomed. You can send via "Issues" or email to crxi.code@gmail.com.
Interested in contributing? Check out the contributing guidelines. Please note that this project is released with a Code of Conduct. By contributing to this project, you agree to abide by its terms.
multipage_streamlit
was created by CRXi crxi.code@gmail.com.
It is licensed under the terms of the Apache License 2.0 license.
multipage_streamlit
was created with cookiecutter
and the py-pkgs-cookiecutter
template.