avatarAseem Wangoo

Summary

This context provides a tutorial on how to create a real-time streaming mechanism using Flutter, Web Sockets, and AWS.

Abstract

The context begins by explaining the drawbacks of polling, a synchronous method for real-time applications, and introduces Web Sockets as a solution. It explains the WebSocket Protocol and how it allows for the creation of full-duplex, bidirectional connections between a client and a server over the web. The tutorial then provides a step-by-step guide on how to implement Web Sockets in Flutter using the web_socket_channel package and AWS. It covers creating a WebSocketChannel, listening to messages from the server using StreamBuilder, sending data to the server, and closing the socket connection. The tutorial also covers creating a serverless function using AWS and deploying it to create a WebSocket API.

Bullet points

  • Drawbacks of polling for real-time applications
  • Introduction to Web Sockets as a solution
  • Explanation of the WebSocket Protocol
  • Step-by-step guide on implementing Web Sockets in Flutter using web_socket_channel package and AWS
  • Creating a WebSocketChannel
  • Listening to messages from the server using StreamBuilder
  • Sending data to the server
  • Closing the socket connection
  • Creating a serverless function using AWS
  • Deploying the serverless function to create a WebSocket API.

Flutter, Web Sockets and AWS

Flutter, Web Sockets and AWS

How can we create our own streaming mechanism? Hmmm…

All in one Flutter resource: https://flatteredwithflutter.com/flutter-web-sockets-and-aws/

History…..

Prior to Web Sockets, we had…polling

Polling: a synchronous method wherein the client makes a request to the server to see if there is any information available. The client receives a response from the server even if there is no information available.

Drawbacks :

In most real-time applications, message frequency is often unpredictable. In addition, polling requires the client to open and close many unnecessary connections.

Web Sockets….

The WebSocket Protocol is a widely supported open standard for developing real-time applications.

It allows the creation of full-duplex, bidirectional connections between a client and a server over the web.

Difference: Web Socket URL contains a special field called Upgrade…

A typical request that includes Upgrade would look something like:

GET /index.html HTTP/1.1
Host: www.example.com
Connection: upgrade
Upgrade: example/1, foo/2

For detailed info, visit this link

Web Socket URL’s start with ws:// or wss://

ws://destination.server.ext

where wss is for secure WebSocket connection.

Flutter, Web Sockets and AWS

Bring Flutter…..

  1. You need to install the web_socket_channel package.

2. Create a WebSocketChannel

final channel = IOWebSocketChannel.connect('ws://echo.websocket.org');

3. Listen to the messages from server using Streambuilder…

StreamBuilder(
  stream: channel.stream,
  builder: (context, snapshot) {
    return Text(snapshot.hasData ? '${snapshot.data}' : '');
  },
);

4. Send data to server using,

channel.sink.add('Hello!');

5. Close the socket connection using,

channel.sink.close();

Sample example here,….

import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/io.dart';
import 'package:flutter/material.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'WebSocket Demo';
    return MaterialApp(
      title: title,
      home: MyHomePage(
        title: title,
        channel: IOWebSocketChannel.connect('ws://echo.websocket.org'),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;
  final WebSocketChannel channel;

  MyHomePage({Key key, @required this.title, @required this.channel})
      : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Form(
              child: TextFormField(
                controller: _controller,
                decoration: InputDecoration(labelText: 'Send a message'),
              ),
            ),
            StreamBuilder(
              stream: widget.channel.stream,
              builder: (context, snapshot) {
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 24.0),
                  child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
                );
              },
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _sendMessage,
        tooltip: 'Send message',
        child: Icon(Icons.send),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  void _sendMessage() {
    if (_controller.text.isNotEmpty) {
      widget.channel.sink.add(_controller.text);
    }
  }

  @override
  void dispose() {
    widget.channel.sink.close();
    super.dispose();
  }
}

Bring AWS…

In the above section, we saw this line,

final channel = IOWebSocketChannel.connect('ws://echo.websocket.org');

instead of ws://echo.websocket.org, we will create our own web socket url using AWS…..

Flutter, Web Sockets and AWS

Begin…

For creating a web socket api, we need to create a serverless function….

  1. Install serverless : This is a nodeJS package which helps us to write Lambdas and deploy them onto AWS
npm install -g serverless

2. Set up your AWS credentials…..(Follow this video)

serverless config credentials --provider aws --key 'YOUR AWS KEY' --secret 'YOUR AWS SECRET'

You should get output as

Serverless: Setting up AWS...
Serverless: Saving your AWS profile in "~/.aws/credentials"...
Serverless: Success! Your AWS access keys were stored under the "default" profile.

3. Create a project (serverless)

# Create a new Serverless Service/Project
serverless create --template aws-nodejs

handler.js -> Where you write your logic...

serverless.yml -> Where you describe your deployment...

In the serverless.yml file,

