ساخت اپلیکیشن فلاتر ToDo با SQLite — از صفر تا صد

خرید بک لینک

دادهها برای کاربران حائز اهمیت بالایی است و از این رو برای آنها راحت نیست که بخواهند به طور مرتب دادههای تکراری را وارد کنند و یا مکرراً منتظر بمانند تا دادههای یکسانی از اینترنت بارگذاری شوند. در چنین مواردی بهتر است دادهها را به صورت محلی ذخیره کنیم. در این مقاله مراحل ساخت اپلیکیشن فلاتر ToDo با پایگاه داده لوکال SQLite توضیح داده میشود. به این منظور از پلاگین sqflite استفاده میکنیم.

اهدافی که در این مطلب دنبال میکنیم عبارت هستند از:

  • ایجاد لیست ToDo
  • مدیریت عملیات CRUD برای ذخیره و بازیابی دادهها

اپلیکیشنی که میخواهیم بسازیم شبیه به تصویر زیر است:

ساخت اپلیکیشن فلاتر ToDo

گام 1

قبل از هر چیز باید کلاس مدل را برای آیتم ToDo ایجاد کنیم. ما از عنوان، توضیح، تاریخ و id برای هر آیتم To-Do استفاده میکنیم. یک کلاس مدل به صورت todo.dart ایجاد کرده و کد زیر را در آن قرار میدهیم:

class Todo {

	int _id;
	String _title;
	String _description;
	String _date;

	Todo(this._title, this._date, [this._description] );

	Todo.withId(this._id, this._title, this._date, [this._description]);

	int get id => _id;

	String get title => _title;

	String get description => _description;

	String get date => _date;


	set title(String newTitle) {
		if (newTitle.length <= 255) {
			this._title = newTitle;
		}
	}
	set description(String newDescription) {
		if (newDescription.length <= 255) {
			this._description = newDescription;
		}
	}

	set date(String newDate) {
		this._date = newDate;
	}

	// Convert a Note object into a Map object
	Map<String, dynamic> toMap() {

		var map = Map<String, dynamic>();
		if (id != null) {
			map['id'] = _id;
		}
		map['title'] = _title;
		map['description'] = _description;
		map['date'] = _date;

		retu map;
	}

	// Extract a Note object from a Map object
	Todo.fromMapObject(Map<String, dynamic> map) {
		this._id = map['id'];
		this._title = map['title'];
		this._description = map['description'];
		this._date = map['date'];
	}
}

گام 2

در این مرحله باید عملیات CRUD پایگاه داده SQLite را پیادهسازی کنیم. به این منظور یک کلاس مجزا ایجاد کرده و همه عملیات درج، بهروزرسانی و حذف را با ایجاد جداولی پیادهسازی میکنیم. پیش از آن باید وابستگیها را اضافه کنیم تا بتوانیم از SQLite در پروژه خود استفاده کنیم. به این منظور به فایل pubspec.yaml بروید و وابستگیهای زیر را به پروژه اضافه کرده و آن را ذخیره کنید:

dependencies:
  flutter:
    sdk: flutter
  sqflite: any
  path_provider: any
  intl: ^0.15.7

در ادامه برخی تابعهای مهم را در کلاس database_helper مشاهده میکنید:

Future<Database> get database async {

		if (_database == null) {
			_database = await initializeDatabase();
		}
		retu _database;
	}

تابع فوق شیء پایگاه داده را ایجاد کرده و یک getter در آن ارائه میکند که در صورت عدم ایجاد وهلهای از پایگاه داده، از آن برای وهلهسازی پایگاه داده استفاده میکنیم. این کار «مقداردهی با تأخیر» (Lazy Initialization) نامیده میشود.

Future<Database> initializeDatabase() async {

		Directory directory = await getApplicationDocumentsDirectory();
		String path = directory.path + 'todos.db';
		var todosDatabase = await openDatabase(path, version: 1, onCreate: _createDb);
		retu todosDatabase;
	}

اگر هیچ شیئی به پایگاه داده انتساب نیافته باشد، از تابع initializeDatabase برای ایجاد پایگاه داده بهره میگیریم. در این تابع مسیر ذخیرهسازی پایگاه داده و ایجاد جداول مطلوب را به دست خواهیم آورد. نام پایگاه داده را todos تعیین میکنیم:

void _createDb(Database db, int newVersion) async {

		await db.execute('CREATE TABLE $todoTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colTitle TEXT, '
				'$colDescription TEXT, $colDate TEXT)');
	}

