Skip to content

Dynamic reloading issue with f7Messages() in tab with f7TabLayout() (chat app) #278

@Ezechieldjohi

Description

@Ezechieldjohi

Hello,
First of all, I would like to express my sincere gratitude for this excellent package. ShinyMobile has been incredibly useful for many projects i build.

I have developed an R Shiny application with ShinyMobile, in which I integrate a chat-like messaging system. The system works perfectly when the application uses a simple layout with f7SingleLayout().

However, when I migrate to a structure with f7TabLayout(), the behavior becomes unstable.


✅ Expected behavior

  • I want the chat system to work smoothly even after navigation between tabs.
  • When I return to the Conversations tab, I expect the messages to be updated according to the selected discussion.

❌ Observed behavior

  1. During initial loading, everything works as expected:

    • The Conversations tab displays correctly.
    • Messages display properly for each selected discussion.
  2. After switching tabs, then returning to the Conversations tab:

    • The discussion list displays correctly.
    • But when I click on a discussion, it is either:
      • empty,
      • or contains messages from the discussion that was previously loaded before the tab change.
    • If I send a new message, it is correctly attributed to the right discussion.
    • When returning to the discussion list, it is properly updated.
    • From that point on, everything starts working normally again until the next tab change.

Thank you in advance for your help 🙏

Reproducible example

library(shiny)
library(shinyMobile)
library(DBI)
library(RSQLite)
library(dplyr)
library(tibble)
library(shinyjs)

# Connexion et création table si besoin
con <- dbConnect(SQLite(), dbname = "chatcontent.db")

