【Streamlit】アプリのページ切り替えの流れとページ再読み込みについて

Streamlitでアプリを作る際にページ切り替えを入れ込む際の作り方と、能動的にページを再読み込み（＝アプリ再起動）させたいけどどうやるんだ！という時のための記事です。

## streamlitの挙動と状態保持変数の説明
streamlitではボタンなどのウィジェットの値が変更される度に全てのコードが再実行されます。
なので、ページ遷移を行うためにはページ管理用の変数を作成し、実行の最初でその値により処理を分岐させるという方法を取るのが一般的かと思います。

イメージとしては下記のようなコードになります。

```streamlit_page_change.py
import streamlit as st

def main():
    # １ページ目表示
    st.sidebar.title("test_streamlit")
    st.markdown("## ボタンでページを変えましょう")
    st.sidebar.button("ページ切り替えボタン", on_click=change_page)

def change_page():
    # ページ切り替えボタンコールバック
    st.session_state["page_control"]=1

def next_page():
    # ２ページ目表示
    st.sidebar.title("ページが切り替わりました")
    st.markdown("## 次のページです")

# 状態保持する変数を作成して確認
if ("page_control" in st.session_state and
   st.session_state["page_control"] == 1):
    next_page()
else:
    st.session_state["page_control"] = 0
    main()
```
※ st.session_state[変数名]でコード再実行が行われても値を保持する変数を定義出来ます

各ウィジェットの引数にあるon_clickやon_changeなどのコールバックを設定した場合、**コールバック関数を実行→アプリが再実行**という順番で動作します。
上記コードはコールバック関数内で変数の値を更新することでページ遷移を行っています。

※ この時、最初のページから次のページに持ち越したい値がある場合は、同様にst.session_stateで定義しておくか、コールバック関数にargs,kwargsなどのキーワード引数として持ち越すという方法があります

## ページ再読み込みのユースケース
あまり実用的ではないかもしれませんが、例えばファイルのアップロードを契機に解析用のページに移動させたい場合などに使えます。

まずは下記のコードを見てみましょう。

```streamlit_file_upload.py
import streamlit as st

def main():
    # １ページ目表示
    st.sidebar.title("test_streamlit")
    st.session_state["file"]=st.sidebar.file_uploader("upload", on_change=change_page)

def change_page():
    # ページ遷移ボタンコールバック
    st.session_state["page_control"]=1

def next_page():
    # ２ページ目表示
    st.sidebar.title("ページが切り替わりました")
    st.markdown("## 次のページです")

# 状態保持する変数を作成して確認
if ("page_control" in st.session_state and
   st.session_state["page_control"] == 1):
    next_page()
else:
    st.session_state["page_control"] = 0
```
ページ遷移後の関数内でst.session_state["file"]の中身を確認するとわかるのですが、値はNoneとなってしまっています。
<img width="300" alt="sukusyo.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638945/4d9af0fb-62b5-4aa9-b051-c233e6a85fe5.png">

ページ遷移をさせずに下記のような形でファイルの中身を読むと、ちゃんと値が格納されています。

```streamlit_file_upload.py
st.session_state["file"]=st.sidebar.file_uploader("upload")
if st.session_state["file"] != None:
    st.markdown("ファイルの中身")
    st.markdown(st.session_state["file"])
```
<img width="400" alt="sukusyo.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638945/31611384-800a-4e37-b5a1-3f11a126f7a8.png">

関数の中身を追えていないのでどのような仕様でこうなっているのかわかりませんが、兎にも角にもページ遷移＋ファイルアップロード情報を持ち越しをしたい場合は、コールバックのon_changeを使わずになんとかするしかありません。

ここで表題にもあるページ再読み込みの機能が役に立ちます。

## 解決法
１行加えるだけです。

```streamlit_file_upload.py
import streamlit as st

def main():
    # １ページ目表示
    st.sidebar.title("test_streamlit")
    st.session_state["file"]=st.sidebar.file_uploader("upload")
    if st.session_state["file"] != None:
        st.session_state["page_control"] = 1
        raise st.experimental_rerun()

def next_page():
    # ２ページ目表示
    st.sidebar.title("ページが切り替わりました")
    st.markdown("## 次のページです")
    st.markdown("ファイルの中身")
    st.markdown(st.session_state["file"])

# 状態保持する変数を作成して確認
if ("page_control" in st.session_state and
   st.session_state["page_control"] == 1):
    next_page()
else:
    st.session_state["page_control"] = 0
    main()
```

<img width="400" alt="sukusyo.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/638945/77f5f8e8-9d16-4a60-1197-0331ad5f2a0c.png">
無事に遷移後のページにアップロードファイルの情報を持ち越すことが出来ました。