سپس جدولها را مانند کد فوق ایجاد میکنیم. در ادامه باید تابعهای درج، بهروزرسانی و حذف را اضافه کنیم. در ادامه کد کامل مربوط به کلاس database_helper را میبینید:

import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:todo_list/Models/todo.dart';

class DatabaseHelper {

	static DatabaseHelper _databaseHelper;    // Singleton DatabaseHelper
	static Database _database;                // Singleton Database

	String todoTable = 'todo_table';
	String colId = 'id';
	String colTitle = 'title';
	String colDescription = 'description';
	String colDate = 'date';

	DatabaseHelper._createInstance(); // Named constructor to create instance of DatabaseHelper

	factory DatabaseHelper() {

		if (_databaseHelper == null) {
			_databaseHelper = DatabaseHelper._createInstance(); // This is executed only once, singleton object
		}
		retu _databaseHelper;
	}

	Future<Database> get database async {

		if (_database == null) {
			_database = await initializeDatabase();
		}
		retu _database;
	}

	Future<Database> initializeDatabase() async {
		// Get the directory path for both Android and iOS to store database.
		Directory directory = await getApplicationDocumentsDirectory();
		String path = directory.path + 'todos.db';

		// Open/create the database at a given path
		var todosDatabase = await openDatabase(path, version: 1, onCreate: _createDb);
		retu todosDatabase;
	}

	void _createDb(Database db, int newVersion) async {

		await db.execute('CREATE TABLE $todoTable($colId INTEGER PRIMARY KEY AUTOINCREMENT, $colTitle TEXT, '
				'$colDescription TEXT, $colDate TEXT)');
	}

	// Fetch Operation: Get all todo objects from database
	Future<List<Map<String, dynamic>>> getTodoMapList() async {
		Database db = await this.database;

//		var result = await db.rawQuery('SELECT * FROM $todoTable order by $colTitle ASC');
		var result = await db.query(todoTable, orderBy: '$colTitle ASC');
		retu result;
	}

	// Insert Operation: Insert a todo object to database
	Future<int> insertTodo(Todo todo) async {
		Database db = await this.database;
		var result = await db.insert(todoTable, todo.toMap());
		retu result;
	}

	// Update Operation: Update a todo object and save it to database
	Future<int> updateTodo(Todo todo) async {
		var db = await this.database;
		var result = await db.update(todoTable, todo.toMap(), where: '$colId = ?', whereArgs: [todo.id]);
		retu result;
	}


	// Delete Operation: Delete a todo object from database
	Future<int> deleteTodo(int id) async {
		var db = await this.database;
		int result = await db.rawDelete('DELETE FROM $todoTable WHERE $colId = $id');
		retu result;
	}

	// Get number of todo objects in database
	Future<int> getCount() async {
		Database db = await this.database;
		List<Map<String, dynamic>> x = await db.rawQuery('SELECT COUNT (*) from $todoTable');
		int result = Sqflite.firstIntValue(x);
		retu result;
	}

	// Get the 'Map List' [ List<Map> ] and convert it to 'todo List' [ List<Todo> ]
	Future<List<Todo>> getTodoList() async {

		var todoMapList = await getTodoMapList(); // Get 'Map List' from database
		int count = todoMapList.length;         // Count the number of map entries in db table

		List<Todo> todoList = List<Todo>();
		// For loop to create a 'todo List' from a 'Map List'
		for (int i = 0; i < count; i++) {
			todoList.add(Todo.fromMapObject(todoMapList[i]));
		}

		retu todoList;
	}

}

گام 3

اکنون باید صفحهها را برای لیست ToDo پیادهسازی کنیم. یک پوشه به نام Screen ایجاد کرده و فایل todo_list.dart را اضافه میکنیم.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:todo_list/Models/todo.dart';
import 'package:todo_list/Utils/database_helper.dart';
import 'package:todo_list/Screens/todo_detail.dart';
import 'package:sqflite/sqflite.dart';

class TodoList extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    retu TodoListState();
  }
}

class TodoListState extends State<TodoList> {
  DatabaseHelper databaseHelper = DatabaseHelper();
  List<Todo> todoList;
  int count = 0;

