نوشتن برنامه های پیشرفته در برنامه نویسی فلاتر

3 سال پیش
نوشتن برنامه های پیشرفته در برنامه نویسی فلاتر

 نوشتن برنامه های پیشرفته در برنامه نویسی فلاتر 

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

در این درس، ما می خواهیم نحوه نوشتن یک برنامه کامل تلفن همراه، expense_calculator را بیاموزیم. هدف از expense_calculator، ذخیره اطلاعات هزینه ماست. ویژگی نوشتن برنامه های پیشرفته در برنامه نویسی فلاتر به شرح زیر است –

  • لیست هزینه
  • فرم برای ورود به هزینه های جدید.
  • گزینه ای برای ویرایش / حذف هزینه های موجود.
  • کل هزینه ها در هر نمونه

 

ما قصد داریم با استفاده از نوشتن برنامه های پیشرفته در برنامه نویسی فلاتر و  ویژگی های پیشرفته فریم ورک فلاتر برنامه expense_calculator را بنویسیم

  • استفاده پیشرفته از ListView برای نشان دادن لیست هزینه.
  • برنامه نویسی فرم
  • برنامه نویسی پایگاه داده SQLite برای ذخیره هزینه های ما.
  • scoped_model مدیریت state برای ساده سازی برنامه نویسی ما.

 

بیایید برنامه نویسی را با برنامه program_calculator آغاز کنیم.

  • یک برنامه فلاتر جدید ، expense_calculator در Android studio ایجاد کنید.
  • pubspec.yaml را باز کرده و وابستگی های بسته را اضافه کنید.
dependencies: 
   flutter: 
      sdk: flutter 
   sqflite: ^1.1.0 
   path_provider: ^0.5.0+1 
   scoped_model: ^1.0.1 
   intl: any

 

این نکات را در اینجا مشاهده کنید –

  • sqflite برای برنامه نویسی پایگاه داده SQLite استفاده می شود.
  • path_provider برای بدست آوردن مسیر برنامه خاص سیستم استفاده می شود.
  • scoped_model برای مدیریت state استفاده می شود.
  • intl برای قالب بندی تاریخ استفاده می شود.

Android studio هشدار زیر را به روز می کند که pubspec.yaml به روز شده است.

Alert Writing Advanced Applications

 

  •  بر روی گزینه Get dependencies را کلیک کنید. Android studio بسته را از اینترنت دریافت کرده و به درستی برای برنامه پیکربندی می کند.
  • کد موجود را در main.dart حذف کنید.
  • برای ایجاد کلاس Expense، فایل جدید Expense.dart را اضافه کنید. کلاس expense دارای خصوصیات و متدهای زیر است.

 

  • property: id – شناسه منحصر به فرد برای نشان دادن ورود هزینه ها در پایگاه داده SQLite.
  • property: amount  – مقدار هزینه شده
  • property: date – تاریخ مصرف هزینه.
  • property: category – طبقه نشان دهنده منطقه ای است که مبلغ در آن هزینه می شود. به عنوان مثال غذا، سفر و غیره ،
  • formattedDate – برای قالب بندی ویژگی تاریخ استفاده می شود
  • fromMap – برای نقشه برداری از جدول از پایگاه داده به خاصیت موجود در شی expense  و ایجاد یک شی expense جدید استفاده می شود.
factory Expense.fromMap(Map<String, dynamic> data) { 
   return Expense( 
      data['id'], 
      data['amount'], 
      DateTime.parse(data['date']),    
      data['category'] 
   ); 
}

 

  • toMap – برای تبدیل شی هزینه به Dart Map استفاده می شود، که می تواند بیشتر در برنامه نویسی پایگاه داده مورد استفاده قرار گیرد
Map<String, dynamic> toMap() => { 
   "id" : id, 
   "amount" : amount, 
   "date" : date.toString(), 
   "category" : category, 
};

 

  • columns – متغیر استاتیک برای نمایش فیلد پایگاه داده استفاده می شود.

کد زیر را در پرونده Expense.dart وارد کرده و ذخیره کنید.

