-->
ads here

Build simple todo application on Android and iOS platform with Flutter

advertise here
Xin chào các bạn, tiếp nối bài viết trước giới thiệu về Flutter, hôm nay mình xin hướng dẫn các bạn xây dựng một ứng dụng di động trên cả 2 hệ điều hành Android và iOS bằng Flutter.
Trong bài viết này, chúng ta sẽ cùng nhau làm 1 Todos list application rất đơn giản nhằm hiểu rõ về các thành phần cơ bản của Flutter cũng như giúp các bạn đang có ý định tiềm hiểu về Flutter sẽ có một ví dụ để tiếp cận tốt hơn với platform mới mẻ này.
Nội dung của blog hôm nay như sau:
  1. Giới thiệu chức năng app, Sketch wireframe
  2. Phân tích các thành phần
  3. Khởi tạo project
  4. Render todo lists
  5. Đánh dấu 1 task đã hoàn thành
  6. Thêm task mới
Ok, chúng ta bắt đầu!

1. Giới thiệu chức năng app, Vẽ Wireframe

1.1. Giới thiệu

Lần này chúng ta sẽ cùng nhau làm một ví dụ về ứng dụng ghi chú task để quản lý công việc đơn giản. Ứng dụng cho phép hiển thị danh sách các task đang hiện có, thêm task mới và đánh dấu một task đã làm xong

2.2. Wireframe

Sau khi đã hình dung ra chức năng của ứng dụng chúng ta sẽ tiến hành vẽ wireframe để từ đó tạo giao diện cho ứng dụng. Ở bước này, mình dùng phần mềm Balsamiq Mockup để vẽ layout. Nếu muốn tiện các bạn có thể dùng giấy bút và tự vẽ cho tiện cũng OK.
Về cơ bản app sẽ gồm 3 layout sau:
Đầu tiên khi mới lần đầu sử dụng app, thì sẽ hiển thị một dòng “Empty task” để người dùng biết rắng chưa có task nào trong ứng dụng
Empty list
Empty task
Để tạo task, chúng ta click vào nút có dấu (+) sau đó nhập nội dung task và chọn “Add task"
Add new task
Add new task
Sau khi tạo xong thì task sẽ được thêm vào danh sách
Todo created
Display list of tasks
Và khi hoàn thành task nào, chúng check vào task đó và sẽ được highlight để phân biệt với các task chưa hoàn thành
Todo done
Mark a task as done

2. Phân tích các thành phần

Dựa vào wireframe, ta thấy được, ứng dụng gồm các thành phần sau:
  • Empty text: Text nằm giữa screen, chỉ hiển thị khi chưa có task nào được tạo
  • List of task: Dánh sách chứa các task item
  • Task item: Chưa nội dung của task và trạng thái đã hoàn thành hay chưa. Khi task đã hoàn thành thì sẽ có màu xanh
    • Text
    • Checkbox
  • Add Task button: Có dạng Floating action button. Khi click vào sẽ hiển thị dialog để tạo task mới
  • Dialog: Chứa text input để nhập nội dung task
Sau khi phân tích xong, tiếp theo chúng ta sẽ tiến hành khỏi tạo project và hiện thực ứng dụng.

3. Khởi tạo project với Android Studio

Trước hết bạn cần đảm bảo rằng môi trường để phát triển ứng dụng Flutter đã được setup trên máy tính. Nếu chưa có, bạn có thể tham khảo bài viết cũ của mình tại đây hoặc xem hướng dẫn tại trang chủ của Flutter. Trong bài viết này mình sẽ sử dụng Android Studio để khởi tạo project
  • Mở Android Studio, chọn Start new Flutter project
Start new project


  • Chọn Flutter Application và click Next
Select Flutter Application


  • Đặt tên cho Project, ở đây mình đặt là to_dos_list, sau đó click Next rồi Finish.
  • Chờ Android load xong thì chúng ta đã khởi tạo xong project Todo List bằng Flutter.

4. Render TodoList

  • Trong main.dart, chỉnh sửa như sau:
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Todo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new TodoList(),
    );
  }
}
class Todo {
  String text;
  bool checked;

  Todo({this.text, this.checked: false});
  String toString() => "$text - $checked";
}

class TodoList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new TodoListState();
  }
}
class TodoListState extends State<TodoList> {
  List<Todo> todos = [];
  
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text('Todos'),
      ),
      body: new Center(
        child: todos.length == 0 ? buildEmpty() : null,
    );
  }

  Widget buildEmpty() {
    return new Text('Empty Task');
  }
}

