If you’re reading this article, you’ve probably heard about Flutter, the trending new cross platform mobile application SDK. Not only have you heard about it, but you’re probably also interested what makes this new tool better than React Native, and why you would want to learn a new language or framework. Hopefully, this article will answer some of your questions.
Let’s see the difference
So, there is React Native and there is Flutter. What’s the difference?
React Native uses the reactive paradigm: when something changes in the tree of Components, its diff algorithm calculates what should be re-rendered.
You write the code for your application in Dart, with the use of composable elements called Widgets. You have simple predefined building blocks like Text, Center, Padding, etc. and you nest widgets (eg. Text widget into Padding widget) to apply styling. You use these basic components to build your complex UI.
Flutter also uses the reactive paradigm, so when something changes in the widget tree then its diff algorithm calculates what should be re-rendered.
While you’re developing your application, Flutter uses JIT (Just In Time) compilation for fast development cycles. In a production app, Flutter uses AOT (Ahead of Time) compilation for fast and reliable native code.
But what makes Flutter exceptionally fast isn’t the compilation. Flutter doesn’t use the Views of the target platform, but instead draws its own Views using a 2D rendering library called Skia. So basically, Flutter provides widgets that look like native Views.
React Native is a big win for mobile developers, and was an inspiration for Flutter, but Flutter takes the cross-platform development a step further by fine-tuning the formula.
Application metadata file
Similar to React native, Flutter has its own metadata file where you can provide some information about your application. This information consists of the package name, the runtime and development dependencies, and the assets and fonts your app wants to use. Flutter doesn’t use the JSON format for its metadata file, but instead goes with YAML, which is a human-friendly data serialization standard. By convention, this metadata file is called pubspec.yaml, and located in the lib/ directory of your project where the dart code of your application goes.
If you modify the list of dependencies in the pubspec file, you need Flutter to download or remove the affected packages. In this case, you should run the following in the root of your project:
flutter packages get
Application entry point
Every application needs an entry point where it begins its runtime. In Flutter, this entry point is the ‘main()’ method in the main.dart file (by default), where you bootstrap your application by calling the ‘runApp()’ method with a widget as a parameter. Generally, this will be your MaterialApp widget, but you can call it with a simple Text widget, too.
You can have multiple dart files with separate runApp() methods, and you can tell Flutter which file to use as an entry point. This way, you can have different runtime configurations for your app.
And then you can run your application like this:
flutter run -t “lib/main_dev.dart”
There are three kinds of Widgets you can use to build your application. Two of them can be viewed as UI focused, and the third can be viewed as data focused.
The StatelessWidget is a UI focused widget that can be used to render a part of the UI. It can receive input data which it can then use to build itself. You can think of it as a “dumb” view which simply renders the UI.
Stateless widgets will rebuild when their parent widget is rebuilt.
Check the constructor of the widget, where I passed the message parameter in curly braces. That’s how you can use named parameters in Dart. I’ve also made the message parameter required by using the ‘@required’ annotation. It’s recommended to use named parameters, they make your life as a developer easier in the long run.
You can create an instance of this widget with the required named parameter:
The StatefulWidget is like the StatelessWidget on steroids. It too can be used to build a part of your UI, but it can also have its own internal state, and react to various lifecycle events.
Stateful widgets will rebuild when their parent widget is rebuilt, or when you call setState() in their state.
As you can see, I’ve modified the previous widget to be a StatefulWidget. Now the state of the widget builds the UI instead of the owner widget, which only provides the state object.
The state contains a single value called ‘upperCase’, which is responsible for how the text is rendered.
To modify the state of the widget, you don’t directly modify the state values, but call the setState() method, which will cause the state to rebuild the UI. If you directly modify the state values, the UI will not respond to the changes.
I’ve also overwritten the initState() and dispose() methods provided by the generic State class. initState() is called when your widget is placed in the widget tree (in React Native this would be the componentDidMount() method), and dispose() is called when it’s removed (in React Native this would be the componentWillUnmount() method).
Stateful widgets also have the didUpdateWidget() and didChangeDependencies() lifecycle hooks which you can react to.
Inherited widgets are a special kind of widget, because they are not responsible for building the UI. Instead, they can be used to efficiently propagate information down the widget tree, and notify widgets about any value changes.
This inherited widget will be initialized with some values, and it will provide values to its children:
Notice how MyExampleWidget gets the values from MyInheritedWidget. In this case MyInheritedWidget acts as a data provider and MyExampleWidget acts as a data consumer.
It makes more sense using inherited widgets when you want to access the data they provide way more deeper in the widget tree, and you don’t want to pass the data through multiple constructors every time you need the provided value.
Flutter currently provides stack and tab navigation only; if you want to use drawer navigation, you have to implement the logic yourself.
Stack navigation is provided by MaterialApp, so this should be your root app widget. There are two ways to push widgets to the stack: the first one is by using named routes, and the second one is to push widgets directly by using MaterialPageRoute.
Defining named routes
For the first solution, the MaterialApp widget has a property called ‘routes’ where you can define a map of widgets. The keys of the map will be the named routes, and the values will be widget builder methods that return a widget for the given route.
When you are using named routes, you have to either define the ‘/’ route, or set the ‘home’ property on the MaterialApp widget.
Navigation with named routes
When you want to navigate, first you should get an instance of the Navigator by calling Navigator.of(context), where “context” is an instance of BuildContext.
Then, on the Navigator instance you should call the pushNamed() method, with the route you want to navigate to as the argument.
You can also call the pushReplacementNamed() method with a route name argument to replace the topmost route with the given route.
Navigation with MaterialPageRoute
You don’t have to use named routes to navigate; you can push widgets directly to stack by using MaterialPageRoute. The advantage of this method is that you can customize the transition animation between two routes, and it’s easier to pass data when navigating.
If you are using this method, you don’t need to get the instance of the Navigator as written before. Instead, you call the static push() method on Navigator with a context and a MaterialPageRoute instance as arguments.
As you can see, the Navigator has a pop() method as well, which will pop the topmost route from the stack.
Hopefully this article has given you some insights into Flutter, and the motivation to try out the SDK.
As you can see, Flutter is similar to React Native, as it uses the reactive paradigm and the composition of views (widgets vs components), but it’s different in that it renders its own views instead of platform specific ones. By taking over the drawing and rendering of the views from the platform, it mostly solves the problem caused by platform segmentation, and gives the applications an always welcome performance boost.