import 'package:intl/intl.dart'; class Expense {
   final int id; 
   final double amount; 
   final DateTime date; 
   final String category; 
   String get formattedDate { 
      var formatter = new DateFormat('yyyy-MM-dd'); 
      return formatter.format(this.date); 
   } 
   static final columns = ['id', 'amount', 'date', 'category'];
   Expense(this.id, this.amount, this.date, this.category); 
   factory Expense.fromMap(Map<String, dynamic> data) { 
      return Expense( 
         data['id'], 
         data['amount'], 
         DateTime.parse(data['date']), data['category'] 
      ); 
   }
   Map<String, dynamic> toMap() => {
      "id" : id, 
      "amount" : amount, 
      "date" : date.toString(), 
      "category" : category, 
   }; 
}

 

کد فوق ساده و قابل توضیح است.

برای ایجاد کلاس SQLiteDbProvider، فایل جدید Database.dart را اضافه کنید. هدف کلاس SQLiteDbProvider به شرح زیر است –

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

 

Future<List<Expense>> getAllExpenses() async { 
   final db = await database; 
   
   List<Map> results = await db.query(
      "Expense", columns: Expense.columns, orderBy: "date DESC"
   );
   List<Expense> expenses = new List(); 
   results.forEach((result) {
      Expense expense = Expense.fromMap(result); 
      expenses.add(expense); 
   }); 
   return expenses; 
}

 

با استفاده از متد getExpenseById اطلاعات مربوط به هزینه خاص را براساس هویت هزینه موجود در پایگاه داده دریافت کنید. برای نشان دادن اطلاعات هزینه خاص به کاربر استفاده خواهد شد.

Future<Expense> getExpenseById(int id) async {
   final db = await database;
   var result = await db.query("Expense", where: "id = ", whereArgs: [id]);
   
   return result.isNotEmpty ? 
   Expense.fromMap(result.first) : Null; 
}

 

با استفاده ار متد getTotalExpense کل هزینه های کاربر را دریافت کنید. برای نشان دادن کل هزینه فعلی به کاربر استفاده خواهد شد.

Future<double> getTotalExpense() async {
   final db = await database; 
   List<Map> list = await db.rawQuery(
      "Select SUM(amount) as amount from expense"
   );
   return list.isNotEmpty ? list[0]["amount"] : Null; 
}

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

Future<Expense> insert(Expense expense) async { 
   final db = await database; 
   var maxIdResult = await db.rawQuery(
      "SELECT MAX(id)+1 as last_inserted_id FROM Expense"
   );
   var id = maxIdResult.first["last_inserted_id"]; 
   var result = await db.rawInsert(
      "INSERT Into Expense (id, amount, date, category)" 
      " VALUES (?, ?, ?, ?)", [
         id, expense.amount, expense.date.toString(), expense.category
      ]
   ); 
   return Expense(id, expense.amount, expense.date, expense.category); 
}

با استفاده از روش بروزرسانی، اطلاعات هزینه موجود را به روز کنید. برای ویرایش و به روزرسانی ورودی هزینه موجود موجود در سیستم توسط کاربر استفاده خواهد شد.

update(Expense product) async {
   final db = await database; 
   
   var result = await db.update("Expense", product.toMap(), 
   where: "id = ?", whereArgs: [product.id]); 
   return result; 
}

با استفاده از روش حذف، اطلاعات هزینه موجود را حذف کنید. این مورد  برای حذف هزینه موجود موجود در سیستم توسط کاربر استفاده خواهد شد .

delete(int id) async {
   final db = await database;
   db.delete("Expense", where: "id = ?", whereArgs: [id]); 
}

 

کد کامل کلاس SQLiteDbProvider به شرح زیر است

import 'dart:async'; 
import 'dart:io'; 
import 'package:path/path.dart'; 
import 'package:path_provider/path_provider.dart'; 
import 'package:sqflite/sqflite.dart'; 
import 'Expense.dart'; 

class SQLiteDbProvider {
   SQLiteDbProvider._(); 
   static final SQLiteDbProvider db = SQLiteDbProvider._(); 
   
   static Database _database; Future<Database> get database async { 
      if (_database != null) 
         return _database; 
      _database = await initDB(); 
      return _database; 
   } 
   initDB() async {
      Directory documentsDirectory = await getApplicationDocumentsDirectory(); 
      String path = join(documentsDirectory.path, "ExpenseDB2.db"); 
      return await openDatabase(
         path, version: 1, onOpen:(db){}, onCreate: (Database db, int version) async {
            await db.execute(
               "CREATE TABLE Expense (
                  ""id INTEGER PRIMARY KEY," "amount REAL," "date TEXT," "category TEXT""
               )
            "); 
            await db.execute(
               "INSERT INTO Expense ('id', 'amount', 'date', 'category') 
               values (?, ?, ?, ?)",[1, 1000, '2019-04-01 10:00:00', "Food"]
            );
            /*await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  ۲, "Pixel", "Pixel is the most feature phone ever", 800, "pixel.png"
               ]
            ); 
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  ۳, "Laptop", "Laptop is most productive development tool", 2000, "laptop.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  ۴, "Tablet", "Laptop is most productive development tool", 1500, "tablet.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  ۵, "Pendrive", "iPhone is the stylist phone ever", 100, "pendrive.png"
               ]
            ); 
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  ۶, "Floppy Drive", "iPhone is the stylist phone ever", 20, "floppy.png"
               ]
            ); */ 
         }
      );
   }
   Future<List<Expense>> getAllExpenses() async {
      final db = await database; 
      List<Map> 
      results = await db.query(
         "Expense", columns: Expense.columns, orderBy: "date DESC"
      );
      List<Expense> expenses = new List(); 
      results.forEach((result) {
         Expense expense = Expense.fromMap(result);
         expenses.add(expense);
      }); 
      return expenses; 
   } 
   Future<Expense> getExpenseById(int id) async {
      final db = await database;
      var result = await db.query("Expense", where: "id = ", whereArgs: [id]); 
      return result.isNotEmpty ? Expense.fromMap(result.first) : Null; 
   }
   Future<double> getTotalExpense() async {
      final db = await database;
      List<Map> list = await db.rawQuery(
         "Select SUM(amount) as amount from expense"
      );
      return list.isNotEmpty ? list[0]["amount"] : Null; 
   }
   Future<Expense> insert(Expense expense) async {
      final db = await database; 
      var maxIdResult = await db.rawQuery(
         "SELECT MAX(id)+1 as last_inserted_id FROM Expense"
      );
      var id = maxIdResult.first["last_inserted_id"]; 
      var result = await db.rawInsert(
         "INSERT Into Expense (id, amount, date, category)" 
         " VALUES (?, ?, ?, ?)", [
            id, expense.amount, expense.date.toString(), expense.category
         ]
      );
      return Expense(id, expense.amount, expense.date, expense.category); 
   }
   update(Expense product) async {
      final db = await database; 
      var result = await db.update(
         "Expense", product.toMap(), where: "id = ?", whereArgs: [product.id]
      ); 
      return result; 
   }
   delete(int id) async {
      final db = await database;
      db.delete("Expense", where: "id = ?", whereArgs: [id]);
   }
}

 

در اینا اینجا،

  • پایگاه داده ویژگی برای بدست آوردن شی SQLiteDbProvider است.
  • initDB متدی است که برای انتخاب و باز کردن پایگاه داده SQLite استفاده می شود.

برای ایجاد ExpenseListModel یک فایل جدید ایجاد کنید، ExpenseListModel.dart. هدف از این مدل نگهداری اطلاعات کامل هزینه های کاربر در حافظه و به روزرسانی رابط کاربری برنامه در هر زمان تغییر هزینه کاربر در حافظه است. این براساس کلاس Model از بسته scoped_model ساخته شده است. این ویژگی ها و روش های زیر را دارد –

  • _items – لیست خصوصی هزینه ها.
  • items − getter for _items به عنوان <UnmodifiableListView <Expense> برای جلوگیری از تغییرات غیرمنتظره یا تصادفی لیست.
  • totalExpense – دریافت کننده کل هزینه ها بر اساس متغیر اقلام.
double get totalExpense {
   double amount = 0.0; 
   for(var i = 0; i < _items.length; i++) { 
      amount = amount + _items[i].amount; 
   } 
   return amount; 
}

 

  • load – برای بارگیری هزینه های کامل از پایگاه داده و در متغیر _items استفاده می شود. همچنین notifyListeners را برای به روزرسانی رابط کاربری فراخوانی می کند.
