Multi-page apps

Is there any support for apps that have multiple pages? My use case is a 3-part workflow that Iā€™d like to separate into three separate views with state carried across them.

Stages generally are: 1- overview of data and category specification, 2- model refinement with active learning, and 3- overview of trained model results).

Is this use case out of scope for streamlit, something thatā€™s possible now, or something thatā€™s planned?

Thanks

6 Likes

Sounds interesting for me as well.

1 Like

Hi. This is in-scope and there are a number of ways to achieve it now.

  1. You can store this part of the state (i.e. which page youā€™re in) in a selectbox as shown in the Udacity demo.
  2. You can use @tvstā€™s SessionState hack but please note that this method is undocumented and may not work in the future.
  3. You can store state as a mutable object in the cache using the ignore_hash=True kwarg. (Hereā€™s an example of how to do that.)
  4. You can wait until we have a proper SessionState implementation but that wonā€™t be until the end of November, according to current plans.

Please followup if you have any questions!

8 Likes

Hi @timforr

When you have developed a multipage app please share you ideas, best practices or code. Iā€™m very interested in the subject.

My multipage app is https://github.com/MarcSkovMadsen/awesome-streamlit and https://awesome-streamlit.org/ for inspiration.

4 Likes

Hi guys,

is anyone working on solutions / Feature for multi-page or is there already
some awesome way to do it in Streamlit?

I am waiting for something nice like:

st.nav({
page1: start.py,
page2: home.py
}}

all in the same directory as index.py. Thus displaying the
pages in the header or Sidebar

st.sidenav({
page1: about.py,
page2: settings.py
}}

Looking forward to this feature!

5 Likes

There are ways of doing this based on a modification of the SessionState gist and the rerun() gist. My method uses a stack of state objects to provide the navigation. I built this handling into a ā€œStreamlitPageā€ base class and subclass all my pages from it. I use a long if ā€¦ elif ā€¦ to do the dispatch (as the navigation varies with each page) but the dict method could be made to work as well.

I shared the modified SessionState (itā€™s attached to one of the Issues on Github). I havenā€™t shared the base class as: a) it will need rework once Streamlit finalize their approach; b) there are still some glitches which should be fixed when we get to upgrading to the 0.54+ releases and the latest SessionState implementation; and c) there are also several other application services (configuration, logging) built into the class that are specific to our environment and I need to refactor to take those out.

1 Like

HI Marc, your application is really good and the idea of using Selectbox for having displays for multiple displays is inspiring. thanks for sharing.

2 Likes

Iā€™ve build pages into classes and Iā€™ve made a manager than verify choice options with ā€˜st.ratioā€™, Iā€™ve created a dictionary and return the options.

look the code:

import streamlit as st
from posting import Posting
from tables import Tables


pages = {'tables':Tables,'posting':Posting}

choice = st.sidebar.radio("Choice your page: ",tuple(pages.keys()))

pages[choice]()
2 Likes

Iā€™ve build the page into the ā€˜initā€™ classes

2 Likes

Can you provide a link to your classes to see what they do? Do you by any chance expose streamlit methods from your class __init__s?

1 Like

Another option I have used is to leverage the file system to save the stateful aspect of your app and save it / load it.

import os
import pickle
import streamlit as st

class State:
    def __init__(self, path='state.pickle', default_state_class=dict):
        self.path = path
        self.default_state_class = default_state_class

    def load(self):
        if os.path.exists(self.path):
            with open(self.path, 'rb') as inf:
                self.state = pickle.load(inf)
        else:
            self.state = self.default_state_class()

    def get_state(self):
        return self.state

    def save(self):
        with open(self.path, 'wb') as outf:
            pickle.dump(self.state, outf)

def rerun():
    raise st.script_runner.RerunException(st.script_request_queue.RerunData(None))

def app():
    store = State()
    store.load()

    name = store.get_state().get('name', None)
    if name:
        st.text(f'Hello {name}')
    else:
        st.text(f'Please enter your name')
        name_input = st.text_input('your name')
        name = name_input

        if name != '':
            store.get_state()['name'] = name

        store.save()
        next_page = st.button('Next page')
        if next_page:
            rerun()

if __name__ == "__main__":
    app()
1 Like

I have followed the example from @Vojaganto so far and works very well.However, I ran into the problem that Reload Ā® does not take into account changes made on subpages. What is the reason for it and is there any way to fix it?

Hello @chris_klose, youā€™d need some session state to keep changes between subpages.
Iā€™ve made a topic on this covering specifically the case of a multi-page app with a settings page here:

Hey Peeps,
I donā€™t want the values added in the app itself to be saved.
My problem is, that if I change the code itself and rerun (by pressing R) the app, the changes made in the code do not become active. I have to start the app again (Streamlit run app.py) so they get active.
So Iā€™d like to know if there is a workaround to solve that. Any ideas?

Hi! Check my framework out:

Hi @timforr ,
Hereā€™s one Streamlit multi-page implementation: