Create a Flutter RSS reader

by Javier Puerto
Tags: Open Source , Tutorial

We are going to explain how to create a simple RSS reader application with the new and exciting framework Flutter, a cross platform solution based on Dart language.

Apple logo dressed as a Google Android and vice versa. Image by Tsahi Levent-Levi, under CC license.

Previously, we have explained how to create cross platform solutions based on Apache Cordova and Angular. Those are great because we can write a HTML+JS based application and run them on several platforms easily. Write once, run anywere. This solution works nice for several kind of applications and it has been consolidated as a valid alternative. The main problem with using a Webview is the performance impact, sometimes the feeling is a bit laggy. Optimize them could be a tricky task. If you are thinking about creating an application that is able to work for Android an iOS but you have concerns about the performance, Flutter is probably your best option.

Introducing Flutter

Flutter is a framework to create cross platform applications for Android and iOS. It is based on the new Dart language. Both were created by Google and they are stable and ready for production. With Flutter the code has to be written only once and is then built for the Dart VM. The Dart VM has native implementations for Android and iOS so it will work exactly the same.

Flutter is focused on performance. The framework uses the Skia engine to create a widget tree representation of the UI. A widget can be stateless or stateful. A change in a state triggers the update of the affected widgets and all their descendants. This is very powerful but must be handled with care to update only the affected widgets, reducing memory and CPU time.

Furthermore, the Flutter team is starting now to experiment with a web and a desktop engine. We hope to see in the future our applications can also be delivered for more platforms.

BeCompany RSS news

Like the example that we had from our previous post, we are going to create a RSS reader for our company blog posts.

We should start by installing Flutter. Once we have the Flutter SDK installed, we can start creating a new project. To start we only have to run:

$ flutter create becompany_rss_reader
…
Run "flutter doctor" for information about installing additional components.

In order to run your application, type:

  $ cd becompany_rss_reader
  $ flutter run

Your application code is in becompany_rss_reader/lib/main.dart.

The flutter tool will generate a default project with the name "becompanyrssreader" and will fetch the dependencies declared in the pubspec.yaml file. We will explain more about this file later.

At the end of the project generation, we got an informative text about the next steps. With an emulator or a real device connected, we can run the command flutter run and we should see something like the following picture.

Flutter sample app.

Some of you will realize that the sample application is using Material Design. Flutter comes out of the box with two widgets libraries, Cupertino and Material.

Create our first screen

Now it is time to create our first screen. Our example needs only one screen. Therefore we leave the routing aside for the time being. Flutter would support routing, however. In this example we will create a file becompany_home_screen.dart.

import 'package:flutter/material.dart';

class BecompanyHomeScreen extends StatelessWidget {
  final news = ['News 1', 'News 2', 'News 3'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BeCompany RSS'),
      ),
      body: ListView.builder(
          itemCount: news.length,
          itemBuilder: (BuildContext ctxt, int index) {
            return Card(
                child: Padding(
              padding: EdgeInsets.all(16.0),
              child: Text(news[index]),
            ));
          }),
    );
  }
}

Then in main.dart we just have to remove the old code and use our new widget.

import 'package:flutter/material.dart';

import 'becompany_home_screen.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BecompanyHomeScreen(),
    );
  }
}

For Flutter applications, the entry point uses to be the main.dart file but another name could be choosen. Important is the method void main(), as it will be the main method of our application. In this case we create a MaterialApp. This class is provided by the Flutter framework and must be the root widget of a Material app. The difference with the example app is that we are defining our home screen to be an instance of BecompanyHomeScreen. Notice that in Dart, the new word is not mandatory to create new objects.

The BecompanyHomeScreen first widget is the Scaffold class. This class is commonly used to create basic Material apps. It handles the title, the application bar, the body or a floating action, and more things. We are going to configure the application bar to display a title and the body to contain a list. For the body we will use the ListView.builder.

Our application should now looks as follows:

Flutter sample app.

Create our feed reader service

The RSS service must read the feed from BeCompany's website and produce a collection of feeds. For the task we are going to search for a plugin that fits our needs.

We found webfeed that does exactly what we want. We will need also the http package. Edit pubspec.yaml and add the two dependencies.

 dependencies:
   flutter:
     sdk: flutter
+  http: ^0.12.0+2
+  webfeed: ^0.4.2

Then we should run flutter packages get in the project directory. Now we are ready to use the new libraries. Let's create a file becompany_rss_service.dart.

import 'package:http/http.dart' as http;
import 'package:webfeed/webfeed.dart';

class BecompanyRssService {
  final _targetUrl = 'https://www.becompany.ch/en/blog/feed.xml';

  Future<AtomFeed> getFeed() =>
      http.read(_targetUrl).then((xmlString) => AtomFeed.parse(xmlString));
}

The service is very simple, it just defines a future that will fetch an URL, and then return the content parsed by the webfeed plugin.

We are going to create a test case to verify that it works as expected. Create the file becompany_rss_service_test.dart in the test directory. We are going to test that the values are correct.

import 'package:flutter_test/flutter_test.dart';

import '../lib/becompany_rss_service.dart';

void main() {
  test('Test to fetch the BeCompany RSS news.', () {
    final service = BecompanyRssService();
    service.getFeed().then((feed) {
      expect(feed.title, 'BeCompany Blog');
      expect(feed.rights, 'BeCompany GmbH');
    });
  });
}

Easy, isn't it?. We can test it with the command flutter test. That's it.