void load() {
   Future<List<Expense>> 
   list = SQLiteDbProvider.db.getAllExpenses(); 
   list.then( (dbItems) {
      for(var i = 0; i < dbItems.length; i++) { 
         _items.add(dbItems[i]); 
      } notifyListeners(); 
   });
}

 

  • byId – برای بدست آوردن هزینه های خاص از متغیر _items استفاده می شود.
Expense byId(int id) { 
   for(var i = 0; i < _items.length; i++) { 
      if(_items[i].id == id) { 
         return _items[i]; 
      } 
   }
   return null; 
}

 

  • add – برای افزودن مورد جدیدی به متغیر _items و همچنین در پایگاه داده استفاده می شود. همچنین notifyListeners را برای به روزرسانی رابط کاربری فراخوانی می کند.
void add(Expense item) {
   SQLiteDbProvider.db.insert(item).then((val) { 
      _items.add(val); notifyListeners(); 
   }); 
}

 

  • Update – برای به روزرسانی مورد هزینه در متغیر _items و همچنین در پایگاه داده استفاده می شود. همچنین notifyListeners را برای به روزرسانی رابط کاربری فراخوانی می کند.
void update(Expense item) {
   bool found = false;
   for(var i = 0; i < _items.length; i++) {
      if(_items[i].id == item.id) {
         _items[i] = item; 
         found = true; 
         SQLiteDbProvider.db.update(item); break; 
      } 
   }
   if(found) notifyListeners(); 
}

 

  • delete – برای حذف مورد هزینه موجود در متغیر _items و همچنین از پایگاه داده استفاده می شود. همچنین notifyListeners را برای به روزرسانی رابط کاربری فراخوانی می کند.
void delete(Expense item) { 
   bool found = false; 
   for(var i = 0; i < _items.length; i++) {
      if(_items[i].id == item.id) {
         found = true; 
         SQLiteDbProvider.db.delete(item.id); 
         _items.removeAt(i); break; 
      }
   }
   if(found) notifyListeners(); 
}

 

کد کامل کلاس ExpenseListModel به شرح زیر است:

import 'dart:collection'; 
import 'package:scoped_model/scoped_model.dart'; 
import 'Expense.dart'; 
import 'Database.dart'; 

class ExpenseListModel extends Model { 
   ExpenseListModel() { 
      this.load(); 
   } 
   final List<Expense> _items = []; 
   UnmodifiableListView<Expense> get items => 
   UnmodifiableListView(_items); 
   
   /*Future<double> get totalExpense { 
      return SQLiteDbProvider.db.getTotalExpense(); 
   }*/ 
   
   double get totalExpense {
      double amount = 0.0;
      for(var i = 0; i < _items.length; i++) { 
         amount = amount + _items[i].amount; 
      } 
      return amount; 
   }
   void load() {
      Future<List<Expense>> list = SQLiteDbProvider.db.getAllExpenses(); 
      list.then( (dbItems) {
         for(var i = 0; i < dbItems.length; i++) {
            _items.add(dbItems[i]); 
         } 
         notifyListeners(); 
      }); 
   }
   Expense byId(int id) {
      for(var i = 0; i < _items.length; i++) { 
         if(_items[i].id == id) { 
            return _items[i]; 
         } 
      }
      return null; 
   }
   void add(Expense item) {
      SQLiteDbProvider.db.insert(item).then((val) {
         _items.add(val);
         notifyListeners();
      }); 
   }
   void update(Expense item) {
      bool found = false; 
      for(var i = 0; i < _items.length; i++) {
         if(_items[i].id == item.id) {
            _items[i] = item; 
            found = true; 
            SQLiteDbProvider.db.update(item); 
            break; 
         }
      }
      if(found) notifyListeners(); 
   }
   void delete(Expense item) {
      bool found = false; 
      for(var i = 0; i < _items.length; i++) {
         if(_items[i].id == item.id) {
            found = true; 
            SQLiteDbProvider.db.delete(item.id); 
            _items.removeAt(i); break; 
         }
      }
      if(found) notifyListeners(); 
   }
}

 

فایل main.dart را باز کنید. کلاسها را به شرح زیر وارد کنید –