provider:
  name: aws
  runtime: nodejs10.x
  logs:
    websocket: true
iamRoleStatements:
    - Effect: Allow
      Action:
        - "execute-api:ManageConnections"
      Resource:
        - "arn:aws:execute-api:*:*:**/@connections/*"
functions:
  connectionHandler:
    handler: handler.connectionHandler
    events:
      - websocket:
          route: $connect
      - websocket:
          route: $disconnect
  defaultHandler:
    handler: handler.defaultHandler
    events:
      - websocket:
          route: $default
  sendMessageHandler:
    handler: handler.sendMessageHandler
    events:
      - websocket:
          route: sendMessage

This configuration is based according to the AWS Api Gateway, which allows to create web socket apis…

As per AWS Api Gateway, the request should be in the payload format :

{
  "action":"sendMessage",
  "data":"YOUR DATA"
}

Here the action, sendMessage corresponds to the serverless.yml file’s sendMessage

sendMessageHandler:
    handler: handler.sendMessageHandler
    events:
      - websocket:
          route: sendMessage

There are 3 handlers here,

  1. connectionHandler : which accepts the connection to the web socket api
  2. defaultHandler : which sends the response if the request is not as per the web socket
  3. sendMessageHandler : sends the response in case of successful request to the web socket…

Lets program these handlers…..

Handlers….

Before starting, you need to install , aws-sdk

//FIRST
const AWS = require(‘aws-sdk’);
//SECOND
require('aws-sdk/clients/apigatewaymanagementapi');
  1. connectionHandler :
module.exports.connectionHandler = async (event, context) => { 
  
if (event.requestContext.eventType === 'CONNECT') {
    //IN CASE YOU WANT TO DO SOMETHING
    return success;
} 
else if (event.requestContext.eventType === 'DISCONNECT') {
    //IN CASE YOU WANT TO DO SOMETHING
    return success;
}
};

This listens to the incoming connections only of type ‘CONNECT’ and ‘DISCONNECT’….

Success is nothing, but another object

const success = {
  statusCode: 200
};

Testing now…..

For deploying

serverless deploy

This gives you the web socket url……

Now, in order to test this url, you need to install wscat.

wscat -c wss://<generated>.execute-api.<region>.amazonaws.com/dev/

If all is well, you should see,

connection Handler and AWS…

2. defaultHandler :

module.exports.defaultHandler = async (event, context) => {
let connectionId = event.requestContext.connectionId;
const endpoint = event.requestContext.domainName + "/" + event.requestContext.stage;
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
    apiVersion: "2018-11-29",
    endpoint: endpoint
});
const params = {
    ConnectionId: connectionId,
    Data: 'Seems like wrong endpoint'
};
return apigwManagementApi.postToConnection(params).promise();
};

This connects via AWS ApiGatewayManagementApi and sends back the response (Seems like wrong endpoint) if the request payload is not valid…….

Note : the response can have only ConnectonId and Data at this moment…

Testing now…..

For deploying

serverless deploy

This gives you the web socket url……

Assuming, in the prev step, wscat is installed….

wscat -c wss://<generated>.execute-api.<region>.amazonaws.com/dev/

If all is well, you should see,

default Handler and AWS…..

3. sendMessageHandler :

module.exports.sendMessageHandler = async (event, context) => {
const body = JSON.parse(event.body);
const message = body.message;
const name = body.name;
let connectionId = event.requestContext.connectionId;
const endpoint = event.requestContext.domainName + "/" + event.requestContext.stage;
const apigwManagementApi = new AWS.ApiGatewayManagementApi({
    apiVersion: "2018-11-29",
    endpoint: endpoint
  });
const params = {
    ConnectionId: connectionId,
    Data: JSON.stringify({ message: message, name: name}),
  };
  return apigwManagementApi.postToConnection(params).promise();
};

We have included the fields ‘message’ and ‘name’ in the payload…

The endpoint responds back extracting the message and name fields and stringifying them…

Testing now…..

For deploying

serverless deploy

This gives you the web socket url……

Assuming, in the prev step, wscat is installed….

wscat -c wss://<generated>.execute-api.<region>.amazonaws.com/dev/

If all is well, you should see,

send Message Handler and AWS….

Flutter and AWS…

Flutter, Web Sockets and AWS

Finally, in the flutter app

  1. We have connected to the generated url from above as
final awsChannel = IOWebSocketChannel.connect(
    'wss://<generated>.execute-api.<region>.amazonaws.com/dev/',
);

2. On the click of Send Wrong Input, we send

awsChannel.sink.add('bd!');

3. On the click of Send Correct Input, we send

awsChannel.sink.add('
 {
 "action":"sendMessage",
 "message":"Hello AWS Web Socket",
 "name":"Aseem"
 }'
);

Articles related to Flutter:

AWS
Flutter
Dart
Mobile App Development
Programming
Recommended from ReadMedium