Display the RSS content

Now it is time to feed our list builder with our blog entries. As we have a Future, the application is synchronous at the moment. We need to make it asynchronous at some point. We can set the main as an async method and then we can await the feeds. Then we will create our widget by passing the feed object to the constructors.

void main() async {
  final AtomFeed feed = await BecompanyRssService().getFeed();
  runApp(MyApp(feed));
}

MyApp will take the feed object and pass it to BecompanyHomeScreen. There we will use it to populate the list.

class BecompanyHomeScreen extends StatelessWidget {
  final AtomFeed feed;

  BecompanyHomeScreen(this.feed);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BeCompany RSS'),
      ),
      body: ListView.builder(
          itemCount: feed.items.length,
          itemBuilder: (BuildContext ctxt, int index) {
            final item = feed.items[index];
            return ListTile(
              title: Text(item.title),
              subtitle: Text('Published at ' +
                  DateFormat.yMd().format(DateTime.parse(item.published))),
              contentPadding: EdgeInsets.all(16.0),
              onTap: () async {
                Navigator.push(
                    context,
                    MaterialPageRoute(
                        builder: (context) => WebViewContainer(
                            item.id.replaceFirst('http', 'https'))));
              },
            );
          }),
    );
  }
}

We now use the number of feed items as itemCount parameter and return ListTile elements with the information obtained from every item. When a user taps an element, everbody expects to see the news' details. As the underlying source is website we have two options: Show the details in a Webview or open a Browser. Flutter has plugins for both use cases. We have chosen to go with the Webview.

We add the needed dependencies.

   http: ^0.12.0+2
   webfeed: ^0.4.2
+  intl: ^0.15.8
+  webview_flutter: ^0.3.9

Then again fetch the packages with flutter packages get. We can now open the item feed's URL in a Webview. Our feeds are still pointing to the non-secure protocol, even when our proxy rewrite the URL to use HTTPS, webview policies do not allow to open non-secure URLs.

Flutter sample app.

Using InheritedWidget

Did you realize that we had to modify all the constructors to pass the feed from the main method to our view? This is not too complex in our tutorial but imagine many levels in the widget tree, then it could become cumbersome. InherithedWidget could help in these cases. All widgets that implements this class expose their state to their descendant widgets.

For our use case we need a FeedStore that will be the parent of the list widget.

class MyApp extends StatelessWidget {
  final AtomFeed feed;

  MyApp(this.feed);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: FeedStore(feed: feed, child: BecompanyHomeScreen()),
    );
  }
}

class FeedStore extends InheritedWidget {
  final AtomFeed feed;

  const FeedStore({
    Key key,
    @required this.feed,
    @required Widget child,
  })  : assert(feed != null),
        assert(child != null),
        super(key: key, child: child);

  static FeedStore of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FeedStore) as FeedStore;
  }

  @override
  bool updateShouldNotify(FeedStore oldWidget) =>
      feed.updated != oldWidget.feed.updated;
}

The FeedStore widget can be accessed easily from descendant widgets. For our case it will be the BeCompanyHomeScreen widget. Instead of passing the feeds by parameter we can do the following:

final feed = FeedStore.of(context).feed;

And we can access feed from all the descendants too.

Using FutureBuilder

Our application could perform better easily. Currently we create the application asynchronously but we wait until the feed is loaded to continue our process. As of now it is not noticeable but consider an application with a heavy process where the user will have to wait until the app is shown. Let's try to overcome this limitation.

FutureBuilder is a widget from the Flutter framework that allows us to pass a future and a method. The method will be called with the progress.

  body: FutureBuilder(
    future: BecompanyRssService().getFeed(),
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      switch (snapshot.connectionState) {
        case ConnectionState.done:
          if (snapshot.hasData) {
            if (snapshot.data != null) {
              final feed = snapshot.data;
              return ListView.builder(
                  itemCount: feed.items.length,
                  itemBuilder: (BuildContext ctxt, int index) {
                    final item = feed.items[index];
                    return ListTile(
                      title: Text(item.title),
                      subtitle: Text('Published at ' +
                          DateFormat.yMd()
                              .format(DateTime.parse(item.published))),
                      contentPadding: EdgeInsets.all(16.0),
                      onTap: () async {
                        Navigator.push(
                            context,
                            MaterialPageRoute(
                                builder: (context) => WebViewContainer(item
                                    .id
                                    .replaceFirst('http', 'https'))));
                      },
                    );
                  });
            }
          }
          break;
        case ConnectionState.none:
        case ConnectionState.active:
        case ConnectionState.waiting:
        default:
          return Align(
            alignment: Alignment.center,
            child: CircularProgressIndicator(),
          );
      }
    },
  ),

The future builder will call the builder method with the current context and a snapshot of the future. The ConnectionState indicates the status of the future action. We can add a switch statement and control what to render in every case. When the connection is done, we have our result ready and we can draw the news list. If the connection has not yet finished, then show a circular progress indicator. Now the application will open without any additional pause and while it is loading the feeds, a circular animation will be shown in the center of the display.

Conclusions

It is fun to work with flutter. It is quick to learn and you can do incredible things relatively easy. You can do even more complex things using plugins or the canvas drawer. Flutter is still very new and thus sometimes not mature enough. Also the plugin ecosystem still has to grow.

I hope that you enjoyed this tutorial. As always you can find the source code on GitHub. Please leave your comments, we want to know your opinions. Thanks for reading.