Welcome to Our Website

WebSocket with Riverpod in Flutter

Websssssssssssssssssssssssocket with riverpod in flutter

Nowadays, many applications need real-time data to provide instant feedback to users, be it a chat application that shows a person typing in real-time or a remote application that plots data directly from a hardware sensor.

We try to solve these problems with REST but face a complex problem: to have near-instant feedback, we must ping the server several times per minute, which can be architecturally difficult to achieve and it overloads the server.

What are WebSockets?

WebSockets represent a long-awaited evolution in the client/server web technology. It defines a fully duplex bi-directional communication channel between the client and server.

In simple words, once after the initial handshake where the server and the client agree to upgrade to WebSockets, (from HTTP) the client and the server can talk in real time without having to continuously make requests (like loading the page again and again).

What is Riverpod?

Riverpod is State-management library that is rewrite of Provider that catches programming errors at compile time rather than at runtime, removes nesting for listening/combining objects, ensures that the code is testable.

Check out Riverpod State management in Flutter to get idea about Riverpod.

To get more detail see its documentation: https://riverpod.dev.

Trading Price Tracker App

You can get Source code of following demo from Github.

To use functionality of Websocket and Riverpod we will create a Live Trading Price Tracker Demo from API of https://developers.binary.com/demos. Here you will find all API related to Trading.

We will create a Demo of where thew will be one Dropdown where there is list of display different markets available in Trading, A Second Dropdown where based on the selection of first dropdown, displays the available symbols available for the selected market. And Text shows the current price. The default color is grey, changes red if the price goes down, green when it goes up. Goes back to grey if the price is unchanged.

For above Demo we will utilise following API’s:

After completing app will have UI some thing like this:

Let’s Get Started

App Structure

Create new Flutter project in you IDE. The structure of App will be as following.

Add Dependency

First we will add WebSocket and Riverpod Dependency into our project through pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  web_socket_channel: latest_version
  flutter_riverpod: latest_version

Add Model Class

Now we will add Model class to handle Websocket response. Create new folder with name model, Inside it model class for Available market by naming model_active_symbol.dart

class ModelActiveSymbol {
  List<activesymbols>? activeSymbols;
  EchoReq? echoReq;
  String? msgType;



  ModelActiveSymbol({activeSymbols, echoReq, msgType});

  ModelActiveSymbol.fromJson(Map<string, dynamic=""> json) {
    if (json['active_symbols'] != null) {
      activeSymbols = <activesymbols>[];
      json['active_symbols'].forEach((v) {
        activeSymbols!.add(ActiveSymbols.fromJson(v));
      });
    }
    echoReq = json['echo_req'] != null
        ?  EchoReq.fromJson(json['echo_req'])
        : null;
    msgType = json['msg_type'];
  }

  Map<string, dynamic=""> toJson() {
    final Map<string, dynamic=""> data =  <string, dynamic="">{};
    if (activeSymbols != null) {
      data['active_symbols'] =
          activeSymbols!.map((v) =&gt; v.toJson()).toList();
    }
    if (echoReq != null) {
      data['echo_req'] = echoReq!.toJson();
    }
    data['msg_type'] = msgType;
    return data;
  }

}

class ActiveSymbols{
  int? allowForwardStarting;
  String? displayName;
  int? exchangeIsOpen;
  int? isTradingSuspended;
  String? market;
  String? marketDisplayName;
  double? pip;
  String? submarket;
  String? submarketDisplayName;
  String? symbol;
  String? symbolType;

  ActiveSymbols(
      {allowForwardStarting,
        displayName,
        exchangeIsOpen,
        isTradingSuspended,
        market,
        marketDisplayName,
        pip,
        submarket,
        submarketDisplayName,
        symbol,
        symbolType});

  ActiveSymbols.fromJson(Map<string, dynamic=""> json) {
    allowForwardStarting = json['allow_forward_starting'];
    displayName = json['display_name'];
    exchangeIsOpen = json['exchange_is_open'];
    isTradingSuspended = json['is_trading_suspended'];
    market = json['market'];
    marketDisplayName = json['market_display_name'];
    pip = json['pip'];
    submarket = json['submarket'];
    submarketDisplayName = json['submarket_display_name'];
    symbol = json['symbol'];
    symbolType = json['symbol_type'];
  }

