Introducing multipage apps! 📄

Hello Carolina !
It will be a pleasure to share some of my experience and difficulties with developing an app using Streamlit.

Well, I’m 46 years old, I’m a systems analyst specializing in Oracle. I worked for many years in large companies and had a lot of experience with different users and as a developer I created a lot of programs in different segments which gave me a good experience.

WEB was never my specialty, but it’s the future, PYTHON is the future in my opinion!
Many years ago I tried to do something that Streamlit is currently doing using React, but the learning curve is very big, since you need to know other technologies like HTML5, CSS, JAVASCRIPT, etc…

Based on that, I was looking for a framework that would meet what Streamlit is proposing, but I only came to know it now.
In that time, I got to know a NOCODE tool called Bubble.io from which you can do almost everything in a simple way, using drag and drop and customizing properties, but not everything is flowers.

Bubble, leaves you stuck in choosing the database, locks you in certain operations that should be simple and has some other limitations and one of them is that you don’t have full control of what you developed!

I have an App that I developed in Bubble for a kayak factory, of which I will be showing some screens here.
I needed to create a backend PYTHON api to access data from a POSTGRESQL base and because of that I wanted to keep
all the code in Python, both the backend which is already ok and the frontend using Streamlit.

It’s been a few days since I’ve been working with Streamlit, I really liked the proposal and it matches what I think, but I could see some difficulties in making the screens of my APP.

I would like to try to point out some things that I see that would be important to have in Streamlit.

Primordial to have an easy way to customize components to deliver a nice UIX
where small details make the difference in a final product.

I noticed an initial difficulty of having to know CSS and find the correct tags to be able to customize the screens,
since this can harm Streamlit itself if it wants to rename the CSS classes, as
this will generate errors in the applications of those who are manually customizing through CSS.

I think that some basic formatting is impossible to escape. For example:

  • Change font, size, style, color and alignment
  • Background color, alignment, border formatting
  • spacing
  • customization of onclick, mouse over events, etc.

Maybe an alternative would be to create a function like: st.css( id_element, css_tag_name, css_tag_value )

st.title( “myTitle”, “This is an example” )