  @override
  Widget build(BuildContext context) {
    if (todoList == null) {
      todoList = List<Todo>();
      updateListView();
    }

    retu Scaffold(
      appBar: AppBar(
        title: Text('Todos'),
      ),
      body: getTodoListView(),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          debugPrint('FAB clicked');
          navigateToDetail(Todo('', '', ''), 'Add Todo');
        },
        tooltip: 'Add Todo',
        child: Icon(Icons.add),
      ),
    );
  }

  ListView getTodoListView() {
    retu ListView.builder(
      itemCount: count,
      itemBuilder: (BuildContext context, int position) {
        retu Card(
          color: Colors.white,
          elevation: 2.0,
          child: ListTile(
            leading: CircleAvatar(
              backgroundColor: Colors.amber,
              child: Text(getFirstLetter(this.todoList[position].title),
                  style: TextStyle(fontWeight: FontWeight.bold)),
            ),
            title: Text(this.todoList[position].title,
                style: TextStyle(fontWeight: FontWeight.bold)),
            subtitle: Text(this.todoList[position].description),
            trailing: Row(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                GestureDetector(
                  child: Icon(Icons.delete,color: Colors.red,),
                  onTap: () {
                    _delete(context, todoList[position]);
                  },
                ),
              ],
            ),
            onTap: () {
              debugPrint("ListTile Tapped");
              navigateToDetail(this.todoList[position], 'Edit Todo');
            },
          ),
        );
      },
    );
  }

  getFirstLetter(String title) {
    retu title.substring(0, 2);
  }

  void _delete(BuildContext context, Todo todo) async {
    int result = await databaseHelper.deleteTodo(todo.id);
    if (result != 0) {
      _showSnackBar(context, 'Todo Deleted Successfully');
      updateListView();
    }
  }

  void _showSnackBar(BuildContext context, String message) {
    final snackBar = SnackBar(content: Text(message));
    Scaffold.of(context).showSnackBar(snackBar);
  }

  void navigateToDetail(Todo todo, String title) async {
    bool result =
        await Navigator.push(context, MaterialPageRoute(builder: (context) {
      retu TodoDetail(todo, title);
    }));

    if (result == true) {
      updateListView();
    }
  }

  void updateListView() {
    final Future<Database> dbFuture = databaseHelper.initializeDatabase();
    dbFuture.then((database) {
      Future<List<Todo>> todoListFuture = databaseHelper.getTodoList();
      todoListFuture.then((todoList) {
        setState(() {
          this.todoList = todoList;
          this.count = todoList.length;
        });
      });
    });
  }

  
}

در کد فوق، یک «نمای لیست» (List View) پیادهسازی میکنیم. در این نمای لیست، ToDo-هایی که وارد شدهاند نمایش مییابند. بنابراین در حال حاضر باید ToDo-ها را در پایگاه داده وارد کنیم. این کار با استفاده از کد زیر انجام مییابد. صفحه دیگری نیز برای افزودن ToDo-ها ایجاد میکنیم.

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:todo_list/Models/todo.dart';
import 'package:todo_list/Utils/database_helper.dart';
import 'package:intl/intl.dart';

class TodoDetail extends StatefulWidget {

	final String appBarTitle;
	final Todo todo;

	TodoDetail(this.todo, this.appBarTitle);

	@override
  State<StatefulWidget> createState() {

    retu TodoDetailState(this.todo, this.appBarTitle);
  }
}

class TodoDetailState extends State<TodoDetail> {

	DatabaseHelper helper = DatabaseHelper();

	String appBarTitle;
	Todo todo;

	TextEditingController titleController = TextEditingController();
	TextEditingController descriptionController = TextEditingController();

	TodoDetailState(this.todo, this.appBarTitle);

