π Everyone!
Welcome to the 1st part of the Flutter TODO app-building series. In this article, we are going to build the main screen on which all the TODOs from a user are shown.
To know more about the project you can go to Introduction article.
This article is complementary to the video series on YouTube, so if you are more of a video person then you can go and watch the video ππ
Breaking The UI Into Parts.
How a user will look at it | How a DEV should look at it |
---|---|
Each part is marked with a number and I have explained what they are and which widget I have used in Flutter.
So as you can see that we have Scaffold
as the parent and then we have appBar
, body
, and floatingActionButton
.
- We have an
AppBar
in which we have atitle
property which is a type ofWidget
so we use aText
widget. - On the trailing(actions) side of the
AppBar
we have anIconButton
which on clicking will log the user out of the app. - Moving to
body
we can see that the date button and our TODO list are vertically aligned, so which widget we can use for this? Yes, you have guessed it right we are going to use aColumn
so the first child is a simpleTextButton
(Added in Flutter 1.22). - Next child in our
Column
will beListView
to be specific as we are going to add data dynamically so we need to use aListView.builder
(This works like aRecyclerView
in Android). - Inside our
ListView.builder
each child is aDismissible
so that the user can dismiss them to delete.Card
is the child of it which gives it an aesthetic look and child is aListTile
. - Getting to
ListTile
we havetitle
which also takes the type ofWidget
so again we are using aText
widget. - Just below the title we have a description and date of creation.
ListTile
also has a property for this which issubtitle
again you have guessed it we need aWidget
but this time we are going to pass aColumn
not aText
widget because we need to have them vertically aligned. Next inColumn
, we have 2Text
widget that is for description and date of creation. - To the left end we have an Icon, to get this we add
leading
inListTile
that is anIconButton
. - To the right end we have again an icon but this time it is a simple
Icon
which is in thetrailing
ofListTile
position. - Finally we have a
FloatingActionButton
with anIcon
as a child.
Oof, this was so looonnng!!, now that you can read the UI we can start building it!! π
It's CODE TIME!!!
First of all, we are going to start by making a StatefulWidget
.
class TODOScreen extends StatefulWidget {
TODOScreen({Key key}) : super(key: key);
@override
_TODOScreenState createState() => _TODOScreenState();
}
class _TODOScreenState extends State<TODOScreen> {
@override
Widget build(BuildContext context) {
// We are going to start building here
}
}
Then we can work on Scaffold
return Scaffold(
appBar: // This is where we are going to add our `AppBar`,
body: // This is where most of the code will go,
floatingActionButton: // This is where we will add `FloatingActionButton`,
);
For the AppBar
we will have a title property and also in action
we will have IconButton
for logout
appBar: AppBar(
title: Text("My TODO app"),
actions: [
IconButton(
onPressed: () {},
icon: Icon(Icons.exit_to_app),
)
],
),
In the body
, we have Column
which contains TextButton
and ListView.builder
.
body: Column(
children: <Widget>[
// Here we will have `TextButton` and `ListView.builder`
],
),
For time being we can just make a simple button with a Placeholder text, then we can work on logic and show the actual date.
TextButton(
onPressed: () {}, // Here we will call `DatePicker`
child: Text("Date"), // In place of "Date" we will add the date which is selected
),
Below the button, we have our ListView.builder
We need to make
shrinkWrap
astrue
inListView.builder
since it is inside aColumn
otherwise this will shower you with errors (I hope you don't like themπ) and will makeitemCount
as 3.
ListView.builder(
shrinkWrap: true,
itemCount: 3,
itemBuilder: (context, index) {
// Here we have to return our list item
},
)
To make our Tasks(items in ListView.builder
) have swipe to delete we need to start by wrapping it with a Dismissible
which will give us properties such as direction
to select the direction of dismissing, background
which is stacked behind the child
of Dismissible
and only show up when the child is swiped.
Dismissible(
// This should be unique for each item hence we are using index
key: ValueKey(index),
// As written above this will be stacked behind the `child`
background: Container(
alignment: Alignment.centerLeft,
color: Colors.red,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.delete),
),
),
direction: DismissDirection.startToEnd, // Only allow to swipe in reading direction
child: // Widget which is actually shown on screen
);
For the child
we are going to have a Card
-> ListTile
then we can add:
-
title
which will show task title -
subtitle
which will be aColumn
and have task description and created date -
leading
which will be anIconButton
to show which task is completed and mark a task as completed -
trailing
which will be anIcon
Card(
child: ListTile(
leading: IconButton(
onPressed: () {},
icon: Icon(Icons.check_circle_outline_outlined),
),
trailing: Icon(Icons.arrow_forward_ios),
title: Text("Title"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Desc"),
Text("Date"),
],
),
),
),
What you can see on your screen?... Yes, this looks kinda weird, Let's fix it.
To fix it first we need to remove the default margin from Card
.
Card(
margin: const EdgeInsets.all(0.0),
.
.
),
Now cards are sticking to each other. This could be easily solved by wrapping Padding
around Dismissible
.
return Padding(
padding: const EdgeInsets.all(8.0),
child: Dismissible(
.
),
),
Yes, this looks much betterπ
Finally, we can work on our FloatingActionButton
! It has a single child which is an Icon
.
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
Congratulations you have completed your UI building!! ππ₯³
Adding Some Logic
Now we can add some logic to change the date which was in the TextButton
.
Remember I told you that in place of "Date" we can put the actual date? Now we are going to work on that.
Let's start by making an instance variable in our State
class and initializing it with DateTime.now()
which will assign it today's date.
DateTime _date = DateTime.now();
Now we need to format our _date
variable and show it to users. For this, we are going to use a package from pub.dev named intl into pubspec.yaml
.
dependencies:
# Other dependencies
intl: ^0.16.1
intl
gives us a lot of formatting options you can look at them by yourselves over here.
For formatting date, we are going to pass _date
to format
method from DateFormat
class which will format it.
We can now replace the "Date" with it so the user will get a clear idea of which tasks are for user on which date.
TextButton(
onPressed: () {},
child: Text(
DateFormat("dd/MM/yyyy").format(_date).toString(),
),
),
Now as the user can see the date they also might want to change, so we are going to implement a date picker in it. As material
library already gives a date picker we are going to use that. For that, we will make another method in our class
.
_showDatePicker() async {
// To make a variable with today's date as we are going to use it a lot of time
final today = DateTime.now();
// This will return a `Future<DateTime>` hence we are awaiting it.
final selectedDate = await showDatePicker(
context: context, // We get it from the `State` class
initialDate: _date, // This is the date which is selected in the date picker
firstDate: DateTime(2020, 11, 1), // This is the start range in date picker which is selectable
lastDate: DateTime(today.year + 1, today.month, today.day), // This is the end range in date picker which is selectable
);
// if user have selected any date and selected date and current date
// is not same then update the date and update the UI
if (selectedDate != null && selectedDate != _date) {
setState(() {
_date = selectedDate;
});
}
}
As we have written the method _showDatePicker()
we can call it when the user clicks on the Button.
TextButton(
onPressed: () {
_showDatePicker();
},
child: Text(
DateFormat("dd/MM/yyyy").format(_date).toString(),
),
),
Woah you have completed your first screen!! π
You can go over here to see the whole code.
Thanks for reading, if you wish you can also join our community on Discord.
See you soon! π Happy Fluttering.
Top comments (0)