Programming models: Flutter?
(By Sergio Guzmán)
This is based on the official docs.
Everything is a widget
Flutter has one core principle: everything is a widget. There is a readily available widget for pretty much everything you will use in your app, including:
Stylistic elements like fonts and colors
Layout elements like margins and paddings
Graphic components like buttons, lists, images, menus, etc
And... animations, user interaction, etc
Lets take a look at a really simple example using the MaterialDesign dart library (just to have a runApp
method implemented). This is our main
method, that just renders a text:
import 'package:flutter/material.dart';
void main() => runApp(
Text(
'Hello I am just a text',
),
);
We can encapsulate our text inside a layout widget (like MaterialApp for instance) to recognize that we have an available space on the screen and thusly change the style of the component:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Text(
'Hello I am just another text',
style: TextStyle(
color: Colors.amber.withOpacity(1.0),
),
)));
Now let´s make it more beautiful by using a default Scaffold widget from the material design lib and adding a FloatingActionButton of course.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text("My app"),
),
body: Text(
'Hello I am just a text',
style: TextStyle(color: Colors.blue.withOpacity(1)),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
),
),
));
Finally, we´ll give it some layout order too by centering the text in a Container:
import 'package:flutter/material.dart';
void main() {
return runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text("My app"),
),
body: Container(
child: Center(
child: (Text(
'Hello I am just a text',
style: TextStyle(color: Colors.blue.withOpacity(1)),
)))),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
),
),
));}
So yeah,everything is handled with a widget. Look at this awesome catalog to get an idea of all the widgets you can use with Flutter.
The downside of using widgets is the nesting hell generated by encapsulating a widget, inside another widget, inside another widget, and so on... To make your code look a little bit nicer during this "widget composition", class-oriented programming comes to the rescue
Nested hell is real, it's the reason why some devs dislike Flutter. Take a look at this Reddit´s post, and this Github issue to get an idea about it
Your own widgets
There are two types of widgets you should look out for: StatelessWidget and StatefulWidget. When you define your own widgets, you will extend from one or the other, depending on your needs.
You want something static? The StatelessWidget is meant for static things, like text, images, fonts, paddings and so on.
import 'package:flutter/material.dart';
class MyView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
Your screen will be changing over time? Go with Stateful
import 'package:flutter/material.dart';
class MyView extends StatefulWidget {
@override
State<StatefulWidget> createState() => _MyViewState();
}
class _MyViewState extends State<MyView> {
@override
Widget build(BuildContext context) {
return Scaffold();
}
}
The StatefulWidget will likely be calling the setState method to repaint the widget tree, depending on the state of your widget. These widgets are used a lot for handling user interaction.
When creating a custom widget, always override the build method, which returns a widget tree to be painted in your app.
So how would this look for our app? Let´s put the body of the MaterialApp widget inside a StatefulWidget and add a counter for the state.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button $_counter times',
style: TextStyle(color: Colors.blue.withOpacity(1)),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
If you notice, whenever we press the FAB button a new funtion is called (_incrementCounter
), that modifies the counter variable.
State management can be tricky, and that´s why we have a whole new section dedicated for this coming up!
Bonus: Expecting the future
Much like in JavaScript, Flutter has an implementation for creating asynchronous tasks and waiting for them: a future. A future is the result of an asynch task that has two possible states: uncompleted (the task is still in progress) and completed (the task ended). For a future to be fulfilled there are two possibilities:
Completed with a value:
Future<T>
will return a value of type TCompleted with error
Here´s an example from the devs at Flutter:
Future<String> createOrderMessage() async {
var order = await fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
// Imagine that this function is
// more complex and slow.
Future.delayed(
Duration(seconds: 2),
() => 'Large Latte',
);
Future<void> main() async {
print('Fetching user order...');
print(await createOrderMessage());
}
Notice the use of await
to wait for the result of a future and async
to declare a function that will have awaits
in its code. Without these two, when you call a future your code won´t wait for a result.
Also... for error handling use a try-catch
try {
var order = await fetchUserOrder();
print('Awaiting user order...');
} catch (err) {
print('Caught error: $err');
}
Futures are important when doing blocking operations (I/O mostly) and making requests to external services (APIs)
Last updated