DEV Community

Cover image for Part 1 - TODO app UI building using Flutter
Darshan Rander
Darshan Rander

Posted on

Part 1 - TODO app UI building using Flutter

πŸ‘‹ 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
Normal View DEV View

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.

  1. We have an AppBar in which we have a title property which is a type of Widget so we use a Text widget.
  2. On the trailing(actions) side of the AppBar we have an IconButton which on clicking will log the user out of the app.
  3. 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 a Column so the first child is a simple TextButton (Added in Flutter 1.22).
  4. Next child in our Column will be ListView to be specific as we are going to add data dynamically so we need to use a ListView.builder (This works like a RecyclerView in Android).
  5. Inside our ListView.builder each child is a Dismissible so that the user can dismiss them to delete. Card is the child of it which gives it an aesthetic look and child is a ListTile.
  6. Getting to ListTile we have title which also takes the type of Widget so again we are using a Text widget.
  7. Just below the title we have a description and date of creation. ListTile also has a property for this which is subtitle again you have guessed it we need a Widget but this time we are going to pass a Column not a Text widget because we need to have them vertically aligned. Next in Column, we have 2 Text widget that is for description and date of creation.
  8. To the left end we have an Icon, to get this we add leading in ListTile that is an IconButton.
  9. To the right end we have again an icon but this time it is a simple Icon which is in the trailing of ListTile position.
  10. Finally we have a FloatingActionButton with an Icon 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
  }
}
Enter fullscreen mode Exit fullscreen mode

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`,
);
Enter fullscreen mode Exit fullscreen mode

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),
    )
  ],
),
Enter fullscreen mode Exit fullscreen mode

In the body, we have Column which contains TextButton and ListView.builder.

body: Column(
  children: <Widget>[
    // Here we will have `TextButton` and `ListView.builder`
  ],
),
Enter fullscreen mode Exit fullscreen mode

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
),
Enter fullscreen mode Exit fullscreen mode

Below the button, we have our ListView.builder

We need to make shrinkWrap as true in ListView.builder since it is inside a Column otherwise this will shower you with errors (I hope you don't like themπŸ˜‰) and will make itemCount as 3.

ListView.builder(
      shrinkWrap: true,
      itemCount: 3,
      itemBuilder: (context, index) {
            // Here we have to return our list item
      },
)
Enter fullscreen mode Exit fullscreen mode

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
);
Enter fullscreen mode Exit fullscreen mode

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 a Column and have task description and created date
  • leading which will be an IconButton to show which task is completed and mark a task as completed
  • trailing which will be an Icon
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"),
      ],
    ),
  ),
),
Enter fullscreen mode Exit fullscreen mode

The issue
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),
  .
  .
),
Enter fullscreen mode Exit fullscreen mode

Now card are sticking to each other
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(
  .
  ),
),
Enter fullscreen mode Exit fullscreen mode

Completed
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),
),
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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(),
  ),
),
Enter fullscreen mode Exit fullscreen mode

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;
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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(),
  ),
),
Enter fullscreen mode Exit fullscreen mode

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.

Discussion (0)