Chúng app của chúng ta sẽ có một title là Flutter Todo, màu chủ đạo là màu xanh. Phần này được khai báo trong class MyApp. Giao diện ứng dụng sẽ được render thông qua class TodoList
TodoList sẽ mở rộng StatefulWidget và override hàm createState() trả về một object TodoListState. Object này có chức năng render ra các thành phần của ứng dụng như list các todo, dialog tạo thêm task… đồng thời quản lý state của TodoList của chúng ta.
Trước tiên chúng ta sẽ định nghĩa 1 todo gồm những trường thông tin gì. Tạo class Todo với 2 field là text (nội dung của task) và checked (trạng thái task đã hoàn thành hay chưa). Sau đó định nghĩa constructor cho Todo. Constructor này sẽ trả về một object Todo từ 1 tham số là text.
Todo list của chúng ta sẽ được lưu trữ dạng mảng ở biến todos, ban đầu được khởi là là mảng rỗng, trong hàm build(), nếu danh sách rỗng.
  • Hiển thị empty text:
Trong hàm build(), ta kiểm tra xem nếu todos không có phần tử nào thì sẽ render ra dòng Empty Task, phần này sẽ được hàm buildEmpty thực hiện. Tiếp theo chúng ta tiến hành render từng item của todo list.
Empty task

  • Render todo Item
Tạo thêm file TodoItem.dart, như sau:
 import 'package:flutter/material.dart';  
 class TodoItem extends StatefulWidget {  
  final String text;  
  final bool checked;  
  TodoItem({this.text, this.checked: false});  
  @override  
  State<StatefulWidget> createState() {  
   return new _TodoItem(text, checked);  
  }  
 }  
 class _TodoItem extends State<TodoItem> {  
  bool checked = false;  
  String text;  
  _TodoItem(this.text, this.checked);  
  void changeChecked(bool value) => setState(() => checked = value);  
  TextStyle getTextStyle () => new TextStyle(  
   color: Colors.black,  
   fontFamily: "Rubik",  
   fontSize: 18.0,  
   decoration: TextDecoration.none,  
   decorationColor: Colors.grey[500],  
  );  
  @override  
  Widget build(BuildContext context) {  
   return new Container (  
    padding: const EdgeInsets.only(left: 15, right: 5),  
    margin: const EdgeInsets.only(top:0, bottom:0),  
    child: Row(  
     mainAxisAlignment: MainAxisAlignment.spaceBetween,  
     children: <Widget>[  
      new Text(  
       text,  
       style: getTextStyle(),  
      ),  
      Container(  
        margin: const EdgeInsets.only(left: 40),  
        child: Row(  
         children: <Widget>[  
          Checkbox(  
           value: checked,  
           onChanged: changeChecked,  
          )  
         ],  
        )  
      ),  
     ],  
    ),  
   );  
  }  
 }  
Sau đó import TodoItem vào main.dart như sau
import 'TodoItem.dart';
Tạo hàm buildTodoList trong TodoListState như sau:
List<Widget> buildTodoList() {
  return todos.map((item) => new TodoItem(text: item.text, checked: item.checked)).toList();
}

Sau đó update hàm build như sau:

...
@override
Widget build(BuildContext context) {
  // TODO: implement build
  return Scaffold(
    appBar: AppBar(
      title: Text('Todos'),
    ),
    body: new Center(
      child: todos.length == 0 ? buildEmpty() : new Column(
        children: buildTodoList(),
      ),
    ),
  );
}
...

Tạo thử 1 TodoItem như sau và thêm vào mảng todos để thử

