R Shiny Chat in Just a Few Lines of Code

Disclaimer:  <wbr />we no longer support the shiny.collections package due to outdated dependencies (rethinker) <div class="c-message__content c-message__content--feature_sonic_inputs" data-qa="message_content"><span class="c-message__body" dir="auto" data-qa="message-text"><em>What is described in this article is still doable, however we are not adding enhancements at this time.</em> </span></div> <div data-qa="message_content"></div> <div data-qa="message_content"></div> <div class="c-message_actions__container c-message__actions" role="group" aria-label="Message actions"></div> <h2 id="intro">Intro</h2> We live in a society heavily reliant upon communication, networking and information exchange. Think about all the chat apps that have recently popped out: WhatsApp, Messenger, Skype, Viber, Slack (not to mention Snapchat or Telegram). They have engrained themselves into our daily lives. You’d be hard pressed to find someone who doesn’t use at least one of these apps on a regular basis; with some using all of them! This leads to the obvious conclusion that written communication has become an integral part of our lives. You may be thinking that, from a software developers perspective, creating communication apps is difficult, tedious and time consuming. I’m going to convince you otherwise. We’re going to build a shiny chat app in no more than <strong>15 minutes</strong> and less than <strong>100 lines</strong> of R code! <h2 id="apps-architecture">App’s architecture</h2> Let’s start with our chat app design. The focus is going to be basic functionality. All we need for a functioning chat app is a message window (1) to store previous comments, a text input (2) and a send button (3). Let’s also add another text input (4) to identify our users with some nicknames. &nbsp; Having laid out out the desired functionality, the question is where to store our data? A text file could be fine, but periodical reading and writing would become unbearable for larger apps. Note that our problem is asynchronous - we want to send a text message only when the user presses a button. The message should then immediately appear on the other user’s screen. You may have heard of the <a href="https://www.rethinkdb.com/">RethinkDB</a> open-source database, which is based on JSONs. Its asynchronous nature makes it perfect for realtime apps. Feel free to visit their <a href="https://www.rethinkdb.com/faq/" target="_blank" rel="noopener noreferrer">documentation website</a> if you want to learn more about <em>RethinkDB</em>. For the purpose of this development let’s assume that our database will include records containing: <code class="highlighter-rouge">user</code>, <code class="highlighter-rouge">text</code> and <code class="highlighter-rouge">time</code> fields. Another more important problem is which framework should we choose to build the app? Although there are several obvious solutions, we are going to take a closer look at R and Shiny. With <code class="highlighter-rouge">shiny.collections</code> package it will be as easy as making pie chart in <code class="highlighter-rouge">ggplot</code>. Can’t wait to see how that’s possible? Let’s move on to the code! <h2 id="gimme-some-code">Gimme some code</h2> <h3 id="chat-ui">Chat UI</h3> At the very beginning, let’s create an R script <code class="highlighter-rouge">chat.R</code>, which will contain our chat code. As I mentioned above, we will use <code class="highlighter-rouge">shiny</code> and <code class="highlighter-rouge">shiny.collections</code> packages. They should be attached first. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">library</span><span class="p">(</span><span class="n">shiny</span><span class="p">)</span> <span class="n">library</span><span class="p">(</span><span class="n">shiny.collections</span><span class="p">)</span></code></pre> </figure> You can install the latest version of <code class="highlighter-rouge">shiny.collections</code> via <code class="highlighter-rouge">devtools</code>. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">devtools</span><span class="o">::</span><span class="n">install_github</span><span class="p">(</span><span class="s2">"Appsilon/shiny.collections"</span><span class="p">)</span></code></pre> </figure> We might need some other libraries but can add them later. As you probably know, each Shiny App consists of <code class="highlighter-rouge">ui</code>, which contains the app’s layout and <code class="highlighter-rouge">server</code> for the app’s logic. First, we implement the <code class="highlighter-rouge">ui</code> part of the app, following the mock-up above. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">ui</span> <span class="o">&lt;-</span> <span class="n">shinyUI</span><span class="p">(</span><span class="n">fluidPage</span><span class="p">(</span>  <span class="n">titlePanel</span><span class="p">(</span><span class="s2">"Chat app (shiny.collections demo)"</span><span class="p">),</span>  <span class="n">div</span><span class="p">(</span><span class="n">textInput</span><span class="p">(</span><span class="s2">"username_field"</span><span class="p">,</span> <span class="s2">"Username"</span><span class="p">,</span> <span class="n">width</span> <span class="o">=</span> <span class="s2">"200px"</span><span class="p">)),</span>  <span class="n">uiOutput</span><span class="p">(</span><span class="s2">"chatbox"</span><span class="p">),</span>  <span class="n">div</span><span class="p">(</span><span class="n">style</span> <span class="o">=</span> <span class="s2">"display:inline-block"</span><span class="p">,</span>  <span class="n">textInput</span><span class="p">(</span><span class="s2">"message_field"</span><span class="p">,</span> <span class="s2">"Your message"</span><span class="p">,</span> <span class="n">width</span> <span class="o">=</span> <span class="s2">"500px"</span><span class="p">)),</span>  <span class="n">div</span><span class="p">(</span><span class="n">style</span> <span class="o">=</span> <span class="s2">"display:inline-block"</span><span class="p">,</span>  <span class="n">actionButton</span><span class="p">(</span><span class="s2">"send"</span><span class="p">,</span> <span class="s2">"Send"</span><span class="p">))</span> <span class="p">))</span></code></pre> </figure> Create an empty server if you want to see the current state of your app: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">server</span> <span class="o">&lt;-</span> <span class="n">shinyServer</span><span class="p">(</span> <span class="k">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{})</span></code></pre> </figure> and add <code class="highlighter-rouge">shinyApp</code> creator at the end of the file. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">shinyApp</span><span class="p">(</span><span class="n">ui</span> <span class="o">=</span> <span class="n">ui</span><span class="p">,</span> <span class="n">server</span> <span class="o">=</span> <span class="n">server</span><span class="p">)</span></code></pre> </figure> After running it you should see something like this: <img src="https://wordpress.appsilon.com/blog-old/assets/article_images/2017-07-02-shiny-chat/firstlook.jpg" /> <h3 id="apps-logic">App’s logic</h3> Great! Now that we have the chat design, we can start thinking of adding some functionality. We should ensure that our database is up and running before engaging the items we’ve created so far. If you have RethinkDB properly <a href="https://www.rethinkdb.com/docs/install/" target="_blank" rel="noopener noreferrer">installed</a>, using it is a piece of cake. Just type in your shell: <figure class="highlight"> <pre><code class="language-bash" data-lang="bash"><span class="gp">$ </span>rethinkdb</code></pre> </figure> If the final line of your shell’s output looks similar to the one below, you are ready to use <code class="highlighter-rouge">shiny.collections</code>. <div class="highlighter-rouge"> <pre class="highlight"><code>Server ready, "user_QTG8WL_n0v" 67f1e11d-0acb-4fe9-ac1c-811b9bbecb66 </code></pre> </div> The first step is to connect a database. Add the following line just before the server definition. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">connection</span> <span class="o">&lt;-</span> <span class="n">shiny.collections</span><span class="o">::</span><span class="n">connect</span><span class="p">()</span></code></pre> </figure> (I am using double colon notation here to avoid ambiguity about which functionality comes from standard <code class="highlighter-rouge">shiny</code> and which from <code class="highlighter-rouge">shiny.collections</code> package). By default <code class="highlighter-rouge">shiny.collections</code> connects to a database named “shiny”, or creates it if it’s not present. The initial content in our server will be: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">chat</span> <span class="o">&lt;-</span> <span class="n">shiny.collections</span><span class="o">::</span><span class="n">collection</span><span class="p">(</span><span class="s2">"chat"</span><span class="p">,</span> <span class="n">connection</span><span class="p">)</span></code></pre> </figure> It makes a collection from a table named <code class="highlighter-rouge">chat</code>. It will be empty until we fill it in. It’s high time to focus on UI elements. Let’s work from top to bottom. The first component is the username field (4). It should definitely not be empty because we need to identify our users somehow. Let’s fill it with some random values from function <code class="highlighter-rouge">get_random_username</code> to start. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">get_random_username</span> <span class="o">&lt;-</span> <span class="k">function</span><span class="p">()</span> <span class="p">{</span>  <span class="n">paste0</span><span class="p">(</span><span class="s2">"User"</span><span class="p">,</span> <span class="nf">round</span><span class="p">(</span><span class="n">runif</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="m">10000</span><span class="p">,</span> <span class="m">99999</span><span class="p">)))</span> <span class="p">}</span></code></pre> </figure> (You can add all function definitons before <code class="highlighter-rouge">ui</code> and <code class="highlighter-rouge">server</code> in your script). The following command added to server will update <code class="highlighter-rouge">username_field</code> with the output of the function while we initialize the chat. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">updateTextInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s2">"username_field"</span><span class="p">,</span>  <span class="n">value</span> <span class="o">=</span> <span class="n">get_random_username</span><span class="p">()</span> <span class="p">)</span></code></pre> </figure> Now, we can add some action to the send button: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">send</span><span class="p">,</span> <span class="p">{</span>  <span class="c1"># ... </span><span class="p">})</span></code></pre> </figure> First thing to do is to form a data structure to send to our database an R list <code class="highlighter-rouge">new_message</code>: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">new_message</span> <span class="o">&lt;-</span> <span class="nf">list</span><span class="p">(</span><span class="n">user</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">username_field</span><span class="p">,</span>                    <span class="n">text</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">message_field</span><span class="p">,</span>                    <span class="n">time</span> <span class="o">=</span> <span class="n">Sys.time</span><span class="p">())</span></code></pre> </figure> Then we use <code class="highlighter-rouge">insert</code> function from <code class="highlighter-rouge">shiny.collections</code>: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">shiny.collections</span><span class="o">::</span><span class="n">insert</span><span class="p">(</span><span class="n">chat</span><span class="p">,</span> <span class="n">new_message</span><span class="p">)</span></code></pre> </figure> and clear text input. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">updateTextInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s2">"message_field"</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="s2">""</span><span class="p">)</span></code></pre> </figure> Once everything is put together, it should look like this: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">send</span><span class="p">,</span> <span class="p">{</span>  <span class="n">new_message</span> <span class="o">&lt;-</span> <span class="nf">list</span><span class="p">(</span><span class="n">user</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">username_field</span><span class="p">,</span>                      <span class="n">text</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">message_field</span><span class="p">,</span>                      <span class="n">time</span> <span class="o">=</span> <span class="n">Sys.time</span><span class="p">())</span>  <span class="n">shiny.collections</span><span class="o">::</span><span class="n">insert</span><span class="p">(</span><span class="n">chat</span><span class="p">,</span> <span class="n">new_message</span><span class="p">)</span>  <span class="n">updateTextInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s2">"message_field"</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="s2">""</span><span class="p">)</span> <span class="p">})</span></code></pre> </figure> The data from database will be displayed in <code class="highlighter-rouge">chatbox</code> (UI item (1)). <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">output</span><span class="o">$</span><span class="n">chatbox</span> <span class="o">&lt;-</span> <span class="n">renderUI</span><span class="p">({</span>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">is_empty</span><span class="p">(</span><span class="n">chat</span><span class="o">$</span><span class="n">collection</span><span class="p">))</span> <span class="p">{</span>    <span class="n">render_msg_divs</span><span class="p">(</span><span class="n">chat</span><span class="o">$</span><span class="n">collection</span><span class="p">)</span>  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>    <span class="n">tags</span><span class="o">$</span><span class="n">span</span><span class="p">(</span><span class="s2">"Empty chat"</span><span class="p">)</span>  <span class="p">}</span> <span class="p">})</span></code></pre> </figure> The <code class="highlighter-rouge">observeEvent</code> function allows us to observe <code class="highlighter-rouge">chat$collection</code> data and render <code class="highlighter-rouge">div</code> with messages only when it changes. There are two conditions here: empty collection and collection with content. Test it with the following logical statement <code class="highlighter-rouge">!is_empty(chat$collection)</code>. It employs <code class="highlighter-rouge">is_empty</code> function from the <code class="highlighter-rouge">purrr</code> package (so don’t forget to add <code class="highlighter-rouge">library(purrr)</code> at the beginning of your R script). When it’s empty, your app should simply return a span with an “Empty chat” notification. If not, the chat should display messages with neat formatting. The <code class="highlighter-rouge">render_msg_divs</code> function is responsible for that: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">render_msg_divs</span> <span class="o">&lt;-</span> <span class="k">function</span><span class="p">(</span><span class="n">collection</span><span class="p">)</span> <span class="p">{</span>  <span class="n">div</span><span class="p">(</span><span class="n">class</span> <span class="o">=</span> <span class="s2">"ui very relaxed list"</span><span class="p">,</span>  <span class="n">collection</span> <span class="o">%&gt;%</span>    <span class="n">arrange</span><span class="p">(</span><span class="n">time</span><span class="p">)</span> <span class="o">%&gt;%</span>    <span class="n">by_row</span><span class="p">(</span><span class="o">~</span> <span class="n">div</span><span class="p">(</span><span class="n">class</span> <span class="o">=</span> <span class="s2">"item"</span><span class="p">,</span>      <span class="n">a</span><span class="p">(</span><span class="n">class</span> <span class="o">=</span> <span class="s2">"header"</span><span class="p">,</span> <span class="n">.</span><span class="o">$</span><span class="n">user</span><span class="p">),</span>      <span class="n">div</span><span class="p">(</span><span class="n">class</span> <span class="o">=</span> <span class="s2">"description"</span><span class="p">,</span> <span class="n">.</span><span class="o">$</span><span class="n">text</span><span class="p">)</span>    <span class="p">))</span> <span class="o">%&gt;%</span> <span class="p">{</span><span class="n">.</span><span class="o">$</span><span class="n">.out</span><span class="p">}</span> <span class="p">)</span> <span class="p">}</span></code></pre> </figure> I must admit that there is some magic going on there. We use <code class="highlighter-rouge">dplyr</code> and <code class="highlighter-rouge">purrrlyr</code> to make the code tidy. Our goal is to create a div with messages listed one after another. Using <code class="highlighter-rouge">div(class = "ui very relaxed list", ...)</code> is an easy way to achieve this. As our <code class="highlighter-rouge">collection</code> comes from unstructured noSQL database we first need to sort all items by time <code class="highlighter-rouge">collection %&gt;% arrange(time)</code>. Next, for each row we create div named “item” with “header” being username (field user from our <code class="highlighter-rouge">chat$collection</code>) and “description” being content of the user’s message (field <code class="highlighter-rouge">text</code>). In order to apply it to every row, we use <code class="highlighter-rouge">by_row</code> function from <code class="highlighter-rouge">purrrlyr</code> package. The function returns a <code class="highlighter-rouge">data.frame</code> with an additional column named <code class="highlighter-rouge">.out</code> with results of the operation applied to respective row. With syntax <code class="highlighter-rouge">%&gt;% {.$.out}</code> we extract only results of that operation as a list. To sum up, whole shiny server code should look like this: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">server</span> <span class="o">&lt;-</span> <span class="n">shinyServer</span><span class="p">(</span><span class="k">function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">session</span><span class="p">)</span> <span class="p">{</span>  <span class="n">chat</span> <span class="o">&lt;-</span> <span class="n">shiny.collections</span><span class="o">::</span><span class="n">collection</span><span class="p">(</span><span class="s2">"chat"</span><span class="p">,</span> <span class="n">connection</span><span class="p">)</span> <br>  <span class="n">updateTextInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s2">"username_field"</span><span class="p">,</span>                  <span class="n">value</span> <span class="o">=</span> <span class="n">get_random_username</span><span class="p">()</span>                  <span class="p">)</span> <br>  <span class="n">observeEvent</span><span class="p">(</span><span class="n">input</span><span class="o">$</span><span class="n">send</span><span class="p">,</span> <span class="p">{</span>    <span class="n">new_message</span> <span class="o">&lt;-</span> <span class="nf">list</span><span class="p">(</span><span class="n">user</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">username_field</span><span class="p">,</span>                        <span class="n">text</span> <span class="o">=</span> <span class="n">input</span><span class="o">$</span><span class="n">message_field</span><span class="p">,</span>                        <span class="n">time</span> <span class="o">=</span> <span class="n">Sys.time</span><span class="p">())</span>    <span class="n">shiny.collections</span><span class="o">::</span><span class="n">insert</span><span class="p">(</span><span class="n">chat</span><span class="p">,</span> <span class="n">new_message</span><span class="p">)</span>    <span class="n">updateTextInput</span><span class="p">(</span><span class="n">session</span><span class="p">,</span> <span class="s2">"message_field"</span><span class="p">,</span> <span class="n">value</span> <span class="o">=</span> <span class="s2">""</span><span class="p">)</span>  <span class="p">})</span> <br>  <span class="n">output</span><span class="o">$</span><span class="n">chatbox</span> <span class="o">&lt;-</span> <span class="n">renderUI</span><span class="p">({</span>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">is_empty</span><span class="p">(</span><span class="n">chat</span><span class="o">$</span><span class="n">collection</span><span class="p">))</span> <span class="p">{</span>      <span class="n">render_msg_divs</span><span class="p">(</span><span class="n">chat</span><span class="o">$</span><span class="n">collection</span><span class="p">)</span>    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>      <span class="n">tags</span><span class="o">$</span><span class="n">span</span><span class="p">(</span><span class="s2">"Empty chat"</span><span class="p">)</span>    <span class="p">}</span>  <span class="p">})</span> <span class="p">})</span></code></pre> </figure> We’re pretty much ready to run our chat but should consider introducing some amendments beforehand. <h3 id="improving-the-view">Improving the view</h3> According to our mock-up with design, the box with messages should be fixed height rectangle but it’s currently just a list. We can fix this by adding this CSS code: <div class="highlighter-rouge"> <pre class="highlight"><code>#chatbox {  padding: .5em;  border: 1px solid #777;  height: 300px;  overflow-y: scroll; } </code></pre> </div> To define our customized style in our shiny app we need to add <code class="highlighter-rouge">tags$head()</code> at the beginning of our <code class="highlighter-rouge">fluidPage</code> definition in UI. Then we insert CSS code. This can be done in two ways: by using <code class="highlighter-rouge">HTML</code> command from <code class="highlighter-rouge">shiny</code>: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">tags</span><span class="o">$</span><span class="n">style</span><span class="p">(</span><span class="n">HTML</span><span class="p">(</span><span class="s2">"#CSS code"</span><span class="p">))</span></code></pre> </figure> or adding a path to CSS file. <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">tags</span><span class="o">$</span><span class="n">link</span><span class="p">(</span><span class="n">rel</span> <span class="o">=</span> <span class="s2">"stylesheet"</span><span class="p">,</span> <span class="n">type</span> <span class="o">=</span> <span class="s2">"text/css"</span><span class="p">,</span> <span class="n">href</span> <span class="o">=</span> <span class="s2">"file_with_style.css"</span><span class="p">)</span></code></pre> </figure> Commonly, for shiny apps, all external files are stored in a <code class="highlighter-rouge">www/</code> folder. We haven’t finished yet. Our <code class="highlighter-rouge">chatbox</code> div has the proper size now, but it is unable to automatically scroll down after receiving a new message. Another issue is that the user is forced to press the <code class="highlighter-rouge">send</code> button every time he or she finishes typing a new message. Most modern chat apps allow you to proceed after pressing ENTER. There’s no need for our app to be worse in that regard. We can easily introduce those two functionalities with the following <a href="https://github.com/Appsilon/shiny.collections/blob/master/inst/examples/www/script.js">JavaScript code</a> <small>(taken from ShinyChat example: look at <strong>Not less important</strong> section).</small> You can add that script to UI’s head with <code class="highlighter-rouge">tags$script</code> command: <figure class="highlight"> <pre><code class="language-r" data-lang="r"><span class="n">tags</span><span class="o">$</span><span class="n">script</span><span class="p">(</span><span class="n">src</span> <span class="o">=</span> <span class="s2">"script.js"</span><span class="p">)</span></code></pre> </figure> <h2 id="launching-the-app">Launching the app</h2> Awesome! You’re ready to launch the chat. If you followed the previous steps carefully you should see a view like this: <img src="https://wordpress.appsilon.com/blog-old/assets/article_images/2017-07-02-shiny-chat/readyapp.jpg" /> Now it’s your turn! Have fun and try to upgrade the chat by adding new functionalities or changing the style. <h2 id="not-less-important">Not less important</h2> You can find the entire code in <code class="highlighter-rouge">chat.R</code> script published in <a href="https://github.com/Appsilon/shiny.collections">shiny.collections</a> repository in <a href="https://github.com/Appsilon/shiny.collections/tree/master/inst/examples" target="_blank" rel="noopener noreferrer">examples</a> folder. The main inspiration for this demo came from a <strong><a href="http://shiny.rstudio.com/gallery/chat-room.html">ShinyChat example</a></strong> from <em>Shiny Gallery</em>. I decided to follow this idea to show that with <code class="highlighter-rouge">shiny.collections</code> you can make it faster and easier (<strong>130 vs 60</strong> of lines of pure R code).

Have questions or insights?

Engage with experts, share ideas and take your data journey to the next level!
Explore Possibilities

Share Your Data Goals with Us

From advanced analytics to platform development and pharma consulting, we craft solutions tailored to your needs.

Talk to our Experts
community
r
tutorials
shiny dashboards