import 'package:flutter/material.dart'; 
import 'package:scoped_model/scoped_model.dart'; 
import 'ExpenseListModel.dart'; 
import 'Expense.dart';

 

با عبور از ویجت <ScopedModel <ExpenseListModel تابع اصلی را اضافه کرده و  runApp را فراخوانی کنید.

void main() { 
   final expenses = ExpenseListModel(); 
   runApp(
      ScopedModel<ExpenseListModel>(model: expenses, child: MyApp(),)
   );
}

 

  • شی expenses  تمام اطلاعات هزینه های کاربر را از پایگاه داده بارگیری می کند. همچنین، هنگامی که برنامه برای اولین بار باز می شود، پایگاه داده مورد نیاز را با جداول مناسب ایجاد می کند.
  • ScopedModel اطلاعات کل هزینه را در کل چرخه زندگی برنامه فراهم می کند و از حفظ حالت برنامه در هر زمان اطمینان حاصل می کند. این ما را قادر می سازد تا از StatelessWidget به جای StatefulWidget استفاده کنیم.

با استفاده از ویجت MaterialApp یک MyApp ساده ایجاد کنید.

class MyApp extends StatelessWidget {
   // This widget is the root of your application. 
   @override 
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Expense',
         theme: ThemeData(
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title: 'Expense calculator'), 
      );
   }
}

ویجت MyHomePage ایجاد کنید تا تمام اطلاعات مربوط به هزینه کاربر همراه با کل هزینه ها در قسمت بالا نمایش داده شود. از دکمه شناور در گوشه پایین سمت راست برای افزودن هزینه های جدید استفاده خواهد شد.

class MyHomePage extends StatelessWidget { 
   MyHomePage({Key key, this.title}) : super(key: key); 
   final String title; 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar( 
            title: Text(this.title), 
         ), 
         body: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return ListView.separated(
                  itemCount: expenses.items == null ? 1 
                  : expenses.items.length + 1, 
                  itemBuilder: (context, index) { 
                     if (index == 0) { 
                        return ListTile(
                           title: Text("Total expenses: " 
                           + expenses.totalExpense.toString(), 
                           style: TextStyle(fontSize: 24,
                           fontWeight: FontWeight.bold),) 
                        );
                     } else {
                        index = index - 1; 
                        return Dismissible( 
                           key: Key(expenses.items[index].id.toString()), 
                              onDismissed: (direction) { 
                              expenses.delete(expenses.items[index]); 
                              Scaffold.of(context).showSnackBar(
                                 SnackBar(
                                    content: Text(
                                       "Item with id, " 
                                       + expenses.items[index].id.toString() + 
                                       " is dismissed"
                                    )
                                 )
                              ); 
                           },
                           child: ListTile( onTap: () { 
                              Navigator.push(
                                 context, MaterialPageRoute(
                                    builder: (context) => FormPage(
                                       id: expenses.items[index].id,
                                       expenses: expenses, 
                                    )
                                 )
                              );
                           }, 
                           leading: Icon(Icons.monetization_on), 
                           trailing: Icon(Icons.keyboard_arrow_right), 
                           title: Text(expenses.items[index].category + ": " + 
                           expenses.items[index].amount.toString() + 
                           " \nspent on " + expenses.items[index].formattedDate, 
                           style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),))
                        ); 
                     }
                  },
                  separatorBuilder: (context, index) { 
                     return Divider(); 
                  }, 
               );
            },
         ),
         floatingActionButton: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return FloatingActionButton( onPressed: () {
                  Navigator.push( 
                     context, MaterialPageRoute(
                        builder: (context) => ScopedModelDescendant<ExpenseListModel>(
                           builder: (context, child, expenses) { 
                              return FormPage( id: 0, expenses: expenses, ); 
                           }
                        )
                     )
                  ); 
                  // expenses.add(new Expense( 
                     // ۲, ۱۰۰۰, DateTime.parse('2019-04-01 11:00:00'), 'Food')
                  ); 
                  // print(expenses.items.length); 
               },
               tooltip: 'Increment', child: Icon(Icons.add), ); 
            }
         )
      );
   }
}

 

