# Quickstart (/en/realtime-media/rtc/get-started-sdk/flutter)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

This page provides a step-by-step guide on how to create a basic real-time app using the Agora RTC SDK. The same steps apply whether you're building voice calling, video calling, interactive live streaming, or broadcast streaming — only a few configuration values change based on your use case.

## Understand the tech

To start a real-time session, implement the following steps in your app:

* **Initialize the engine**: Before calling other APIs, create and initialize an engine instance.

* **Join a channel**: Call methods to create and join a channel.

  * **Join as a host**: A live streaming event has one or more hosts. A host publishes audio and video to the channel and can also subscribe to streams from other hosts.

  * **Join as audience**: Audience members can only subscribe to streams published by hosts.

  <CalloutContainer type="info">
    <CalloutTitle>
      Note
    </CalloutTitle>

    <CalloutDescription>
      For the voice calling and video calling use cases, each user joins as a host.
    </CalloutDescription>
  </CalloutContainer>

* **Send and receive audio and video**: Hosts publish streams to the channel. Audience members subscribe to audio and video streams published by hosts.

* For streaming applications, set the latency level based on your use case:

  * **Interactive live streaming**: Optimized for real-time interaction with ultra-low latency. Use this when hosts and audience need to interact quickly, such as in live Q\&A sessions, interactive classrooms, or auctions.

  * **Broadcast streaming**: Optimized for scalability with slightly higher latency. Use this for large-scale events where one-way broadcasting is sufficient, such as concerts, sports events, or news broadcasts.

