Build a Real-Time Meeting App for Android with Jetpack Compose

8 min read

Real-time video communication is a crucial feature across various industries in our daily lives, and its incorporation into applications has become integral. This tutorial guides constructing an Android Video Calling application using Stream’s Video SDK for Jetpack Compose.

Jaewoong E.
Jaewoong E.
Published August 31, 2023

Real-time video communication has emerged as an indispensable feature in contemporary applications, finding utility across many sectors, including dating, social platforms, education, gaming, and diverse communities. This functionality enables seamless real-time interaction through the simultaneous exchange of voice and video, enhancing user experiences beyond mere text communication.

Yet, creating an in-house video-calling application demands a considerable allocation of resources. The endeavor involves designing and constructing a robust WebRTC protocol and relevant server architecture to establish seamless peer-to-peer connections. This entails compiling pre-built native libraries for each platform and developing dedicated WebRTC clients for iOS, Android, and the web.

Additionally, the process requires meticulous consideration of codecs, resolutions, edge networking, and various technical intricacies to ensure optimized user experiences, minimizing delays in transmitting media streams. So opting for a pre-existing solution can be an astute technical decision, notably when your team needs more resources and expertise to construct real-time video protocols from scratch.

The Stream Video SDK presents various pre-built video and audio communication solutions to effectively address these challenges. Leveraging Stream's extensive global edge network infrastructure guarantees a more rapid and dependable experience for video calls and live streams.

This tutorial provides an extensive guide to seamlessly create a real-time meeting room application for Android, utilizing Stream Video SDK in conjunction with the Meeting Room Compose GitHub project.

Configure Gradle Dependencies

To get started, you need to add the Stream Video Compose SDK and Jetpack Compose dependencies in your app’s build.gradle.kts file found in app/build.gradle.kts.

groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
dependencies { // Stream Video Compose SDK implementation("io.getstream:stream-video-android-ui-compose:0.5.1") // Optionally add Jetpack Compose if Android studio didn't automatically include them implementation(platform("androidx.compose:compose-bom:2023.10.01")) implementation("androidx.activity:activity-compose:1.7.2") implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-tooling") implementation("androidx.compose.runtime:runtime") implementation("androidx.compose.foundation:foundation") implementation("androidx.compose.material:material") }

The Android Video SDK provides two essential dependencies:

  • Video Core SDK: This handles communication with the Stream backend server, establishes WebRTC connections, manages socket reconnections, and encompasses all the necessary functionalities for constructing video solutions.
  • Video Compose SDK: This enables the rendering of audio and video media streams by integrating Compose UI components with the Video Core SDK.

This tutorial will leverage the Video Compose SDK to expedite the creation of a real-time meeting room application.

Get Your Stream API Key

Next, you must acquire an API key from your Stream Dashboard. You can bypass this section if you already possess a Stream account. However, if you're new to Stream, follow the steps outlined below:

  1. Go to the Stream login page.
  2. If you have a GitHub account, simply click the "Continue with GitHub" button, and you'll be able to complete the registration in just a matter of seconds. This process takes around 30 seconds and doesn't require completing any forms.
  1. Go to the Dashboard and click the Create App button like the below.
  2. Fill in the blanks like the below and click the Create App button.
  1. You will find the Key displayed as shown in the image below; make sure to note it for future reference.

Your project setup is completed! Now, let’s implement joining a meeting room with Jetpack Compose.

Create And Join a Meeting Room

Now, you’re ready to create and join a meeting room. Open up MainActivity.kt and replace the MainActivity class with:

kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val apiKey = "REPLACE_WITH_API_KEY" val userId = "REPLACE_WITH_USER_ID" val userToken = "REPLACE_WITH_TOKEN" val callId = "REPLACE_WITH_CALL_ID" // step1 - initialize StreamVideo. For a production app we recommend adding the client to your Application class or di module. val client = StreamVideoBuilder( context = applicationContext, apiKey = apiKey, user = User( id = userId, name = "Tutorial", role = "admin", ), token = userToken, ).build() // step2 - join a call, which type is `default` and id is `123`. val call = client.call(type = "default", id = callId) lifecycleScope.launch { call.join(create = true) } setContent { // step3 - apply VideTheme VideoTheme { // step4 - render videos CallContent( modifier = Modifier.fillMaxSize(), call = call, onBackPressed = { finish() }, onCallAction = { callAction -> when (callAction) { is FlipCamera -> call.camera.flip() is ToggleCamera -> call.camera.setEnabled(callAction.isEnabled) is ToggleMicrophone -> call.microphone.setEnabled(callAction.isEnabled) is LeaveCall -> finish() else -> Unit } }, ) } } } }

Now, let's break down the code step by step.

Initialize Stream Video SDK

Firstly, you need to initialize the Video SDK to access the Video call APIs:

kt
1
2
3
4
5
6
7
8
9
10
val client = StreamVideoBuilder( context = applicationContext, apiKey = apiKey, user = User( id = userId, name = "Tutorial", role = "admin", ), token = userToken, ).build()

In this sample code, we've grouped everything together in the MainActivity.kt for the sake of clarity. However, in a real-world scenario, it's recommended to initialize the Video SDK in your Application class or a dedicated dependency injection (DI) module.

We recommend exploring the Client & Authentication documentation for a deeper dive into the Stream Video client and authentication. This resource will provide you with comprehensive insights and guidance.

Create And Join A Meeting Room

Your next step is to create and join a new call. Stream SDK offers incredibly straightforward APIs that allow you to accomplish this in just a few lines of code, as shown below:

kt
1
2
3
4
val call = client.call(type = "default", id = callId) lifecycleScope.launch { call.join(create = true) }

You can get the Call instance using the client.call() method, which requires a call type and call id. The call type controls which features are enabled and set up permissions. In this tutorial, you’ll use the default call type.

Once you have obtained the call instance, you can initiate participation in the meeting room using the join method. This function facilitates your connection to the Stream server, enabling the exchange of media streams. By invoking this method, your application will establish communication with the Stream server, facilitating the emission and reception of media streams.

We recommend exploring the Joining & Creating Calls documentation for a deeper understanding of creating and joining calls. This resource will provide you with comprehensive insights into the process.

Rendering Video Streams With Jetpack Compose

Moving forward, you will focus on rendering the video calls within your application's user interface. The Stream SDK simplifies this task by providing ready-to-use UI components tailored for video calls. These components include participant grids, participant information displays, camera and microphone controls, Android runtime permissions management, picture-in-picture support, and more.

To streamline the implementation of your meeting room screen, you can use the Compose components, specifically VideoTheme and CallContent. This will enable you to integrate the necessary UI elements effortlessly.

kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
setContent { VideoTheme { CallContent( modifier = Modifier.fillMaxSize(), call = call, onBackPressed = { finish() }, onCallAction = { callAction -> when (callAction) { is FlipCamera -> call.camera.flip() is ToggleCamera -> call.camera.setEnabled(callAction.isEnabled) is ToggleMicrophone -> call.microphone.setEnabled(callAction.isEnabled) is LeaveCall -> finish() else -> Unit } }, ) } }

The CallContent incorporates three distinct sections of UI elements, as depicted in the image below:

Building your own app? Get early access to our Livestream or Video Calling API and launch in days!
  • AppBar: Positioned at the top of the screen, the app bar provides essential information. This includes participant count, back button, and status notifications relevant to your call state, such as screen sharing or recording.
  • Video: As the central component within CallContent, the video section renders participant videos in a grid format, effectively displaying all video interactions, such as the information of each participant, reactions, network connectivity levels, speaking levels, and more.
  • Controls: The control panel lets you manage tangible settings within the meeting room. This includes toggling the camera and microphone, flipping the camera view, and exiting the meeting room.

We recommend exploring the CallContent and UI Components Overview documentation for a deeper understanding of Compose Video components. This resource will provide comprehensive insights into the overall Compose Video APIs.

Running The Project

Lastly, let’s run the project. But before running the project, you may have already noticed that you need particular prerequisites to run the provided sample code above, such as an API key and a valid user token. While user tokens are typically generated by your server-side API, for this sample project, you can conveniently use our demo credentials to facilitate testing.

By visiting the Testing Credentials page, you will find a set of demo tokens that you can use to replace REPLACE_WITH_USER_ID, REPLACE_WITH_TOKEN, and REPLACE_WITH_CALL_ID with the actual values provided below:

Once you've run your project, you can test multiple participants by joining a meeting room using our web application. The screenshots below showcase a meeting room with multiple participants involving two web browsers and an Android device:

Note: If you can’t view other participants across web and mobile platforms, make sure that you use the same call ID.

In this section, you've learned about various API usages, including joining and rendering video streams using Stream Video SDK. Now, let's take a step further and explore advanced UI implementation to create a comprehensive meeting application akin to platforms like Google Meet or Zoom.

Build a Lobby Screen

Next, let's create a lobby screen that allows you to preview the video and manage the camera and microphone settings before entering a meeting room. This pivotal feature is crucial for a meeting application, enabling you to fine-tune your setup before joining a meeting.

The Stream Video Compose SDK provides a dedicated UI component that simplifies the implementation of the lobby screen. This component seamlessly integrates video preview functionality, camera and microphone control, and the handling of Android runtime permissions.

To incorporate the lobby screen, you can leverage the CallLobby Compose API, as demonstrated in the example below:

kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val isCameraEnabled by call.camera.isEnabled.collectAsState() val isMicrophoneEnabled by call.microphone.isEnabled.collectAsState() CallLobby( call = call, modifier = Modifier.fillMaxWidth(), isCameraEnabled = isCameraEnabled, isMicrophoneEnabled = isMicrophoneEnabled, onCallAction = { action -> when (action) { is ToggleCamera -> call.camera.setEnabled(!action.isEnabled) is ToggleMicrophone -> call.microphone.setEnabled(!action.isEnabled) else -> Unit } }, )

If you build the code above, you’ll see the result below:

The CallLobby component allows you to customize participant labels, enable the camera and microphone, manage Android runtime permissions, implement your own video fallback, and more. For further insights into the CallLobby Compose API, refer to the official documentation for Call Lobby.

You’ve implemented the lobby screen with Jetpack Compose so that you can navigate from the lobby screen to the meeting room screen by using Compose Navigation. You’ll not cover the details of Compose Navigation in this tutorial so that you can complete the navigation according to your preferences.

Build Your Custom Meeting Room Screen

While you employ the CallContent Composable to construct the meeting room screen in the preceding section, it's important to note that the CallContent Composable is the highest level of the Stream UI components. The Stream SDK also provides a set of lower-level APIs to construct higher-level APIs like the CallContent, such as ParticipantVideo, ParticipantsGrid, and FloatingParticipantVideo.

Nonetheless, the CallContent Composable offers a seamless way to customize each component with minimal effort. For example, if you look at the Google Meet application, the meeting room UI elements are slightly different from the CallContent Composable, as shown in the screenshot below.

This tutorial walks you through creating a stylized meeting room application, similar to Google Meet, by tailoring the CallContent Composable to your preferences.

Customize The ParticipantsGrid And VideoRenderer

To begin with, let's apply styling to the participant grid, aligning it with the aesthetics of Google Meet. As evident in the image above, each participant's video renderer features rounded corners and specific padding between renderers.

For CallContent, you have the flexibility to personalize the grid's style and configure each video renderer, as illustrated in the example below:

kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CallContent( call = call, videoContent = { ParticipantsGrid( call = call, modifier = Modifier .fillMaxSize() .weight(1f) .padding(6.dp), style = RegularVideoRendererStyle(), videoRenderer = { modifier, _, participant, style -> ParticipantVideo( modifier = modifier .padding(4.dp) .clip(RoundedCornerShape(8.dp)), call = call, participant = participant, style = style, ) }, ) } }

As demonstrated in the provided example, you have the freedom to tailor the participant's grid by incorporating your own videoContent composable parameter. Essentially, CallContent is constructed using ParticipantsGrid, which, in turn, relies on ParticipantVideo components. The Stream's Compose UI components empower you to fully customize these elements, enabling you to implement your own designs and functionalities.

To delve deeper into video renderer customization, you can refer to the comprehensive guide provided in the UI Cookbook: Video Renderer.

Customize The Control Action Buttons

Lastly, it's time to customize the control action buttons, allowing you to configure your meeting room's settings. This includes toggling the camera and microphone, flipping the camera, and leaving the call.

kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
val isCameraEnabled by call.camera.isEnabled.collectAsState() val isMicrophoneEnabled by call.microphone.isEnabled.collectAsState() CallContent( modifier = Modifier.background(color = VideoTheme.colors.appBackground), call = callViewModel.call, controlsContent = { ControlActions( call = call, actions = listOf( { ToggleMicrophoneAction( modifier = Modifier.size( VideoTheme.dimens.controlActionsButtonSize, ), isMicrophoneEnabled = isMicrophoneEnabled, onCallAction = { call.microphone.setEnabled(it.isEnabled) }, ) }, { ToggleCameraAction( modifier = Modifier.size( VideoTheme.dimens.controlActionsButtonSize, ), isCameraEnabled = isCameraEnabled, onCallAction = { call.camera.setEnabled(it.isEnabled) }, ) }, { FlipCameraAction( modifier = Modifier.size( VideoTheme.dimens.controlActionsButtonSize, ), onCallAction = { call.camera.flip() }, ) }, { CancelCallAction( modifier = Modifier.size( VideoTheme.dimens.controlActionsButtonSize, ), onCallAction = { call.leave() finish() }, ) }, ), ) }, )

The CallContent composable provides a straightforward way to customize the control action buttons. You can achieve this by incorporating your own list of composable buttons. As demonstrated in the example above, you have the flexibility to create and arrange each control action button according to your preferences.

Once the project is built, you'll observe the stylish outcome below, reminiscent of the Google Meet aesthetics:

Conclusion

This tutorial wraps up the process of creating a real-time meeting room using Jetpack Compose and Stream Video SDK, with a design reminiscent of Google Meet. The complete source code is available on GitHub.

You can find the author of this article on GitHub or Twitter @github_skydoves if you have any questions or feedback. If you’d like to stay up to date with Stream, follow us on Twitter @getstream_io for more great technical content.

As always, happy coding!

— Jaewoong

Integrating Video With Your App?
We've built a Video and Audio solution just for you. Check out our APIs and SDKs.
Learn more ->