Creating a Cross-Platform Todo Aggregator with Flutter | VPS and VPN
HostArmada - Affordable Cloud SSD Web Hosting

Creating a Cross-Platform Todo Aggregator with Flutter


Flutter is a UI toolkit from Google (but open source with a strong community) for creating natively compiled applications for mobile, web, and desktop. This places Flutter alongside other cross-platform toolkits and frameworks such as React Native and Xamarin as well as specific native options such as C#, Java/Kotlin, and Objective-C/Swift. Thanks to the increase in the adoption of JavaScript everywhere, the market for cross-platform development toolkits and frameworks has narrowed, but why should any developer consider Flutter for their cross-platform needs. In this article, I look at how to get started with Flutter, and it’s positives, negatives, and pitfalls.

I always find it useful to have an itch you’re trying to scratch when learning a new tool, so I took this opportunity to solve one of mine that Flutter seemed well suited too. As a long-term freelancer, generally working across multiple projects at one time, I use multiple sources of to-dos. For a while, I’ve used a great Chrome extension called Taco that aggregates from a lot of sources, but not all of the ones I use, and I wanted more from it. I’m old fashioned and also like applications and apps as opposed to websites, and would ideally like something that can work offline as much as possible. As far as I could tell, Flutter could fulfill all these needs, and it’s one main negative that I’d still need some form of backend service too, something I only really realized about 3/4 through working on this article. I won’t cover creating an entire application if this scope in this article, but cover creating the basics, and maybe cover later steps in future articles, or you can keep an eye on the project GitHub repo.

Dart

Flutter is a toolkit built on top of Dart, a language also built primarily for UI work, and with familiar syntax, drawing inspiration from JavaScript, Java, and other languages.

Getting Started

Flutter and Dart (from now on, I refer to Flutter, but sometimes mean both) have excellent editor and IDE support. I used the Visual Studio Code extension, but there are also options for Android Studio and IntelliJ. If neither of these work for you, the command line tools installed as part of the Flutter toolkit are also comprehensive. For this tutorial, I use Visual Studio Code.

However, installing the Flutter toolkit is a little fiddly, and an odd complexity for an otherwise developer experience focussed project. Some of this complexity comes from it supporting so many build targets. If you’re already developing for iOS or Android, you may already have some of the prerequisites installed. As there are a lot of instructions, it’s probably best you take a look at the official installation guide.

As a macOS user, I always look for a Homebrew option, there is no official solution, but I was able to use brew cask install socheatsok78/flutter/flutter with no issues so far.

Creating a project

With Flutter installed, it’s time to create a project. It generally seems easier to add support for a platform when you create a project. I started by adding web support, as that seems the broadest. This is probably an odd choice, as Flutter was primarily created for mobile support, and this is reflected by web and desktop support currently in beta. But that’s what I want for my project, and I didn’t encounter any issues.

Switch Flutter to the beta channel with:

As you’re using the beta channel, make sure you’re up to date with:

And enable web support with:

You need a browser for web support, which seems like an obvious statement, but Flutter needs additional support if you don’t use Google Chrome (which I don’t). If you do use Chrome, run flutter devices, and Chrome should be listed. If you want to use a different browser, add the path to the executable as an environment variable. So for using Chromium on macOS, use the following:

Now create a new project, and then run it:

This command runs the default Flutter project, next change that.

Create an App

Flutter loads lib/main.dart by default, but as an object-oriented language, you can create classes and helpers in other files and include them with import statements.

While we recreate some of the code currently in lib/main.dart, it’s probably easier to start from scratch, so delete everything from line 5 down, leaving the below in place:

The code above is the typical boilerplate for most Flutter applications. The main function present calls the runApp function, passing into it a widget to run.

Similar to React, you build Flutter UIs from widgets. Widgets describe what their view should look like, given their current configuration and state. When a widget’s state changes, the widget rebuilds its description, which the framework diffs against the previous description to determine the minimal changes needed in the underlying render tree to transition from one state to the next.

Now create the ToDoApp widget with the code below:

Most widgets that are subclasses of either StatelessWidget or StatefulWidget, depending on whether your widget manages any state. This widget doesn’t manage state, and therefore extends StatelessWidget.

Every widget provides a build() method that describes how to display the widget in terms of other, lower-level widgets.

This widget returns a Material design based UI, which a web application doesn’t strictly need, but it gives a lot of other UI candy for free, so keep it for know. The MaterialApp method returns a title, and a ChangeNotifierProvider as the top level of the widget tree, let’s dig into that provider class in more detail.

The ChangeNotifier class is a plugin announced at IO19 perfect for loading changing data sources from the internet.