در اینجا:

  • ScopedModelDescendant برای انتقال مدل هزینه به ویجت ListView و FloatingActionButton استفاده می شود.
  • ویجت ListView.separated و ListTile برای لیست اطلاعات هزینه استفاده می شود.
  • ویجت Dismissible برای حذف ورودی هزینه با استفاده از اشاره کش رفتن استفاده می شود.
  • Navigator برای باز کردن رابط ویرایش ورودی هزینه استفاده می شود. با ضربه زدن روی ورودی هزینه می توان آن را فعال کرد.

یک ویجت FormPage ایجاد کنید. هدف از ویجت FormPage افزودن یا به روزرسانی ورودی هزینه است. همچنین اعتبار سنجی هزینه را کنترل می کند.

class FormPage extends StatefulWidget { 
   FormPage({Key key, this.id, this.expenses}) : super(key: key); 
   final int id; 
   final ExpenseListModel expenses; 
   
   @override _FormPageState createState() => _FormPageState(id: id, expenses: expenses); 
}
class _FormPageState extends State<FormPage> {
   _FormPageState({Key key, this.id, this.expenses}); 
   
   final int id; 
   final ExpenseListModel expenses; 
   final scaffoldKey = GlobalKey<ScaffoldState>(); 
   final formKey = GlobalKey<FormState>(); 
   
   double _amount; 
   DateTime _date; 
   String _category; 
   
   void _submit() {
      final form = formKey.currentState; 
      if (form.validate()) {
         form.save(); 
         if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category)); 
            else expenses.update(Expense(this.id, _amount, _date, _category)); 
         Navigator.pop(context); 
      }
   }
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         key: scaffoldKey, appBar: AppBar(
            title: Text('Enter expense details'),
         ), 
         body: Padding(
            padding: const EdgeInsets.all(16.0), 
            child: Form(
               key: formKey, child: Column(
                  children: [
                     TextFormField( 
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration( 
                           icon: const Icon(Icons.monetization_on), 
                           labelText: 'Amount', 
                           labelStyle: TextStyle(fontSize: 18)
                        ), 
                        validator: (val) {
                           Pattern pattern = r'^[1-9]\d*(\.\d+)?$'; 
                           RegExp regex = new RegExp(pattern); 
                           if (!regex.hasMatch(val)) 
                           return 'Enter a valid number'; else return null; 
                        }, 
                        initialValue: id == 0 
                        ? '' : expenses.byId(id).amount.toString(), 
                        onSaved: (val) => _amount = double.parse(val), 
                     ), 
                     TextFormField( 
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration( 
                           icon: const Icon(Icons.calendar_today),
                           hintText: 'Enter date', 
                           labelText: 'Date', 
                           labelStyle: TextStyle(fontSize: 18), 
                        ), 
                        validator: (val) {
                           Pattern pattern = r'^((?:19|20)\d\d)[- /.]
                              (۰[۱-۹]|۱[۰۱۲])[- /.](۰[۱-۹]|[۱۲][۰-۹]|۳[۰۱])$'; 
                           RegExp regex = new RegExp(pattern); 
                           if (!regex.hasMatch(val)) 
                              return 'Enter a valid date'; 
                           else return null; 
                        },
                        onSaved: (val) => _date = DateTime.parse(val), 
                        initialValue: id == 0 
                        ? '' : expenses.byId(id).formattedDate, 
                        keyboardType: TextInputType.datetime, 
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.category),
                           labelText: 'Category', 
                           labelStyle: TextStyle(fontSize: 18)
                        ),
                        onSaved: (val) => _category = val, 
                        initialValue: id == 0 ? '' 
                        : expenses.byId(id).category.toString(),
                     ), 
                     RaisedButton( 
                        onPressed: _submit, 
                        child: new Text('Submit'), 
                     ), 
                  ],
               ),
            ),
         ),
      );
   }
}

 

در اینجا:

  • از TextFormField برای ایجاد ورودی فرم استفاده می شود.
  • خصیصه validator از TextFormField برای اعتبارسنجی عنصر فرم به همراه الگوهای RegEx استفاده می شود.
  • از تابع _submit همراه با شی expenses برای افزودن یا به روزرسانی هزینه ها در پایگاه داده استفاده می شود.

کد کامل فایل main.dart به شرح زیر است