  Map<string, dynamic=""> toJson() {
    final Map<string, dynamic=""> data =  <string, dynamic="">{};
    data['allow_forward_starting'] = allowForwardStarting;
    data['display_name'] = displayName;
    data['exchange_is_open'] = exchangeIsOpen;
    data['is_trading_suspended'] = isTradingSuspended;
    data['market'] = market;
    data['market_display_name'] = marketDisplayName;
    data['pip'] = pip;
    data['submarket'] = submarket;
    data['submarket_display_name'] = submarketDisplayName;
    data['symbol'] = symbol;
    data['symbol_type'] = symbolType;
    return data;
  }

}

class EchoReq {
  String? activeSymbols;
  String? productType;

  EchoReq({activeSymbols, productType});

  EchoReq.fromJson(Map<string, dynamic=""> json) {
    activeSymbols = json['active_symbols'];
    productType = json['product_type'];
  }

  Map<string, dynamic=""> toJson() {
    final Map<string, dynamic=""> data =  <string, dynamic="">{};
    data['active_symbols'] = activeSymbols;
    data['product_type'] = productType;
    return data;
  }
}

Add one more model class name model_tick.dart

class ModelTick {
  EchoReq? echoReq;
  String? msgType;
  Subscription? subscription;
  Tick? tick;

  ModelTick({echoReq, msgType, subscription, tick});

  ModelTick.fromJson(Map<String, dynamic> json) {
    echoReq = json['echo_req'] != null
        ?  EchoReq.fromJson(json['echo_req'])
        : null;
    msgType = json['msg_type'];
    subscription = json['subscription'] != null
        ?  Subscription.fromJson(json['subscription'])
        : null;
    tick = json['tick'] != null ?  Tick.fromJson(json['tick']) : null;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data =  <String, dynamic>{};
    if (echoReq != null) {
      data['echo_req'] = echoReq!.toJson();
    }
    data['msg_type'] = msgType;
    if (subscription != null) {
      data['subscription'] = subscription!.toJson();
    }
    if (tick != null) {
      data['tick'] = tick!.toJson();
    }
    return data;
  }
}

class EchoReq {
  int? subscribe;
  String? ticks;

  EchoReq({subscribe, ticks});

  EchoReq.fromJson(Map<String, dynamic> json) {
    subscribe = json['subscribe'];
    ticks = json['ticks'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data =  <String, dynamic>{};
    data['subscribe'] = subscribe;
    data['ticks'] = ticks;
    return data;
  }
}

class Subscription {
  String? id;

  Subscription({id});

  Subscription.fromJson(Map<String, dynamic> json) {
    id = json['id'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data =  <String, dynamic>{};
    data['id'] = id;
    return data;
  }
}

class Tick {
  double? ask;
  double? bid;
  int? epoch;
  String? id;
  int? pipSize;
  double? quote;
  String? symbol;

  Tick(
      {ask,
        bid,
        epoch,
        id,
        pipSize,
        quote,
        symbol});

  Tick.fromJson(Map<String, dynamic> json) {
    try{
    ask =  json['ask'];
    }catch(e){
      int tempAsk = json['ask'];
      ask = tempAsk.toDouble();
    }
    bid = json['bid'].toDouble();
    epoch = json['epoch'];
    id = json['id'];
    pipSize = json['pip_size'];
    quote = getDouble(json['quote']);
    symbol = json['symbol'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data =  <String, dynamic>{};
    data['ask'] = ask;
    data['bid'] = bid;
    data['epoch'] = epoch;
    data['id'] = id;
    data['pip_size'] = pipSize;
    data['quote'] = quote;
    data['symbol'] = symbol;
    return data;
  }

  double getDouble(dynamic data){
    try{
      double value =  data;
      return value;
    }catch(e){
      int tempValue = data;
      return tempValue.toDouble();
    }
  }
}

Add WebScoket class

Create new folder inside lib and name it web_socket, Inside it create new dart file name web_socker.dart and we will create Single ton WebSocket class by adding following code:

import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';

class WebSocket{

  static const String CONNECT_URL = "wss://ws.binaryws.com/websockets/v3?app_id=1089";
  static late WebSocketChannel channel;
  static final WebSocket _webSocket = WebSocket._internal();
  static late Function(dynamic event) listener;