# Création de la table 'products'
dbExecute(con, "
CREATE TABLE IF NOT EXISTS products (
  id TEXT PRIMARY KEY
);
")

# Création de la table 'messages' avec conversation_id
dbExecute(con, "
CREATE TABLE IF NOT EXISTS messages (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  product_id TEXT NOT NULL,
  conversation_id TEXT NOT NULL,
  nickname TEXT NOT NULL,
  message TEXT NOT NULL,
  date TEXT DEFAULT (datetime('now')),
  FOREIGN KEY(product_id) REFERENCES products(id)
);
")

dbDisconnect(con)

# Fonction pour créer l'ID de conversation
create_conversation_id <- function(product_id, person1, person2) {
  persons <- sort(c(person1, person2))
  paste(product_id, persons[1], persons[2], sep = "_")
}

# Fonction pour extraire les infos d'une conversation_id
parse_conversation_id <- function(conversation_id) {
  parts <- strsplit(conversation_id, "_")[[1]]
  if(length(parts) >= 3) {
    product_id <- parts[1]
    person1 <- parts[2]
    person2 <- parts[3]
    list(product_id = product_id, person1 = person1, person2 = person2)
  } else {
    NULL
  }
}

defautmessages_df <- tibble::tibble(
  id = 1:6,
  product_id = c("prod001", "prod001", "prod002", "prod002", "prod003", "prod003"),
  conversation_id = c(
    "prod001_alice_bob", "prod001_alice_bob",
    "prod002_charlie_dana", "prod002_charlie_dana",
    "prod003_jon_doe", "prod003_jon_doe"
  ),
  nickname = c("alice", "bob", "charlie", "dana", "jon", "doe"),
  message = c(
    "Salut Bob !", 
    "Salut Alice, comment ça va ?",
    "Hello Dana, dispo pour parler ?",
    "Oui, dis-moi tout.",
    "Tu as vu le produit ?", 
    "Oui, ça m'intéresse."
  ),
  date = as.character(Sys.time() - c(3600, 3500, 2000, 1900, 1000, 900))
)

### comment after first run
con <- DBI::dbConnect(RSQLite::SQLite(), dbname = "chatcontent.db")
DBI::dbWriteTable(con, "messages", defautmessages_df, append = TRUE, row.names = FALSE)
DBI::dbDisconnect(con)
###
ui <- f7Page(
  title = "Chat Marketplace",
  options = list(dark = FALSE, theme = "auto", filled = FALSE, color = "#065961"),
  useShinyjs(),
  
  f7TabLayout(
    navbar = f7Navbar(),
    tags$style(HTML("

    
  .headerfullconversation {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 999;
    justify-content: space-between;
    align-items: center;
    padding: 1rem;
    background-color: white;

    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  }
  
  #mymessagebar {
    display: none;
  }

  
  ")),
    
    f7Tabs(
      id = "tabs",
      f7Tab(
        tabName = "Conversations",
        icon = f7Icon("chat_bubble_2"),
        active = TRUE,
        div(id = "chatlist-content",
            uiOutput("conversations_list"),
            
        ),
        div(id = "detailconversation-content",
            uiOutput("FullChatdetail")
        )
      ),
      f7Tab(
        tabName = "Tab2",
        icon = f7Icon("plus"),
        tagList(
          sliderInput("bins", "Nombre de classes", 5, 50, 20),
          plotOutput("hist")
          
        )
      )
    )
    
  )
)

server <- function(input, output, session) {
  user <- reactiveValues(
    nickname = NULL, 
    product_id = NULL,
    conversation_id = NULL,
    other_person = NULL,
    userconversations = NULL
  )
  
  current_conversation <- reactiveVal("")
  last_message_id <- reactiveVal(0)
  conversations <- reactiveVal(data.frame())
  firstLoad <- reactiveVal(TRUE)
  
  load_conversations <- function() {
    con <- dbConnect(SQLite(), dbname = "chatcontent.db")
    
    query <- "
    SELECT 
      conversation_id,
      product_id,
      MAX(date) as last_message_date,
      COUNT(*) as message_count,
      GROUP_CONCAT(DISTINCT nickname) as participants
    FROM messages 
    GROUP BY conversation_id 
    ORDER BY last_message_date DESC
    "
    
    data <- dbGetQuery(con, query)
    dbDisconnect(con)
    
    conversations(data)
  }
  
  observe({
    load_conversations()
  })
  
  observe({
    invalidateLater(5000, session)
    load_conversations()
    
  })
  
  output$conversations_list <- renderUI({
    conv_data <- conversations()
    
    if (nrow(conv_data) == 0) {
      return(f7Block("Aucune conversation pour le moment"))
    }
    
    conv_items <- lapply(1:nrow(conv_data), function(i) {
      row <- conv_data[i, ]
      
      conv_info <- parse_conversation_id(row$conversation_id)
      
      if (!is.null(conv_info)) {
        title <- paste("Produit:", conv_info$product_id)
        subtitle <- paste("Entre:", conv_info$person1, "et", conv_info$person2)
        footer <- paste(row$message_count, "messages -", 
                        format(as.POSIXct(row$last_message_date), "%d/%m %H:%M"))
        
        div(
          class = "conversation-item",
          style = "padding: 10px; border-bottom: 1px solid #ddd; cursor: pointer;",
          onclick = paste0("Shiny.setInputValue('select_conversation', '", 
                           row$conversation_id, "', {priority: 'event'});"),
          
          div(style = "font-weight: bold; font-size: 16px;", title),
          div(style = "color: #666; font-size: 14px; margin-top: 2px;", subtitle),
          div(style = "color: #999; font-size: 12px; margin-top: 4px;", footer)
        )
      }
    })
    
  })
  
  observeEvent(input$select_conversation, {
    
    user$nickname <- sample(strsplit(input$select_conversation, "_")[[1]][2:3], 1)
    user$product_id <- strsplit(input$select_conversation, "_")[[1]][1]
    user$conversation_id <- input$select_conversation 
    shinyjs::runjs("document.getElementById('detailconversation-content').style.display = 'block';")    
    
    shinyjs::runjs("document.querySelector('navbar').style.zIndex = '0';")
    runjs("
  const style = document.createElement('style');
  style.innerHTML = `
    .navbar, .navbars {
      z-index: 0 !important;
    }
    .navbar {
      z-index: 0 !important;
      height: 0px;
    }
    .toolbar {
      z-index: 0 !important;
      height: 0px;
    }
    
    .messagebar {
      position: fixed;
      bottom: 0;
      left: 0;
      z-index: 1000 !important;
    }
    
    #mymessagebar {
      display: flex !important;
    }
    
  `;
  document.head.appendChild(style);
")
    
    
    
    shinyjs::runjs("document.getElementById('chatlist-content').style.display = 'none';")    
    shinyjs::runjs("document.getElementById('headerfullconversation').style.display = 'flex';")
    
  })
  
  observeEvent(input$back_to_list, {
    updateTextInput(session, "conversation_id", value = NULL)
    current_conversation("")
    last_message_id(0)
    firstLoad(TRUE)
    
    user$nickname <- NULL
    user$product_id <- NULL
    user$conversation_id <- NULL
    user$other_person <- NULL
    shinyjs::runjs("document.getElementById('chatlist-content').style.display = 'flex';")    
    shinyjs::runjs("document.getElementById('detailconversation-content').style.display = 'none';")    
    runjs("
  const style = document.createElement('style');
  style.innerHTML = `
    
    .recalculating{
      opacity: 1;
    }
    .toolbar {
      z-index: 600 !important;
      height: 4rem;
    }
  `;
  document.head.appendChild(style);
")
    
  })
  
  output$FullChatdetail <- renderUI({
    div(
      
      div( class = "headerfullconversation", id ="headerfullconversation",
           f7Button("back_to_list", "← Retour aux conversations"),
      ),
      f7Messages(id = paste0("mymessages", input$select_conversation, sep = "_"), title = ""),
      f7MessageBar(inputId = "mymessagebar", placeholder = "Message")
    )
  }) 
  
  load_all_messages <- function() {
    req(user$conversation_id)
    
    con <- dbConnect(SQLite(), dbname = "chatcontent.db")
    data <- dbReadTable(con, "messages") %>%
      filter(conversation_id == user$conversation_id) %>%
      arrange(id)
    dbDisconnect(con)
    
    if (nrow(data) > 0) {
      last_message_id(max(data$id))
      
      msgs <- lapply(seq_len(nrow(data)), function(i) {
        row <- data[i, ]
        f7Message(
          text = row$message,
          name = row$nickname,
          type = if (row$nickname == user$nickname) "sent" else "received",
          header = row$date
        )
      })
      
      updateF7Messages(id = paste0("mymessages", input$select_conversation, sep = "_"), msgs)
    }
  }
  
  load_new_messages <- function() {
    req(user$conversation_id)
    
    con <- dbConnect(SQLite(), dbname = "chatcontent.db")
    new_data <- dbReadTable(con, "messages") %>%
      filter(conversation_id == user$conversation_id, id > last_message_id()) %>%
      arrange(id)
    dbDisconnect(con)
    
    if (nrow(new_data) > 0) {
      last_message_id(max(new_data$id))
      
      new_msgs <- lapply(seq_len(nrow(new_data)), function(i) {
        row <- new_data[i, ]
        f7Message(
          text = row$message,
          name = row$nickname,
          type = if (row$nickname == user$nickname) "sent" else "received",
          header = row$date
        )
      })
      
      updateF7Messages(
        id = paste0("mymessages", input$select_conversation, sep = "_"),
        new_msgs,
        showTyping = FALSE
      )
    }
  }
  
  observe({
    req(user$nickname, user$product_id, user$conversation_id, firstLoad())
    load_all_messages()
    firstLoad(FALSE)
  })
  
  observe({
    req(user$nickname, user$product_id, user$conversation_id, !firstLoad())
    
    invalidateLater(2000, session)
    load_new_messages()
  })
  
  observeEvent(input[["mymessagebar-send"]], {
    req(user$nickname, user$product_id, user$conversation_id)
    
    new_msg <- tibble(
      product_id = user$product_id,
      conversation_id = user$conversation_id,
      nickname = user$nickname,
      message = input$mymessagebar,
      date = as.character(Sys.time())
    )
    
    con <- dbConnect(SQLite(), dbname = "chatcontent.db")
    dbAppendTable(con, "messages", new_msg)
    
    # Récupérer l'ID du message qui vient d'être inséré
    new_id <- dbGetQuery(con, "SELECT last_insert_rowid() as id")$id
    dbDisconnect(con)
    
    # Mettre à jour last_message_id avec le nouveau message
    last_message_id(new_id)
    
    f7_msg <- f7Message(
      text = input$mymessagebar,
      name = user$nickname,
      type = "sent",
      header = Sys.time()
    )
    
    updateF7Messages(
      id = paste0("mymessages", input$select_conversation, sep = "_"),
      list(f7_msg),
      showTyping = FALSE
    )
  })
  
  output$hist <- renderPlot({
    hist(rnorm(100), breaks = input$bins, col = "skyblue", border = "white")
  })
  
}

shinyApp(ui, server)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions