Exploring Flutter as a React Developer

Eugen Sawitzki
comsystoreply
Published in
9 min readJan 18, 2022
Flutter

React has been my favorite front end technology for almost four years now. I have also worked with Angular JS, Angular >= v2, Vue.js and some Java based front end frameworks, but I fell so deeply in love with React that for a long time I simply didn’t want to try out other things. So I didn’t jump on any hype train in the last few years, because I simply thought the new stuff is not doing anything better than React.
And I thought the same when I first heard about Flutter. Until the beginning of 2022. At this time Flutter seemed to be everywhere. At least from my perspective. More and more clients are looking for Flutter developers. Colleagues were talking about it and how easy it is to get started with.
So just out of curiosity I decided to give it a try and see for myself what Flutter and Dart (the programming language Flutter is based on) are all about.

In this blog post I want to share my experiences during my first steps with Flutter, show you some things which felt familiar for me as a developer working mainly with React and also tell you a little about some features of Flutter and Dart which really impressed me.
What you should not expect to find in this post is a fully fledged Flutter tutorial or a comprehensive comparison between Flutter and React. It’s just about the thoughts I had while playing around with it.
I have implemented a small app, which for now just includes the setup needed to run it on Android. You can find the full code here.

The Plan

First I had to think about what I actually want to implement and which features do I want to explore.
The first point which immedeiately came to my mind was fetching data from an API. Any modern front end framework has to be able to make HTTP requests as conveniently as possible. For me one of the simplest to use APIs out there is the PokéAPI. So I decided to use it for this app as well.
The next requirements were state management and data flow between parent and child components. Most modern JavaScript based front end technologies do a great job here, so how does Flutter deal with these things?
This was already good enough to get a first feeling about how convenient it will be to work with Flutter. During implementation some more points came up, which I really wanted to try in order to really be able to form an opinion. These have been local persistence and routing.

Setup and creating a new App

The setup required to start writing Flutter apps is as easy as it should be. Just follow the official documentation and you are good to go. Briefly summarizing what needs to be done:

  • Install the Flutter SDK and add it to your PATH environment variable.
  • Install either XCode (IOS) or Android Studio (Android) or both, in order to build apps for these target platforms and to use the device simulator. As I am using Intellij IDEA for my daily work, I just had to add the Android SDK to it and didn’t need to install an additional IDE.
  • Configure a virtual mobile device or connect your physical device.
  • Create a new Flutter project directly from your IDE and you are ready to try the example app.

This is the first point where Flutter impressed me. The setup process was seamless and the dummy application that is provided when starting a new project already has a lot of features you need to know as a beginner.
The dummy app is a simple counter app. When you open themain.dart file, you will be able to explore features like StatelessWidget, StatefulWidget, theming, layout widgets like Column and Center and the very powerful Scaffold widget, which makes it easy to create an app with an app bar, a floating action button, a drawer menu and many things more.

If you want to learn more about what widgets are and which exist you should take a look at this part of the documentation: https://docs.flutter.dev/development/ui/widgets-intro

What does a React Developer recognize?

The first thing I noticed when I was looking at this dummy app was how the state management looks like. I don’t want to make this a React tutorial so I will just briefly show which two types of state management exist in React without using any additional libraries.
There are class based components which have an instance variable called state. This variable holds all the state your component needs to manage. To update the state you have to call this.setState with an object containing the piece of state you want to overwrite.

class MyComponent extends Component {
state = {
someObject: {...},
someList: [...],
someString: "someString",
someNumber: 1,
...
}
...setSomeString = (newString) => {
this.setState({someString: newString});
}
render() {
return <div>Hello World</div>;
}
}

Moreover (and is the more modern way of writing stateful components) there are function components. These make use of so called React Hooks and are able to update each piece of state independently.

function MyComponent() {
const [someObject, setSomeObject] = useState({...});
const [someList, setSomeList] = useState([...]);
const [someString, setSomeString] = useState("someString");
const [someNumber, setSomeNumber] = useState(1);
... const updateSomeString = (newString) => {
setSomeString(newString);
}
return <div>Hello World</div>;
}

In Flutter state management looks like a mix of these two approaches. All your Widgets are classes. Instead of having a single state instance variable you have multiple ones. One for each piece of state. Similar to functional React components. Like in React with class based components you have to call the method setState in order to perform an update. And also here you just have to overwrite the variable you actually want to change.

class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);

@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Object _someObject = {...};
List<> _someList = [...];
String _someString = "someString";
int _someNumber = 1;
void setSomeString(String newString) {
setState(() { _someString = newString; });
}
@override
Widget build(BuildContext context) {
return Container(
child: const Text('Hello World'),
);
}
}

And the similarities don’t stop here. Also the way parent widgets communicate with child widgets and vice versa is pretty much the same as in React. Parents provide values and callbacks to their children. The children use the values to calculate and render something and call the callbacks, whenever the parent needs to be informed about some action.
In general React developers will feel at home right away when trying Flutter for the first time. Putting aside the fact that you have to use Dart instead of JavaScript/TypeScript. Both technologies work in a declarative way. Meaning, you don’t perform each UI change manually but rather change the value of the state and the UI reacts to these state changes. You can find more to read about the declarative UI here.

Now let’s see what working with HTTP requests looks like.

Requests and JSON

As mentioned already, a modern framework should make it as easy as possible to perform HTTP requests. In Flutter/Dart this is definitely the case. In my example app I wanted to fetch a Pokémon by a random id.

GIF showing how a random Pokemon is rendered in the app.
Fetching random Pokémon

All I needed to do was to add the dependency on the http package into the pubspec.yml file and run flutter packages get. As simple as installing an npm package.
The Code for fetching data looks like this:

Future<Pokemon?> fetchPokemon(int index) async {
final Uri url =
Uri.parse("https://pokeapi.co/api/v2/pokemon/$index");
var response = await http.get(url);
if (response.statusCode != 200) {
return null;
}
return Pokemon.fromJson(jsonDecode(response.body));
}

async/await works the same way as in JavaScript. Just with a Future instead of a Promise.
Looks quite neat, doesn’t it? But just because converting the response.body to the Pokemon type is not visible here. jsonDecode just creates a Map<String, dynamic>. So you still have to manually covert this Map to the needed type.
After searching the web, I found one solution which looks okay, but is not 100% pretty. The recommended way to create a Type from a JSON response is to use a factory constructor and cast each entry in the Map to the needed type.

factory Pokemon.fromJson(Map<String, dynamic> json) {
return Pokemon(
name: json['name'] as String,
id: json['id'] as int,
imageUrl: json['sprites']['front_default'] as String
);
}

Local Persistence

As you can see in above GIF my plan was to add a button under each random Pokémon to be able to catch them. Caught Pokémon then should be stored in the state and also be persisted locally, so that the state gets restored when you open the app the next time.
Flutter has three native ways to persist data on your device.

  • SQLite Database
  • Key-Value-Store (like LocalStorage in the browser)
  • Writing/reading files to/from the file system

For my use case I went with SQLite. And what do I have to say here? It just works as advertised. I followed the documentation and wrote a database connector Singleton class. When I start the app and create the Singleton, the database connection is established and the table gets created, if it doesn’t exist yet. Afterwards I can easily write and read data to and from the database. You can see the whole connector class here.

Routing

The last feature I wanted to try is routing. As Flutter is some kind of a mobile first framework, I expected that routing will be simple to handle, as it is an essential feature for mobile apps.
And also in this case Flutter didn’t disappoint me. Flutter has multiple ways to define routes and navigate between them. I went for the solution using the Navigator with named routes. This is one of the recommended ways for smaller application with just a few routes.
So I defined the routes in the build method of my root widget and was able to call Navigator.pushNamed(...) when ever I want to navigate to a different route.

@override
Widget build(BuildContext context) {
return MaterialApp(
...
initialRoute: '/route-random-pokemon',
routes: {
'/route-random-pokemon': (context) => RandomPokemonPage(
caughtPokemon: _caughtPokemon,
onCatchPressed: _addToCaughtPokemon,
drawer: _getListDrawer(context),
),
'/route-caught-pokemon': (context) => CaughtPokemonPage(
drawer: _getListDrawer(context),
caughtPokemon: _caughtPokemon,
),
},
);
}

Again, very easy and elegant. But this was not the last time I was surprised about how easy such complex things can be implemented.
In the above snippet you can see, that I provide a drawer prop to my pages. That’s where I have put my links to switch the routes. Before I tried to add a drawer to my pages, I thought it will be a lot of work. Especially as the user would expect to be able to both open the drawer via swiping from the side and via a burger menu in the app bar. Here, the Scaffoldwidget really shines. I just had to add a Drawer widget to the Scaffold and it automatically added a burger menu and made it possible to open it via swiping.

Gif showing how routing from the RandomPokemonPage to the CaughtPokemonPage works.
Navigating to CaughtPokemonPage

Conclusion

I had a very nice developer experience playing around with Flutter. Implementing most use cases was as easy as I expected it to be in a modern mobile first framework. Some times even easier then I believed it could be.
Since I’m used to working with React, I recognized many patterns and paradigms. Coming from a declarative UI technology I felt at home right from the beginning. For some things like working with JSON responses I would look into some libraries to make it more convenient. If you happen to know one or want to recommend other libraries which are a must have for Flutter, I would really appreciate if you leave a comment.
All in all Flutter really caught my interest. When I will develop a mobile app the next time, I would choose Flutter to do it.

This blogpost is published by Comsysto Reply GmbH

--

--

Eugen Sawitzki
comsystoreply

Software-Developer at Comsysto Reply GmbH in Munich, Germany