![Realtime communication workflow](https://assets-docs.agora.io/images/video-sdk/video-call.svg)

## Prerequisites

* A camera and a microphone.
* A valid Agora account and project. See [Agora account management](/en/realtime-media/rtc/manage-agora-account) for details.
* [Flutter](https://docs.flutter.dev/get-started/install) 2.10.5 or higher with Dart 2.14.0 or higher.
* [Android Studio](https://developer.android.com/studio), IntelliJ, VS Code, or another IDE that supports Flutter. See [Set up an editor](https://docs.flutter.dev/get-started/editor).
* Prepare your development and testing environment according to your [target platform](/en/realtime-media/rtc/reference/supported-platforms).

  Run `flutter doctor` to confirm that your development environment is set up correctly for Flutter development.

## Set up your project

<Tabs defaultValue="new">
  <TabsList>
    <TabsTrigger value="new">
      Create a new project
    </TabsTrigger>

    <TabsTrigger value="existing">
      Add to an existing project
    </TabsTrigger>
  </TabsList>

  <TabsContent value="new">
    From the Terminal, run the following commands to create a new project named `agora_project`, or follow the [steps for your IDE](https://docs.flutter.dev/get-started/test-drive?tab=vscode#choose-your-ide):

    ```bash
    flutter create agora_project
    cd agora_project
    ```
  </TabsContent>

  <TabsContent value="existing">
    To add Realtime Communication to your existing project:

    1. Open your Flutter project and navigate to the `lib` folder.
    2. Add a new file to the `lib` folder and name it `agora_logic.dart`.
  </TabsContent>
</Tabs>

### Install the SDK

Install the Agora RTC SDK and other dependencies.

1. Add the [latest version](https://pub.dev/packages/agora_rtc_engine) of Agora RTC SDK to your Flutter project:

   ```bash
   flutter pub add agora_rtc_engine
   ```

2. Add the permission processing package:

   ```bash
   flutter pub add permission_handler
   ```

   The `dependencies` in your `pubspec.yaml` file should look like the following:

   ```yaml
   dependencies:
     flutter:
       sdk: flutter
     agora_rtc_engine: ^6.5.0  # Agora Flutter SDK, please use the latest version
     permission_handler: ^11.3.1  # Package for managing runtime permissions
     cupertino_icons: ^1.0.8
   ```

3. Install the dependencies.

   Execute the following command in the project path:

   ```bash
   flutter pub get
   ```

## Implement Realtime Communication

This section guides you through the implementation of basic real-time audio and video interaction in your app.

The following figure illustrates the essential steps:

<Accordions>
  <Accordion title="Quick start sequence">
    ![Realtime Communication quick start sequence](https://assets-docs.agora.io/images/video-sdk/quick-start-sequence.svg)
  </Accordion>
</Accordions>

This guide includes [complete sample code](#complete-sample-code) that demonstrates implementing basic real-time interaction. To understand the core API calls in the sample code, review the following implementation steps.

### Import packages

Import the following packages in your dart file.

```dart
import 'dart:async';

import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
```

### Initialize the engine

For real-time communication, initialize an `RtcEngine` instance. Use `RtcEngineContext` to specify the [App ID](/en/realtime-media/rtc/manage-agora-account#get-the-app-id), and other configuration parameters. In your dart file, add the following code:

Unlike other platforms, Flutter sets the channel profile once, when you initialize the engine, instead of at join time. Set it according to your use case:

| Use case                   | Channel profile                  | Client role                                     | Latency level                                   |
| -------------------------- | -------------------------------- | ----------------------------------------------- | ----------------------------------------------- |
| Voice calling              | `channelProfileCommunication`    | `clientRoleBroadcaster`                         | N/A                                             |
| Video calling              | `channelProfileCommunication`    | `clientRoleBroadcaster`                         | N/A                                             |
| Interactive live streaming | `channelProfileLiveBroadcasting` | `clientRoleBroadcaster` or `clientRoleAudience` | `audienceLatencyLevelUltraLowLatency` (default) |
| Broadcast streaming        | `channelProfileLiveBroadcasting` | `clientRoleBroadcaster` or `clientRoleAudience` | `audienceLatencyLevelLowLatency`                |

```dart
// Set up the Agora RTC engine instance
Future<void> _initializeAgoraVoiceSDK() async {
  _engine = createAgoraRtcEngine();
  await _engine.initialize(const RtcEngineContext(
    appId: "<-- Insert app Id -->",
    // Set the channel profile according to your use case (see table above)
    channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
  ));
}
```

<CalloutContainer type="info">
  <CalloutTitle>
    Note
  </CalloutTitle>

  <CalloutDescription>
    * Only broadcasters can publish media. Audience members cannot publish until promoted to broadcaster.
    * Choose Communication mode for calls where every user publishes, typically fewer than 17 participants. Choose Live Broadcasting mode for larger events with separate hosts and audience.
    * The latency level only applies to the audience role, and affects your [pricing](/en/realtime-media/rtc/reference/pricing#subscription-packages) tier.
  </CalloutDescription>
</CalloutContainer>

### Join a channel

To join a channel, call `joinChannel` with the following parameters:

* **Channel name**: The name of the channel to join. Clients that pass the same channel name join the same channel. If a channel with the specified name does not exist, it is created when the first user joins.

* **Authentication token**: A dynamic key that authenticates a user when the client joins a channel. In a production environment, you obtain a token from a [token server](/en/realtime-media/rtc/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/rtc/manage-agora-account#generate-temporary-tokens).

* **User ID**: A 32-bit signed integer that identifies a user in the channel. You can specify a unique user ID for each user yourself. If you set the user ID to `0` when joining a channel, the SDK generates a random number for the user ID and returns the value in the `onJoinChannelSuccess` callback.

* **Channel media options**: Configure `ChannelMediaOptions` to define publishing and subscription settings, optimize performance for your specific use-case, and set optional parameters.

Set the `clientRoleType` and `audienceLatencyLevel` in `ChannelMediaOptions` according to your use case (see table above). The following example shows the configuration for interactive live streaming with the broadcaster role:

```dart
// Join a channel
Future<void> _joinChannel() async {
  await _engine.joinChannel(
    token: token,
    channelId: channel,
    options: const ChannelMediaOptions(
      autoSubscribeVideo: true, // Automatically subscribe to all video streams
      autoSubscribeAudio: true, // Automatically subscribe to all audio streams
      publishCameraTrack: true, // Publish camera-captured video
      publishMicrophoneTrack: true, // Publish microphone-captured audio
      // Use clientRoleBroadcaster to act as a host, or clientRoleAudience for audience
      clientRoleType: ClientRoleType.clientRoleBroadcaster,
      // Only takes effect when clientRoleType is clientRoleAudience
      audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelLowLatency,
    ),
    uid: 0,
  );
}
```

### Subscribe to RTC SDK events

The SDK provides the `RtcEngineEventHandler` for subscribing to channel events. To use it, pass an instance of `RtcEngineEventHandler` to `registerEventHandler` and implement the event methods you want to handle.

Call `registerEventHandler` to bind the event handler to the SDK.

```dart
// Register an event handler for Agora RTC
void _setupEventHandlers() {
  _engine.registerEventHandler(
    RtcEngineEventHandler(
      onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
        debugPrint("Local user ${connection.localUid} joined");
        setState(() => _localUserJoined = true);
      },
      onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
        debugPrint("Remote user $remoteUid joined");
        setState(() => _remoteUid = remoteUid);
      },
      onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
        debugPrint("Remote user $remoteUid left");
        setState(() => _remoteUid = null);
      },
    ),
  );
}
```

When a remote user joins the channel, the `onUserJoined` callback is triggered. Use the remote user's `uid` returned in the callback, to create an `AgoraVideoView` control for displaying the video stream from the remote user.

<CalloutContainer type="info">
  <CalloutTitle>
    Note
  </CalloutTitle>

  <CalloutDescription>
    To ensure that you receive all RTC SDK events, register the event handler before joining a channel.
  </CalloutDescription>
</CalloutContainer>

### Display the local video

To display the local video, enable the video module by calling `enableVideo`, then start the local video preview with `startPreview`.

```dart
Future<void> _setupLocalVideo() async {
  // The video module and preview are disabled by default.
  await _engine.enableVideo();
  await _engine.startPreview();
}
```

To render the local video, add the following widget inside your UI’s widget tree, such as in the build method of your `StatefulWidget`:

```dart
// Displays the local user's video view using the Agora engine.
Widget _localVideo() {
  return AgoraVideoView(
    controller: VideoViewController(
      rtcEngine: _engine, // Uses the Agora engine instance
      canvas: const VideoCanvas(
        uid: 0, // Specifies the local user
        renderMode: RenderModeType.renderModeHidden, // Sets the video rendering mode
      ),
    ),
  );
}
```

### Display remote video

To render a remote video, add the following widget inside your UI’s widget tree, such as in the build method of your `StatefulWidget`:

```dart
// If a remote user has joined, render their video, else display a waiting message
Widget _remoteVideo() {
  if (_remoteUid != null) {
    return AgoraVideoView(
      controller: VideoViewController.remote(
        rtcEngine: _engine, // Uses the Agora engine instance
        canvas: VideoCanvas(uid: _remoteUid), // Binds the remote user's video
        connection: const RtcConnection(channelId: channel), // Specifies the channel
      ),
    );
  } else {
    return const Text(
      'Waiting for remote user to join...',
      textAlign: TextAlign.center,
    );
  }
}
```

### Handle permissions

Request microphone and camera permissions.

```dart
Future<void> _requestPermissions() async {
  await [Permission.microphone, Permission.camera].request();
}
```

<CalloutContainer type="info">
  <CalloutTitle>
    Note
  </CalloutTitle>

  <CalloutDescription>
    If your target platform is iOS or macOS, add the microphone and camera permission declarations required for real-time interaction to [`Info.plist`](https://help.apple.com/xcode/mac/current/#/dev3f399a2a6).

    | **Device** | **Key**                                | **Value**       |
    | :--------- | :------------------------------------- | :-------------- |
    | Microphone | Privacy - Microphone Usage Description | for audio calls |
    | Camera     | Privacy - Camera Usage Description     | for video calls |
  </CalloutDescription>
</CalloutContainer>

### Start and close the app

To start Realtime Communication, request microphone and camera permissions, initialize the Agora SDK instance, set up event handlers, join a channel, and display the local video.

```dart
await _requestPermissions();
await _initializeAgoraVideoSDK();
await _setupLocalVideo();
_setupEventHandlers();
await _joinChannel();
```

To stop Realtime Communication, leave the channel and release the engine instance.

```dart
// Leaves the channel and releases resources
Future<void> _cleanupAgoraEngine() async {
  await _engine.leaveChannel();
  await _engine.release();
}
```

<CalloutContainer type="info">
  <CalloutTitle>
    Note
  </CalloutTitle>

  <CalloutDescription>
    After you call `release`, you no longer have access to the methods and callbacks of the SDK. To use Realtime Communication features again, create a new engine instance.
  </CalloutDescription>
</CalloutContainer>

### Complete sample code

A complete code sample demonstrating the basic process of real-time interaction is provided for your reference. To use the sample code, copy the following code to replace the entire contents of the `.dart` file in your project.

<Accordions>
  <Accordion title="Complete sample code for Realtime Communication">
    ```dart
    import 'dart:async';
    import 'package:agora_rtc_engine/agora_rtc_engine.dart';
    import 'package:flutter/material.dart';
    import 'package:permission_handler/permission_handler.dart';

    void main() => runApp(const MyApp());

    // Fill in the app ID obtained from Agora console
    const appId = "<-- Insert app Id -->";
    // Fill in the temporary token generated from Agora Console
    const token = "<-- Insert token -->";
    // Fill in the channel name you used to generate the token
    const channel = "<-- Insert channel name -->";

    // Main App Widget
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);

      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: _MainScreen(),
        );
      }
    }

    // Video Call Screen Widget
    class _MainScreen extends StatefulWidget {
      const _MainScreen({Key? key}) : super(key: key);

      @override
      _MainScreenScreenState createState() => _MainScreenScreenState();
    }

    class _MainScreenScreenState extends State<_MainScreen> {
      int? _remoteUid; // Stores remote user ID
      bool _localUserJoined = false; // Indicates if local user has joined the channel
      late RtcEngine _engine; // Stores Agora RTC Engine instance

      @override
      void initState() {
        super.initState();
        _startLiveStreaming();
      }

      // Initializes Agora SDK
      Future<void> _startLiveStreaming() async {
        await _requestPermissions();
        await _initializeAgoraVideoSDK();
        await _setupLocalVideo();
        _setupEventHandlers();
        await _joinChannel();
      }

      // Requests microphone and camera permissions
      Future<void> _requestPermissions() async {
        await [Permission.microphone, Permission.camera].request();
      }

      // Set up the Agora RTC engine instance
      Future<void> _initializeAgoraVideoSDK() async {
        _engine = createAgoraRtcEngine();
        await _engine.initialize(const RtcEngineContext(
          appId: appId,
          channelProfile: ChannelProfileType.channelProfileLiveBroadcasting,
        ));
      }

      // Enables and starts local video preview
      Future<void> _setupLocalVideo() async {
        await _engine.enableVideo();
        await _engine.startPreview();
      }

      // Register an event handler for Agora RTC
      void _setupEventHandlers() {
        _engine.registerEventHandler(
          RtcEngineEventHandler(
            onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
              debugPrint("Local user ${connection.localUid} joined");
              setState(() => _localUserJoined = true);
            },
            onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
              debugPrint("Remote user $remoteUid joined");
              setState(() => _remoteUid = remoteUid);
            },
            onUserOffline: (RtcConnection connection, int remoteUid, UserOfflineReasonType reason) {
              debugPrint("Remote user $remoteUid left");
              setState(() => _remoteUid = null);
            },
          ),
        );
      }

      // Join a channel
      Future<void> _joinChannel() async {
        await _engine.joinChannel(
          token: token,
          channelId: channel,
          options: const ChannelMediaOptions(
            autoSubscribeVideo: true,
            autoSubscribeAudio: true,
            publishCameraTrack: true,
            publishMicrophoneTrack: true,
            clientRoleType: ClientRoleType.clientRoleBroadcaster,
            // If setting the client role to audience, uncomment the following line to configure the latency level
            // audienceLatencyLevel: AudienceLatencyLevelType.audienceLatencyLevelUltraLowLatency,      
          ),
          uid: 0,
        );
      }

      @override
      void dispose() {
        _cleanupAgoraEngine();
        super.dispose();
      }

      // Leaves the channel and releases resources
      Future<void> _cleanupAgoraEngine() async {
        await _engine.leaveChannel();
        await _engine.release();
      }

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Agora| **Interactive live streaming** |')),
          body: Stack(
            children: [
              Center(child: _remoteVideo()),
              Align(
                alignment: Alignment.topLeft,
                child: SizedBox(
                  width: 100,
                  height: 150,
                  child: Center(
                    child: _localUserJoined
                        ? _localVideo()
                        : const CircularProgressIndicator(),
                  ),
                ),
              ),
            ],
          ),
        );
      }

        // Displays remote video view
      Widget _localVideo() {
        return AgoraVideoView(
          controller: VideoViewController(
            rtcEngine: _engine,
            canvas: const VideoCanvas(
              uid: 0,
              renderMode: RenderModeType.renderModeHidden,
            ),
          ),
        );
      } 

      // Displays remote video view
      Widget _remoteVideo() {
        if (_remoteUid != null) {
          return AgoraVideoView(
            controller: VideoViewController.remote(
              rtcEngine: _engine,
              canvas: VideoCanvas(uid: _remoteUid),
              connection: const RtcConnection(channelId: channel),
            ),
          );
        } else {
          return const Text(
            'Waiting for remote user to join...',
            textAlign: TextAlign.center,
          );
        }
      }
    }
    ```
  </Accordion>
</Accordions>

<CalloutContainer type="info">
  <CalloutTitle>
    Note
  </CalloutTitle>

  <CalloutDescription>
    In the `appId` and `token` fields, enter the corresponding values you obtained from Agora Console. Use the same `channel` name you filled in when generating the temporary token.
  </CalloutDescription>
</CalloutContainer>

### Create a user interface

To connect the sample code to your existing UI, ensure that your widget tree includes the `_remoteVideo` and  `_localVideo` widgets used to [Display the local video](#display-the-local-video) and [Display remote video](#display-remote-video).

Alternatively, use the following sample code to generate a basic user interface:

### Sample code to create the user interface

```dart
// Build UI to display local video and remote video
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Agora Video Call')),
    body: Stack(
      children: [
        Center(child: _remoteVideo()),
        Align(
          alignment: Alignment.topLeft,
          child: SizedBox(
            width: 100,
            height: 150,
            child: Center(
              child: _localUserJoined
                  ? _localVideo()
                  : const CircularProgressIndicator(),
            ),
          ),
        ),
      ],
    ),
  );
}
```

## Test the sample code

Take the following steps to test the sample code:

1. In `main.dart` update the values for `appId`, and `token` with values from Agora Console. Fill in the same `channel` name you used to generate the token.

2. Connect a target device to your development computer.

3. Open Terminal and execute the following command in the project folder to run the sample project:

   ```bash
   flutter run
   ```

4. Launch the App, grant microphone and camera permissions. If you set the user role to host, you see yourself in the local view.

5. On a second target device, repeat the previous steps to install and launch the client. Alternatively, use the [Web demo](https://webdemo.agora.io/basicVideoCall/index.html) to join the same channel and test the following use-cases:

   * If users on both devices join the channel as hosts, they can see and hear each other.
   * If one user joins as host and the other as audience, the host can see themselves in the local video window; the audience can see the host in the remote video window and hear the host.

   On an Android device, the app UI appears similar to the following:

   ![quickstart-ui-flutter.png](https://assets-docs.agora.io/images/video-sdk/quickstart-ui-flutter.png)

## Reference

This section contains content that completes the information on this page, or points you to documentation that explains other aspects of this product.

* If a firewall is deployed in your network environment, refer to [Connect with Cloud Proxy](/en/realtime-media/rtc/build/manage-connection-and-quality/cloud-proxy) to use Agora services normally.

### Next steps

After implementing the quickstart sample, read the following documents to learn more:

* To ensure communication security in a test or production environment, best practice is to obtain and use a token from an authentication server. For details, see [Secure authentication with tokens](/en/realtime-media/rtc/build/authenticate-users/authentication-workflow).

### Sample project

Agora provides open source sample projects on [GitHub](https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main/example/lib/examples) for your reference. Download or view [join\_channel\_video.dart](https://github.com/AgoraIO-Extensions/Agora-Flutter-SDK/tree/main/example/lib/examples/basic/join_channel_video) for a more detailed example.

### API reference

* [`enableVideo`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_enablevideo)

* [`registerEventHandler`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_addhandler)

* [`setClientRole`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_setclientrole2)

* [`setChannelProfile`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_setchannelprofile)

* [`startPreview`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_startpreview2)

* [`AgoraVideoView`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_agoravideoview.html)

* [`joinChannel`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_joinchannel2)

* [`leaveChannel`](https://api-ref.agora.io/en/video-sdk/flutter/6.x/API/class_irtcengine.html#api_irtcengine_leavechannel2)

### Frequently asked questions

* [How can I fix black screen issues?](/en/api-reference/faq/quality/video_blank#black-screen-on-the-local-side)

* [Why can't I turn on the camera?](/en/api-reference/faq/quality/video_camera)

* [How can I listen for audience joining or leaving a channel?](/en/api-reference/faq/integration/audience_event)

* [How can I solve channel-related issues?](/en/api-reference/faq/integration/channel)

* [How can I set the log file?](/en/api-reference/faq/integration/set_log_file)

### See also

* [SDK error codes](/en/realtime-media/rtc/reference/error-codes)
* [Connection status management](/en/realtime-media/rtc/build/manage-connection-and-quality/connection-status-management)

    
  
      
  
      
  
      
  
      
  
      
  