	@override
  Widget build(BuildContext context) {

		TextStyle textStyle = Theme.of(context).textTheme.title;

		titleController.text = todo.title;
		descriptionController.text = todo.description;

    retu WillPopScope(

	    onWillPop: () {
		    moveToLastScreen();
	    },

	    child: Scaffold(
	    appBar: AppBar(
		    title: Text(appBarTitle),
		    leading: IconButton(icon: Icon(
				    Icons.arrow_back),
				    onPressed: () {
		    	    moveToLastScreen();
				    }
		    ),
	    ),

	    body: Padding(
		    padding: EdgeInsets.only(top: 15.0, left: 10.0, right: 10.0),
		    child: ListView(
			    children: <Widget>[

				    Padding(
					    padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
					    child: TextField(
						    controller: titleController,
						    style: textStyle,
						    onChanged: (value) {
						    	debugPrint('Something changed in Title Text Field');
						    	updateTitle();
						    },
						    decoration: InputDecoration(
							    labelText: 'Title',
							    labelStyle: textStyle,
							    border: OutlineInputBorder(
								    borderRadius: BorderRadius.circular(5.0)
							    )
						    ),
					    ),
				    ),

				    Padding(
					    padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
					    child: TextField(
						    controller: descriptionController,
						    style: textStyle,
						    onChanged: (value) {
							    debugPrint('Something changed in Description Text Field');
							    updateDescription();
						    },
						    decoration: InputDecoration(
								    labelText: 'Description',
								    labelStyle: textStyle,
								    border: OutlineInputBorder(
										    borderRadius: BorderRadius.circular(5.0)
								    )
						    ),
					    ),
				    ),

				    Padding(
					    padding: EdgeInsets.only(top: 15.0, bottom: 15.0),
					    child: Row(
						    children: <Widget>[
						    	Expanded(
								    child: RaisedButton(
									    color: Theme.of(context).primaryColorDark,
									    textColor: Theme.of(context).primaryColorLight,
									    child: Text(
										    'Save',
										    textScaleFactor: 1.5,
									    ),
									    onPressed: () {
									    	setState(() {
									    	  debugPrint("Save button clicked");
									    	  _save();
									    	});
									    },
								    ),
							    ),

							    Container(width: 5.0,),

							    Expanded(
								    child: RaisedButton(
									    color: Theme.of(context).primaryColorDark,
									    textColor: Theme.of(context).primaryColorLight,
									    child: Text(
										    'Delete',
										    textScaleFactor: 1.5,
									    ),
									    onPressed: () {
										    setState(() {
											    debugPrint("Delete button clicked");
											    _delete();
										    });
									    },
								    ),
							    ),

						    ],
					    ),
				    ),


			    ],
		    ),
	    ),

    ));
  }

  void moveToLastScreen() {
		Navigator.pop(context, true);
  }

	// Update the title of todo object
  void updateTitle(){
    todo.title = titleController.text;
  }

	// Update the description of todo object
	void updateDescription() {
		todo.description = descriptionController.text;
	}

	// Save data to database
	void _save() async {

		moveToLastScreen();

		todo.date = DateFormat.yMMMd().format(DateTime.now());
		int result;
		if (todo.id != null) {  // Case 1: Update operation
			result = await helper.updateTodo(todo);
		} else { // Case 2: Insert Operation
			result = await helper.insertTodo(todo);
		}

		if (result != 0) {  // Success
			_showAlertDialog('Status', 'Todo Saved Successfully');
		} else {  // Failure
			_showAlertDialog('Status', 'Problem Saving Todo');
		}

	}


	void _delete() async {

		moveToLastScreen();

		if (todo.id == null) {
			_showAlertDialog('Status', 'No Todo was deleted');
			retu;
		}

		int result = await helper.deleteTodo(todo.id);
		if (result != 0) {
			_showAlertDialog('Status', 'Todo Deleted Successfully');
		} else {
			_showAlertDialog('Status', 'Error Occured while Deleting Todo');
		}
	}

	void _showAlertDialog(String title, String message) {

		AlertDialog alertDialog = AlertDialog(
			title: Text(title),
			content: Text(message),
		);
		showDialog(
				context: context,
				builder: (_) => alertDialog
		);
	}

}

اینک کار طراحی اپلیکیشن به پایان رسیده است. ترمینال را باز کرده و پس از اتصال گوشی از طریق USB، دستور زیر را اجرا کنید:

flutter run

نتیجه

ساخت اپلیکیشن فلاتر ToDo

ساخت اپلیکیشن فلاتر ToDo

سخن پایانی

در این راهنما با شیوه استفاده از ویجتهای فلاتر و پیادهسازی آن در یک اپلیکیشن ToDo آشنا شدید. علاوه بر آن با شیوه افزودن دادهها در پایگاه داده لوکال در فلاتر را نیز فرا گرفتید.

اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:

==

telegram
twitter

میثم لطفی

«میثم لطفی» دانشآموخته ریاضیات و شیفته فناوری به خصوص در حوزه رایانه است. وی در حال حاضر علاوه بر پیگیری همه علاقهمندیهای خود در رشتههای برنامهنویسی، کپیرایتینگ و تولید محتوای چندرسانهای، در زمینه نگارش مقالاتی با محوریت نرمافزار نیز با مجله فرادرس همکاری دارد.

نوشته ساخت اپلیکیشن فلاتر ToDo با SQLite — از صفر تا صد اولین بار در مجله فرادرس. پدیدار شد.

مطالب درسی...

ما را در سایت مطالب درسی دنبال می‌کنید

برچسب: نویسنده: خنجی بازدید: 345 تاريخ: شنبه 28 دی 1398 ساعت: 19:17

صفحه بندی