The code block:

It also gives a perfect time to explain some other Flutter (and Dart) concepts.

Dart supports generic types, so ChangeNotifierProvider<AppState> you can think of as (, and this sounds odd, I know) a “Change notifier provider based on app state.” The builder parameter uses the builder pattern to set the state, and the child parameter (you will see this a lot) defines the child widget of this widget.

First create the AppState type by creating a new file in lib called app_state.dart and then import into the main application by adding import 'package:fluttertodo/app_state.dart'; to the top of main.dart.

In app_state.dart add the following:

You may notice in your IDE that the import 'package:http/http.dart' as http; generates an error, this is because you don’t have the package defined as a dependency. In the pubspec.yml file at the project top level, add the following under dependencies:

Saving the file triggers flutter pub get in your IDE, but you can also run the same command manually to install the dependency.

Back inside app_state.yml. The class definition uses the with keyword to use mixins, a familiar concept from other OOP languages to add features to a class, in this case adding the ChangeNotifier class.

The next four lines initialize variables, including the string that represents the data source, more on that later.

Flutter uses the Future class to represent a potential future value, similar to promises in JavaScript. So the method below (when called) checks the URL we define for JSON data, and unless there’s a problem notifies any listeners of the data:

At the bottom of the class, the getResponseJson method populates a dynamic list from the JSON response using the dynamic type, useful when the Dart code analyzer isn’t sure what the type might be:

Back in main.dart, first, fix another missing dependency by adding import 'package:provider/provider.dart'; to the top of the file, and provider: ^2.0.1 to the dependencies in pubspec.yml.

Now, time to create the ToDoList widget, add the following to the bottom of main.dart:

Some of this code is familiar by now, but there are also some new concepts.

The build method uses the BuildContext class, which is similar to a React context, to interact with parent widgets, in this case, the ToDoapp widget.

Inside the build() method, first define its state, which is the provider that listens for changes.

The widget returns a subsequent Scaffold class from the Material library that provides a default app bar (appBar: AppBar(),), title, and a body property that holds the widget tree for the home screen.

Inside the body() of the scaffold is a further SingleChildScrollView class that defines a view that child widgets can scroll in.

Inside the SingleChildScrollView widget are a series of Container, Center, and Column widgets. These are a selection of Flutter’s widgets (plus parameters for customizing layout) for positioning UI elements on a screen. You generally use the Container class as the overall parent, and the names of the child widgets are reasonably descriptive.

Finally, at the bottom of the widget tree inside the Column widget is a children property, which means the widget has more than one direct child. In this case, it’s a RaisedButton Material widget that when clicked (the onPressed event) fetches data via the fetchData() method of the custom AppState class. Notice that even the text of the button is another child Text widget.

The second of the two children is another custom class that defines how to display our data. Create a new file in the lib folder called response_display.dart, and add the code below to it:

And add import 'package:fluttertodo/response_display.dart'; to the top of main.dart.

A lot of this code should start to be reasonably familiar now, so I focus on the new concepts.

This if/else statement checks the current value of the isFetching variable from the AppState class, displaying a CircularProgressIndicator widget until the data is ready, and then loads a ListView widget to display the list of data.

The builder() method is similar to the build() method of other widgets but creates a scrollable list of widgets that are created on-demand as they become visible by the user scrolling through them.

The shrinkWrap property defines if the list of contents determines the extent of the scroll view, true makes sense for this use case.

The length of the JSON response determines the itemCount of the list, and the itemBuilder creates the actual widget instances for each item in the list. In this example, it’s just a Text widget that displays the content of a key from each item in the JSON, but it could also be another widget.

This version of the app doesn’t aggregate tasks from multiple sources yet, as that involves a lot of backend work outside of Flutter. At the moment, this version is set up to fetch the cards for a specific member from Trello. In the long run, I will create a backend that combines To Do feeds and provides a feed with images, an original service link, and more. Changing the feed changes the UI code a little, but not too much, depending on how complex you want to make each list item widget.

Build and Run

With the code in place, run the web version with:

And to create a desktop version (currently macOS only, and you need XCode):

But, you may find that some plugins lack desktop support yet.

And finally, for Android support, after setting up your Android development setup, you can run as usual from your editor, or with flutter run -d {EMULATOR_NAME}.

Worth a Flutter?

Forgive my use of British slang in the heading, but I couldn’t resist. After experimenting with Flutter, is it worth it? Once you start to understand the basic concepts, development becomes relatively quick, though that learning curve is initially steep. If you have used similar cross-platform or widget-based frameworks before then, that learning curve is shorter.

Leave a Reply

Your email address will not be published. Required fields are marked *