st.css( “myTitle”, “font-family” , “Roboto” )
st.css( “myTitle”, “font-weight”, “bold” )
st.css( “myTitle”, “font-size” , “18px” )
st.css( “myTitle”, “font-color” , “#FFFFFF” )

st.image( “myImage”, "st.image(“app\images\logo-kayak-otters-original.jpg”, use_column_width=True)
st.css( “myImage”, “align”, “left” )

col1, col2, col3=st.columns(3)

st.css( col1, “align” , “left” )
st.css( col1, “font-family” , “Roboto” )
st.css( col1, “font-weight” , “bold” )

st.css( col2, “align” , “center” )
st.css( col3, “align” , “right” )

Note: The interesting thing would be a way for the developer to discover the names of tags and possible values!

*** Going further and I think it would also be more productive for the user to be able to copy a html or bootstrap code
that already has a lot of formatted and responsive stuff and introduce it to Streamlit in a way that is possible
customize onclick, onchange, onmouse, etc events for Python functions.

Bootstrap example:

buttons="""

Left Middle Right
"""

st.markdown( buttons, unsafe_allow_html= True )

How to assign button click to a Python function ?

st.set_event( “btn1”, onclick=“myPythonFunction” )

def myPythonFunction():
st.sucess(“Button 1 was pressed !”)

Follow some app screens that I´d like to create with Streamlit

Best, Ricardo Pascoal

@kmcgrady I think your solution just works for those who will not use the sidebar. A general use case is an authentication UI without the sidebar as the gatekeeper and use the multi-app with the sidebar after authenticated. So a solution like hiding the sidebar at runtime is desired.

2 Likes

Thanks for the feedback @tonylegend. I will pass it up!

1 Like

If I use emojis in the file name of any of the pages within the pages folder it works just fine, but if I try to put an emoji in the file name of the main page it throws an error:

UnicodeEncodeError: ‘latin-1’ codec can’t encode character ‘\U0001f3ae’ in position 12: ordinal not in range(256)

Any advice/guidance appreciated!

Hi everyone!

Thanks for the nice update, really cool work which will be very nice in developing larger apps!

I have had one thought that might be interesting to think about now that the multi-page apps are possible though. In my opinion it would be good to think about the way we have to import and / or define functions, in combination with caching.

In a lot of my cases (I’m building interactive image-analysis apps), I’m using similar functions in all my ‘pages’ (slightly different image analysis tools). For example, I’m using an intensity correction function in all my apps, lets call it modify_intensities() for this example. In order to speed things up, I like to use st.experimental_memo() on this function.

Previously, all of the different ‘pages’ were added to the same ‘main’ file, which meant all ‘pages’ could access my custum function modify_intensities(), which can easily then be used withst.experimental_memo().

If I would choose to split up my different applications into different pages with the new update, I would have to define modify_intensities() in all these pages, and add st.experimental_memo() on all these pages as well. This is fine on first sight, but not easy to maintain when I find some way to improve my function. Then I would have to go over all my page-files and change all these functions manually. Personally I’m fine with doing this for now, since the scale of my projects is not extremely large at the moment. However, since the apps I’m building are ever expanding, I think I can predict that these apps will become more difficult to maintain up-to-date in the future.

What I think would be ideal, is the use of a functions.py file. I got something working that is (kind of) solving this issue, but it is not the most elegant in my opinion.
Lets say I have a functions file called “custom_functions.py” next to my main.py script. I can then use from custom_functions import * or from custom_functions import modify_intensities to easily import the functions I want to all pages, while I only need to maintain one file to update all pages at once.
Problem here is the caching functionality. If I would like to (always, in all pages) use st.experimental_memo() on modify_intensities(), I would have to do this in every page-file:

from custom-functions import *

@st.experimental_memo()
def cached_modify_intensities(x):
    return modify_intensities(x)

It is the cleanest and easiest solution that I could think of right now, but maybe there is a better way that I don’t know of. Do you think this should be improved, or is this just a detail that I’m worried about?

Ideally I think it would be great if (somehow) we could already define the caching in the custom_functions.py file, but that will be difficult I think.

Again, thanks for all the great work, I’m really happy with the functionality of streamlit, and also with the pace that the improvements are being made!

1 Like

This might be an issue with file encoding…can you resave your file as UTF-8?

thank you this is very helpful!

Thank you. I think it’s actually a compatibility issue between another package I’m importing and having an emoji in the filename of the file streamlit is running.

Hi @DirkRemmers,

I feel like you should be able to decorate your modify_intensities function with @st.experimental_memo in the custom_functions file and have things work as expected without the extra boilerplate. Am I missing something that would prevent this from working?

I previously wrote a toy app that essentially did this (memoized a function defined in a shared file and imported it on several different pages), which makes me believe that doing this should work.

Is there a way to hide selected files in the pages directory so they don’t appear in the sidebar?

Is there any way to use packages instead of modules for pages?
I have this specific files structure:

  • main.py
  • pages/
    • page_01/
      • main.py
      • front.py
      • logic.py
      • …py
    • page_02/
    • page_03/

How can I use streamlit to show page_01 from pages/pages_01/main.py? or even pages/pages_01/__init__.py?

1 Like

Hi @vdonato , thanks for the quick reply!

I tested this, and for me it did not seem to work. Maybe there is a flaw in the way I coded this though, so I thought it might be nice to build a small example app for you to see what I mean.

You can visit the example app here.

I tried to describe what I mean as good as possible, but please let me know if something is not clear.

Thanks for checking it out!

Dirk

Hi @DirkRemmers,

Looks like you just forgot the @ in the decorator @st.experimental_memo(suppress_st_warning=True) of test_function.

Hi @fredzannarbor,

This currently isn’t possible. Do you want to do something like this to dynamically control which pages are displayed based on some arbitrary criteria?

Sounds like this request has come up more than once, so I’ll relay it to our product team as we’ll be evaluating feedback from the launch and may pick some of the more frequently requested features to build in the near-ish future.

1 Like

Hi @LucasLeRay,

The way multipage apps currently works does not include looking into subdirectories. Streamlit will only scan .py files directly inside the pages/ directory - so the layout you propose won’t work. The Python files / packages that would be useful to your pages should be put in another directory than pages/ .

w/r/t hiding pages – it could be as simple as prefixing the filename with a “.” The more elaborate approach would be some sort of dict with condition met:list of pages to display. I don’t currently absolutely need that, although I would avail myself of it if it did exist, for example to provide a “Publisher-centric” view of my page for book-lovers.

issue created allow flexibility in layout of multipage sidebar · Issue #4848 · streamlit/streamlit · GitHub

Is there a way to get the info that appears below the sidebar (my app’s text) appear in every sidebar page without specifying it in every sidebar page?

Hi @vdonato ,

That is very true, how stupid of me!
Sorry for wasting your time on this, and thanks for helping out!
I changed it, and it indeed works now.
Thanks!

Found a workaround for this:
Add this to the pages you want to hide the page list.

st.markdown("<style> ul {display: none;} </style>", unsafe_allow_html=True)

It works only if there’s no more ‘ul’ in your page except for the page list.

1 Like