تشکیل داد نرم افزار یادداشت با فلاتر و دارت – از صفر تا صد
فلاتر یک فریمورک توسعه و گسترش تلفن همراه چندپلتفرمی اوپن سورس میباشد که از سوی گوگل عرضه گردیده‌است. نرم افزار‌ها‌ی فلاتر با دارت نوشته می گردند. فلاتر به طور پیش‌فرض مجهز به کامپوننت‌های «متریال طراحی صفحه» (Material Design) میباشد و همین فرمان موجب گردیده‌است تا تشکیل داد نرم افزار با ظواهر و شم عالی با به کارگیری از فلاتر بسیار آسوده باشد. در فلاتر هر چیزی یک ویجت از نوع باحالت یا این که بی‌شرایط به حساب می آید. در‌این راهنما تحت عنوان یک پروژه برای استارت یادگیری فلاتر، ‌مبادرت به تشکیل داد نرم افزار یادداشت با فلاتر و دارت خوا هیم کرد.
درصورتی که هنوز فلاتر را روی سیستم خویش نصب نکرده‌اید، آن را به یار یک طراحی اپلیکیشن در مشهد  IDE جانبداری‌گردیده نصب نمایید. ارشادوراهنمایی‌های مایحتاج درین کاغذ (+) انجام شده میباشد.
 
آغاز پروژه را تهیه می کنیم. فرایند عمل به طور ذیل میباشد:
 
یک پروژه فلاتر در اندروید استودیو ساخت و ساز نمایید یا این که فرمان flutter create notes را در ترمینال یا این که CMD وارد نمائید.
در فولدر main.dart کلاس homepage را حذف کرده و یک فولدر نو با کلاس homepage خودتان ساخت فرمائید که Stateful Widget را توسعه و گسترش دهد. این کلاس مشتمل بر چارچوب کلی نرم افزار ما خواهد بود.
کلاس ویجت باحالت دیگری ساخت نمایید. این کلاس مشتمل بر نصیب Body میباشد که یک نمای Staggered را برای Home در خویش مکان داده میباشد. اسم آن را StaggeredGridPage میگذاریم.
 
کلیک نمایید
درین نرم افزار کوشش می کنیم که اختراع به خرج بدهیم و یادداشت‌ها را به نحوه Staggered جالبی اکران دهیم. از این پکیج دارت برای (+) ‌ساخت و ساز نمای کانال‌ای Staggered استعمال می کنیم. از SQLite نیز برای ذخیره داده‌های یادداشت‌ها روی دستگاه به کارگیری می کنیم.
 
در‌پی قطعه کدی را از pubspec.yaml می بینید که تعلق‌های فهرست گردیده را الزام نموده است. آنان‌را اضافه کرده، فولدر را ذخیره نمائید و از فرمان فلاتر flutter packages get برای نصب تعلق‌های اضافه گردیده تازه استعمال نمایید.
 
1dependencies:
2 flutter:
3 sdk: flutter
4
5 cupertino_icons: ^0.1.2
6 flutter_staggered_grid_view: ^0.2.7
7 auto_size_text: ^1.1.2
8 sqflite:
9 path:
10 intl: ^0.15.7
11 share: ^0.6.1
 
یک کلاس برای یادداشت‌ها ساخت‌و‌ساز نمائید. ما به تابع toMap برای کوئری‌های مقر داده نیاز داریم.
 
فولدر note.dart
1class Note {
2 int id;
3 String title;
4 String content;
5 DateTime date_created;
6 DateTime date_last_edited;
7 Color note_color;
8 int is_archived = 0;
9
10 Note(this.id, this.title, this.content, this.date_created, this.date_last_edited,this.note_color);
11
12 Map toMap(bool forUpdate) {
13 var data = {
14// 'id': id, since id is auto incremented in the database we don't need to send it to the insert query.
15 'title': utf8.encode(title),
16 'content': utf8.encode( content ),
17 'date_created': epochFromDate( date_created ),
18 'date_last_edited': epochFromDate( date_last_edited ),
19 'note_color': note_color.value,
20 'is_archived': is_archived // for later use for integrating archiving
21 };
22 if(forUpdate){ data["id"] = this.id; }
23 return data;
24 }
25
26// Converting the date time object into int representing seconds passed after midnight 1st Jan, 1970 UTC
27int epochFromDate(DateTime dt) { return dt.millisecondsSinceEpoch ~/ 1000; }
28
29void archiveThisNote(){ is_archived = 1; }
30}
مشاهده بی نقص کدها
کد کوئری‌های مقر داده SQLite برای کلاس note فوق و جدول مربوطه به طور پایین میباشد:
 