  factory WebSocket(Function(dynamic event) listen) {
    listener = listen;
    return _webSocket;
  }

  WebSocket._internal(){
    channel = WebSocketChannel.connect(
      Uri.parse(CONNECT_URL),
    );
    ///Receive data of send request in stream listener
    channel.stream.listen(listener);
  }

  ///send request
  void sendRequest(Map<String,dynamic> request){
    channel.sink.add(jsonEncode(request));
  }

}

Add Controller

Now to separate logic from UI we will create notifier folder and inside it we will create controller for Available market in trading and controller for market price update. With the help of Riverpod this controllers state is going to be managed.

Now create file name market_notifier.dart and add following logic inside it:

final socketProvider = ChangeNotifierProvider<MarketProvider>((ref) {
  return MarketProvider(ref);
});

class MarketProvider extends ChangeNotifier{

  late final WebSocket webSocket = WebSocket(setUpListener());
  late ModelActiveSymbol modelActiveSymbol;
  late ModelTick modelTick;
  Map<String,List<ActiveSymbols>> uniqueActiveSymbolByMarketName = <String,List<ActiveSymbols>>{};
  late String selectedMarket;
  late ActiveSymbols selectedSymbol;
  double defaultPrice =-1;
  bool isSymbolChanged = true;
  final Ref ref;

  MarketProvider(this.ref){
    getMarketPlace();
  }

  ///Get initial market places
  getMarketPlace(){
    Map<String,String> activeSymbolRequest ={
      "active_symbols": "brief",
      "product_type": "basic"
    };
    webSocket.sendRequest(activeSymbolRequest);

  }

  ///Listener of web socket called at every event
  Function(dynamic event) setUpListener(){
    return ((event) {
      Map<String, dynamic> response = jsonDecode(event);

      /// Get all Market
      if (response["msg_type"] == "active_symbols") {
        modelActiveSymbol = ModelActiveSymbol.fromJson(response);
        for (var element in modelActiveSymbol.activeSymbols!) {
          if (uniqueActiveSymbolByMarketName.containsKey(element.marketDisplayName)) {
            uniqueActiveSymbolByMarketName[element.marketDisplayName]?.add(element);
          }
          else {
            uniqueActiveSymbolByMarketName.putIfAbsent(element.marketDisplayName!, () => [element]);
          }
        }
        selectedMarket = uniqueActiveSymbolByMarketName.keys.first;
        selectedSymbol = uniqueActiveSymbolByMarketName[selectedMarket]![0];
        notifyListeners();
        getTickData(selectedSymbol);
      }

      /// Get Price base on selected market symbol
      if (response["msg_type"] == "tick") {
        modelTick = ModelTick.fromJson(response);
        if(defaultPrice==-1 || isSymbolChanged){
          defaultPrice = modelTick.tick?.quote??-1;
          isSymbolChanged = false;
        }
        ref.read(tickProvider.state).state = AsyncData(modelTick);
      }
    }
    );
  }

  ///Update selected value from drop-down
  updateValue<T>(T item){
    if(T == String){
      selectedMarket =(item as String);
      selectedSymbol = uniqueActiveSymbolByMarketName[selectedMarket]![0];
    }
    else{
      selectedSymbol = (item as ActiveSymbols);
    }
    isSymbolChanged = true;
    forgetOldTick();
    getTickData(selectedSymbol);
    notifyListeners();
  }

  ///Get price from web-socket of selected symbol
  getTickData(ActiveSymbols activeSymbols){
    Map<String,String> ticksRequest ={
      "ticks": activeSymbols.symbol!,
      "subscribe": "1"
    };
    webSocket.sendRequest(ticksRequest);
  }

  ///Remove last symbol price before getting new symbol price
  forgetOldTick(){
    Map<String,String> forgetTicksRequest ={
      "forget": modelTick.tick?.id??"",
    };
    webSocket.sendRequest(forgetTicksRequest);
  }

}

Also controller for market price name trade_notifier.dart and add following logic inside it:

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/model_tick.dart';

final tickProvider = StateProvider<asyncvalue<modeltick>&gt;((ref) {
  return const AsyncValue.loading();
});</asyncvalue<modeltick>

Add UI screen

As you can see in folder structure there is a view folder inside which there is re-usable widgets which is generic dropdown and market price.

Create file dropdown.dart and add following UI code:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../models/model_active_symbol.dart';
import '../../notifier/market_notifier.dart';

class DropDown<T> extends ConsumerStatefulWidget {
  const DropDown({Key? key}) : super(key: key);