import 'package:flutter/material.dart'; 
import 'package:scoped_model/scoped_model.dart'; 
import 'ExpenseListModel.dart'; 
import 'Expense.dart'; 

void main() { 
   final expenses = ExpenseListModel(); 
   runApp(
      ScopedModel<ExpenseListModel>(
         model: expenses, child: MyApp(), 
      )
   ); 
}
class MyApp extends StatelessWidget {
   // This widget is the root of your application. 
   @override
   Widget build(BuildContext context) {
      return MaterialApp(
         title: 'Expense',
         theme: ThemeData(
            primarySwatch: Colors.blue, 
         ), 
         home: MyHomePage(title: 'Expense calculator'), 
      );
   }
}
class MyHomePage extends StatelessWidget {
   MyHomePage({Key key, this.title}) : super(key: key);
   final String title;

   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         appBar: AppBar(
            title: Text(this.title),
         ),
         body: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) { 
               return ListView.separated(
                  itemCount: expenses.items == null ? 1 
                  : expenses.items.length + 1, itemBuilder: (context, index) { 
                     if (index == 0) { 
                        return ListTile( title: Text("Total expenses: " 
                        + expenses.totalExpense.toString(), 
                        style: TextStyle(fontSize: 24,fontWeight: 
                        FontWeight.bold),) ); 
                     } else {
                        index = index - 1; return Dismissible(
                           key: Key(expenses.items[index].id.toString()), 
                           onDismissed: (direction) {
                              expenses.delete(expenses.items[index]); 
                              Scaffold.of(context).showSnackBar(
                                 SnackBar(
                                    content: Text(
                                       "Item with id, " + 
                                       expenses.items[index].id.toString() 
                                       + " is dismissed"
                                    )
                                 )
                              );
                           }, 
                           child: ListTile( onTap: () {
                              Navigator.push( context, MaterialPageRoute(
                                 builder: (context) => FormPage(
                                    id: expenses.items[index].id, expenses: expenses, 
                                 )
                              ));
                           }, 
                           leading: Icon(Icons.monetization_on), 
                           trailing: Icon(Icons.keyboard_arrow_right), 
                           title: Text(expenses.items[index].category + ": " + 
                           expenses.items[index].amount.toString() + " \nspent on " + 
                           expenses.items[index].formattedDate, 
                           style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),))
                        );
                     }
                  }, 
                  separatorBuilder: (context, index) {
                     return Divider(); 
                  },
               ); 
            },
         ),
         floatingActionButton: ScopedModelDescendant<ExpenseListModel>(
            builder: (context, child, expenses) {
               return FloatingActionButton(
                  onPressed: () {
                     Navigator.push(
                        context, MaterialPageRoute(
                           builder: (context)
                           => ScopedModelDescendant<ExpenseListModel>(
                              builder: (context, child, expenses) { 
                                 return FormPage( id: 0, expenses: expenses, ); 
                              }
                           )
                        )
                     );
                     // expenses.add(
                        new Expense(
                           // ۲, ۱۰۰۰, DateTime.parse('2019-04-01 11:00:00'), 'Food'
                        )
                     );
                     // print(expenses.items.length); 
                  },
                  tooltip: 'Increment', child: Icon(Icons.add), 
               );
            }
         )
      );
   } 
}
class FormPage extends StatefulWidget {
   FormPage({Key key, this.id, this.expenses}) : super(key: key); 
   final int id; 
   final ExpenseListModel expenses; 
   
