ably.com
Open in
urlscan Pro
2606:4700:10::6814:58c3
Public Scan
URL:
https://ably.com/tutorials/realtime-cryptocurrency-app-flutter
Submission: On January 14 via api from BY — Scanned from DE
Submission: On January 14 via api from BY — Scanned from DE
Form analysis
3 forms found in the DOMGET /search
<form class="flex items-start" action="/search" method="get">
<div class="relative w-full">
<svg class="text-cool-black absolute top-12 left-16" style="width: 1.5rem; height: 1.5rem">
<use xlink:href="#sprite-icon-gui-search"></use>
</svg>
<input type="search" name="q" class="ui-input pl-48 h-48" placeholder="Search" autocomplete="off" data-id="meganav-search-input">
<div class="absolute w-full mt-8 z-10 hidden shadow-container rounded-lg bg-white border border-mid-grey" data-id="meganav-search-autocomplete-container">
<ol class="m-16" data-id="meganav-search-autocomplete-list"></ol>
</div>
</div>
<button type="submit" class="ui-btn-secondary flex-shrink-0 ml-8 sm:ml-16 md:ml-24 xl:ml-32"> Search </button>
</form>
GET /search
<form class="mb-16" action="/search" method="get">
<div class="relative w-full">
<svg class="text-cool-black absolute top-12 left-16 hover:text-gui-hover" style="width: 1.5rem; height: 1.5rem">
<use xlink:href="#sprite-icon-gui-search"></use>
</svg>
<button type="button" class="absolute top-12 right-16 p-0 focus:outline-gui-focus m-0 md:hidden invisible" data-id="meganav-search-input-clear">
<svg class="text-dark-grey " style="width: 1.5rem; height: 1.5rem">
<use xlink:href="#sprite-icon-gui-cross-circled-fill"></use>
</svg>
</button>
<input type="search" name="q" class="ui-input px-48 h-48" style="{{" maxwidth:="" "none"="" }}="" placeholder=" Search" autocomplete="off" data-id="meganav-mobile-search-input">
<div class="absolute w-full mt-8 z-10 hidden shadow-container rounded-lg bg-white border border-mid-grey" data-id="meganav-search-autocomplete-container">
<ol class="m-16" data-id="meganav-search-autocomplete-list"></ol>
</div>
</div>
</form>
<form id="modal-newsletter-form" class="c-form">
<h4 class="u-fs-3 u-m-b-small">Data streaming tutorials, realtime insights, and Ably announcements straight to your inbox</h4>
<input type="email" name="EMAIL" id="newsletter-popout-email" placeholder="Your email address" class="c-input c-input--primary u-m-b-tiny">
<input type="submit" value="Subscribe" name="subscribe" id="newsletter-popout-subscribe" class="c-button c-button--primary c-button--block">
</form>
Text Content
Created by iconfieldfrom the Noun Project How we use cookies to improve your experience. Accept and close Your browser has Javascript disabled. Please enable it to use this site. Hide this warning * Products Products THE ABLY PLATFORM Easily power any realtime experience in your application. No complex infrastructure to manage or provision. Just a simple API that handles everything realtime, and lets you focus on your code. Explore how it works PRODUCTS * Pub/Sub Channels Build infinitely scalable realtime applications. * Spaces (Beta) Create multi-user collaborative environments. * LiveSync (Pre release) Keep clients in sync with any relational database. TECHNOLOGY * Predictable performance A low-latency and high-throughput global network. * Guaranteed ordering & delivery Data is delivered - in order - even after disconnections. * Fault tolerant infrastructure Redundancy is built in at global and regional levels. * High scalability & availability Built for scale with legitimate 99.999% uptime SLAs. * Global edge network An edge network of 15 core routing datacenters and 205+ PoPs. Explore Four Pillars of Dependability * Solutions Solutions SOLUTIONS * Live Chat Deliver highly reliable chat experiences at scale. * Multiplayer Collaboration Bring collaborative multiplayer experiences to your users. * Data Broadcast Broadcast realtime event data to millions of devices around the globe. * Data Synchronization Keep your frontend and backend in realtime sync, at global scale. * Notifications Deliver cross-platform push notifications with a simple unified API. * Asset Tracking (Beta) Track assets in realtime with a solution optimised for last mile logistics. INDUSTRY * EdTech Deliver interactive learning experiences. * FinTech Deliver personalised financial data in realtime. * Automotive, Logistics, & Mobility Power diagnostics, order tracking and more. * B2B Platforms Empower your customers with realtime solutions. * Healthcare (HIPAA) Provide trustworthy, HIPAA-compliant realtime apps. * eCommerce & Retail Enrich customer experiences with realtime updates. * Sports, Media & Audience Engagement Deliver engaging global realtime experiences. * Gaming Power ultra fast and reliable gaming experiences. * IoT & Connected Devices Monitor and control global IoT deployments in realtime. * Company Company WHY COMPANIES CHOOSE ABLY * Customers Ably supports customers across multiple industries. * Case studies Discover how customers are benefiting from Ably. * Compare our tech Choose the right realtime service. * Partners Ably collaborates and integrates with AWS. * Resources Learn more about realtime with our handy resources. * About Ably Find out more about Ably’s mission. * Careers Discover our open roles and core Ably values. * Events Join Ably at upcoming events. BLOG * Creating shared live schedules using Bryntum and Ably Jan 11, 2024 * 5 React trends to get ahead of in 2024 Jan 10, 2024 * 10 realtime data sources you won't believe are free! Jan 8, 2024 More from our Blog * Developers Developers EXPLORE * Documentation Technical guides to help you build with Ably. * Quickstart guides Documentation to help you get started quickly. * Integrations Find out more about Ably integrations. * Live examples Discover our features and their use cases. * SDKs Download an SDK to help you build realtime apps faster. * Tutorials & Demos Get stuck in with our hands-on resources. * Chat apps reference guide Learn how to build chat apps with Ably. * Multiplayer reference guide Learn how to build collaborative features with Ably. QUICK LINKS * Discord * GitHub * Changelog * Status * Support & FAQs * Pricing * Contact us * Login * Search Popular pages * How does Ably work? * Quickstart guide * Publish/Subscribe Messaging * Platform Support * Sign up free * Login * Popular pages * How does Ably work? * Quickstart guide * Publish/Subscribe Messaging * Platform * Products Back THE ABLY PLATFORM Easily power any realtime experience in your application. No complex infrastructure to manage or provision. Just a simple API that handles everything realtime, and lets you focus on your code. Explore how it works PRODUCTS * Pub/Sub Channels Build infinitely scalable realtime applications. * Spaces (Beta) Create multi-user collaborative environments. * LiveSync (Pre release) Keep clients in sync with any relational database. TECHNOLOGY * Predictable performance A low-latency and high-throughput global network. * Guaranteed ordering & delivery Data is delivered - in order - even after disconnections. * Fault tolerant infrastructure Redundancy is built in at global and regional levels. * High scalability & availability Built for scale with legitimate 99.999% uptime SLAs. * Global edge network An edge network of 15 core routing datacenters and 205+ PoPs. Explore Four Pillars of Dependability * Solutions Back SOLUTIONS * Live Chat Deliver highly reliable chat experiences at scale. * Multiplayer Collaboration Bring collaborative multiplayer experiences to your users. * Data Broadcast Broadcast realtime event data to millions of devices around the globe. * Data Synchronization Keep your frontend and backend in realtime sync, at global scale. * Notifications Deliver cross-platform push notifications with a simple unified API. * Asset Tracking (Beta) Track assets in realtime with a solution optimised for last mile logistics. INDUSTRY * EdTech Deliver interactive learning experiences. * FinTech Deliver personalised financial data in realtime. * Automotive, Logistics, & Mobility Power diagnostics, order tracking and more. * B2B Platforms Empower your customers with realtime solutions. * Healthcare (HIPAA) Provide trustworthy, HIPAA-compliant realtime apps. * eCommerce & Retail Enrich customer experiences with realtime updates. * Sports, Media & Audience Engagement Deliver engaging global realtime experiences. * Gaming Power ultra fast and reliable gaming experiences. * IoT & Connected Devices Monitor and control global IoT deployments in realtime. * Company Back -------------------------------------------------------------------------------- WHY COMPANIES CHOOSE ABLY * Customers Ably supports customers across multiple industries. * Case studies Discover how customers are benefiting from Ably. * Compare our tech Choose the right realtime service. * Partners Ably collaborates and integrates with AWS. * Resources Learn more about realtime with our handy resources. * About Ably Find out more about Ably’s mission. * Careers Discover our open roles and core Ably values. * Events Join Ably at upcoming events. BLOG * Creating shared live schedules using Bryntum and Ably Jan 11, 2024 * 5 React trends to get ahead of in 2024 Jan 10, 2024 * 10 realtime data sources you won't believe are free! Jan 8, 2024 More from our Blog * Developers Back -------------------------------------------------------------------------------- EXPLORE * Documentation Technical guides to help you build with Ably. * Quickstart guides Documentation to help you get started quickly. * Integrations Find out more about Ably integrations. * Live examples Discover our features and their use cases. * SDKs Download an SDK to help you build realtime apps faster. * Tutorials & Demos Get stuck in with our hands-on resources. * Chat apps reference guide Learn how to build chat apps with Ably. * Multiplayer reference guide Learn how to build collaborative features with Ably. QUICK LINKS * Discord * GitHub * Changelog * Status * Support & FAQs * Pricing -------------------------------------------------------------------------------- Contact us Sign up free Tutorials Show tutorial in: JavaScript Java Android Kotlin Python PHP Ruby Node.js TypeScript Obj-C Swift Go C# .NET C++ Flutter C CSS Appcelerator PhoneGap curl Toggle menu Mobile BUILDING A REALTIME CRYPTOCURRENCY APP WITH FLUTTER Learn how to build a live cryptocurrency app using Ably's Flutter plugin * Overview * 1. Pre-requisites * 2. Building the Realtime Cryptocurrency Charts * 3. Building the Flutter Chat Room * 4. Viewing Recent Tweets for Each Coin * 5. Conclusion and next steps View as single page AUTHORS BUILDING A REALTIME CRYPTOCURRENCY APP WITH FLUTTER Learn how to implement realtime messaging in Flutter by building a cryptocurrency app that shows a live dashboard, chat room and Twitter feed. Flutter is a toolkit made by Google for building cross-platform apps easily. In this tutorial, we’ll show you how to build a realtime cryptocurrency app with 3 screens as described below: 1. Dashboard screen: This will be the default home screen where we will display realtime data for cryptocurrency prices from Ably’s Coindesk data stream on the Ably Hub (more on the Hub later). Each currency will have its own line graph that shows the changes in the price over time, along with the actual updated price. 2. Chat room screen: This will be the chat screen which you’ll see when the chat icon is clicked. We will create a public chat room where all users who have the app can chat with others who are currently in the room. 3. Twitter feed screen: Clicking on the name of any given cryptocurrency on the dashboard will bring up another screen showing the Twitter feed containing the latest tweets with that cryptocurrency mentioned. The tech world is increasingly moving towards event-driven systems, giving rise to a need for fast and reactive applications. The Ably Flutter plugin provides a robust and easy way to create Flutter apps with realtime capabilities. It is a wrapper around the Cocoa and Java client library SDKs, providing iOS and Android support for those using Flutter and Dart. We’ll see how to build our app using this. 1. PRE-REQUISITES 1. Before we get started, please make sure that you have Flutter correctly installed on your machine. You can do it by following the Flutter installation guide. 2. Project files: You can start from scratch and follow along with the tutorial by creating a new Flutter project in the desired location, and removing all the unnecessary code: flutter create live-cryptocurrency-streaming-app or clone the GitHub repo that already has the full project. 3. Add the packages mentioned in step 1.4 (Packages and dependencies) to the pubspec.yaml file and run: flutter pub get 4. The next step is to sign up for a free Ably account to obtain an Ably API key. This is needed to make the Ably Flutter plugin work. 5. Ably has a set of streaming data sources that can be used free of charge in your apps and services. They are hosted on the Ably Hub. For our application, we’ll make use of the Cryptocurrency pricing data stream. At the time of this writing, it supports the BTC, XRP, and ETH currencies and shows the corresponding prices in USD. Go ahead and click on the ‘Subscribe’ button to get access to this data stream from your Ably account. 6. Next, you need to sign up for a Twitter developer account to get your Twitter API keys. This is needed to get the Twitter feed screen working. It’s not necessary as the application as a whole will still work even with the Twitter feed missing. 1.1. PROJECT FILES STRUCTURE Lib // root folder of all dart files in a Flutter app |_ service |____ ably_service.dart |____ twitter_api_service.dart |_ view |____ dashboard.dart |____ twitter_feed.dart |____ chat.dart |_ config.dart |_ main.dart This is how our project’s structure will look like. We’ll keep the UI separate from the data source by creating services. Go ahead and create these as empty files for now. The most important file and the main focus of this tutorial will be the ably_service.dart file. This is where we will write all the code to communicate with Ably realtime. If you cloned the full project, you would notice the config_example.dart file, which has a few constants to hold the secret keys for Ably and Twitter APIs. Please paste your keys from the previous steps here, and rename the file to config.dart, you will also find notes to guide you inside the file. In case you are starting from scratch, create a new file named config.dart in the lib folder and paste your keys in it as follows: const String AblyAPIKey = 'Your Ably API Key goes here'; // The following keys should be taken from your Twitter developer account const String OAuthConsumerKey = ''; const String OAuthConsumerSecret = ''; const String OAuthToken = ''; const String OAuthTokenSecret = ''; IMPORTANT: We highly recommend not to commit this file into a public GitHub repository. So, make sure to immediately add this to your .gitignore file, if it’s not already there. 1.2. PACKAGES AND DEPENDENCIES In Flutter, we can make use of third-party packages to add extra functionality and make it easier to do many things without needing to re-invent the wheel. The pub.dev website has a list of all the packages you can use in your Flutter projects. To use a package we just have to add the package name and version in the pubspec.yaml file as follows. Go ahead and add these in your file. dependencies: flutter: sdk: flutter ably_flutter: ^1.2.0-preview.1 get_it: ^5.0.1 syncfusion_flutter_charts: ^18.3.52 http: ^0.12.2 intl: ^0.16.1 tweet_ui: ^2.4.2 twitter_api: ^0.1.2 Here’s an explanation on the packages we’ve added. 1. ably_flutter Ably’s Flutter package is a wrapper around the existing iOS and Android packages to provide scalable pub/sub messaging infrastructure out of the box. We will use it to connect to the Ably realtime service. 2. get_it We’ll use the get_it package for locating the services and using them in the UI classes. It’s a popular package used to manage state in a Flutter app and to separate our business logic from the UI. We will see how to use it to connect our services with the views later in the tutorial. 3. syncfusion_flutter_charts Syncfusion provides a wide range of packages for Flutter, this charts package is easy to use and can be highly customized. We will use it for the charts on the dashboard page. 4. intl The most popular internationalization package for Flutter, we will use it for dates formatting. 5. twitter_api Twitter has a complicated way of setting up a request, to simplify the process we will use this package which abstracts away that complexity for us. 6. http As we will connect to the Twitter API in one of the screens, this popular package provides us with an easy way to send HTTP requests. However, as you will see later, we won’t be using this package to send HTTP requests but only to work around a small issue with the twitter_api package. 7. tweet_ui A ready-made widget to display different types of tweets by simply passing the relevant JSON data. 2. BUILDING THE REALTIME CRYPTOCURRENCY CHARTS Let’s go back to the Ably Cryptocurrency prices page on the Hub that you subscribed to in the previous steps. Each cryptocurrency has a display name and a code. Also, each currency has a unique channel in the Hub. We’ll use the code to connect to the specific channel for the particular currency. The display name is what the user will see when using the app. Inside the ably_service.dart file, we will start with a const List variable that will store the currently available currencies on the Hub. If any new currency is added to the source, we can append it to this list and the whole app will be updated. const List<Map> _coinTypes = [ { "name": "Bitcoin", "code": "btc", }, { "name": "Ethereum", "code": "eth", }, { "name": "Ripple", "code": "xrp", }, ]; 2.1. CRYPTOCURRENCY DATA MODEL We need a model to hold the coin information and deliver it to the UI code. Instead of sending the raw data received from Ably immediately, we will use this model to map the received data. This will improve the readability of the code and completely separate the service layer. class Coin { final String code; final double price; final DateTime dateTime; Coin({ this.code, this.price, this.dateTime, }); } 2.2. REALTIME SERVICE CLASS Let’s create the main service class AblyService, and initialize it with a private constructor. Class AblyService { AblyService._(this._realtime); } The reason we do this is that we want this service to be a Singleton i.e. initialized only once at the time of launching the app. We don’t want to initialize a new instance of the service each time we need to access its methods. Instead, we need all the methods to use the same connection and instance information. To initialize our service, we will write a special static method. We’ll create and return the private instance of this class that can be used anywhere in the app. We’ll also add the configuration necessary to establish a realtime connection to Ably upon first initialization. static Future<AblyService> init() async { /// initialize client options for your Ably account using your private API /// key final ably.ClientOptions _clientOptions = ably.ClientOptions.fromKey(APIKey); /// initialize real-time object with the client options final _realtime = ably.Realtime(options: _clientOptions); /// connect the app to Ably's Realtime services supported by this SDK await _realtime.connect(); /// return the single instance of AblyService with the local _realtime /// instance to /// be set as the value of the service's _realtime property, so it can be /// used in all methods. return AblyService._(_realtime); } Let’s take a moment to understand what we did here. You can see that we passed the local _realtime property to the constructor of the AblyService class which will set the class-level _realTime property allowing other methods to use it. Let’s now connect to the cryptocurrency channel and subscribe to the coin prices. For this, we will add a method called getCointUpdates(). This method will establish the connection, listen to the stream of messages coming from Ably, and map each message to a Coin object. List<CoinUpdates> _coinUpdates = []; List<CoinUpdates> getCoinUpdates() { if (_coinUpdates.isEmpty) { for (int i = 0; i < _coinTypes.length; i++) { String coinName = _coinTypes[i]['name']; String coinCode = _coinTypes[i]['code']; _coinUpdates.add(CoinUpdates(name: coinName)); //launch a channel for each coin type ably.RealtimeChannel channel = _realtime.channels .get('[product:ably-coindesk/crypto-pricing]$coinCode:usd'); //subscribe to receive a Dart Stream that emits the channel messages final Stream<ably.Message> messageStream = channel.subscribe(); //map each stream event to a Coin and listen to updates messageStream.where((event) => event.data != null).listen((message) { _coinUpdates[i].updateCoin( Coin( code: coinCode, price: double.parse('${message.data}'), dateTime: message.timestamp, ), ); }); } } return _coinUpdates; } Let’s understand the code above. We iterate over the constant _coinTypes list that we created before. For each coin type in the list, we obtain and subscribe to the relevant Ably channel. Each such channel contains a Dart Stream emitting new events as they are published on the channel. You can read more on Dart Streams to get a better understanding. 2.3. NOTIFYING THE UI OF NEW DATA To consume the data stream easily in the UI, we will create a new class that extends the ChangeNotifier interface, which is the simplest way in Flutter to get notified when anything changes. You can think of it as the messenger responsible for delivering each new message emitted from the stream to the UI. class CoinUpdates extends ChangeNotifier { CoinUpdates({this.name}); final String name; Coin _coin; Coin get coin => _coin; updateCoin(value) { this._coin = value; notifyListeners(); } } Any UI widget that registers a listener for this object will get a notification whenever it has to rebuild with new data. The update will happen when calling the updateCoin() method, which will assign the new coin data to _coin, then call the notifyListeners() method. We chose to transform Stream events into ChangeNotifier updates because they are much easier to use in the UI as they always have a valid value and won’t complain if they have multiple subscriptions. 2.4. SUBSCRIBE TO ABLY CHANNELS Let’s break down the previous function and understand the subscription step in detail: 1. Get the realtime channel relevant to the data stream we are interested in ably.RealtimeChannel channel = _realtime.channels.get('[product:ably-coindesk/crypto-pricing]$coinCode:usd'); In a production-level app, it would be a good idea to check if the requested channel was successfully obtained. 2. Subscribe to that channel final Stream<ably.Message> messageStream = channel.subscribe(); 3. The returned type from subscribe() is a Stream<Message>, which feels a bit odd because we just subscribed to something. In reality, the subscribe tells Ably to start transmitting data. 4. So we register a listener for this message stream, and use the where method to filter out null values. messageStream.where((event) => event.data != null).listen((message) {}); As we never stop listening to the channels in this app, we can ignore the StreamSubscription object that is returned from listen(). Inside the listener, whenever a new message arrives, we call the `updateCoin()` method and pass it a new Coin mapped from the Message data. The return type of this function is a List<CoinUpdates> which has the same length as _coinTypes list and with a CoinUpdates object for every currency defined in _coinTypes. To be safe in case this function is called more than once, we wrap the for loop in an if condition that checks if the channel subscriptions already exists. We have now finished setting up our service, it’s time to see how we will use it to show the graphs of the price. To be able to access our services easily we use the service locator package get_it. Feel free to use the package/provider or any other solution that you are comfortable with. The following diagram visualizes how the data will flow from Ably to our App’s UI. 2.5. REGISTERING SERVICES WITH GET_IT The first step is to register the service using the get_it package. We will do that asynchronously in the main method, as we want this service to be available as soon as the app is launched. GetIt getIt = GetIt.instance; void main() { getIt.registerSingletonAsync<AblyService>(() => AblyService.init()); runApp(MyApp()); } Check out the package documentation on GitHub for more information on how asynchronous registration with get_it works. As this is an asynchronous registration, it won’t be available to our UI immediately. Hence, we will wait for it to become available before using it. For this, we will use a FutureBuilder widget which will show a loading spinner until get_it reports that all services are ready. Inside the dashboard.dart file, make a StatelessWidget and paste the following code inside the build() method. return Scaffold( appBar: AppBar( title: Text( "Live cryptocurrency by Ably", style: TextStyle(fontSize: 16), ), actions: [ IconButton( icon: Icon(Icons.chat_bubble), onPressed: () => _navigateToChatRoom(context), ) ], bottom: PreferredSize( child: Container( color: Colors.white, height: 1.0, ), preferredSize: Size.fromHeight(1.0), ), ), body: FutureBuilder( future: getIt.allReady(), builder: (context, snapshot) { if (!snapshot.hasData) return Center(child: CircularProgressIndicator()); else return GraphsList(); }, ), ); 2.6. LISTENING TO CRYPTOCURRENCY PRICES Now that we are sure the service will be ready at the time we use it, make a new StatefulWidget with the name GraphsList so that we can register a listener in the initState() method to listen to the cryptocurrency prices. List<CoinUpdates> prices = []; @override void initState() { prices = getIt<AblyService>().getCoinUpdates(); super.initState(); } On initial load of this page, the prices will not be ready because the app is most likely establishing a connection with Ably. To manage this, we’ll need the service to tell us what the current connection status is. For this let’s get back to AblyService class and add a new property called connection of type Stream. This will report any changes to our connection status to Ably. Stream<ably.ConnectionStateChange> get connection => _realtime.connection.on(); Since it’s a Stream object, we will use a StreamBuilder widget inside the GraphsList widget to read the connection status, and then decide what to display accordingly. Back to GraphsList widget, add the following code to the build() method. StreamBuilder<ably.ConnectionStateChange>( // As we are behind the FutureBuilder we can safely access AblyService stream: getIt<AblyService>().connection, builder: (context, snapshot) { if (!snapshot.hasData) { return CircularProgressIndicator(); } else if (snapshot.data.event == ably.ConnectionEvent.connected) { // return the list of graphs, SingleChildScrollView( // see section below ); } else if (snapshot.data.event == ably.ConnectionEvent.failed) { return Center(child: Text("No connection.")); } else { // In a real app we would also add handling of possible errors return CircularProgressIndicator(); } }, ), With all the cases handled, we are ready to display the list of charts if the connection is successful. 2.7. DISPLAYING CHARTS WITH REAL DATA In the same file, i.e. dashboard.dart, return the following from the build() method: SingleChildScrollView( child: Column( children: [ for (CoinUpdates update in prices) CoinGraphItem(coinUpdates: update), ], ), ), Instead of using a ListView.builder widget, we have just used a Column widget with a for-collection operation. In this case, using a Column widget is more convenient since the ListView, by default, will dispose off the states of any child that isn’t visible anymore. That’s good behaviour in case a list is very long, but since we know that the number of graphs is limited and don’t want them to be disposed off or rebuilt each time the user scrolls up or down, a Column should work fine. Each CoinGraphItem widget will require CoinUpdates. As it is extending the ChangeNotifier, it will register a listener for price updates and push each new price update into a Queue. We can’t use a List here because the size of the list will become huge very quickly needing too many resources. We don’t really have to show all the historical prices but just the last 100. Using a Queue would make it easy to remove the first item if the length exceeds the required length. Queue<Coin> queue = Queue(); String coinName = ''; VoidCallback _listener; @override void initState() { widget.coinUpdates.addListener( _listener = () { setState(() { queue.add(widget.coinUpdates.coin); }); if (queue.length > 100) { queue.removeFirst(); } }, ); if (coinName.isEmpty) coinName = widget.coinUpdates.name; super.initState(); } To be safe, it’s always good practice to cancel any listeners at disposal: @override void dispose() { widget.coinUpdates.removeListener(_listener); super.dispose(); } We are all set up and ready! Now we just need the queue to be turned into a list so that the graph can start rendering the data. We’ll show the price on the Y-axis and time on the X-axis. Here we will use the Syncfusion Flutter Charts package to render the charts. Why Syncfusion? Their Flutter Charts package is a beautifully-crafted charting widget to visualize data. It contains a gallery of 30+ charts and graphs that can be fully customized with options to include animations and render huge amounts of data in seconds. You can try it yourself for free. The following code displays the price graphs: @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.all(15), padding: EdgeInsets.all(15), height: 410, decoration: BoxDecoration( color: Color(0xffEDEDED).withOpacity(0.05), borderRadius: BorderRadius.circular(8.0)), child: AnimatedSwitcher( duration: Duration(milliseconds: 500), child: queue.isEmpty ? Center( key: UniqueKey(), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(), SizedBox( height: 24, ), Text('Waiting for coin data...') ], ), ) : Column( key: ValueKey(coinName), children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ FlatButton( onPressed: () => _navigateToTwitterFeed(coinName), textColor: Colors.white, child: Row( children: [ Image.asset( 'assets/icon_awesome_twitter.png', height: 20, ), SizedBox(width: 10), Text( "#$coinName", style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, ), ), ], ), ), AnimatedSwitcher( duration: Duration(milliseconds: 200), child: Text( "\$${widget.coinUpdates.coin.price.toStringAsFixed(2)}", key: ValueKey(widget.coinUpdates.coin.price), style: TextStyle( fontSize: 20, ), ), ), ], ), SizedBox(height: 25), SfCartesianChart( enableAxisAnimation: true, primaryXAxis: DateTimeAxis( dateFormat: intl.DateFormat.Hms(), intervalType: DateTimeIntervalType.minutes, desiredIntervals: 10, axisLine: AxisLine(width: 2, color: Colors.white), majorTickLines: MajorTickLines(color: Colors.transparent), ), primaryYAxis: NumericAxis( numberFormat: intl.NumberFormat('##,###.00'), desiredIntervals: 5, decimalPlaces: 2, axisLine: AxisLine(width: 2, color: Colors.white), majorTickLines: MajorTickLines(color: Colors.transparent), ), plotAreaBorderColor: Colors.white.withOpacity(0.2), plotAreaBorderWidth: 0.2, series: <LineSeries<Coin, DateTime>>[ LineSeries<Coin, DateTime>( animationDuration: 0.0, width: 2, color: Theme.of(context).primaryColor, dataSource: queue.toList(), xValueMapper: (Coin coin, _) => coin.dateTime, yValueMapper: (Coin coin, _) => coin.price, ) ], ) ], ), ), ); } You can see the price graphs in the following screenshot: With that, we finished building the first page, fully functional with realtime updates from the Ably Cryptocurrency data stream on the Hub. 3. BUILDING THE FLUTTER CHAT ROOM In the previous section, we subscribed to a channel as a consumer of a public data stream. As we don’t have publishing rights on that data stream, we can’t add data to it. In this section, we will take a look at how to create private channels programmatically, subscribe to them as well as publish new messages. Building the chat room with Ably realtime capabilities is fairly simple! In our AblySevice class, we will add two methods, one to listen to the latest messages as long as a user is on the chat room screen, and another method to send new messages. ChatUpdates getChatUpdates() { ChatUpdates _chatUpdates = ChatUpdates(); _chatChannel = _realtime.channels.get('public-chat'); var messageStream = _chatChannel.subscribe(); messageStream.listen((message) { _chatUpdates.updateChat( ChatMessage( content: message.data, dateTime: message.timestamp, isWriter: message.name == "${_realtime.clientId}", ), ); }); return _chatUpdates; } Channel names are unique for a specific Ably app, so channels of different apps can have the same name (if you want to send messages from one app to the other you can do this by using the API Streamer). For our chat, we will use the channel name public-chat. We’ll use this channel to send and receive realtime chat messages. If the channel doesn’t exist at the time this function is called, it will be created as a new one automatically. Just like we did previously with prices, we will create a ChatUpdates class as a ChangeNotifier holding the most recently published message and push it to a queue in the UI. We’ll call the getChatUpdates() method on the same instance of AblyService that we previously registered with the get_it package. Doing this will subscribe our app to the chat channel enabling it to receive updates whenever a new message is published on that channel. For publishing our messages, we will add the sendMessage() method to the service. Future sendMessage(String content) async { _realtime.channels.get('public-chat'); await _chatChannel.publish(data: content, name: "${_realtime.clientId}"); } To publish messages, we call the publish method on the chat channel instance. The name parameter in the publish method is optional, it can be used to differentiate various types of messages that are sent over the same channel. We set the event name in the publish method as client ID of the connected device. This will enable us to differentiate the messages sent by the current user from the messages sent by others on the same chat channel. To avoid unnecessary code for this demo app, we don’t store the clientIDs anywhere. This means that you will have a new clientID every time you start the app. With this, our chat infrastructure in the service is done. So let’s move to the chat view. Once we open it, we need it to initialize a listener, just like we did on the main page DashboardView. In the chat.dart file, we will initialize the channel and set up our listener: Queue<ChatMessage> messages = Queue(); ChatUpdates chatUpdates; VoidCallback _listener; @override void initState() { super.initState(); chatUpdates = getIt<AblyService>().getChatUpdates(); chatUpdates.addListener( _listener = () { if (chatUpdates.message != null) setState(() { messages.addFirst(chatUpdates.message); }); if (messages.length > 100) { messages.removeFirst(); } }, ); } As we have the client ID sent with each message, we know if a message is coming from the current user or from other users on the same channel. We can change the look of the message bubble accordingly. To display the chat bubbles we will use a ListView.builder() widget. As messages should appear in the reverse order, the first item in the messages queue is always the most recent message. So we will display the items in reverse order so that the first message always appears at the bottom. Flexible( child: ListView.builder( reverse: true, itemCount: messages.length, itemBuilder: (context, index) { return ChatMessageBubble( message: messages.toList()[index], isWriter: messages.toList()[index].isWriter, ); }, ), ), Each list item is a message, so we will create a custom widget to render a message bubble. The message bubble itself will have two different looks, one when the message is from the current user, and another for messages from other users. class ChatMessageBubble extends StatelessWidget { const ChatMessageBubble({ Key key, this.message, this.isWriter = false, }) : super(key: key); final ChatMessage message; final bool isWriter; final double radius = 15; @override Widget build(BuildContext context) { return Container( margin: EdgeInsets.all(15), child: Column( crossAxisAlignment: isWriter ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.all(10), alignment: Alignment.centerLeft, decoration: BoxDecoration( color: isWriter ? Theme.of(context).primaryColor.withOpacity(0.5) : Colors.white12, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(isWriter ? radius : 0), bottomRight: Radius.circular(isWriter ? 0 : radius), topLeft: Radius.circular(radius), topRight: Radius.circular(radius), ), ), width: MediaQuery.of(context).size.width * 0.6, constraints: BoxConstraints(minHeight: 50), child: Text(message.content), ), SizedBox(height: 5), Align( alignment: isWriter ? Alignment.bottomRight : Alignment.bottomLeft, child: Text( intl.DateFormat.Hm().format(message.dateTime), style: TextStyle(color: Colors.white24), textAlign: TextAlign.left, ), ) ], ), ); } } We that we’ve fully implemented the chat screen. Let’s move on to the final one. 4. VIEWING RECENT TWEETS FOR EACH COIN To display Tweets that have a hashtag of the coin name, we will create a second service to connect to the Twitter API. It’s a good practice to separate different data sources into separate service classes. Before starting, it’s worth noting that this tutorial is using Twitter API 1.0. If you want to try this part in your own app you will have to register for a Twitter developer account to get your private API keys. When you log into your Twitter developer account, you can generate your keys in the developer console. Copy them out and add them to the config.dart file. To query the Twitter API manually using an HTTP request is a bit complex as it requires a lot of calculations to get the signature for each request. To make our life a bit easier, we will use the twitter_api and tweet_ui packages to display the tweets. It’s worth noting that the twitter_api package that deals with all the signature and authorization details only works for v1.0 API of Twitter. Please note that you can still implement the API access using Dart only. You can now switch to the twitter_api_service.dart file. Before we can use the twitterApi method, we will have to initialize it with all the required keys: TwitterAPIService({this.queryTag}) { _twitterApi = twitterApi( consumerKey: OAuthConsumerKey, consumerSecret: OAuthConsumerSecret, token: OAuthToken, tokenSecret: OAuthTokenSecret, ); } To get the recent tweets we will use the standard Twitter search endpoint: static const String path = "search/tweets.json"; With the twitterApi instance initialized with our keys, it’s time to request tweets based on the hashtag passed through the constructor: Future<List> getTweetsQuery() async { try { // Make the request to twitter Response response = await _twitterApi.getTwitterRequest( // Http Method "GET", // Endpoint you are trying to reach path, // The options for the request options: { "q": queryTag, "count": "50", }, ); final decodedResponse = json.decode(response.body); return decodedResponse['statuses'] as List; } catch (error) { rethrow; } } This time the response won’t be of type Stream, but a Future. It uses the http package under the hood, so the returned type from the request is an http Response object which needs to be decoded. This is the reason we explicitly imported the http package – to give a type to the response. We could proceed without it with just final response, but it’s a good practice in Flutter and Dart to always be explicit with types. final decodedResponse = json.decode(response.body); The response body is a Map object. All the tweet data is inside a list, and this list has a key called statuses. That’s why the returned value is decodedResponse['statuses']. Let’s now switch to the twitter_feed.dart file to implement the UI. As mentioned before, we will use the tweet_ui package to display the Tweets in their familiar design. You don’t have to use this package and you can always implement your own widget for the tweets if you like. We could have registered the Twitter service via get_it too but as we always create the Tweets page dynamically without needing to persist data, we can create a new instance every time the TwitterfeedView is pushed. To do this, we first initialize a service instance in getTweets() method using the hashtag that was passed from the dashboard. Then, we will call the getTweetsQuery() method. As it returns a Future, we need to await the result. When the result is ready, we update the local state of the widget using setState which will call the build method to switch from displaying a loading spinner to the actual list of tweets. We can’t do this directly inside initState() because this API is asynchronous, and initState() method can’t be defined as an async function. We will use a separate async method called getTweets() that we will call from the initState() method. We can do this safely as we have already ensured that the page will render correctly even with no data received. Future getTweets() async { final twitterService = TwitterAPIService(queryTag: widget.hashtag); try { final List response = await twitterService.getTweetsQuery(); setState(() { tweetsJson = response; }); } catch (error) { setState(() { errorMessage = 'Error retrieving tweets, please try again later.'; }); } } If any exception is rethrown by the service, which could happen if you don’t use valid keys or if there is a network problem, it will display a nice error message without the app pausing on the exception. That’s all! We have implemented all the three screens in our Flutter cryptocurrency app. 5. CONCLUSION AND NEXT STEPS * If you would like to read more on Ably’s realtime services, read the official documentation which shows example code snippets for Flutter and offers deeper explanations on various concepts. * The full source code for this project is available on GitHub. * Understand the client-side considerations when building realtime apps with Flutter and WebSockets. * Read more on Simple Flutter app state management with ChangeNotifier. * Read more on Using Flutter packages. * Learn more about Dart Streams in their official YouTube video. * If you have any questions, feel free to reach out to us and we’ll be happy to help you out. * You can follow the roadmap and request new features directly on Ably Flutter plugin’s GitHub repo. Previous Toggle menu 1. Pre-requisites Talk to our technical team If you're having technical or account issues just get in touch. Start a live chat Don’t want to chat? Get in touch via our contact page Try our APIs for free Our free plan includes * 6m messages per month * 200 peak connections * 200 peak channels * Loads of features Start building THE ABLY PLATFORM Easily power any realtime experience in your application via a simple API that handles everything realtime. * Pub/sub messaging * Push notifications * Third-party integrations * Multiple protocol messaging ABLY IS FOR * Ably Asset Tracking * Extend Kafka to the edge * EdTech * Automotive, Logistics, & Mobility * B2B Platforms * Healthcare * eCommerce & Retail * Sports & Media * Gaming * IoT & Connected Devices DEVELOPERS * Start in 5 minutes * Documentation * Tutorials * Changelog * Support & FAQs * SDKs * System status WHY ABLY * Customers * Case Studies * Four Pillars of Dependability * Compare our tech * Multi protocol support * Third-party integrations ABOUT * About Ably * Pricing * Blog * Careers * Open protocol policy * Press & Media * Contact us -------------------------------------------------------------------------------- We're hiring! Learn more at Glassdoor We're hiring! Learn more at Glassdoor -------------------------------------------------------------------------------- Cookies Legals Data Protection Privacy SOC 2 Type 2 Certified HIPAA Compliant EU GDPR Certified 256-bit AES Encryption Close Dialog Yes No Close Dialog DATA STREAMING TUTORIALS, REALTIME INSIGHTS, AND ABLY ANNOUNCEMENTS STRAIGHT TO YOUR INBOX sprite-discord sprite-facebook sprite-github sprite-glassdoor sprite-google sprite-icon-display-48hrs sprite-icon-display-about-ably-col sprite-icon-display-api-keys sprite-icon-display-api sprite-icon-display-asset-tracking-col sprite-icon-display-browser sprite-icon-display-calendar sprite-icon-display-call-mobile sprite-icon-display-careers-col sprite-icon-display-case-studies-col sprite-icon-display-chat-col sprite-icon-display-chat-stack-col sprite-icon-display-chat-stack sprite-icon-display-cloud-servers sprite-icon-display-compare-tech-col sprite-icon-display-customers-col sprite-icon-display-data-broadcast-col sprite-icon-display-data-synchronization-col sprite-icon-display-docs-col sprite-icon-display-documentation sprite-icon-display-events-col sprite-icon-display-examples-col sprite-icon-display-gdpr sprite-icon-display-general-comms sprite-icon-display-hipaa sprite-icon-display-integrations-col sprite-icon-display-it-support-access sprite-icon-display-it-support-helpdesk sprite-icon-display-kafka-at-the-edge-col sprite-icon-display-laptop sprite-icon-display-lightbulb-col sprite-icon-display-live-chat sprite-icon-display-map-pin sprite-icon-display-message sprite-icon-display-padlock-closed sprite-icon-display-platform sprite-icon-display-play sprite-icon-display-privacy-shield-framework sprite-icon-display-push-notifications-col sprite-icon-display-quickstart-guides-col sprite-icon-display-resources-col sprite-icon-display-sdks-col sprite-icon-display-servers sprite-icon-display-shopping-cart sprite-icon-display-sla sprite-icon-display-soc2-type2 sprite-icon-display-tech-account-comms sprite-icon-display-tutorials-demos-col sprite-icon-display-virtual-events-col sprite-icon-display-virtual-events sprite-icon-gui-ably-badge sprite-icon-gui-arrow-bidirectional-horizontal sprite-icon-gui-arrow-bidirectional-vertical sprite-icon-gui-arrow-down sprite-icon-gui-arrow-left sprite-icon-gui-arrow-right sprite-icon-gui-arrow-up sprite-icon-gui-burger-menu sprite-icon-gui-check-circled-fill-black sprite-icon-gui-check-circled-fill sprite-icon-gui-check-circled sprite-icon-gui-checklist-checked sprite-icon-gui-clock sprite-icon-gui-close sprite-icon-gui-copy sprite-icon-gui-cross-circled-fill sprite-icon-gui-cross-circled sprite-icon-gui-dash-circled sprite-icon-gui-disclosure-arrow sprite-icon-gui-document-generic sprite-icon-gui-enlarge sprite-icon-gui-external-link sprite-icon-gui-filter-flow-step-1 sprite-icon-gui-filter-flow-step-2 sprite-icon-gui-filter-flow-step-3 sprite-icon-gui-history sprite-icon-gui-info sprite-icon-gui-link-arrow sprite-icon-gui-link sprite-icon-gui-live-chat sprite-icon-gui-minus sprite-icon-gui-plus sprite-icon-gui-quote-marks-solid sprite-icon-gui-refresh sprite-icon-gui-resources sprite-icon-gui-search sprite-icon-gui-tick sprite-icon-gui-warning sprite-icon-live-updates-results-metrics-col sprite-icon-multi-user-spaces-col sprite-icon-social-x sprite-icon-tech-apachekafka sprite-linkedin sprite-quote sprite-stackoverflow sprite-twitter sprite-youtube