فولدر SqliteHandler.dart
1import 'package:sqflite/sqflite.dart';
2import 'package:path/path.dart';
3import 'package:sqflite/sqlite_api.dart';
4import 'dart:async';
5import 'Note.dart';
6
7class NotesDBHandler {
8
9 final databaseName = "notes.db";
10 final tableName = "notes";
11
12
13 final fieldMap = {
14 "id": "INTEGER PRIMARY KEY AUTOINCREMENT",
15 "title": "BLOB",
16 "content": "BLOB",
17 "date_created": "INTEGER",
18 "date_last_edited": "INTEGER",
19 "note_color": "INTEGER",
20 "is_archived": "INTEGER"
21 };
22
23
24 static Database _database;
25
26
27 Future get database async {
28 if (_database != null)
29 return _database;
30
31 _database = await initDB();
32 return _database;
33 }
34
35
36 initDB() async {
37 var path = await getDatabasesPath();
38 var dbPath = join(path, 'notes.db');
39 // ignore: argument_type_not_assignable
40 Database dbConnection = await openDatabase(
41 dbPath, version: 1, onCreate: (Database db, int version) async {
42 print("executing create query from onCreate callback");
43 await db.execute(_buildCreateQuery());
44 });
45
46 await dbConnection.execute(_buildCreateQuery());
47 _buildCreateQuery();
48 return dbConnection;
49 }
50
51
52// build the create query dynamically using the column:field dictionary.
53 String _buildCreateQuery() {
54 String query = "CREATE TABLE IF NOT EXISTS ";
55 query += tableName;
56 query += "(";
57 fieldMap.forEach((column, field){
58 print("$column : $field");
59 query += "$column $field,";
60 });
61
62
63 query = query.substring(0, query.length-1);
64 query += " )";
65
66 return query;
67
68 }
69
70 static Future dbPath() async {
71 String path = await getDatabasesPath();
72 return path;
73 }
74
75 Future insertNote(Note note, bool isNew) async {
76 // Get a reference to the database
77 final Database db = await database;
78 print("insert called");
79
80 // Insert the Notes into the correct table.
81 await db.insert('notes',
82 isNew ? note.toMap(false) : note.toMap(true),
83 conflictAlgorithm: ConflictAlgorithm.replace,
84 );
85
86 if (isNew) {
87 // get latest note which isn't archived, limit by 1
88 var one = await db.query("notes", orderBy: "date_last_edited desc",
89 where: "is_archived = ?",
90 whereArgs: [0],
91 limit: 1);
92 int latestId = one.first["id"] as int;
93 return latestId;
94 }
95 return note.id;
96 }
97
98
99 Future copyNote(Note note) async {
100 final Database db = await database;
101 try {
102 await db.insert("notes",note.toMap(false), conflictAlgorithm: ConflictAlgorithm.replace);
103 } catch(Error) {
104 print(Error);
105 return false;
106 }
107 return true;
108 }
109
110
111 Future archiveNote(Note note) async {
112 if (note.id != -1) {
113 final Database db = await database;
114
115 int idToUpdate = note.id;
116
117 db.update("notes", note.toMap(true), where: "id = ?",
118 whereArgs: [idToUpdate]);
119 }
120 }
121
122 Future deleteNote(Note note) async {
123 if(note.id != -1) {
124 final Database db = await database;
125 try {
126 await db.delete("notes",where: "id = ?",whereArgs: [note.id]);
127 return true;
128 } catch (Error){
129 print("Error deleting ${note.id}: ${Error.toString()}");
130 return false;
131 }
132 }
133 }
134
135
136 Future>> selectAllNotes() async {
137 final Database db = await database;
138 // query all the notes sorted by last edited
139 var data = await db.query("notes", orderBy: "date_last_edited desc",
140 where: "is_archived = ?",
141 whereArgs: [0]);
142
143 return data;
144
145 }
146
147
148
149}
مشاهده بدون نقص کدها
فعلا کاغذ مهم نرم افزار متریال بایستی یک چارچوب (Scaffold) از پوشه HomePage.dart داشته باشد که بدنه آن به طور StaggeredGridView میباشد. در قسمت AppBar این چارچوب یک دکمه اکشن قرار میدهیم تا استفاده کننده بتواند در میان موقعیت‌های اکران لیستی و Staggered تعیین نماید. فراموش نکنید که Body را داخل SafeArea قرار دهید، زیرا میخواهیم نرم افزار روی موبایل‌های امروزی نیز تلاش قابل قبولی داشته باشد.
 
کتابخانه نمای Staggered یک‌سری یادداشت برای نما الزام می‌نماید که به طور دینامیک بر مبنای پهنا اندازه شیت اکران انتخاب میگردد. این حالت مستلزم این میباشد که تعداد یادداشت‌هایی که قرار میباشد در کنار هم اکران یابند، معلوم گردیده باشد. در حالت افقی موبایل یا این که روی تبلت، تعداد یادداشت‌ها را به طور افقی روی 3 عدد و برای شرایط عمودی روی موبایل روی عدد 2 تهیه میکنیم.
 