  @override
  ConsumerState<DropDown<T>> createState() => _DropDownState();
}

class _DropDownState<T> extends ConsumerState<DropDown<T>> {

  late T dropDownValue;
  List<T> items = [];

  @override
  Widget build(BuildContext context) {
    var socketNotifier = ref.watch(socketProvider);
    var activeSymbolsByMarketName = socketNotifier.uniqueActiveSymbolByMarketName;
    Iterable<String>? marketNames = activeSymbolsByMarketName.keys;

    ///Market places
    if(T == String) {
      if (marketNames.isNotEmpty) {
        items = (marketNames.toList() as List<T>);
        dropDownValue = (socketNotifier.selectedMarket as T);
      }
      ///Symbols as per selected market
    }else{
      if (marketNames.isNotEmpty && activeSymbolsByMarketName.isNotEmpty) {
        items = (activeSymbolsByMarketName[socketNotifier.selectedMarket] as List<T>);
        dropDownValue = (socketNotifier.selectedSymbol as T);
      }
    }

    ///Generic dropdown
    return items.isNotEmpty? InputDecorator(
      decoration: const InputDecoration(border: OutlineInputBorder()),
      child: DropdownButtonHideUnderline(
        child: DropdownButton<T>(

          value: dropDownValue,
          icon: const Icon(Icons.keyboard_arrow_down),

          items: items.map((T item) {
            return DropdownMenuItem<T>(
              value: item,
              child: T == String ? Text(item as String):  Text((item as ActiveSymbols).symbol??""),
            );
          }).toList(),

          onChanged: (T? newValue) {
            socketNotifier.updateValue<T>(newValue as T);
          },
        ),
      ),
    ):Container();

  }
}

Now text widget to display Live market price which will change colour depending on increase or decrease of market price, Add file market_price.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../notifier/market_notifier.dart';
import '../../notifier/trade_notifier.dart';

class MarketPriceText extends ConsumerWidget {
  const MarketPriceText({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    var tickNotifier = ref.watch(tickProvider);
    var defaultValue = ref.read(socketProvider).defaultPrice;

    /// Price displaying widget
    return tickNotifier.maybeWhen(
      data:(data){
        var currentValue = data.tick!.quote!;

        ///Set color base on value
        Color color = currentValue > defaultValue? Colors.green : currentValue < defaultValue ? Colors.red : Colors.grey;

        return Text(data.tick?.quote.toString()??"",
          style: TextStyle(fontSize: 20,color:color ),);
      },
      orElse: () => const Center(
        child: Text("Loading...."),
      ),);
  }
}

Now we Create a screen where those above widget is call and overall price tracking is done which is screen_price_tracker.dart

import 'package:flutter/material.dart';
import 'package:trading_price_tracker/view/widget/dropdrown.dart';
import 'package:trading_price_tracker/view/widget/market_price.dart';
import '../models/model_active_symbol.dart';

class ScreenTradingPrices extends StatelessWidget {
  const ScreenTradingPrices({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: SafeArea(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 15,vertical: 15),
            child: Column(
              children:  const [

                SizedBox(height: 20,),

                ///Market places
                DropDown<String>(),

                SizedBox(height: 20,),

                ///Active symbols according to market
                DropDown<ActiveSymbols>(),

                SizedBox(height: 55,),

                /// Live Price of symbols
                MarketPriceText()
              ],
            ),
          ),
        )
    );
  }
}

Lastly make change in main.dart file to call all above changes and launch app

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:trading_price_tracker/view/screen_price_tracker.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Trading price Tracker',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: const ScreenTradingPrices(),
    );
  }
}

That’s It

Now you can launch the app and see the Real-Time price tracking of any market selected from Dropdown. This is only possible through WebSocket like service. After launching app it is going to be look like this.

real_time_price_tracking

Where can you use it?

The realtime web existed before WebSockets, but it was difficult to accomplish, often slower, and based on hacking web technologies that weren’t built for realtime applications. The WebSocket protocol paved the path for a genuinely real-time web and expanded the possibilities of Internet communication.

USE CASES:

  • Web-based games
  • Chatting Applications
  • Trading Applications