# Quickstart (/en/realtime-media/video/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 Video Calling app using the Agora Video SDK.

    ## Understand the tech [#understand-the-tech-6]

    To start a Video Calling 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.

    * **Send and receive audio and video**: All users can publish streams to the channel and subscribe to audio and video streams published by other users in the channel.

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

    ## Prerequisites [#prerequisites-6]

    * A camera and a microphone.
    * A valid Agora account and project. See [Agora account management](/en/realtime-media/video/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/video/reference/supported-platforms).

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

    ## Set up your project [#set-up-your-project-6]

    <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 Video Calling 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-sdk-6]

    Install the Agora Video SDK and other dependencies.

    1. Add the [latest version](https://pub.dev/packages/agora_rtc_engine) of Agora Video 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 Video Calling [#implement-video-calling-6]

    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">
        ![Video Calling 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-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 [#initialize-the-engine-5]

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

    ```dart
    // Set up the Agora RTC engine instance
    Future<void> _initializeAgoraVoiceSDK() async {
      _engine = createAgoraRtcEngine();
      await _engine.initialize(const RtcEngineContext(
        appId: "<-- Insert app Id -->",
        channelProfile: ChannelProfileType.channelProfileCommunication,
      ));
    }
    ```

    ### Join a channel [#join-a-channel-6]

    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/video/build/authenticate-users/deploy-token-server) in your security infrastructure. For the purpose of this guide [Generate a temporary token](/en/realtime-media/video/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.

    For Video Calling, set the `clientRoleType` to `clientRoleBroadcaster`.

    ```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,
        ),
        uid: 0,
      );
    }
    ```

    ### Subscribe to Video SDK events [#subscribe-to-video-sdk-events-4]

    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">
      <CalloutDescription>
        To ensure that you receive all Video SDK events, register the event handler before joining a channel.
      </CalloutDescription>
    </CalloutContainer>

    ### Display the local video [#display-the-local-video-6]

    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 [#display-remote-video-6]

    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 [#handle-permissions-4]

    Request microphone and camera permissions for Video Calling.

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

    <CalloutContainer type="info">
      <CalloutTitle>
        Information
      </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 [#start-and-close-the-app-4]

    To start Video Calling, 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 Video Calling, 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>
        Warning
      </CalloutTitle>

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

    ### Complete sample code [#complete-sample-code-6]

    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 real-time Video Calling">
        ```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();
            _startVideoCalling();
          }

          // Initializes Agora SDK
          Future<void> _startVideoCalling() 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.channelProfileCommunication,
            ));
          }

          // 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,
              ),
              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 Video Calling')),
              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>
        Information
      </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 [#create-a-user-interface-6]

    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 [#sample-code-to-create-the-user-interface-5]

    ```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 [#test-the-sample-code-6]

    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 [#reference-6]

    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/video/build/manage-connection-and-quality/cloud-proxy) to use Agora services normally.

    ### Next steps [#next-steps-6]

    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/video/build/authenticate-users/authentication-workflow).

    ### Sample project [#sample-project-6]

    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 [#api-reference-6]

    * [`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 [#frequently-asked-questions-6]

    * [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 [#see-also-6]

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

    
  
      
  
      
  
      
  
      
  
      
  