پوشه StaggeredView.dart
1import 'dart:convert';
2import 'package:flutter/material.dart';
3import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
4import '../Models/Note.dart';
5import '../Models/SqliteHandler.dart';
6import '../Models/Utility.dart';
7import '../Views/StaggeredTiles.dart';
8import 'HomePage.dart';
9
10class StaggeredGridPage extends StatefulWidget {
11 final notesViewType;
12 const StaggeredGridPage({Key key, this.notesViewType}) : super(key: key);
13 @override
14 _StaggeredGridPageState createState() => _StaggeredGridPageState();
15}
16
17class _StaggeredGridPageState extends State {
18
19 var noteDB = NotesDBHandler();
20 List> _allNotesInQueryResult = [];
21 viewType notesViewType ;
22
23@override
24 void initState() {
25 super.initState();
26 this.notesViewType = widget.notesViewType;
27 }
28
29@override void setState(fn) {
30 super.setState(fn);
31 this.notesViewType = widget.notesViewType;
32 }
33
34 @override
35 Widget build(BuildContext context) {
36 GlobalKey _stagKey = GlobalKey();
37 if(CentralStation.updateNeeded) { retrieveAllNotesFromDatabase(); }
38 return Container(child: Padding(padding: _paddingForView(context) , child:
39 new StaggeredGridView.count(key: _stagKey,
40 crossAxisSpacing: 6, mainAxisSpacing: 6,
41 crossAxisCount: _colForStaggeredView(context),
42 children: List.generate(_allNotesInQueryResult.length, (i){ return _tileGenerator(i); }),
43 staggeredTiles: _tilesForView() ,
44 ),
45 )
46 );
47 }
48
49 int _colForStaggeredView(BuildContext context) {
50 if (widget.notesViewType == viewType.List) { return 1; }
51 // for width larger than 600, return 3 irrelevant of the orientation to accommodate more notes horizontally
52 return MediaQuery.of(context).size.width > 600 ? 3 : 2 ;
53 }
54
55 List _tilesForView() { // Generate staggered tiles for the view based on the current preference.
56 return List.generate(_allNotesInQueryResult.length,(index){ return StaggeredTile.fit( 1 ); }
57 ) ;
58}
59
60EdgeInsets _paddingForView(BuildContext context){
61 double width = MediaQuery.of(context).size.width;
62 double padding ;
63 double top_bottom = 8;
64 if (width > 500) {
65 padding = ( width ) * 0.05 ; // 5% padding of width on both side
66 } else {
67 padding = 8;
68 }
69 return EdgeInsets.only(left: padding, right: padding, top: top_bottom, bottom: top_bottom);
70}
71
72
73 MyStaggeredTile _tileGenerator(int i){
74 return MyStaggeredTile( Note(
75 _allNotesInQueryResult[i]["id"],
76 _allNotesInQueryResult[i]["title"] == null ? "" : utf8.decode(_allNotesInQueryResult[i]["title"]),
77 _allNotesInQueryResult[i]["content"] == null ? "" : utf8.decode(_allNotesInQueryResult[i]["content"]),
78 DateTime.fromMillisecondsSinceEpoch(_allNotesInQueryResult[i]["date_created"] * 1000),
79 DateTime.fromMillisecondsSinceEpoch(_allNotesInQueryResult[i]["date_last_edited"] * 1000),
80 Color(_allNotesInQueryResult[i]["note_color"] ))
81 );
82 }
83
84 void retrieveAllNotesFromDatabase() {
85 // queries for all the notes from the database ordered by latest edited note. excludes archived notes.
86 var _testData = noteDB.testSelect();
87 _testData.then((value){
88 setState(() {
89 this._allNotesInQueryResult = value;
90 CentralStation.updateNeeded = false;
91 });
92 });
93 }
94}
مشاهده بدون نقص کدها
این نما به کاشی‌هایی (Tiles) برای اکران یادداشت‌ها نیاز دارااست. آن کاشی که ما برای نما پیاده سازی می کنیم بایستی تیتر و محتوای یادداشت را به طور پیش‌اکران ارائه نماید. برای مدیر ارتفاع متعدد متن یادداشت از یک کتابخانه (+) ‌جهت ساخت و ساز نمای متنی با پیشرفت خود کار به کارگیری می کنیم. کافی میباشد محدودیت خط را تعریف‌و‌تمجید کنیم تا ویجت به طور اتوماتیک توسعه یابد و محتوا را تا جایی که بدین محدودیت می رسد، اکران دهد.
 
همچون segue در iOS و Intent در اندروید، برای ناوبری میان ورقه‌ها در فلاتر از Navigator استعمال میکنیم.