List<Todo> todos = [new Todo(text: “Test todo item")];
  • Sau đó run và xem kết quả
Render todo list


5. Đánh dấu 1 task là đã hoàn thành

Theo như wireframe, khi một task được đánh dấu đã hoàn thành bằng cách tick vào check box, thì nội dung text sẽ được gạch ngang đồng thời background sẽ chuyển thành màu xanh lá cây. Ta sửa file TodoItem.dart như sau:

import 'package:flutter/material.dart';  
 class TodoItem extends StatefulWidget {  
  final String text;  
  final bool checked;  
  TodoItem({this.text, this.checked: false});  
  @override  
  State<StatefulWidget> createState() {  
   return new _TodoItem(text, checked);  
  }  
 }  
 class _TodoItem extends State<TodoItem> {  
  bool checked = false;  
  String text;  
  _TodoItem(this.text, this.checked);  
  void changeChecked(bool value) => setState(() => checked = value);  
  TextStyle getTextStyle () => new TextStyle(  
   color: checked ? Colors.red[500] : Colors.black,  
   fontFamily: "Rubik",  
   fontSize: 18.0,  
   decoration: checked ? TextDecoration.lineThrough : TextDecoration.none,  
   decorationColor: Colors.grey[500],  
  );  
  @override  
  Widget build(BuildContext context) {  
   return new Container (  
    padding: const EdgeInsets.only(left: 15, right: 5),  
    margin: const EdgeInsets.only(top:0, bottom:0),  
    decoration: new BoxDecoration(  
     border: new Border(top: BorderSide.none, left: BorderSide.none, right: BorderSide.none, bottom: BorderSide(color: Colors.grey[500] ,width:1, style: BorderStyle.solid)),  
     color: checked ? Colors.lightGreenAccent : Colors.transparent,  
    ),  
    child: Row(  
     mainAxisAlignment: MainAxisAlignment.spaceBetween,  
     children: <Widget>[  
      new Text(  
       text,  
       style: getTextStyle(),  
      ),  
      Container(  
        margin: const EdgeInsets.only(left: 40),  
        child: Row(  
         children: <Widget>[  
          Checkbox(  
           value: checked,  
           onChanged: changeChecked,  
          )  
         ],  
        )  
      ),  
     ],  
    ),  
   );  
  }  
 }  

Lưu lại và đợi hot reload, ta được kết qủa như sau:

Task done


6. Thêm task mới

Chúng ta sẽ tạo một Float action button để mở một dialog để nhập task mới. Ta sửa hàm build của TodoListState trong main.dart như sau:

@override
Widget build(BuildContext context) {
  // TODO: implement build
  return Scaffold(
    appBar: AppBar(
      title: Text('Todos'),
    ),
    body: new Center(
      child: todos.length == 0 ? buildEmpty() : new Column(
        children: buildTodoList(),
      ),
    ),
    floatingActionButton: FloatingActionButton(
      tooltip: 'Increment',
      child: Icon(Icons.add),
      onPressed: () => print(“Add new task"),
    ),
  );
}

Ta được UI như sau, khi click vào nút có (+) sẽ in ra dòng Add new task  trên console
Float action button


Tiếp theo chúng ta cần hiển thị dialog với input text để có thể nhập liệu. Trong TodoListState, ta thêm field inputText kiểu String và hàm xử lý thay đổi của input text onChangeText . Sau đó để hiển thị dialog, ta sẽ thêm hàm showAddTodoDialog: để xử lý việc hiển thị dialog

showAddTodoDialog(BuildContext context) {
  showDialog(
    context: context,
    builder: (BuildContext context) {
      return new AlertDialog(
        content: new Container(
          child: new Row(
            children: <Widget>[
              new Expanded(
                child: new TextField(
                  decoration: InputDecoration(
                    labelText: 'Add Task',
                    hintText: 'Enter task here'
                  ),
                  onChanged: onChangeText,
                ),
              )
            ]
          ),
        ),
        actions: <Widget>[
          FlatButton(
            child: const Text('Cancel'),
            onPressed: () {
              Navigator.of(context).pop(true);
            },
          ),
          FlatButton(
            child: const Text('Add task'),
            onPressed: () {
              print(inputText);
              var newTodoObj = new Todo(text: inputText);
              todos.add(newTodoObj);
              var updatedTodos = todos;
              setState(() {
                todos = updatedTodos;
              });
              Navigator.of(context).pop(true);
            },
          ),
        ],
      );
    }
  );
}

Sau đó update để khi click button sẽ hiển thị dialog,

...
floatingActionButton: FloatingActionButton(
  tooltip: 'Increment',
  child: Icon(Icons.add),
  onPressed: () => showAddTodoDialog(context),
),
...

Lưu lại và chạy hot reload, ta được kết quả như sau:
Show dialog
Handle input
Finish




Kết

Vậy là chúng ta vừa hoàn thành xong một ứng dụng đơn giản bằng Flutter, hi vọng bài viết này sẽ giúp các bạn có thể sử dụng Flutter một cách tốt hơn và tạo ra được những app dành riêng cho bản thân.
Mã nguồn của app mình sẽ để trên github tại đây cho các bạn tham khảo.
Nếu bài viết hay và có ích, bạn có thể share cho nhiều người khác cùng biết, nếu có bất kì ý kiến đóng góp xây dựng, đừng ngần ngại để lại bình luận dưới bài viết này nhé. Thanks

Reference:


Advertisement
COMMENTS ()