   @override 
   _FormPageState createState() => _FormPageState(id: id, expenses: expenses); 
}
class _FormPageState extends State<FormPage> {
   _FormPageState({Key key, this.id, this.expenses}); 
   final int id; 
   final ExpenseListModel expenses; 
   final scaffoldKey = GlobalKey<ScaffoldState>(); 
   final formKey = GlobalKey<FormState>(); 
   double _amount; DateTime _date; 
   String _category;
   void _submit() {
      final form = formKey.currentState; 
      if (form.validate()) {
         form.save(); 
         if (this.id == 0) expenses.add(Expense(0, _amount, _date, _category)); 
         else expenses.update(Expense(this.id, _amount, _date, _category)); 
         Navigator.pop(context); 
      } 
   } 
   @override 
   Widget build(BuildContext context) {
      return Scaffold(
         key: scaffoldKey, appBar: AppBar( 
            title: Text('Enter expense details'), 
         ), 
         body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Form(
               key: formKey, child: Column(
                  children: [
                     TextFormField(
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration( 
                           icon: const Icon(Icons.monetization_on), 
                           labelText: 'Amount', 
                           labelStyle: TextStyle(fontSize: 18)
                        ), 
                        validator: (val) {
                           Pattern pattern = r'^[1-9]\d*(\.\d+)?$'; 
                           RegExp regex = new RegExp(pattern); 
                           if (!regex.hasMatch(val)) return 'Enter a valid number'; 
                           else return null; 
                        },
                        initialValue: id == 0 ? '' 
                        : expenses.byId(id).amount.toString(), 
                        onSaved: (val) => _amount = double.parse(val), 
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.calendar_today), 
                           hintText: 'Enter date', 
                           labelText: 'Date', 
                           labelStyle: TextStyle(fontSize: 18), 
                        ),
                        validator: (val) {
                           Pattern pattern = r'^((?:19|20)\d\d)[- /.]
                           (۰[۱-۹]|۱[۰۱۲])[- /.](۰[۱-۹]|[۱۲][۰-۹]|۳[۰۱])$'; 
                           RegExp regex = new RegExp(pattern); 
                           if (!regex.hasMatch(val)) return 'Enter a valid date'; 
                           else return null; 
                        },
                        onSaved: (val) => _date = DateTime.parse(val), 
                        initialValue: id == 0 ? '' : expenses.byId(id).formattedDate, 
                        keyboardType: TextInputType.datetime, 
                     ),
                     TextFormField(
                        style: TextStyle(fontSize: 22), 
                        decoration: const InputDecoration(
                           icon: const Icon(Icons.category), 
                           labelText: 'Category', 
                           labelStyle: TextStyle(fontSize: 18)
                        ), 
                        onSaved: (val) => _category = val, 
                        initialValue: id == 0 ? '' : expenses.byId(id).category.toString(), 
                     ),
                     RaisedButton(
                        onPressed: _submit, 
                        child: new Text('Submit'), 
                     ),
                  ],
               ),
            ),
         ),
      );
   }
}

 

  • اکنون، برنامه را اجرا کنید.
  • با استفاده از دکمه شناور هزینه های جدید اضافه کنید.
  • با ضربه زدن روی ورودی هزینه ها، هزینه های موجود را ویرایش کنید.
  • با کشیدن ورودی هزینه در هر دو جهت، هزینه های موجود را حذف کنید.

برخی از عکسهای صفحه نوشتن برنامه های پیشرفته در برنامه نویسی فلاتر به شرح زیر است –

Expense Calculator

Enter Expense Details

Total Expenses

Total Expenses

 

منبع.

لیست جلسات قبل آموزش برنامه نویسی فلاتر

  1. معرفی برنامه نویسی فلاتر
  2. آموزش نصب فلاتر
  3. اصول ایجاد یک برنامه فلاتر در Android Studio
  4. معماری فریم ورک برنامه نویسی فلاتر
  5. مقدمه ای بر برنامه نویسی دارت
  6. مقدمه ای بر ویجت ها در برنامه نویسی فلاتر
  7. آموزش طرح بندی در برنامه نویسی فلاتر 
  8. ژست های حرکتی در برنامه نویسی فلاتر 
  9. مدیریت State در برنامه نویسی فلاتر
  10. آموزش انیمیشن در برنامه نویسی فلاتر
  11. آموزش نوشتن کد خاص اندروید در برنامه نویسی فلاتر
  12. آموزش نوشتن کد مخصوص IOS در برنامه نویسی فلاتر
  13. مقدمه ای بر پکیج ها در برنامه نویسی فلاتر 
  14.  مفهوم پایگاه داده در برنامه نویسی فلاتر 
  15. آموزش بین المللی کردن در برنامه نویسی فلاتر
  16. تست کردن برنامه فلاتر
  17. آموزش استقرار برنامه فلاتر
  18. آموزش ابزارهای توسعه در برنامه نویسی فلاتر
0
برچسب ها :
نویسنده مطلب erfan molaei

دیدگاه شما

بدون دیدگاه