Flutter is the new shiny framework for mobile development by Google, it was first released last year and this month it reached the 1.0 version. I've known Flutter for some months now, but since it reached 1.0 I decided to take a deeper look at it. I've developed mobile applications before using native Android with Java and cross-platform with Xamarim and NativeScript, it was not anything serious, but it is an area I find interesting and some people can argue, it is the near future.
Presentations done, now let's go to the juicy details. In my opinion, the most remarkable feature of Flutter is how easy it is to understand its layouts mechanics. Basically, everything is a widget and you can put a widget anywhere you want. The widgets are very descriptive, have attributes that cover almost all common usages, it is effortless and simple to build a layout. Since everything is a widget, the framework is very permissive, dynamic and logical, you have no fear of doing something wrong, just do it, and with hot reload as fast a blink you can test everything easily.
My experience with Flutter is amateur level, I did the most basic tutorial on Flutter website, read some things about how Flutter handles layouts, principally this great article that summarizes everything you need to know about Flutter layout structure. I would never dare to try replicate Twitter layout in none of the aforementioned frameworks, but with Flutter I had enough confidence to do it because I had no fear of failure.
So we have something like this:
To make it simpler, I made the layout taking in consideration only simple tweets, meaning only with text, no images, quotes, etc. Just to start. However, the rest will be present, the app bar at the top, the floating button to tweet and the bottom bar with tabs. Does it look too complex? Divide and conquer is the best approach for that!
First, let's see the app bar at the top. We can see that we have a circle avatar and a text written "Home". An app bar is a very common design pattern in Android apps and Flutter knows that,because of that Flutter has widgets to make it easy to build one. To do that we use the Scaffold
widget that has a parameter called appBar
that accepts an AppBar
widget as follows:
return MaterialApp(
title: "Twitter Layout",
theme: new ThemeData(
primaryColor: Color.fromRGBO(21, 32, 43, 1.0),
accentColor: Colors.blueAccent[200],
),
home: new Scaffold(
appBar: new AppBar(
title: Row(
children: <Widget>[
Container(
child: CircleAvatar(
child: new Text("L"),
radius: 15.0,
),
margin: EdgeInsets.only(right: 30.0),
),
new Text("Home")
],
),
elevation: 4.0,
),
body: _buildBody(context)
),
);
As you can see, the Scaffold
widget is wrapped by a MaterialApp
widget that makes is it easy to build apps using Material Design, design directives for Android apps by Google, which the Twitter app clearly uses. The AppBar
widgets has a title
parameter that accepts any widget, although generally it is just a Text
widget. Therefore, I created a Row
widget that is responsible to place its children aligned horizontally, the way we want in this case. We add the children of the Row
widget in a children
parameter. In this case the widget has a Container
and Text
as children. The Container
widget is a very convenient widget because it enables you to custom spacing, positioning and painting configurations to its child. In this case, I added a CircleAvatar
widget as its child and added a margin of 30 pixels to the right of the circle to give separation from the "Home" text. The child of a CircleAvatar
widget can be any widget, but we generally use text or an image, to make it simple I used a Text
with the letter "L". The radius indicates the size of the circle. If you notice, the circle on the app bar is a little smaller than the ones on the tweets, so I had to configure that. The parameter
on the AppBar
widget indicates the elevation of the app bar, it is what gives that shadow below it. In the end it was not necessary to make an app bar, a circular avatar and all the spacing configuration from the ground up, Flutter makes it easy to achieve the result you want with the use of common patterns. The Scaffold
widget also has a body
parameter, where, in this case, all the app content resides. Let's get into it.
To organize the layout I made the body building logic on a _buildBody()
method which is as follows:
Widget _buildBody(context){
return new Container(
child: new Column(
children: <Widget>[
new Flexible(
child: Scaffold(
body: new ListView.builder(
itemBuilder: (_, int index) => _tweets[index],
itemCount: _tweets.length,
reverse: false,
),
floatingActionButton: FloatingActionButton(
onPressed: null, child: Icon(Icons.edit)),
)),
new Container(
decoration:
new BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTabsBar(context),
),
],
),
);
}
The _buildBody()
return a Widget
, which is, in this case, a Container
. The container has a Column
widget as child, which all the magic happens. In the image used as base above we can see that we have the list of tweets and right below it the tabs bar, besides the floating button. The children of the Column
widget are placed the way they appear on the screen. First, we have a Flexible
widget and, as the names suggests, it is flexible, meaning in this case that it will occupy the remaining of the space of the parent, this is where our list and floating button are. The Flexible
widget child is a Scaffold
widget. Nobody said that it can be used only was the main widget of a an app like we used before although it is very common that way. It is used here to facilitate the creation of the floating button since it has a floatingActionButton
attribute, which takes a FloatingActionButton
widget with an edit icon as child. It also has an onPressed
attribute to configure the event, but since the goal here is just to make the layout, it is null
for now. Only this will make the floating button appear exactly where we want it. As before, on the body
parameter of he Scaffold
widget, we have the main content, the list of tweets, that is implemented using ListView
, which is kind of a specialized column that enables scrolling when the content does not fit entirely in the widget. It simply builds the list from a list tweets, which we will see later.
For now, let's focus on the other child of the Column
, the container which has the method _buildTabsBar()
as child. Let's see how the tabs are structured.
Widget _buildTabsBar(context) {
return Container(
height: 60,
color: Color.fromRGBO(21, 32, 43, 1.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(Icons.home, color: Theme.of(context).accentColor),
Icon(Icons.search, color: Colors.grey[100]),
Icon(Icons.notifications_none, color: Colors.grey[100]),
Icon(Icons.mail_outline, color: Colors.grey[100])
],
),
);
}
Making the bar is very simple and that's what makes it fantastic. It is just a Container
with a Row
as child that has the icons as children, that's it. I configure the height of the container and its color. Then, in the Row
I used the mainAxisAlignment
attribute to make the space be evenly distributed between the icons making look like the original layout. I still remember how it is a pain to center things using HTML and CSS, not to mention make the space even.
So, let's see the last part, the tweet. It looks like the part where there are more elements to take care of and it is right, but it is not that much more complex than the rest of the layout if you visualize it as a collection of rows and columns. To make the tweet reusable and easy to use in a ListView
I created a Tweet
class containing all the layout for the tweet:
class Tweet extends StatelessWidget {
Tweet({this.user, this.userHandle, this.text});
final String user;
final String userHandle;
final String text;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: Color.fromRGBO(21, 32, 43, 1.0),
border: Border(bottom: BorderSide())),
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CircleAvatar(
child: Text(user.substring(0, 1)),
),
_tweetContent()
],
),
);
}
...
}
To make it simple, it is stateless and only gets the user, user handle and the tweet text to build the tweet. A full-fledged tweet would probably be statetul because of the counts for likes, retweets and answers and have much more parameters. The build()
method returns a Container with the basic layout configuration, padding and a border at the bottom acting as a divider for the tweets, then it has a Row
as child. The row contains the circle avatar of the user who tweeted and the tweet content, which is built through a method for organization sake. mainAxisAlignment
and crossAxisAlignment
set as start
is very important because it makes the circle avatar to be placed at top-left corner as needed.
The _tweetContent()
method returns the content of the tweet, of course. Below is the entire method since it is easier to connect the dots by seeing all elements, after that I will explain the code.
Widget _tweetContent(){
return Flexible(
child: Container(
margin: EdgeInsets.only(left: 10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
children: <Widget>[
Text(user,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold)),
Container(
margin: EdgeInsets.only(left: 5.0),
child: Text(userHandle + " · 30m",
style: TextStyle(color: Colors.grey[400])),
)
],
),
Container(
margin: EdgeInsets.only(top: 5.0),
child: Text(text, style: TextStyle(color: Colors.white))),
Container(
margin: EdgeInsets.only(top: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
Icon(Icons.message, color: Colors.white),
Container(
margin: EdgeInsets.only(left: 3.0),
child: Text("15",
style: TextStyle(color: Colors.white)),
)
],
),
Row(
children: <Widget>[
Icon(Icons.repeat, color: Colors.white),
Container(
margin: EdgeInsets.only(left: 3.0),
child: Text("15",
style: TextStyle(color: Colors.white)),
)
],
),
Row(
children: <Widget>[
Icon(Icons.favorite_border, color: Colors.white),
Container(
margin: EdgeInsets.only(left: 3.0),
child: Text("15",
style: TextStyle(color: Colors.white)),
)
],
),
Icon(Icons.share, color: Colors.white)
],
),
)
],
),
),
);
}
The parent element is a Flexible
widget which allows the element to occupy the rest of the space available, the space on the row created on the build
method above. Then we have a Container
which allow to configure the margin necessary on the left of the content to separate it from the circle avatar. Finally, we have a Column
, where the fun starts.
Again we have crossAxisAlignment
and mainAxisAlignment
as start
to make the content to be positioned a the top-left corner. The first child of the column is a Row
that contains the user name, its handle and the time of the tweet. To make the user handle be slightly separated from the user name we wrap the Text
into a Container
as we've done before. The second child of the column is a Container
that wraps the actual text of the tweet with a margin at the top. Finally, we have another container with a Row
as child to build the reply, retweet, like and share buttons. mainAxisAlignment
is set as spaceBetween
so to that all icons will have an equal space between them and will be evenly distributed through the width of the row. Very convenient configuration, positioning and spacing was always something very troublesome for me in other frameworks. Each icon is a Row
itself containing the icon and the number of replies, RTs, etc beside it. Simple, isn't it?
So let's see the final result below:
It is close enough except for the icons which are probably Twitter exclusive. The tweets are just a sample.
I hope this post will be of any use for people who are thinking about taking a look at Flutter and how to get started. For me it was love at the first sight when it comes to layout creation and unless it is limited about interactivity, where I still have to experiment, I believe that it will be popular very soon!
Top comments (8)
Hey, this was an amazing write up and it's nice to see Flutter getting used more in Brazil, but you should take note to the fact that splitting widgets to methods is a performance antipattern. I will also update mine fixing this problem (and also adding BLoCs!) but yeah, we shouldn't really use manual widgets outside of the build method.
Great to know, but I was just following the documentation on this:
It is probably because it is a starter level tutorial and they didn't want to complicate things, maybe it is written somewhere in the more advanced documentation, but I will keep that in mind.
Sadly the documentation is very basic (for now, I hope) and doesn't cover most of the best practices, so most things are still being discovered by the community.
Well, we can say that it is the very best way to learn something, when it is still new and there are many details to discover. :)
Thanks for this tutorial. Building a clone app layout helps a lot in learning about the programming ecosystem. Learned a lot about flutter UI layouts and design while going through this article. Stepwise guidance with code and screenshots are easy to follow. These type of social app templates help a lot to develop own. There are a lot of flutter templates that support every functionality available in social apps. These templates help to learn as well as develop their own app with powerful features.
Hi,
I hope to get your consent to translate and shared with Chinese developers, I will indicate the source and author.
Sure, you can translate it. Share the link once you are finished although I will not understand anything. hahaha
hahaha, ok,Thanks !