<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tadas Petra</title>
    <description>The latest articles on DEV Community by Tadas Petra (@tadaspetra).</description>
    <link>https://dev.to/tadaspetra</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F397852%2F06b2e1ef-b221-4a27-83d9-e66311a63cad.png</url>
      <title>DEV Community: Tadas Petra</title>
      <link>https://dev.to/tadaspetra</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tadaspetra"/>
    <language>en</language>
    <item>
      <title>Build a Video Call App with Gemini AI Summarization</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Tue, 11 Jun 2024 13:18:58 +0000</pubDate>
      <link>https://dev.to/tadaspetra/how-to-build-a-video-call-with-gemini-summarization-4mj</link>
      <guid>https://dev.to/tadaspetra/how-to-build-a-video-call-with-gemini-summarization-4mj</guid>
      <description>&lt;p&gt;AI is taking over the world. Resistance is futile. Now, we could either fight the inevitable or succumb to it. In this guide, we will walk through how to combine AI with Agora and build a video call app that uses AI to summarize the call you just had.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pkrzxfgg8zrl0l67jrg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pkrzxfgg8zrl0l67jrg.png" alt="Screenshots from final app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Flutter&lt;/li&gt;
&lt;li&gt;A developer account with &lt;a href="https://console.agora.io" rel="noopener noreferrer"&gt;Agora&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Agora Speech-to-Text Server (Can use &lt;a href="https://github.com/tadaspetra/agora-server" rel="noopener noreferrer"&gt;this example server&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Gemini API Key&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;Our starting point will be a simple video call app built with Agora. This guide assumes you have a fundamental understanding of how a simple video call works using Agora. &lt;/p&gt;

&lt;p&gt;If you do not have a grasp on Agora Fundamentals, you can take a look at the &lt;a href="https://docs.agora.io/en/video-calling/get-started/get-started-sdk?platform=flutter" rel="noopener noreferrer"&gt;Flutter quickstart guide within the documentation&lt;/a&gt; or you could dive deeper with the full &lt;a href="https://course-demo-two.vercel.app/flutter" rel="noopener noreferrer"&gt;Video Call with Agora Flutter course&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This guide will build upon a simple starter video call, which &lt;a href="https://github.com/tadaspetra/agora/tree/main/call_summary/starter-app" rel="noopener noreferrer"&gt;you can find here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The starter code has a landing screen with only one button that invites you to join a call. This call happens on a single channel called &lt;code&gt;test&lt;/code&gt; (it's a demo, okay). You have the remote users' video, your local video, and an end-call button within the call screen. We add and remove the users from the view using the event handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speech to Text
&lt;/h2&gt;

&lt;p&gt;Agora has a product called Real Time Transcription that you can enable to start transcribing the call of a specific channel.&lt;/p&gt;

&lt;p&gt;Real-Time Transcription is a RESTful API that uses an AI microservice to connect to your call and transcribe the spoken audio. This transcription is streamed directly into your video call using the &lt;code&gt;onStreamMessage&lt;/code&gt; event. Optionally, it can also be written to a cloud provider, which we will do in this guide as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fompebjnf2mvhihtdsree.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fompebjnf2mvhihtdsree.png" alt="diagram of how the video call transcription works"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Real-Time Transcription should be implemented on your business server for a few reasons. With the backend controlling the microservices, you can ensure that only one instance of Real-Time Transcription runs within each channel. You also need to pass your token to the transcription service, so by doing it on the backend, you don't expose that token on the client side.&lt;/p&gt;

&lt;p&gt;We will use &lt;a href="https://github.com/tadaspetra/agora-server" rel="noopener noreferrer"&gt;this server as our backend&lt;/a&gt;. This server exposes two endpoints: one for starting the transcription and another for ending it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Start Real Time Transcription
&lt;/h4&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

/start-transcribing/&amp;lt;--Channel Name--&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A successful response will contain the Task ID and the Builder Token, which you must save in your app since you will need to use it to stop the transcription.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

{taskId: &amp;lt;--Task ID Value--&amp;gt;, builderToken: &amp;lt;--Builder Token Value--&amp;gt;}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Stop Real Time Transcription
&lt;/h4&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

/stop-transcribing/&amp;lt;--Channel Name--&amp;gt;/&amp;lt;--Task ID--&amp;gt;/&amp;lt;--Builder Token--&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Start Transcription within the Call
&lt;/h2&gt;

&lt;p&gt;To make a network call from your Flutter application, you can use the &lt;code&gt;http&lt;/code&gt; package. Ensure you use the same App ID on both the app and the backend server. Then, call your API to start the transcribing. &lt;/p&gt;

&lt;p&gt;Within the &lt;a href="//./lib/call.dart"&gt;&lt;code&gt;call.dart&lt;/code&gt;&lt;/a&gt; file, you can add this &lt;code&gt;startTranscription&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;startTranscription&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$serverUrl&lt;/span&gt;&lt;span class="s"&gt;/start-transcribing/&lt;/span&gt;&lt;span class="si"&gt;$channelName&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Transcription Started'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;taskId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s"&gt;'taskId'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;builderToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonDecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s"&gt;'builderToken'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Couldn&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;t start the transcription : &lt;/span&gt;&lt;span class="si"&gt;${response.statusCode}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will call this function right after our join call method so that it starts as soon as the first user joins the channel. As part of a successful response, you will receive a Task ID and a Builder Token. Save these because you will need to use them to stop the transcription.&lt;/p&gt;

&lt;p&gt;When the transcription starts successfully, it acts as a "bot" has joined the call. It's not a real user, but it has its own UID, defined within your backend server. If you are using the &lt;a href="https://github.com/tadaspetra/agora-server" rel="noopener noreferrer"&gt;server I linked above&lt;/a&gt;, the UID is &lt;code&gt;101&lt;/code&gt;. You can exclude this from the remote user's list in the &lt;code&gt;onUserJoined&lt;/code&gt; event.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="nl"&gt;onUserJoined:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RtcConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;remoteUid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remoteUid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_remoteUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remoteUid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  End Transcription
&lt;/h2&gt;

&lt;p&gt;To end the transcription, we use a function similar to the starting function. This function will be called &lt;code&gt;stopTranscription&lt;/code&gt; and requires us to pass the Task ID and the Builder Token to stop the Real-Time Transcription service.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="n"&gt;Future&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;stopTranscription&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;Uri&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;$serverUrl&lt;/span&gt;&lt;span class="s"&gt;/stop-transcribing/&lt;/span&gt;&lt;span class="si"&gt;$taskId&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;$builderToken&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Transcription Stopped'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Couldn&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;t stop the transcription : &lt;/span&gt;&lt;span class="si"&gt;${response.statusCode}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We will call the &lt;code&gt;stopTranscription&lt;/code&gt; method in our call screen's &lt;code&gt;dispose&lt;/code&gt; method. This will stop the transcription before we leave the channel and release the engine resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieve the Transcription
&lt;/h2&gt;

&lt;p&gt;You can access the transcription during the video call by using the &lt;code&gt;onStreamMessage&lt;/code&gt; event in the event handler. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;span class="nl"&gt;onStreamMessage:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RtcConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;streamId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Uint8List&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;messageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You will notice the code above prints out an array of numbers that only mean something to you if you are an all-knowing AI. These numbers are generated using &lt;a href="https://protobuf.dev" rel="noopener noreferrer"&gt;Google's Protocol Buffers&lt;/a&gt; (also refered to as protobuf).&lt;/p&gt;

&lt;p&gt;Protobufs encode data in a platform-agnostic way. This means that apps or software can retrieve this data and serialize it according to their language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decode the Transcription
&lt;/h2&gt;

&lt;p&gt;We will use a Protocol Buffer to decode the message. In this case, we will serialize the random-looking numbers into an object called &lt;code&gt;Message&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Start by creating a &lt;code&gt;.proto&lt;/code&gt; file with the following content: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

syntax = "proto3";

package call_summary;

message Message {
  int32 vendor = 1;
  int32 version = 2;
  int32 seqnum = 3;
  int32 uid = 4;
  int32 flag = 5;
  int64 time = 6;
  int32 lang = 7;
  int32 starttime = 8;
  int32 offtime = 9;
  repeated Word words = 10;
}
message Word {
  string text = 1;
  int32 start_ms = 2;
  int32 duration_ms = 3;
  bool is_final = 4;
  double confidence = 5;
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Put this file in a new folder: &lt;code&gt;lib/protobuf/file.proto&lt;/code&gt;. This is the input file for the generator to create our &lt;code&gt;Message&lt;/code&gt; object. &lt;/p&gt;

&lt;p&gt;To use protobuf, you must install the protobuf compiler on your computer. It's available via package managers for Mac (&lt;code&gt;brew install protobuf&lt;/code&gt;) and Linux (&lt;code&gt;apt install -y protobuf-compiler&lt;/code&gt;). For Windows or if you need a specific version, check the &lt;a href="https://protobuf.dev/downloads/" rel="noopener noreferrer"&gt;Prottobuf downloads page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You must also install the &lt;code&gt;protobuf&lt;/code&gt; dart package within your project using &lt;code&gt;flutter pub add protobuf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now run the following command in your terminal. Four files should be generated in the same &lt;code&gt;lib/protobuf&lt;/code&gt; folder.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

protoc --proto_path= --dart_out=. lib/protobuf/file.proto  


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now that the protobuf is set up, we can use the new &lt;code&gt;Message&lt;/code&gt; object to retrieve our transcription in English. This object contains a &lt;code&gt;words&lt;/code&gt; array with the transcribed sentences. Using the &lt;code&gt;isFinal&lt;/code&gt; variable, we trigger a print statement whenever the sentence finishes.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nl"&gt;onStreamMessage:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RtcConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;streamId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;Uint8List&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;messageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isFinal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Save the Transcription&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;We have covered the transcription part. Now, we need to get the transcribed text, store it, and then use it to prompt an AI to give us a summary. The Real-Time Transcription service sends the transcribed audio in chunks, so as audio chunks are processed, the serialized data is sent in bursts, each triggering the &lt;code&gt;onStreamMessage&lt;/code&gt; event. The simplest way to store it is to concatenate a long string of responses. There are more sophisticated ways to do it, but it is good enough for this demo.&lt;/p&gt;

&lt;p&gt;We can hold a string called &lt;code&gt;transcription&lt;/code&gt; and add the text as it finalizes.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nl"&gt;onStreamMessage:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RtcConnection&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;streamId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;Uint8List&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;messageType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;messageSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="n"&gt;Message&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isFinal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;transcription&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;},&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Get Summary&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;In the &lt;a href="//./lib/main.dart"&gt;&lt;code&gt;main.dart&lt;/code&gt;&lt;/a&gt;, we can connect to Gemini using our API key and prompt it to summarize the video call. When you receive this response, you can call &lt;code&gt;setState&lt;/code&gt; and update the &lt;code&gt;summary&lt;/code&gt; variable to see your changes reflected on the main page.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;As I was testing this app, I noticed that the response liked to mention the transcript I passed. Because of this, I added some extra prompts so it does not mention the transcript.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once we need to pass the transcript string to the &lt;code&gt;retrieveSummary&lt;/code&gt;, we'll pass the function to &lt;a href="//./lib/call.dart"&gt;call.dart&lt;/a&gt; and call it when the call ends.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="kd"&gt;late&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;GenerativeModel&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="nd"&gt;@override&lt;/span&gt;&lt;br&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;br&gt;
  &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;model:&lt;/span&gt; &lt;span class="s"&gt;'gemini-pro'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;apiKey:&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;retrieveSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="n"&gt;transcription&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;br&gt;
      &lt;span class="s"&gt;'This is a transcript of a video call that occurred. Please summarize this call in a few sentences. Dont talk about the transcript just give the summary. This is the transcript: &lt;/span&gt;&lt;span class="si"&gt;$transcription&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;br&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;&lt;br&gt;
  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;br&gt;
  &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;br&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;br&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;&lt;br&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Done&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For4uilzmk2iehaizvukb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For4uilzmk2iehaizvukb.png" alt="diagram of how the video call, transcription and AI all work together"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that, we have built an application that triggers a Real-Time Transcription service as soon as someone joins the channel. Then, this transcript is saved on the client side so it can prompt Gemini for a summary and share it with the user.&lt;/p&gt;

&lt;p&gt;Congratulations, you are on the path to succumbing to the AI overlords. &lt;/p&gt;

&lt;p&gt;You can find the &lt;a href="https://github.com/tadaspetra/agora/tree/main/call_summary" rel="noopener noreferrer"&gt;complete code here&lt;/a&gt;. And dive into the &lt;a href="https://docs-beta.agora.io/en/real-time-transcription/get-started#rest-api" rel="noopener noreferrer"&gt;Real-Time Transcription documentation&lt;/a&gt; to build upon this guide.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Build a NextJS Video Call App</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Thu, 04 Jan 2024 17:07:12 +0000</pubDate>
      <link>https://dev.to/tadaspetra/build-a-nextjs-video-call-app-1mp3</link>
      <guid>https://dev.to/tadaspetra/build-a-nextjs-video-call-app-1mp3</guid>
      <description>&lt;p&gt;Video calling has become an essential feature in today's digital landscape. Whether it's for remote work, online education, or staying connected with loved ones, the ability to communicate face-to-face over the internet is crucial. As a developer, you might be looking for a reliable solution to integrate video calling into your NextJS application. Look no further! In this article, we will explore how to implement a video call feature using the Agora React SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Will Use
&lt;/h2&gt;

&lt;p&gt;Of course, we need the NextJS framework, since it is the topic of this article. &lt;/p&gt;

&lt;p&gt;We will use Agora for the video call portion of the app. Specifically, we will use the &lt;code&gt;agora-rtc-react&lt;/code&gt; package. This package is a React SDK for interfacing with &lt;a href="https://docs.agora.io/en/video-calling/get-started/get-started-sdk?platform=react-js" rel="noopener noreferrer"&gt;Agora&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We will use Tailwind CSS for minor styling of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an Agora Account
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sso2.agora.io/en/v7/signup" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; for an Agora account, and log in to the dashboard.&lt;/p&gt;

&lt;p&gt;Navigate to the Project List tab under the Project Management tab. Create a project by clicking the blue Create button.&lt;/p&gt;

&lt;p&gt;Retrieve the App ID, which we’ll use to authorize the app requests as we develop the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize the NextJS Project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create your NextJS project by using &lt;code&gt;npx create-next-app@latest&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Add Tailwind CSS when prompted by the NextJS installer.&lt;/li&gt;
&lt;li&gt;Add the Agora UI Kit by using &lt;code&gt;npm install agora-rtc-react&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;PUBLIC_AGORA_APP_ID = '&amp;lt;---Your App Id---&amp;gt;'&lt;/code&gt; to your &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Structure of the Project
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

├── components
│   └── Call.tsx
├── app
|   └── Layout.tsx
│   └── page.tsx
│   └── channel
│       └── [channelName]
│           └── page.tsx
├── public
│   └── favicon.png
├── package.json
├── next.config.js
├── tsconfig.json
└── tailwing.config.cjs


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Building a Form with NextJS
&lt;/h2&gt;
&lt;h3&gt;
  
  
  'use client'
&lt;/h3&gt;

&lt;p&gt;The form we want to build is a very simple form, where we take the input from the user and route the user to the &lt;code&gt;channel/&amp;lt;form input here&amp;gt;&lt;/code&gt; route of the website. &lt;/p&gt;

&lt;p&gt;This needs to happen client side because you are receiving the input from the user, and based on that input send them to a new route. For that, we will use the &lt;code&gt;useRouter&lt;/code&gt; hook from &lt;code&gt;next/navigation&lt;/code&gt;. And to use it client side we have to add &lt;code&gt;'use client'&lt;/code&gt; at the top of our file.&lt;/p&gt;
&lt;h3&gt;
  
  
  Form UI
&lt;/h3&gt;

&lt;p&gt;The next step is creating the UI for the form input. For this, we create a form element with an input element named "channel". &lt;/p&gt;

&lt;p&gt;All the magic happens in the form's &lt;code&gt;onSubmit&lt;/code&gt; method where we can access the data(&lt;code&gt;e&lt;/code&gt;) from the submit action. Whenever a form is submitted, the default action is to reload the page, however, since we want to lead a user to a new page we want to handle this action manually. Using &lt;code&gt;e.preventDefault()&lt;/code&gt; we can stop the default page refresh. &lt;/p&gt;

&lt;p&gt;The next step is to reroute our app to the appropriate channel page. We first define a variable called &lt;code&gt;target&lt;/code&gt; which asserts that we have a "channel" value in our form event. &lt;/p&gt;

&lt;p&gt;And lastly using the router we imported from &lt;code&gt;next/navigation&lt;/code&gt; we can route this page to &lt;code&gt;/channel/&amp;lt;inputted channel name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;
    &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"mb-4 mt-20 text-4xl font-extrabold leading-none tracking-tight text-gray-900"&lt;/span&gt;
  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-black"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;NextJS&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; x &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-500"&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Agora&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/channel/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"md:flex md:items-center mt-6"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;
          &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"&lt;/span&gt;
          &lt;span class="na"&gt;htmlFor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"inline-full-name"&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          Channel Name
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
          &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500"&lt;/span&gt;
          &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"inline-full-name"&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"channel"&lt;/span&gt;
          &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Enter channel name"&lt;/span&gt;
          &lt;span class="na"&gt;required&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"text-center"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
        &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"inline-flex items-center justify-center px-5 py-3 mt-5 text-base font-medium text-center text-white bg-blue-400 rounded-lg hover:bg-blue-500 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-900"&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our form should look like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhifrer897jsqfsacufad.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhifrer897jsqfsacufad.png" alt="Enter Channel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Call Page
&lt;/h2&gt;

&lt;p&gt;In the Call component that we will build with the &lt;code&gt;agora-rtc-react&lt;/code&gt; package, we want to print the name of the channel we have joined.&lt;/p&gt;

&lt;p&gt;The redirect we used in the previous section is dynamic, and it will be different depending on the channelName we entered. To handle this in Next, we need to create a &lt;code&gt;channel&lt;/code&gt; folder and a &lt;code&gt;[channelName]&lt;/code&gt; folder within, and a &lt;code&gt;page.tsx&lt;/code&gt; file. The square brackets signify that the dynamic channel name in our URL will be passed as a parameter in this component.&lt;/p&gt;

&lt;p&gt;So we retrieve the channel name from our params and display it in the top left of our screen above the video call component. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Call&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@/components/Call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex w-full flex-col"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"absolute z-10 mt-2 ml-12 text-2xl font-bold text-gray-900"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Call&lt;/span&gt; &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PUBLIC_AGORA_APP_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Add the Call Component
&lt;/h2&gt;

&lt;p&gt;The Call component will contain two key parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Videos of all the participants&lt;/li&gt;
&lt;li&gt;End call button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The more challenging part is displaying the videos of all the participants. To do that we need to create our Agora client and pass it to the &lt;code&gt;AgoraRTCProvider&lt;/code&gt;, which initializes and gives us access to the Agora RTC service. Inside this, we can now display the videos component and an end-call button:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; 

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AgoraRTC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;AgoraRTCProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;LocalVideoTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;RemoteUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useJoin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useLocalCameraTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useLocalMicrophoneTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;usePublish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useRTCClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useRemoteAudioTracks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useRemoteUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agora-rtc-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRTCClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AgoraRTC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;codec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vp8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rtc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AgoraRTCProvider&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Videos&lt;/span&gt; &lt;span class="na"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;AppID&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"fixed z-10 bottom-0 left-0 right-0 flex justify-center pb-4"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"px-5 py-3 text-base font-medium text-center text-white bg-red-400 rounded-lg hover:bg-red-500 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-900 w-40"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;End Call&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AgoraRTCProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Videos
&lt;/h3&gt;

&lt;p&gt;The Videos component will be the part of the site that displays the videos of all the participants. Many hooks are used to set up the call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;useLocalMicrophoneTrack() retrieves the current user's microphone.&lt;/li&gt;
&lt;li&gt;useLocalCameraTrack() retrieves the current user's video input.&lt;/li&gt;
&lt;li&gt;useRemoteUsers() retrieves all the user information for the remote users.&lt;/li&gt;
&lt;li&gt;useRemoteAudioTracks() retrieves the audio for those users.&lt;/li&gt;
&lt;li&gt;usePublish() publishes the current user's video and audio.&lt;/li&gt;
&lt;li&gt;useJoin() joins the channel for the video call.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isLoadingMic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localMicrophoneTrack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocalMicrophoneTrack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isLoadingCam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localCameraTrack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocalCameraTrack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remoteUsers&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRemoteUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;audioTracks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRemoteAudioTracks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;usePublish&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;localMicrophoneTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localCameraTrack&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nf"&gt;useJoin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;appid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we need to make sure all the audio for the remote users is started:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="nx"&gt;audioTracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally, we need to define our UI: first a loading state, while we wait for the local user's microphone and video to begin, and then a grid of all the users who are on the call:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deviceLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isLoadingMic&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isLoadingCam&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deviceLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center pt-40"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading devices...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;minmax(0, 1fr) &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col justify-between w-full h-screen p-1"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`grid  gap-1 flex-1`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;gridTemplateColumns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
          &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
              &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unit&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalVideoTrack&lt;/span&gt; &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;localCameraTrack&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-full"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RemoteUser&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our final video call should look like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoidmib42ycml33600i6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoidmib42ycml33600i6.png" alt="Video Call"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;With that, we have a complete video call experience. Here's how we built it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create our NextJS project with Tailwind.&lt;/li&gt;
&lt;li&gt;Install the Agora SDK.&lt;/li&gt;
&lt;li&gt;Create a form to input the channel name.&lt;/li&gt;
&lt;li&gt;Redirect the site to a URL with the channel name.&lt;/li&gt;
&lt;li&gt;Display the channel name with a video call.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code for this project can be found &lt;a href="https://github.com/tadaspetra/nextjs-video-call" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You can find out more about Agora video calling &lt;a href="https://docs.agora.io/en/video-calling/get-started/get-started-sdk?platform=react-js" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Complete Guide to Flutter State Management</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Thu, 05 Oct 2023 12:52:24 +0000</pubDate>
      <link>https://dev.to/tadaspetra/complete-guide-to-state-management-218a</link>
      <guid>https://dev.to/tadaspetra/complete-guide-to-state-management-218a</guid>
      <description>&lt;p&gt;State management is absolutely crucial once you are building an app that has some complexity. It is a tough topic, but at the core, it's quite simple. I will try to break it down for you in the simplest way that I can. This is the complete guide to state management.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/VcS0Qa2lw3g"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  What is State?
&lt;/h2&gt;

&lt;p&gt;If we break down the phrase &lt;strong&gt;State Management&lt;/strong&gt; you can see that it is a way to &lt;strong&gt;manage&lt;/strong&gt; your &lt;strong&gt;state&lt;/strong&gt;. But what is &lt;code&gt;State&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;There are two types of data you can have in an application. You can either have regular data/information (that is hard coded and cannot change) or you can have &lt;strong&gt;state&lt;/strong&gt; which is "fancy data" that can be updated. &lt;/p&gt;

&lt;p&gt;The most common example of a state is user information. You might create a &lt;code&gt;UserInfo&lt;/code&gt; state that can be used and updated within your application. For example, you might have a &lt;code&gt;Hello, Tadas&lt;/code&gt; displayed on your home screen, and the &lt;code&gt;Tadas&lt;/code&gt; part of the information would be retrieved from your &lt;code&gt;UserInfo&lt;/code&gt; state. And if I wanted to change my name to &lt;code&gt;T-Dog&lt;/code&gt; I could go into the settings page and update my &lt;code&gt;UserInfo&lt;/code&gt; state there.&lt;/p&gt;

&lt;p&gt;There are many more examples of the state within the application: News Feed, Follower Count, Todo Items, Countdown Timer, etc. &lt;/p&gt;

&lt;p&gt;The simplest definition is that a state is just data that can be changed (within a widget).&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Management?
&lt;/h2&gt;

&lt;p&gt;State management is the topic of &lt;em&gt;managing&lt;/em&gt; that &lt;strong&gt;state&lt;/strong&gt;. What does it mean to manage it? And why is it important to do it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Why?
&lt;/h3&gt;

&lt;p&gt;Let's take the above example of having a &lt;code&gt;UserInfo&lt;/code&gt; state that holds information about the current user. There are so many ways that the information here can be updated.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When the user creates an account.&lt;/li&gt;
&lt;li&gt;In the settings page.&lt;/li&gt;
&lt;li&gt;Whenever they are followed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As well as many places where the information can be read.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name on the home page.&lt;/li&gt;
&lt;li&gt;Name on the profile page.&lt;/li&gt;
&lt;li&gt;Profile picture on every page.&lt;/li&gt;
&lt;li&gt;Follower count on the profile page. &lt;/li&gt;
&lt;li&gt;Whether they are following a specific person on their profile page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And there are many more examples. So what would happen if we didn't have any state management and we needed to build an app that could do all the above?&lt;/p&gt;

&lt;p&gt;To see the information, you would need to pass the class containing all that information to &lt;strong&gt;every. single. screen.&lt;/strong&gt; That sounds bad, but what's even worse? It can be updated in many places. So let's say the user decides to change their name to &lt;code&gt;T-Dog&lt;/code&gt;. Without state management, it will only get updated on one screen, so you would need to set up some way to let every single other screen know that the name got updated. &lt;/p&gt;

&lt;p&gt;This sounds like a complex mess. Thankfully we have state management. &lt;/p&gt;

&lt;p&gt;A proper State Management solution solves two problems. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It centralizes all the data in one place, so there is a single source of truth.&lt;/li&gt;
&lt;li&gt;Updates the UI whenever there have been changes in that data.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Set State
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;setState&lt;/code&gt; is the simplest form of state management. It can update your UI whenever you call it, but it is limited to a single widget by default. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  State Management using Flutter only
&lt;/h2&gt;

&lt;p&gt;If you've heard about the topic of state management, it's most likely come from people debating what package has the best state management solution. But you do not need to use a package to &lt;em&gt;manage your state&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In fact, every state management package has been built using Flutter itself, so of course it is possible to do this with strictly Flutter code. Let's walk through an example of how you can implement your own state management solution, for the basic starter counter app.&lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Warning!
&lt;/h4&gt;

&lt;p&gt;This will be kind of complicated, but it will teach you what is happening under the hood of most state management packages.&lt;br&gt;
If you are using a package you don't &lt;em&gt;&lt;strong&gt;really&lt;/strong&gt;&lt;/em&gt; need to know this and can skip to the next section, but I would recommend reading through it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Centralizing the Data
&lt;/h3&gt;

&lt;p&gt;The most important thing is having &lt;strong&gt;one source of truth&lt;/strong&gt; for all the data related to a specific function. Since we are making a counter application the main (and only feature in this case) is the actual counter. For bigger applications, you might have a collection of data about the user. So you would set up a &lt;code&gt;UserInfo&lt;/code&gt; class to be the central location for everything related to the user's information. In this case, we will create a class called &lt;code&gt;CounterState&lt;/code&gt; within a &lt;code&gt;state.dart&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CounterState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="n"&gt;CounterState&lt;/span&gt; &lt;span class="n"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nl"&gt;counter:&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have added a &lt;code&gt;copyWith&lt;/code&gt; method that allows you to update the value of the class. So let's say you have instantiated the &lt;code&gt;CounterState&lt;/code&gt; with a value of &lt;code&gt;1&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;h4&gt;
  
  
  Note
&lt;/h4&gt;

&lt;p&gt;You can use a VSCode extension called &lt;a href="https://marketplace.visualstudio.com/items?itemName=dotup.dart-data-class-generator"&gt;&lt;strong&gt;Dart Data Class Generator&lt;/strong&gt;&lt;/a&gt; to create this &lt;code&gt;copyWith&lt;/code&gt; method for you.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;CounterState&lt;/span&gt; &lt;span class="n"&gt;_counterState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;counter:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To update this value to &lt;code&gt;2&lt;/code&gt; you would take the instantiated object and call the &lt;code&gt;copyWith&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;_counterState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;counter:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might seem cumbersome given that it is only a counter that we are doing this with, but using this &lt;code&gt;copyWith&lt;/code&gt; method is crucial when you have more than one piece of data. It allows you to to copy all the other data, and only change what you define within the &lt;code&gt;copyWith&lt;/code&gt; method.&lt;/p&gt;

&lt;h3&gt;
  
  
  Providing the Data with an InheritedWidget
&lt;/h3&gt;

&lt;p&gt;The state is now defined, but we need to be able to give access to this state to other parts of the application. To do this you need to use an &lt;code&gt;InheritedWidget&lt;/code&gt;. You can find more information on &lt;a href="https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html"&gt;InheritedWidget on the Flutter Docs&lt;/a&gt;, but the simple explanation is it allows widgets lower in the widget tree access to the data within the &lt;code&gt;InheritedWidget&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;First, you have to define the &lt;code&gt;InheritedWidget&lt;/code&gt;. I will name it &lt;code&gt;Provider&lt;/code&gt; in this case because we will be providing the data to other parts of the app. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You will see later why I named it this way.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;InheritedWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;CounterState&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;CounterState&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dependOnInheritedWidgetOfExactType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;updateShouldNotify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt; &lt;span class="n"&gt;oldWidget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;oldWidget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;updateShouldNotify&lt;/code&gt; function lets you define when the &lt;code&gt;InheritedWidget&lt;/code&gt; should update your application. You would most likely want to update the app whenever the data changes, however, you have the option to customize that. Either way, whenever your &lt;code&gt;updateShouldNotify&lt;/code&gt; function returns &lt;code&gt;true&lt;/code&gt; it lets all the widgets that are using your &lt;code&gt;data&lt;/code&gt; state know that it should rebuild. In this case, we are going to let our app know about the changes every time whenever the previous &lt;code&gt;data&lt;/code&gt; doesn't match the current &lt;code&gt;data&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;As I mentioned earlier, &lt;code&gt;InheritedWidget&lt;/code&gt; lets the widgets that are found below it in the widget tree know that the data has been updated, and thus they get rebuilt. In this case, we want all the affected widgets to know that the data has been updated, so we will wrap the whole application with our &lt;code&gt;InheritedWidget&lt;/code&gt; which we named &lt;code&gt;Provider&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="n"&gt;_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;counter:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we have access to the current state of our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updating the State
&lt;/h3&gt;

&lt;p&gt;This above solution is close, but we are missing a critical step, which is updating the state. We defined a constant &lt;code&gt;_state&lt;/code&gt; variable that you can't really change. So we will need to create one more widget that holds the state. This widget will have 2 important roles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hold the state.&lt;/li&gt;
&lt;li&gt;Be the only place where the state can get updated.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why is that second point so important? Let's say you are working on a big application, that has counters all over the place. Let's say you decide that the counters should actually be incremented by 2 instead of 1. Now you would have to go through every place in the code that is updating counters and make sure that it gets incremented in the new and correct way. But having it all in one place means you can just update it in that one place and it gets reflected throughout the rest of the application.&lt;/p&gt;

&lt;p&gt;Again, with a simple example like this, it might not seem like such a big deal, but when you have a giant application it becomes a lot more important.&lt;/p&gt;

&lt;p&gt;The other big benefit is if you are running into a bug associated with some feature, it becomes a lot easier to figure out where the problem is since it can only be happening in one place.&lt;/p&gt;

&lt;p&gt;So in this case we are going to create a class called &lt;code&gt;AppStateHolder&lt;/code&gt;. Just like the name suggests it will hold the &lt;strong&gt;app state&lt;/strong&gt; which in this case is a &lt;code&gt;CounterState&lt;/code&gt;, and it will have functions that will be the interface for the rest of the application to update the &lt;code&gt;CounterState&lt;/code&gt; using the &lt;code&gt;copyWith&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppStateHolder&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;StatefulWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppStateHolder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;key:&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="n"&gt;AppStateHolderState&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findAncestorStateOfType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppStateHolderState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nd"&gt;@override&lt;/span&gt;
 &lt;span class="n"&gt;AppStateHolderState&lt;/span&gt; &lt;span class="n"&gt;createState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AppStateHolderState&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppStateHolderState&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppStateHolder&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;CounterState&lt;/span&gt; &lt;span class="n"&gt;_counterState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;counter:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;newCounter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_counterState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;_counterState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_counterState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;counter:&lt;/span&gt; &lt;span class="n"&gt;newCounter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;});&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="nd"&gt;@override&lt;/span&gt;
 &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Provider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
 &lt;span class="n"&gt;_counterState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks like a lot of boilerplate code, but really it's doing only those two things mentioned above. It holds the state and passes it to the rest of our app using the &lt;code&gt;Provider&lt;/code&gt;, and now it also allows us to update that state using the &lt;code&gt;add()&lt;/code&gt; function. &lt;/p&gt;

&lt;h3&gt;
  
  
  Using the State within your App
&lt;/h3&gt;

&lt;p&gt;To finally put everything together need to change from wrapping our whole app with the &lt;code&gt;Provider&lt;/code&gt; to wrapping it with &lt;code&gt;AppStateHolder&lt;/code&gt;. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: This still internally wraps it with a &lt;code&gt;Provider&lt;/code&gt; inside the &lt;code&gt;AppStateHolder&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="n"&gt;runApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;AppStateHolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;MyApp&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have access to a full state management solution for our counter application. &lt;/p&gt;

&lt;p&gt;We can access the current counter state using &lt;code&gt;Provider.of(context).counter&lt;/code&gt;, and we can update the state of the application using &lt;code&gt;AppStateHolder.of(context).add()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;As you can see from this example, it was a lot of setup just to get a stupid little counter to work. I feel like there could be some pre-written code to make it a bit easier...(Read the next section)&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Code
&lt;/h3&gt;

&lt;p&gt;The example code for this is located &lt;a href="https://github.com/hungrimind/flutter/tree/main/code/state_management"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why use a package?
&lt;/h2&gt;

&lt;p&gt;Hopefully, it is pretty clear why from the previous section. But I want to make it clear, you by no means &lt;strong&gt;have&lt;/strong&gt; to use one. If your app is simple enough, just using &lt;code&gt;setState&lt;/code&gt; is enough. If you are working at a big enough organization it might even be worth it to build your own complete state management solution that you have full control of. But for me personally, using a package is the way to go. &lt;/p&gt;

&lt;p&gt;Packages do something similar to what we did, but with more features and more robustness, while at the same time making it a lot less set up for the developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Options
&lt;/h2&gt;

&lt;p&gt;I can only really recommend two options, and these recommendations are my own personal opinion.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://riverpod.dev"&gt;Riverpod&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Riverpod is my favorite option and the one I would recommend for most people to use. If you understood the example that we covered, you will notice some similarities between that and Riverpod. Riverpod uses &lt;code&gt;Providers&lt;/code&gt; to provide data to the rest of your application. For holding state you should use a &lt;code&gt;NotifierProvider&lt;/code&gt; which &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provides the state of your application.&lt;/li&gt;
&lt;li&gt;Creates an interface to update that state using a &lt;code&gt;Notifier&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You will notice this will look similar to what we created above, just with way less boilerplate. &lt;/p&gt;

&lt;p&gt;If you want to dive deeper into how you would Riverpod with Firebase, which is the most popular database choice for Flutter apps, I have built &lt;a href="https://dev.to/flutter/login-with-riverpod-and-firebase"&gt;a whole course just on that.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my opinion, Riverpod is the least boilerplate, while also utilizing the core Flutter features properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://bloclibrary.dev/#/"&gt;Bloc&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Bloc is another very popular solution within the Flutter community. It is known to scale well, and be a good solution to big projects.&lt;/p&gt;

&lt;p&gt;Once again this is just my personal opinion, but I don't choose Bloc because there is a lot of boilerplate again. Although probably less than setting it up yourself, there is the Bloc paradigm that you have to learn. This could be seen as a positive because it forces you to follow good code practices, but I personally enjoy packages with less overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Others
&lt;/h3&gt;

&lt;p&gt;There are many other options, so feel free to explore, but these 2 are the big dogs in the Flutter community, and you can't go wrong with either.&lt;/p&gt;

&lt;p&gt;To learn more flutter topics visit: &lt;a href="https://hungrimind.com/flutter"&gt;https://hungrimind.com/flutter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>statemanagement</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build a Video Call App with Astro and ReactJS</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Fri, 22 Sep 2023 18:53:33 +0000</pubDate>
      <link>https://dev.to/tadaspetra/build-a-video-call-app-with-astro-and-reactjs-eal</link>
      <guid>https://dev.to/tadaspetra/build-a-video-call-app-with-astro-and-reactjs-eal</guid>
      <description>&lt;p&gt;Astro is a framework that renders static HTML to your website with no JavaScript unless you explicitly ask for it and it is gaining popularity fast. But can you add video calls to your Astro website?&lt;/p&gt;

&lt;p&gt;The answer is yes! This article will teach you exactly how to do it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Will Use
&lt;/h2&gt;

&lt;p&gt;Of course, we need the Astro framework, since it is the title of this article. &lt;/p&gt;

&lt;p&gt;We will use Agora for the video call portion of the app. Specifically, we will use the &lt;code&gt;agora-rtc-react&lt;/code&gt; package. This package is a React SDK for interfacing with &lt;a href="https://docs.agora.io/en/video-calling/get-started/get-started-sdk?platform=react-js"&gt;Agora&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Currently, there is no native Astro package. But Astro was built to be used with almost any popular web framework. So the &lt;code&gt;agora-rtc-react&lt;/code&gt; package will work just fine.&lt;/p&gt;

&lt;p&gt;We will use Tailwind CSS for minor styling of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an Agora Account
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sso2.agora.io/en/v6/signup"&gt;Sign up&lt;/a&gt; for an Agora account, and log in to the dashboard.&lt;/p&gt;

&lt;p&gt;Navigate to the Project List tab under the Project Management tab. Create a project by clicking the blue Create button.&lt;/p&gt;

&lt;p&gt;Retrieve the App ID, which we’ll use to authorize the app requests as we develop the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize the Astro Project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create your Astro project by using &lt;code&gt;npm create astro@latest&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Add Tailwind CSS by using &lt;code&gt;npm astro add tailwind&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add React by using &lt;code&gt;npm astro add react&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the Agora UI Kit by using &lt;code&gt;npm install agora-rtc-react&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;PUBLIC_AGORA_APP_ID = '&amp;lt;---Your App Id---&amp;gt;'&lt;/code&gt; to your &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Structure of the Project
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── src
│   ├── components
│   │    └── Call.tsx
│   ├── layouts
│   │   └── Layout.astro
│   └── pages
│       ├── index.astro
│       └── channel
│           └── [channelName].astro
├── public
│   └── favicon.png
├── package.json
├── astro.config.js
├── tsconfig.json
└── tailwing.config.cjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building a Form with Astro
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Astro SSG vs SSR
&lt;/h3&gt;

&lt;p&gt;By default, Astro projects use a static site generator (SSG) that creates a static site which you cannot change. Astro provides a way to interface with various frameworks (React, Svelte, Vue, etc.) using a mechanism called islands. Forms need to be interactive, and we could use an island to get this done. However, you can create a form with Astro that ships with zero JavaScript.&lt;/p&gt;

&lt;p&gt;To do that, we need to enable the server-side renderer (SSR) by adding &lt;code&gt;output: 'server'&lt;/code&gt; to the &lt;code&gt;astro.config.mjs&lt;/code&gt; file. This means that our pages will get rendered on the server. This brings the benefits of being able to access server-side data and to run some functions before the page loads. &lt;/p&gt;

&lt;h3&gt;
  
  
  Form UI
&lt;/h3&gt;

&lt;p&gt;For the case of creating and submitting a form, we will load the page initially to display the form. Then whenever we submit the form, it will act much like an API endpoint. &lt;/p&gt;

&lt;p&gt;Normally, when you submit a form the data is added to the URL, and you would have to parse it out. By changing the form's data transfer method to POST, it will send the form data as part of the &lt;code&gt;Request&lt;/code&gt; body. For this application, the only data we need is the name of the channel the user is trying to join:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-4 mt-20 text-4xl font-extrabold leading-none tracking-tight text-gray-900"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-orange-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Astro&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; x &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-500"&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Agora&lt;span class="nt"&gt;&amp;lt;/span&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{error}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"md:flex md:items-center mt-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
                    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"&lt;/span&gt;
                    &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"inline-full-name"&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    Channel Name
                &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
                    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500"&lt;/span&gt;
                    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"inline-full-name"&lt;/span&gt;
                    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
                    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"channelName"&lt;/span&gt;
                &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
                &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"inline-flex items-center justify-center px-5 py-3 mt-5 text-base font-medium text-center text-white bg-blue-400 rounded-lg hover:bg-blue-500 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-900"&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our form should look like this:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1d4dmnspwms1g0f3sym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1d4dmnspwms1g0f3sym.png" alt="Enter Channel" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Retrieve Form Data and Redirect to the Call Page
&lt;/h2&gt;

&lt;p&gt;Once the form is submitted with the data in the &lt;code&gt;Request&lt;/code&gt;, the page is reloaded and the data is sent to the server. This data can be retrieved server-side and acted on before the page is displayed. In this case, we will check for errors. If we find any, we will render the same page again to show the user the error. If there is no error, we will redirect the user to the page associated with the video call channel they entered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;channelName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/channel/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Call Page
&lt;/h2&gt;

&lt;p&gt;In the Call component that we will build with the &lt;code&gt;agora-rtc-react&lt;/code&gt;, we want to print the name of the channel we have joined.&lt;/p&gt;

&lt;p&gt;The redirect we used in the previous section is dynamic, and it will be different depending on the channelName we entered. To handle this in Astro, we need to create a &lt;code&gt;channel&lt;/code&gt; folder and in the folder define a file named &lt;code&gt;[channelName].astro&lt;/code&gt;. The square brackets signify that the dynamic &lt;code&gt;${channelName}&lt;/code&gt; in our URL will be passed as a parameter in this component. We just need to retrieve the channel name and display it in our UI.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;client:only&lt;/code&gt; directive
&lt;/h3&gt;

&lt;p&gt;For our Call component we pass the &lt;code&gt;channelName&lt;/code&gt; and the &lt;code&gt;appId&lt;/code&gt;. But we also need to add a &lt;code&gt;client:only="react"&lt;/code&gt; directive. This directive is needed for creating an Astro island. As mentioned, Astro generates static HTML code for the site, and the video call that we create needs to be interactive. &lt;/p&gt;

&lt;p&gt;So without this directive Astro will try to render static HTML from this React component. However, by using &lt;code&gt;client:only&lt;/code&gt; we skip the server-side rendering, and just let React do what React wants to do: render only on the client. &lt;/p&gt;

&lt;p&gt;Since Astro supports many frameworks we also need to specify which framework it needs to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;---
import Call from "../../components/Call.tsx";

const { channelName } = Astro.params;
---

&amp;lt;div class="static m-0 p-0"&amp;gt;
    &amp;lt;p class="absolute z-10 mt-2 ml-12 text-2xl font-bold text-gray-900"&amp;gt;
        {channelName!}
    &amp;lt;/p&amp;gt;
    &amp;lt;Call
        client:only="react"
        channelName={channelName!}
        appId={import.meta.env.PUBLIC_AGORA_APP_ID}
    /&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add the Call Component
&lt;/h2&gt;

&lt;p&gt;This last component is a pure React component. The Call component will contain three key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Videos of all the participants&lt;/li&gt;
&lt;li&gt;Channel name&lt;/li&gt;
&lt;li&gt;End call button&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The channel name is a simple text at the top left of the screen. The call button is at the bottom middle of the screen. Whenever you click the button, it returns you to the previous screen, where you can join a different video call.&lt;/p&gt;

&lt;p&gt;The interesting part is displaying the videos of all the participants. In order to do that we need to create an &lt;code&gt;AgoraRTCProvider&lt;/code&gt;, which initializes and gives us access to the Agora RTC service. Inside this we can now display the video:&lt;/p&gt;

&lt;h3&gt;
  
  
  Videos
&lt;/h3&gt;

&lt;p&gt;The Videos component will be the part of the site that displays the videos of all the participants. Many hooks are used to set up the call:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;useLocalMicrophoneTrack() retrieves the current user's microphone.&lt;/li&gt;
&lt;li&gt;useLocalCameraTrack() retrieves the current user's video input.&lt;/li&gt;
&lt;li&gt;useRemoteUsers() retrieves all the user information for the remote users.&lt;/li&gt;
&lt;li&gt;useRemoteAudioTracks() retrieves the audio for those users.&lt;/li&gt;
&lt;li&gt;usePublish() publishes the current user's video and audio.&lt;/li&gt;
&lt;li&gt;useJoin() joins the channel for the video call.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isLoadingMic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localMicrophoneTrack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocalMicrophoneTrack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isLoadingCam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localCameraTrack&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLocalCameraTrack&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;remoteUsers&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRemoteUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;audioTracks&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRemoteAudioTracks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;usePublish&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;localMicrophoneTrack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;localCameraTrack&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nf"&gt;useJoin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;appid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AppID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to make sure all the audio for the remote users is started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;audioTracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;track&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we need to define our UI: first a loading state, while we wait for the local user's microphone and video to begin, and then a grid of all the users who are in the call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;deviceLoading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isLoadingMic&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;isLoadingCam&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deviceLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center pt-40"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading devices...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;numCols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;numRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numUsers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;numCols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;numRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;numCols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;numRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;numCols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;numRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nx"&gt;numCols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;numRows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col justify-between w-full h-screen p-1"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`grid grid-cols-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;numCols&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; grid-rows-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;numRows&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; gap-1 flex-1`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LocalVideoTrack&lt;/span&gt; &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;localCameraTrack&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;play&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"w-full h-full"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;remoteUsers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RemoteUser&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our final video call should look like this:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoidmib42ycml33600i6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftoidmib42ycml33600i6.png" alt="Video Call" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;With that, we have a complete video call experience. Here's how we built it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create our Astro project.&lt;/li&gt;
&lt;li&gt;Install Tailwind CSS, React, and the Agora SDK.&lt;/li&gt;
&lt;li&gt;Create a form to input the channel name.&lt;/li&gt;
&lt;li&gt;Redirect the site to a URL with the channel name.&lt;/li&gt;
&lt;li&gt;Display the channel name with a video call.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code for this project can be found &lt;a href="https://github.com/tadaspetra/astro-video-call"&gt;here&lt;/a&gt;. You can find out more about Agora video calling &lt;a href="https://docs.agora.io/en/video-calling/get-started/get-started-uikit?platform=reactjs"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>astro</category>
      <category>react</category>
      <category>agora</category>
      <category>videocall</category>
    </item>
    <item>
      <title>Build a Video Call App with Astro</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Tue, 01 Aug 2023 20:06:48 +0000</pubDate>
      <link>https://dev.to/tadaspetra/build-a-video-call-app-with-astro-1p02</link>
      <guid>https://dev.to/tadaspetra/build-a-video-call-app-with-astro-1p02</guid>
      <description>&lt;p&gt;A framework called Astro is gaining popularity, and so I decided to learn web development to give it a shot. I built a couple of sites with it, and then I thought: "I wonder if you can build video call apps with Astro." &lt;/p&gt;

&lt;p&gt;The answer is yes! This article will teach you exactly how you can do it too.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Will Use
&lt;/h2&gt;

&lt;p&gt;Of course, we need Astro, since it is the title of this article. &lt;/p&gt;

&lt;p&gt;The second part of the title is about video calls. For that, we will use Agora. Specifically, we will use the &lt;code&gt;agora-react-uikit&lt;/code&gt; package. This package is a React wrapper for the &lt;a href="https://docs.agora.io/en/video-calling/get-started/get-started-uikit?platform=web" rel="noopener noreferrer"&gt;Agora UI Kit&lt;/a&gt;, which is a set of UI components that make it easy to build a video call app. &lt;/p&gt;

&lt;p&gt;Currently, there is no native Astro package. But given that we can use almost any popular web framework in an Astro application, the &lt;code&gt;agora-react-uikit&lt;/code&gt; will work just fine.&lt;/p&gt;

&lt;p&gt;We will also use Tailwind CSS to do some minor styling of the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an Agora Account
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sso2.agora.io/en/v6/signup" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; for an Agora account and log in to the dashboard.&lt;/p&gt;

&lt;p&gt;Navigate to the Project List tab under the Project Management tab, and create a project by clicking the blue Create button.&lt;/p&gt;

&lt;p&gt;Retrieve the App ID, which we’ll use to authorize the app requests as we develop the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialize the Astro Project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create your Astro project using &lt;code&gt;npm create astro@latest&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Add Tailwind CSS using &lt;code&gt;npx astro add tailwind&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add React using &lt;code&gt;npx astro add react&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the Agora UI Kit using &lt;code&gt;npm install agora-react-uikit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;PUBLIC_AGORA_APP_ID = '&amp;lt;---Your App Id---&amp;gt;'&lt;/code&gt; to your &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Structure of the Project
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

├── src
│   ├── components
│   │    └── Call.tsx
│   ├── layouts
│   │   └── Layout.astro
│   └── pages
│       ├── index.astro
│       └── channel
│           └── [channelName].astro
├── public
│   └── favicon.png
├── package.json
├── astro.config.js
├── tsconfig.json
└── tailwing.config.cjs


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Building a Form with Astro
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Astro SSG vs SSR
&lt;/h3&gt;

&lt;p&gt;By default Astro projects use a static site generator (SSG) that creates a static site which you cannot change. Astro provides a way to interface with different frameworks (React, Svelte, Vue, etc.) using a mechanism called islands. Forms need to be interactive, and we could definitely use an island to get this done. However it is possible to create a form with Astro that ships with zero JavaScript.&lt;/p&gt;

&lt;p&gt;To do that, we need to enable the server-side renderer (SSR) by adding &lt;code&gt;output: 'server'&lt;/code&gt; to the &lt;code&gt;astro.config.mjs&lt;/code&gt; file. This means that our pages will get rendered on the server. This brings the benefits of being able to access server-side data and to run some functions before the page loads. &lt;/p&gt;
&lt;h3&gt;
  
  
  Form UI
&lt;/h3&gt;

&lt;p&gt;For the case of creating and submitting a form, we will load the page initially to show the form. Then whenever we submit the form, it will act much like an API endpoint. &lt;/p&gt;

&lt;p&gt;Normally, when you submit a form the data will be added to the URL, and you would have to parse it out. By changing the form's data transfer method to POST, it will send the form data as part of the &lt;code&gt;Request&lt;/code&gt; body. For this application the only data we need is the name of the channel the user is trying to join:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"flex flex-col items-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt;
        &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"mb-4 mt-20 text-4xl font-extrabold leading-none tracking-tight text-gray-900"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-orange-500"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Astro&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt; x &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-blue-500"&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Agora&lt;span class="nt"&gt;&amp;lt;/span&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;{error}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"md:flex md:items-center mt-6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt;
                    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"block text-gray-500 font-bold md:text-right mb-1 md:mb-0 pr-4"&lt;/span&gt;
                    &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"inline-full-name"&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    Channel Name
                &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt;
                    &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"bg-gray-200 appearance-none border-2 border-gray-200 rounded w-full py-2 px-4 text-gray-700 leading-tight focus:outline-none focus:bg-white focus:border-blue-500"&lt;/span&gt;
                    &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"inline-full-name"&lt;/span&gt;
                    &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt;
                    &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"channelName"&lt;/span&gt;
                &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"text-center"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt;
                &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"inline-flex items-center justify-center px-5 py-3 mt-5 text-base font-medium text-center text-white bg-blue-400 rounded-lg hover:bg-blue-500 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-900"&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our form should look like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1d4dmnspwms1g0f3sym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa1d4dmnspwms1g0f3sym.png" alt="Enter Channel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieve Form Data and Redirect to Call Page
&lt;/h2&gt;

&lt;p&gt;Once the form is submitted with the data in the &lt;code&gt;Request&lt;/code&gt;, the page will be reloaded and the data will be sent to the server. This data can be retrieved server-side and acted on before the page is displayed. In this case, we will check for errors. If we find any, we will render the same page again to show the user the error. If there is no error, we will redirect the user to the page associated with the video call channel they entered:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;channelName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Astro&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/channel/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Call Page
&lt;/h2&gt;

&lt;p&gt;Other than the call component that will be provided by the &lt;code&gt;agora-react-uikit&lt;/code&gt;, the call page will be simple. We want to print out the name of the channel we have joined.&lt;/p&gt;

&lt;p&gt;The redirect we used in the previous section is dynamic, and it will be different depending on the channelName we entered. To handle this in Astro, we need to create a &lt;code&gt;channel&lt;/code&gt; folder and in the folder define a file named &lt;code&gt;[channelName].astro&lt;/code&gt;. The square brackets signify that the dynamic &lt;code&gt;${channelName}&lt;/code&gt; in our URL will be passed as a parameter in this component. We just need to retrieve it and display it in our UI.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;client:only&lt;/code&gt; directive
&lt;/h3&gt;

&lt;p&gt;For our Call component we pass the &lt;code&gt;channelName&lt;/code&gt; and &lt;code&gt;appId&lt;/code&gt;. But we also need to add a &lt;code&gt;client:only="react"&lt;/code&gt; directive. This directive is needed for creating an Astro island. As mentioned, Astro is generating static HTML code for the site, and the video call that we create needs to be interactive. &lt;/p&gt;

&lt;p&gt;So without this directive Astro will try to render static HTML from this React component. However by using &lt;code&gt;client:only&lt;/code&gt; we skip the server-side rendering, and just let React do what React wants to do: render only on the client. &lt;/p&gt;

&lt;p&gt;Since Astro supports many frameworks we also need to specify which framework it needs to us:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

---
import Call from "../../components/Call.tsx";

const { channelName } = Astro.params;
---

&amp;lt;div class="static m-0 p-0"&amp;gt;
    &amp;lt;p class="absolute z-10 mt-2 ml-12 text-2xl font-bold text-gray-900"&amp;gt;
        {channelName!}
    &amp;lt;/p&amp;gt;
    &amp;lt;Call
        client:only="react"
        channelName={channelName!}
        appId={import.meta.env.PUBLIC_AGORA_APP_ID}
    /&amp;gt;
&amp;lt;/div&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Add Call Component
&lt;/h2&gt;

&lt;p&gt;This last component is a pure React component. We have a state variable &lt;code&gt;activeCall&lt;/code&gt; that we will use to toggle between the video call screen when &lt;code&gt;true&lt;/code&gt; and a rejoin and back button when &lt;code&gt;false&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;To use the Agora UI Kit, we need to pass &lt;code&gt;rtcProps&lt;/code&gt; containing the App ID and the channel name, in addition to the callbacks. In this case, we define only the &lt;code&gt;EndCall&lt;/code&gt; callback, which will change the &lt;code&gt;activeCall&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;AgoraUIKit&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agora-react-uikit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AppInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   
  &lt;span class="nl"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Call&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;AppInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;activeCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setActiveCall&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rtcProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// enter your agora appid here&lt;/span&gt;
    &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channelName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// your agora channel&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;EndCall&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setActiveCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;


  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;activeCall&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100vw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100vh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AgoraUIKit&lt;/span&gt; &lt;span class="na"&gt;rtcProps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;rtcProps&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;callbacks&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; 
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'ml-12 flex flex-col'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'pt-10'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"px-5 py-3 mt-5 text-base font-medium text-center text-white bg-gray-400 rounded-lg hover:bg-gray-500 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-900 w-40"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setActiveCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Rejoin Call&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;" px-5 py-3 mt-5 text-base font-medium text-center text-white bg-blue-400 rounded-lg hover:bg-blue-500 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-900 w-40"&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Back&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Our final video call should look like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy07njiqoasenpg2ztgcf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy07njiqoasenpg2ztgcf.png" alt="Video Call"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;With that, we have a complete video call experience. Here's how we built it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create our Astro project.&lt;/li&gt;
&lt;li&gt;Install Tailwind CSS, React, and the Agora UI Kit.&lt;/li&gt;
&lt;li&gt;Create a form to input the channel name.&lt;/li&gt;
&lt;li&gt;Redirect the site to a URL with the channel name.&lt;/li&gt;
&lt;li&gt;Display the channel name with a video call.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code for this project can be found &lt;a href="https://github.com/tadaspetra/agora/tree/main/astrovideocall" rel="noopener noreferrer"&gt;here&lt;/a&gt;. And you can find out more about Agora video calling &lt;a href="https://docs.agora.io/en/video-calling/get-started/get-started-uikit?platform=web" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Live Streaming to Multiple Platforms with Multiple Users</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Fri, 08 Oct 2021 00:48:28 +0000</pubDate>
      <link>https://dev.to/tadaspetra/live-streaming-to-multiple-platforms-with-multiple-users-5848</link>
      <guid>https://dev.to/tadaspetra/live-streaming-to-multiple-platforms-with-multiple-users-5848</guid>
      <description>&lt;p&gt;There are some pretty good solutions to have collaborative livestreams with other creators. But if you are like me, you probably think they are a little too expensive, and you probably wish there was more customizability. Thankfully, Agora exists and you can build your own services like this!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sso.agora.io/en/signup?utm_source=medium&amp;amp;utm_medium=blog&amp;amp;utm_campaign=live-streaming-to-multiple-platforms-with-multiple-users"&gt;An Agora Developer Account - Sign Up Here&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Flutter SDK&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/agora_rtc_engine"&gt;Agora RTC package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pub.dev/packages/agora_rtm"&gt;Agora RTM package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since this is such a big project the plan is to break it down and cover the most important concepts. If you want the full code, you can find it here:&lt;br&gt;
&lt;a href="https://github.com/tadaspetra/flutter-projects/tree/main/streamer"&gt;https://github.com/tadaspetra/flutter-projects/tree/main/streamer&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  App Overview
&lt;/h2&gt;

&lt;p&gt;Before getting into those 3 main points, I believe a general overview of what we are trying to accomplish and how we are going to do it would be beneficial. The goal of this application is simple: &lt;strong&gt;allow multiple users to join and stream their video to a streaming platform.&lt;/strong&gt; (Using a host application that manage the users and what gets actually pushed to the streaming platform)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnuuqt6p4bcvk6wlyftq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnuuqt6p4bcvk6wlyftq.png" alt="App Layout" width="800" height="389"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow of the application is made to be as simple as possible. You start off in the main screen where you need to enter which channel you are trying to join and your name. Then you have two options. You can join as a Director of the call, or a Participant within the call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Director&lt;/strong&gt; - Manages all the users and where and how the stream gets sent out. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Participant&lt;/strong&gt; - Joins the call and has a simple call interface.&lt;/p&gt;

&lt;p&gt;In this article we won't be covering the basics of Agora and how it works. If you aren't familiar with Agora here are a couple good places to start, then you can come back to this one. &lt;/p&gt;

&lt;p&gt;Video Call: &lt;a href="https://youtu.be/zVqs1EIpVxs"&gt;https://youtu.be/zVqs1EIpVxs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Livestreaming: &lt;a href="https://youtu.be/kE0ehPMGgVc"&gt;https://youtu.be/kE0ehPMGgVc&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Participant View
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79k74w5mgg3cjuxvk5gd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79k74w5mgg3cjuxvk5gd.png" alt="Participant" width="800" height="670"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article won't spend much time on the participant view. The logic here should be very similar to any other type of video call, except for 4 big differences. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When the user first joins they are in a lobby, so their camera and audio are off.&lt;/li&gt;
&lt;li&gt;When the host brings them into the stage, their video and audio should turn on.&lt;/li&gt;
&lt;li&gt;Video and Audio can be controlled by the host.&lt;/li&gt;
&lt;li&gt;Instead of seeing everybody in the call, they will only see the people on stage.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To the person using the device, they shouldn't see much of a difference from any regular video call. The only extra thing is the "lobby screen" that they will have, before actually joining the call. If there are people on the stage, the current participant will be able to see them, even if they are not on the stage yet. They will be able to watch the output call that gets sent to the streaming platform, but without the custom transcoding. Now all these complex operations are done using Agora's RTM Messages&lt;/p&gt;
&lt;h2&gt;
  
  
  RTM Messaging
&lt;/h2&gt;

&lt;p&gt;Since this is no simple video call application, it will require some way for the director to control the participants. For this we will use the &lt;code&gt;agora_rtm&lt;/code&gt; package. This package allows you to send real time data between the everybody in the channel. The &lt;code&gt;agora_rtc_engine&lt;/code&gt; package is actually built on top of &lt;code&gt;agora_rtm&lt;/code&gt;. The difference is that RTM allows you to send any data, while RTC makes it easy to send video and audio data. For this application, only the director will be able to send out RTM messages, and the participants are only going to be receiving them. There are three types of functions we need to allow the director to have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mute or unmute audio.&lt;/li&gt;
&lt;li&gt;Enable or disable video.&lt;/li&gt;
&lt;li&gt;Send out list of active users.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To mute the user the director sends out a channel wide RTM message with the following format "mute uid", except instead of the word "uid" they send the specific &lt;code&gt;uid&lt;/code&gt; of the user to be muted. Upon receiving this message the participant checks if this &lt;code&gt;uid&lt;/code&gt; is their &lt;code&gt;uid&lt;/code&gt;. If it is, then the user mutes themselves. This works the same way with unmuting and disabling and enabling video except using the keywords "unmute uid", "enable uid", and "disable uid".&lt;/p&gt;

&lt;p&gt;The slightly trickier part is the active users. Normally if you're using agora, you would display all the broadcasters within that call. But in this case, some of them are in the lobby, and they should not be displayed to the viewers. To handle this we use RTM messages again to send all the users that &lt;strong&gt;should&lt;/strong&gt; be displayed. The format is "activeUsers uid,uid,uid" except replace the word "uid" with the specific &lt;code&gt;uid&lt;/code&gt;  of the active users. &lt;/p&gt;



&lt;p&gt;So far we have covered almost everything from the participant point of view. Now let's transition to the Director, which is where most of the magic happens.&lt;/p&gt;


&lt;h2&gt;
  
  
  Director Controller
&lt;/h2&gt;

&lt;p&gt;The director in this app has a lot of functions and things to keep track of. To keep things organized we will use &lt;code&gt;riverpod&lt;/code&gt;, a popular state management solution for Flutter. &lt;/p&gt;

&lt;p&gt;If you have never used it, here is a good place to start: &lt;a href="https://www.youtube.com/watch?v=8qzip8tVmqU"&gt;https://www.youtube.com/watch?v=8qzip8tVmqU&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DirectorController&lt;/code&gt; that we have defined here will be a &lt;code&gt;StateNotifierProvider&lt;/code&gt;. This helps separate our business logic from the UI section of our application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;directorController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StateNotifierProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;autoDispose&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectorController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DirectorModel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DirectorController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This controller will have functions that the rest of our code will be able to access, including &lt;code&gt;joinCall()&lt;/code&gt;, &lt;code&gt;leaveCall()&lt;/code&gt;, &lt;code&gt;toggleUserAudio()&lt;/code&gt;, &lt;code&gt;addUserToLobby()&lt;/code&gt;, &lt;code&gt;promoteToActiveUser()&lt;/code&gt;, &lt;code&gt;startStream()&lt;/code&gt;, and lots of other ones. This controller will also store all the data that we need to keep track of within our app.&lt;/p&gt;

&lt;p&gt;As the participants are only receiving the RTM messages, the director will only be sending out RTM messages. &lt;/p&gt;

&lt;p&gt;In order for the director to be able to send out RTM messages, you need to set up a client and a channel using RTM. This is very similar to what happens with RTC Engine behind the scenes. You need to create a client, log into the client, then create a channel and join the channel. Once this is done, you are ready to send out the RTM messages. The participants will need to do the same thing in order to receive messages on the &lt;code&gt;onMessageReceived&lt;/code&gt; callback. &lt;/p&gt;

&lt;p&gt;To actually send out the message you need to use the &lt;code&gt;sendMessage&lt;/code&gt; function that is provided on that channel. To format the message correctly use this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="n"&gt;AgoraRtmMessage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"unmute &lt;/span&gt;&lt;span class="si"&gt;${state.activeUsers.elementAt(index).uid}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the same approach for all the other messages like "mute uid", "enable uid", "disable uid", and "activeUsers uid,uid,uid".&lt;/p&gt;

&lt;p&gt;So those are the infrastructure details, of what enables us to actually be able to be manage users and streams. Let's get into the details of how the director part of this app actually works. The main three features that we are going to cover are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Muting and Disabling Video of other users&lt;/li&gt;
&lt;li&gt;Moving users between main stage and lobby&lt;/li&gt;
&lt;li&gt;Transcoding Each Video and pushing it to Streaming Platforms&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Muting and Disabling Video
&lt;/h2&gt;

&lt;p&gt;Now given that we have the infrastructure with RTM messaging all set up, this section might sound trivial, but there are actually a lot of pieces for this that need to be accounted for and synced up. &lt;/p&gt;

&lt;h3&gt;
  
  
  Director App
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Muting/Unmuting user's&lt;/li&gt;
&lt;li&gt;Disabling/Enabling user's video&lt;/li&gt;
&lt;li&gt;Current states of audio and video for each user&lt;/li&gt;
&lt;li&gt;Update if user changes their own state&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Participant App
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Mute/Unmute yourself&lt;/li&gt;
&lt;li&gt;Disable/Enable own video&lt;/li&gt;
&lt;li&gt;Mute/Unmute from director&lt;/li&gt;
&lt;li&gt;Disable/Enable video from director&lt;/li&gt;
&lt;li&gt;Current State of audio and video&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To do all this and have it synced up there are lots of different parts that control the audio. The best way to go about this is to look at the different scenarios.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Participant mutes/unmutes themselves&lt;/strong&gt;
When the participant decides to mute themselves they need to call &lt;code&gt;muteLocalAudioStream()&lt;/code&gt;, then update their own button state to show that they are muted, and then on the director side &lt;code&gt;remoteAudioStateChanged&lt;/code&gt; event should get triggered, which in should update the current state for that specific user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Participant disables/enables video&lt;/strong&gt;
Same process as above, except call the function &lt;code&gt;muteLocalVideoStream()&lt;/code&gt; and the event on the director side should be &lt;code&gt;remoteVideoStateChanged&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Director mutes/unmutes user&lt;/strong&gt;
Director needs to send an RTM message with either "mute uid" or "unmute uid". Then the user with the matching &lt;code&gt;uid&lt;/code&gt; will follow the same execution as if they were muting themselves, and then again the director should see the &lt;code&gt;remoteAudioStateChanged&lt;/code&gt; event trigger, and they can update the local state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Director disables/enables video&lt;/strong&gt;
Same process as muting, but instead the stream message will be "enable uid" or "disable uid"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's add another layer of complexity to this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage and Lobby
&lt;/h2&gt;

&lt;p&gt;The idea here isn't too complex, but it comes with a couple caveats that need to be taken care of. The only person that will be able to see both lobby and stage is the director. The &lt;code&gt;DirectorController&lt;/code&gt; will hold a separate list of active users and lobby users. A normal flow for a participant user would be to join the channel, and they will be directly added to the lobby. Then the director is in complete control and can move them to and from the stage as they please. &lt;/p&gt;

&lt;p&gt;The flow for moving a person to and from the stage is very similar. First remove them from the previous list (lobby or active) and add them to the other list. Then update send out the new list of &lt;code&gt;activeUsers&lt;/code&gt; to everybody using a RTM message&lt;/p&gt;

&lt;p&gt;Not too bad, but here is where the complexity comes in. You don't want the lobby users to be able to talk over the users in the stage so they should be muted whilst in the lobby. And since they are in the lobby there is no need to take up extra bandwidth for their video either. Because of this we need to add a couple more scenarios for audio and video control. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Participant first joins channel&lt;/strong&gt;
Since they are added directly into the lobby, they need to be muted and video disabled immediately. Whenever a participant joins a channel, they automatically mute themselves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Participant moved to Stage&lt;/strong&gt;
When they are moved to the main stage their video and audio need to be enabled, so that the audience can see them. This should follow the same logic as director unmuting or enabling video.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Participant moved to Lobby&lt;/strong&gt;
When they are moved to the lobby their video and audio need to be enabled, so that the audience can see them. This should follow the same logic as director muting or disabling video.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Transcoding
&lt;/h2&gt;

&lt;p&gt;Once you have the activeUser list synced up with both the director section of the app, and the participant section of the app, the last step left is to broadcast it out to the streaming platforms. For this we will first transcode all the incoming videos to the desired layout, and then publish and unpublish our streams to the desired platforms using Real-Time Messaging Protocol (RTMP). &lt;/p&gt;

&lt;p&gt;First we need to define what layout we want to have for our output video. In this case we will only support up to 8 people in a call. But you can extend the same concept to as many callers as you want. We also take into account that our stream will be a 1080p streams so have 1920x1080 pixels to work with. Give that information the layout will look like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zlspgqgbkg42lq1ht1z.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2zlspgqgbkg42lq1ht1z.jpeg" alt="Table" width="700" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To actually send out this information we need to create a list of &lt;code&gt;TranscodingUser&lt;/code&gt; and set up each of the users layouts accordingly. Once they are in the list we create a &lt;code&gt;LiveTranscoding&lt;/code&gt; object with the list, and tell the &lt;code&gt;RTCEngine&lt;/code&gt; that this is what we want it to look like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kt"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;transcodingUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;zOrder:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;alpha:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;960&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TranscodingUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activeUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;elementAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;540&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"too many members"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;LiveTranscoding&lt;/span&gt; &lt;span class="n"&gt;transcoding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LiveTranscoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;transcodingUsers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;height:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;!.&lt;/span&gt;&lt;span class="na"&gt;setLiveTranscoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcoding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we have the layout for our stream configured, we need to actually send it out. With this app we have the capabilities to send it out to multiple locations. For this you will need a URL where your stream should be pushed out to. For YouTube it is pretty straight forward. You will need the Stream Url + a backslash ("/") + the Stream Key which are all give in your livestreaming dashboard. For Twitch it is a similar concept, and you can read about it here: &lt;a href="https://help.twitch.tv/s/article/guide-to-broadcast-health-and-using-twitch-inspector?language=en_US"&gt;https://help.twitch.tv/s/article/guide-to-broadcast-health-and-using-twitch-inspector?language=en_US&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that you have all your links, you can loop through all of them and call the &lt;code&gt;addPublishUrl()&lt;/code&gt; on the RTCEngine with the transcodingEnabled parameter set to true. And it's done! Your stream should have appeared on the platforms.&lt;/p&gt;

&lt;p&gt;Lastly, you will want to update the transcoding when someone is added or removed from the main stage, and end the stream. To update, you need to update the transcoding layout accordingly, and then &lt;code&gt;setLiveTranscoding()&lt;/code&gt; again. And to remove a stream call the &lt;code&gt;removePublishUrl()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If this app seems a bit complex, that's because it is. But there are full-blown companies that take months to build an MVP (Minimum Viable Product) for something like this. And even if they can build it, they don't have anywhere close to the infrastructure and reliability that the SD-RTN brings. This is a very complex application, but with Agora it becomes achievable.&lt;/p&gt;

&lt;p&gt;You can find the code for this app &lt;a href="https://github.com/tadaspetra/flutter-projects/tree/main/streamer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Resources
&lt;/h2&gt;

&lt;p&gt;To learn more about the Agora Flutter SDK and other use cases, see the developer guide &lt;a href="https://docs.agora.io/en"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can also have a look at the complete documentation for the functions discussed above and many more &lt;a href="https://docs.agora.io/en/Video/API%20Reference/flutter/index.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And I invite you to join the Agora.io &lt;a href="https://agoraio.slack.com/"&gt;Developer Slack Community&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>flutter</category>
    </item>
    <item>
      <title>How to Grow Your Blog</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Wed, 05 May 2021 13:16:32 +0000</pubDate>
      <link>https://dev.to/tadaspetra/how-to-grow-your-blog-29fc</link>
      <guid>https://dev.to/tadaspetra/how-to-grow-your-blog-29fc</guid>
      <description>&lt;p&gt;Growing a blog in this day and age is no easy task. There is so much content online that getting noticed seems like an insurmountable task. But here are just a few approaches that will increase your chances. &lt;/p&gt;

&lt;h3&gt;
  
  
  Grow Social Presence
&lt;/h3&gt;

&lt;p&gt;Much easier said than done. But with a big social presence you will have built a community of people that already enjoy the content you create. This means you already have a community that will enjoy your blog, and will be more likely to share it with others.&lt;/p&gt;

&lt;p&gt;Now you see the value in social presence, but how do you build this. I'll be honest, this isn't going to be the article that is going to teach you. There is so much involved in this, and so many different ways to do it, but I hope to at least point you in the right direction.&lt;/p&gt;

&lt;p&gt;For me there are 2 key points I focus on, and I believe are the core concepts behind my "success"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistently putting out content&lt;/li&gt;
&lt;li&gt;Getting 1% better every time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Creating content is at the core of most social media growth. If you create content that people enjoy, they will follow along to see more of that content. But to achieve real growth you have to be consistent. Prove to your audience that if they follow you, they WILL see more of that content. Then every time you put out some content, it's like a lottery ticket with the algorithms and get a lot of views. Then if you put out content consistently it's like another ticket to the algorithm lottery.&lt;/p&gt;

&lt;p&gt;You can also increase your odds with the lottery ticket. The better the content is, the higher your chances of winning the prize. It's difficult to go from poor quality to great quality instantaneously, but if you try to improve one aspect on every iteration, and you stay consistent, you will see huge improvements over time. &lt;/p&gt;

&lt;h3&gt;
  
  
  Write Great Blogs
&lt;/h3&gt;

&lt;p&gt;We mentioned "creating content" to grow your social presence in the last section, and I purposely made that vague. Blogs definitely fall into that section. If you want to have a successful blog, growing your social presence through blogging content is probably the best approach.&lt;/p&gt;

&lt;p&gt;Same rules apply as I mentioned above. Consistency and consistent improvement will bring you results. Here are just some of the aspects of your blog that you can/should improve as you write more. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO&lt;/li&gt;
&lt;li&gt;Clickable titles&lt;/li&gt;
&lt;li&gt;Great supporting images&lt;/li&gt;
&lt;li&gt;Article structure&lt;/li&gt;
&lt;li&gt;Story-telling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a lot more to blogging than just these, but the deeper you go into the topic, the more you will discover, and the more you will learn about what you can improve. Just make sure to spend time learning about how to improve, and practicing that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cross-posting
&lt;/h3&gt;

&lt;p&gt;This is the easiest step that you can take. Each platform has it's own algorithms, and will spread your content differently. By sharing it to all the platforms, you will increase the odds of your article to gain traction. It's like getting extra lottery tickets, for free. (not completely free as it takes some work, but a lot less work than writing a whole blog)&lt;/p&gt;

&lt;p&gt;But there is one important thing to remember when doing this. It can destroy your SEO if you don't add a canonical URL to the blog. If google see's the same exact article posted in multiple places, it will rank it a lot lower in their search algorithm. There is a really easy fix for this, and that is adding a canonical URL to the articles. This URL will point to the "main" article. So choose your platform of choice and add the link to that article on all the cross-posted articles.&lt;/p&gt;

&lt;p&gt;This can be a bit annoying and tedious to do for every article, but there is a handy tool at &lt;a href="https://www.krossa.io"&gt;krossa.io&lt;/a&gt; that not only makes cross-posting easy, but has a beautiful UI for writing your blogs. On top of that each platform is slightly different, but using &lt;a href="https://www.krossa.io"&gt;krossa&lt;/a&gt; you can be confident that it will look the same across all platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;There are obviously a lot more than just these 3 tips to grow your blog, but these are the ones that have had the biggest returns for me. I recommend you give them a shot and let me know how it goes for you and what your biggest struggles are on Twitter &lt;a href="https://twitter.com/tadaspetra"&gt;@tadaspetra&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to Learn Flutter in 2021</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Mon, 01 Feb 2021 14:12:18 +0000</pubDate>
      <link>https://dev.to/tadaspetra/how-to-learn-flutter-in-2021-1297</link>
      <guid>https://dev.to/tadaspetra/how-to-learn-flutter-in-2021-1297</guid>
      <description>&lt;p&gt;Flutter is about to have a really big year this year! With the event on March 3rd, 2021 coming up, as well as the community growing bigger and bigger, the upside of Flutter is looking enormous.&lt;/p&gt;

&lt;p&gt;But this means that there will be a lot of new people starting out with Flutter as well. And starting out something new is tough no matter what it is. Hopefully this is a one stop shop for you on how you should learn, along with some resources that worked for me.&lt;/p&gt;

&lt;p&gt;By the way if you would rather watch the video version of this article, here it is. &lt;br&gt;
If you prefer reading, keep going 😊&lt;/p&gt;

&lt;p&gt;(Video for this)[&lt;a href="https://www.youtube.com/watch?v=CKY-Nh6YZeY&amp;amp;feature=emb_title"&gt;https://www.youtube.com/watch?v=CKY-Nh6YZeY&amp;amp;feature=emb_title&lt;/a&gt;]&lt;/p&gt;

&lt;h1&gt;
  
  
  Fundamentals
&lt;/h1&gt;

&lt;p&gt;In my opinion, the biggest problem with flutter is how easy it is to create apps. It's so easy that people can create a working application without knowing much about what they are doing.&lt;br&gt;
Before you start diving into Flutter I would really recommend you have at least a bit of Object Oriented Programming knowledge. And the more you have the better. There are a lot of resources to learn and understand these topics with Dart. But if you are coming from another language like C++ or JavaScript, Dart will be very easy to pick up. I think I spent only a couple days getting used to the syntax and was able to dive right into Flutter framework.&lt;/p&gt;

&lt;h1&gt;
  
  
  Learning Resources
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Flutter Documentation
&lt;/h2&gt;

&lt;p&gt;The documentation is always my first step to learning anything involving flutter. Flutter team has a clear focus on providing high quality documentation, and it is the first step I take whenever I run into issues.&lt;br&gt;
 &lt;a href="https://flutter.dev/docs"&gt;Flutter Docs&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Codelabs
&lt;/h2&gt;

&lt;p&gt;The very initial stages of learning Flutter I followed the google codelabs. You know you can trust these because these are curated by the flutter team, so they are definitely high quality.&lt;br&gt;
&lt;a href="https://codelabs.developers.google.com/?cat=flutter"&gt;Codelabs&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  YouTube
&lt;/h2&gt;

&lt;p&gt;Youtube is of course my favorite resource. This is where I do most of my learning. It has helped so much that I decided to do my part and help contribute, but here are just some channels that I use!&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UCNaJHBXsvbfkItVMNmzmTPQ"&gt;My Channel&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/user/MrShadowFate"&gt;Robert Brunhage&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UCU8Mj6LLoNBXqqeoOD64tFg"&gt;Fun With Flutter&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UCSIvrn68cUk8CS8MbtBmBkA"&gt;Resocoder&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UCFTM1FGjZSkoSPDZgtbp7hA"&gt;MTechViral&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UCsBjURrPoezykLs9EqgamOA"&gt;Fireship&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UC2d0BYlqQCdF9lJfydl_02Q"&gt;FilledStacks&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/user/Lionranger"&gt;FlutterExplained&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UCwXdFgeE9KYzlDdR7TG9cMw"&gt;Official Flutter Channel&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  freeCodeCamp
&lt;/h2&gt;

&lt;p&gt;Free code camp is one of the biggest resources for learning coding online. I am sure they will have more and more coding content. There are a couple video courses for Flutter, including mine :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/aiTTClKJbnw"&gt;freeCodeCamp Course&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Bootcamps
&lt;/h1&gt;

&lt;p&gt;One of the top bootcamps for starting flutter is by Angela Yu. I haven't personally taken it, but I have heard amazing things about it! It is also 4.7 stars with 90,000 students so I am sure it is amazing. It also covers lots of OOP topics like Encapsulation, Inheritance, and Polymorphism.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.udemy.com/course/flutter-bootcamp-with-dart/"&gt;Angela Yu's Course&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Awesome Flutter
&lt;/h1&gt;

&lt;p&gt;This is an amazing GitHub repository that leads you to an enormous amount of resources for learning Flutter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Solido/awesome-flutter"&gt;Awesome Flutter&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Get involved in the Community
&lt;/h1&gt;

&lt;p&gt;This is an indirect way of learning Flutter, but I think being part of the community helps you grow as a developer as well, as helps you make friends who might potentially help you when you run into problems. The developer community is pretty big on Twitter. Here I created a list of some of my favorite Flutter developers on Twitter.&lt;br&gt;
&lt;a href="https://twitter.com/i/lists/1273736213665636359"&gt;https://twitter.com/i/lists/1273736213665636359&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also feel free to join me and other flutter developers on discord&lt;/p&gt;

&lt;h1&gt;
  
  
  Learn by Building
&lt;/h1&gt;

&lt;p&gt;Now for the key point, these are all just resources for learning. In my opinion the best way to learn is by actually building software. Once you have fundamentals down, just go ahead and build apps, and when you run into issues take a look or go through these resources and see if you can figure them out. You can learn a lot more by doing than just by watching or reading.&lt;/p&gt;

&lt;p&gt;Some apps that you can practice learning are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Todo App&lt;/li&gt;
&lt;li&gt;Chat App&lt;/li&gt;
&lt;li&gt;E-Commerce App&lt;/li&gt;
&lt;li&gt;Social Network App&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are able to build these apps and have them function well, you are going to be well on your way to becoming a quality developer. Then focus on making your code more efficient, and improving in different aspects, and you will be a pro in no time.&lt;br&gt;
Happy Coding Everybody! Let's make 2021 an amazing year for Flutter&lt;br&gt;
If you want more content from me you can find me &lt;a class="mentioned-user" href="https://dev.to/tadaspetra"&gt;@tadaspetra&lt;/a&gt; on all platforms, but mainly on Youtube 😊&lt;br&gt;
&lt;a href="https://www.youtube.com/channel/UCNaJHBXsvbfkItVMNmzmTPQ"&gt;Tadas Petra&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
    </item>
    <item>
      <title>How to Start a Youtube Channel with 0 Online Presence</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Mon, 07 Dec 2020 14:04:57 +0000</pubDate>
      <link>https://dev.to/tadaspetra/how-to-start-a-youtube-channel-with-0-online-presence-2ce6</link>
      <guid>https://dev.to/tadaspetra/how-to-start-a-youtube-channel-with-0-online-presence-2ce6</guid>
      <description>&lt;p&gt;January 15th, 2020 was the day I decided to put my cringey first video out in public. A day later I checked back and it got 4 views. Now almost 11 months later the channel has amassed almost 10,000 subscribers and over 40,000 monthly views. Here's how I did it, and how I believe others can do it too.&lt;/p&gt;

&lt;p&gt;Before we start, I know "almost 10,000 subscribers" is a bit of a stretch. As I'm typing it I am at 8,725 subscribers, but the 10,000 makes a much catchier introduction. Either way at this rate, I should be able to get to 10,000 before my 1-year mark.&lt;/p&gt;

&lt;p&gt;Also I know there are a lot of other YouTubers that have gotten a lot more then 10,000 subscribers in their first year, but I believe my story might still help those people, since it didn't include any "viral" videos. This whole article will basically come down to hard work, and consistency. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Your Channel
&lt;/h2&gt;

&lt;p&gt;There are a lot of people that stress over the details at the beginning. I am going to tell you right now, your channel name, and banner are not very important if you are just starting out. There is a lot of learning to do, and a lot of figuring out what your channel is going to be and what route you are going to take before worrying about those details. &lt;/p&gt;

&lt;p&gt;My advice, just start with your name if you are serious about doing this longterm. If you don't want people finding it at first, pick a different name, but do NOT spend days thinking about it. After you start the channel, create a banner, fill in the bio and all the information youtube asks you to fill out, and move on. &lt;/p&gt;

&lt;p&gt;This should not take more then a couple hours or a day. I promise you it will not be very important at the beginning. I have changed my youtube name 3 times. I am currently on my 4th channel name, and I changed that when I was at 8,000 subscribers. (I think this is the last time I will change it. It is just my name now). &lt;/p&gt;

&lt;h2&gt;
  
  
  The Biggest Obstacle
&lt;/h2&gt;

&lt;p&gt;The biggest obstacle for anybody starting out, that hasn't done this before, is releasing that first video. The first video will be the hardest video you will ever make. Not only are there so many things to learn, like recording software, camera, editing software, uploading to youtube, thumbnail, etc, but unless you have done film before, it will be cringey. &lt;strong&gt;BUT THAT'S OKAY&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to any of the top YouTube channels and go to their first video. Some of them might have deleted it, but those that haven't, you will see that their first video is just as cringey as the one you made. Knowing your first video will not be your best video should not stop you at all. Think about any skill you have learned. The first code you wrote was probably embarrassing, the first time you rode a bike you probably looked like a drunk person trying to get home after a night out. Nobody masters a skill on the first try, but know you will get better. And if you really don't like it after you have put out a lot more videos you can delete it (but don't do this until after you are monetized, because the watch hours on that video won't count towards your monetization requirements).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Second Biggest Obstacle
&lt;/h2&gt;

&lt;p&gt;If you have gotten over the first obstacle, you are ready for the second one. If you can get over this second one, you WILL be successful. The second obstacle is &lt;strong&gt;consistency&lt;/strong&gt;. I would recommend anybody first starting, out to pick a release schedule that you are going to stick to. If you have a full time job, I think once a week or once every two weeks depending how detailed your videos are is a good schedule. When I started I did 2x a week with a full time job. After 6 months or so, I had to step down to 1 a week. &lt;/p&gt;

&lt;p&gt;But the obstacle of consistency is not just with the amount of videos you release. It is consistently getting better every video. There are so many aspects of video that can get better, just try your best to improve one thing for every video. You can improve things like thumbnail design, title, description, lighting, your cuts, introduction, microphone, camera, etc.&lt;/p&gt;

&lt;p&gt;As long as you are consistently improving and releasing videos you will see the quality of your work getting better and better, and as the quality gets better, the more people will want to watch, and thus your channel will grow. &lt;/p&gt;

&lt;h2&gt;
  
  
  Marketing
&lt;/h2&gt;

&lt;p&gt;Now if you are releasing consistent videos that are getting better and better, that should be enough in the long run. But getting those first initial viewers that get the ball rolling for your channel is a bit hard to get. They can come naturally but marketing and promoting your channel will definitely help you get there faster.&lt;/p&gt;

&lt;p&gt;The best marketing in my opinion is social media marketing. This is a free but difficult one to do. The title of the article says starting with 0 online presence, but that's not how you should continue once you start. On YouTube it is harder to interact with people in you niche, other then comments. Growing an audience on other platforms like Twitter or Instagram will definitely help your YouTube channel as well. On those mediums you can actually interact with your followers and get to meet some other great people. And once your audience is growing on there you can share your videos too!&lt;/p&gt;

&lt;p&gt;But since we don't have an online presence at the start, we need to look for other options. My two favorite is Reddit and Facebook groups. The key here is to find subreddits and groups that match your content. For example, most of my videos are about Flutter framework. On reddit there is r/FlutterDev and Facebook has groups like "Let's Flutter with Dart". There is also other options like the Flutter Discord server and many more. Main thing to keep in mind is to not spam your video everywhere. Don't be annoying. Only post it in places where people might actually find it interesting and useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put it all together
&lt;/h2&gt;

&lt;p&gt;Those three overgeneralized tips are pretty much all you need. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get Started&lt;/li&gt;
&lt;li&gt;Stay Consistent&lt;/li&gt;
&lt;li&gt;Market Yourself&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There are a lot of different niches, and lots of different types of videos. Try to be unique and offer something to your viewers. Then follow these 3 and I am sure you can replicate what I have done as well.&lt;/p&gt;

&lt;p&gt;I will be writing more articles that have more details about growing a YouTube channel. Follow me on Twitter so you don't miss those: &lt;a href="https://twitter.com/tadaspetra"&gt;https://twitter.com/tadaspetra&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And also if you are curious about my channel here it is: &lt;a href="https://www.youtube.com/tadaspetra"&gt;https://www.youtube.com/tadaspetra&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for your time and have a wonderful day!&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>youtube</category>
      <category>twitter</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Coding Fundamentals Are Underrated</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Tue, 01 Dec 2020 14:57:31 +0000</pubDate>
      <link>https://dev.to/tadaspetra/coding-fundamentals-are-underrated-1l9n</link>
      <guid>https://dev.to/tadaspetra/coding-fundamentals-are-underrated-1l9n</guid>
      <description>&lt;p&gt;With all the new technologies and frameworks, it is becoming easier and easier to create apps that "work". I put the &lt;strong&gt;work&lt;/strong&gt; in quotes, because even though the app &lt;strong&gt;might&lt;/strong&gt; function, it will most likely not scale and will probably have some big bugs as well. Without good coding fundamentals, you will almost certainly have a tough time building maintainable and scalable code.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Coding Fundamental?
&lt;/h2&gt;

&lt;p&gt;A fundamental is an adjective that describes forming a necessary base or core. By this it means to have a good understanding of the core of what coding is. Frameworks like React and Flutter are just complex combinations of the fundamental underlying code. But even though a lot of the underlying code is abstracted, understanding what is actually going on will still help you tons.&lt;/p&gt;

&lt;h2&gt;
  
  
  How To Learn?
&lt;/h2&gt;

&lt;p&gt;Coding Fundamentals can range from anything like naming variables to software architecture and everything in between. You can learn these fundamentals in any language, and they will translate to any other language. My two favorite languages to learn these are C and C++. With C you will really get to understand what exactly is happening in your code. It is a low level language so you have to be more direct about how you access memory, inputs, and many other things. Then it is definitely important to move on to some Object Oriented Programming, since this is what most higher level languages follow. If you start with C it will be a bit easier to transition to C++. BUT these are just my favorites, you can learn fundamentals using any language.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Fundamentals will Help?
&lt;/h2&gt;

&lt;p&gt;Like I said at the beginning, having good code fundamentals will help you make your code scalable and maintainable. Let me demonstrate that to you using the two previous examples: naming variables and architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Naming variables&lt;/strong&gt; might sound like trivial task. But if you have worked on any big project you definitely know how important they are. With a small app you don't need to overthink this, and it is okay to have "x" or "y" as a variable name. But when your software has 100s or 1000s of variables and multiple people working on the same software they become super important. At that point it becomes critical for your code to be &lt;strong&gt;readable&lt;/strong&gt;. There is a reason why google has a whole website dedicated to coding conventions and a big section is naming.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://google.github.io/styleguide/cppguide.html#Naming"&gt;Google C++ Naming Conventions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Software Architecture&lt;/strong&gt; is one of the most important coding fundamentals, if not THE most important. Software Architecture is the layout of you code, and how it is separate into different Objects, Files, Functions, etc. This is such an important topic that most bigger teams have people dedicated only to that topic! With good software architecture, a team can work on separate parts of the code simultaneously and not impede on anybody else. It will also keep things organized and the developers will still be able to understand what is going on no matter how big the codebase gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal Experience
&lt;/h2&gt;

&lt;p&gt;I have been uploading videos to YouTube for 10 months now with a strong concentration on Flutter. Although I love the Flutter framework and I think it will keep growing over the years, I believe it has made the barrier to programming a lot smaller as well. This is great for experienced people, because now they can build apps quicker and easier. But there are a lot of people that are just starting programming, and they might be starting with Flutter. All that is fine, but I can definitely see people lacking their coding fundamentals. The most common questions I get are not necessarily questions about Flutter. They are questions that would be covered by knowing the fundamentals.&lt;/p&gt;

&lt;p&gt;Because of this, I have decided to start another series on my channel covering the code fundamentals, where I will start from the very basics of programming and move my way into more difficult topics. My goal is to hopefully have a complete reference of everything related to code, and can guide people to the specific videos if they are missing the fundamentals.&lt;/p&gt;

&lt;p&gt;If you want to follow along, this playlist will be featured at the top of my channel, and will be updated weekly!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/1VSUJWbtnTw"&gt;What is Programming?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for your time! If you have any feedback or want to connect with me:&lt;br&gt;
&lt;a href="https://www.youtube.com/tadaspetra"&gt;Youtube&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://twitter.com/tadaspetra"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.twitch.tv/tadaspetra"&gt;Twitch&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.instagram.com/tadaspetra/"&gt;Insta&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Website (work in progress): &lt;a href="https://tadaspetra.com/#/"&gt;tadaspetra.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you enjoy my work and would like to support me:&lt;br&gt;
&lt;a href="https://www.patreon.com/tadaspetra"&gt;Patreon&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Quitting My Job With Only $150 Monthly Side Income</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Fri, 20 Nov 2020 01:26:30 +0000</pubDate>
      <link>https://dev.to/tadaspetra/quitting-my-job-with-only-150-monthly-side-income-2k6e</link>
      <guid>https://dev.to/tadaspetra/quitting-my-job-with-only-150-monthly-side-income-2k6e</guid>
      <description>&lt;h2&gt;
  
  
  Why I left
&lt;/h2&gt;

&lt;p&gt;Since I was in highschool, I never wanted to work a 9-5 job. I have been the person that likes to have control of what I am doing, and with the things that I build. &lt;/p&gt;

&lt;h3&gt;
  
  
  What I don't like about 9-5
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A 9-5 forces you to stay there even when you have finished your work.&lt;/li&gt;
&lt;li&gt;You rely on your boss and people above you to make you be successful.&lt;/li&gt;
&lt;li&gt;The amount of work you put in doesn't directly correlate with what you get.&lt;/li&gt;
&lt;li&gt;You don't have control of the companies future&lt;/li&gt;
&lt;li&gt;There are a lot of corporate politics that you have to maneuver&lt;/li&gt;
&lt;li&gt;You are not allowed to speak your mind freely, and really voice your opinions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are just some of the reasons, but there are a lot more. That is why I have always wanted to figure out a way to make money without working a 9-5. However then came university and internships, and one thing led to another, and I found myself at my last semester of university. During that last semester I started taking things a lot more seriously. I would wake up at 3am before going to class to learn web development, but nothing worked out so I ended up taking a job. &lt;/p&gt;

&lt;p&gt;Now as far as a 9-5 job went, the job I had was as good as it gets. My boss was the best boss I could have ever asked for. He taught me more then I could ever image. He helped me quickly grow into a leader, but the passion to create something of my own and work on my own terms never disappeared. &lt;/p&gt;

&lt;h2&gt;
  
  
  How I left
&lt;/h2&gt;

&lt;p&gt;Like I said, I have been trying to find a way to make money since the last semester of university. I have tried to create multiple startups which included trying to build a social networks site, then an app, then various other apps. But none of them had much success. Then at the beginning of 2020 I had realized while learning all these technologies I had learned a lot through all the failures and struggles. I decided it would be a good idea to put all this knowledge online.&lt;/p&gt;

&lt;p&gt;Starting a youtube channel was always something I wanted to do. I actually started multiple channels when I was a kid, playing minecraft or some other games. But I never kept up and just deleted them after some time. This time I started one with a purpose. At first I didn't tell anybody that I started it, not even my girlfriend. But then I got my first 50 subscribers and I let her know, then it kept growing and growing. And now at over 7,500 subscribers it has grown bigger than I have ever imagined. &lt;/p&gt;

&lt;p&gt;Now I know what you're thinking 7,500 subscribers is not nearly enough to be making adequate money and thus quitting your job. I fully agree with you. But like I said, this has been something I have wanted to do since highschool. So I have been saving up money this whole time I have been working, and just waiting for an opportunity to give it my all. There is a little of bit of promise I believe, and with my savings I have given myself a year of hardwork to make this promise a reality. &lt;/p&gt;

&lt;p&gt;I have also been saving all my vacation days for this year, for a long trip. Instead I will now use this vacation for the month of December as a grace period to give it my all. And then my last official day of work will be January 15th, which will mark exactly 1 year since I uploaded my first video.&lt;/p&gt;

&lt;h2&gt;
  
  
  Whats the plan
&lt;/h2&gt;

&lt;p&gt;As the title says I have a cashflow of around $150 a month. Obviously this is not even close to enough to cover rent for me. Like I said I have about a year of savings to give myself a real shot at increasing that number. Here are the following things I will be trying to do to get to that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep growing my youtube channel&lt;/strong&gt; - I have a lot of ideas that I think will not only help the channel grow, but help a lot of people learn a lot about programming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Other ways to monetize related to YouTube&lt;/strong&gt; - there are a lot of ways to monetize a channel other then youtube ads. For example sponsored videos, creating articles for the videos, affiliate links, patreon, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creating a Start Up&lt;/strong&gt; - There is an app I have been working on for quite some time now. The goal is to finish it up early 2021 and release it. I am working on with one other person and the feedback we have gotten is very positive thus far.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are a couple other methods I have been thinking about, but those are the main 3 that I believe have the most potential. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I know this is a big risk, but it is also the most excited I have been in my life. By the end of 2021 we will all know whether I have made the best decision of my life, or the biggest mistake of my life. Stay tuned for more on this journey by following along:&lt;/p&gt;

&lt;p&gt;Youtube: &lt;a href="https://www.youtube.com/tadaspetra"&gt;https://www.youtube.com/tadaspetra&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Twitter: &lt;a href="https://twitter.com/tadaspetra"&gt;https://twitter.com/tadaspetra&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Twitch: &lt;a href="https://www.twitch.tv/tadaspetra"&gt;https://www.twitch.tv/tadaspetra&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Insta: &lt;a href="https://www.instagram.com/tadaspetra/"&gt;https://www.instagram.com/tadaspetra/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My Website (work in progress): &lt;a href="https://tadaspetra.com/#/"&gt;https://tadaspetra.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you enjoy my work and would like to support me:&lt;/p&gt;

&lt;p&gt;Patreon: &lt;a href="https://www.patreon.com/tadaspetra"&gt;https://www.patreon.com/tadaspetra&lt;/a&gt;&lt;/p&gt;

</description>
      <category>quitting</category>
      <category>flutter</category>
      <category>youtube</category>
      <category>twitter</category>
    </item>
    <item>
      <title>Flutter Basics | Layouts</title>
      <dc:creator>Tadas Petra</dc:creator>
      <pubDate>Sun, 26 Jul 2020 21:37:08 +0000</pubDate>
      <link>https://dev.to/tadaspetra/flutter-basics-layouts-25fe</link>
      <guid>https://dev.to/tadaspetra/flutter-basics-layouts-25fe</guid>
      <description>&lt;p&gt;One of the reasons flutter is growing so much and becoming one of the biggest development frameworks out there is because of how easy it is to generate beautiful looking layouts. With only a handful of widgets, you can create complete and functional UI for your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://youtu.be/NrXQuAHf1mc"&gt;Video Layout Tutorial&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaffold &amp;amp; AppBar
&lt;/h3&gt;

&lt;p&gt;The scaffold is the very base widget of every screen that you will have on your app. You can think of it as the base building block of your screen. In order to put widgets on the screen there has to be something to put it on. Think of it like a bulletin board. In order to stick things onto it, you need the actual board. That's the Scaffold widgets role. The AppBar is just as it sounds. It is a bar that is at the top of the page usually containing a heading for the page that you are on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container
&lt;/h3&gt;

&lt;p&gt;Containers are the most basic of building blocks that flutter has to offer. If you put a Container around any widget, it will not change how that widget looks at all. However, if you add in some properties that the Container provides, you can change the look of the widget completely. With Containers, you can change padding, add color, add borders, etc…&lt;br&gt;
Center&lt;br&gt;
Center widgets are a bit boring. It is very similar to a container, except with a lot less properties. Its' main function is just to center the content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Row &amp;amp; Column
&lt;/h3&gt;

&lt;p&gt;The Row and Column widgets are what you will most likely use for the core layout of most apps. Most app development frameworks and even any frameworks that work with a UI utilize rows and columns for their layouts in some way. Just like on a graph paper or excel, you can place your widgets in the correct row and column to achieve the look that you want to get. With flutter you are able to nest the rows and columns to have more and more accuracy and control of your layout.&lt;/p&gt;

&lt;h3&gt;
  
  
  ListView
&lt;/h3&gt;

&lt;p&gt;ListView is pretty much a list of Row widgets stacked on top of each other. Except it has two main function that make it insanely useful:&lt;br&gt;
It has scrolling enabled, so you can add as many items to that list and they will just scroll down on your screen. (It also adds padding at the edges of the ListView so it always looks nice)&lt;br&gt;
It only loads the state and all the widgets elements as they become visible while you're scrolling. If you have a ListView of 1,000,000 widgets your app will behave without a single hiccup. It also destroys the states and widget elements as they go out of the view so you can scroll down to the millionth widget without any performance issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stack
&lt;/h3&gt;

&lt;p&gt;Stack widget is just as it sounds. You can stack things on top of one another. Whether it's some text over an image or how instagram creates a little heart over the picture when you like it.&lt;/p&gt;

&lt;p&gt;Those are all the basic and very powerful widgets that flutter provides for us. Make sure to look at all the properties when using them, because there is a lot more things they can do that I haven't covered.&lt;/p&gt;

&lt;p&gt;Thanks for Reading and Stay Creative!&lt;/p&gt;

&lt;p&gt;Follow me on Twitter: &lt;a class="mentioned-user" href="https://dev.to/tadaspetra"&gt;@tadaspetra&lt;/a&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>layouts</category>
      <category>dart</category>
      <category>framework</category>
    </item>
  </channel>
</rss>
