स्पंदन - लेखन उन्नत अनुप्रयोग

इस अध्याय में, हम यह जानने जा रहे हैं कि एक पूर्ण विकसित मोबाइल एप्लिकेशन, खर्च_संवाददाता कैसे लिखें। व्यय_क्यूलर का उद्देश्य हमारे व्यय की जानकारी संग्रहीत करना है। आवेदन की पूरी सुविधा इस प्रकार है -

  • व्यय सूची।

  • नए खर्चों में प्रवेश के लिए फॉर्म

  • मौजूदा खर्चों को संपादित / हटाने का विकल्प।

  • किसी भी उदाहरण पर कुल खर्च।

हम फ़्लटर ढांचे के नीचे वर्णित उन्नत सुविधाओं का उपयोग करके cost_calculator एप्लिकेशन को प्रोग्राम करने जा रहे हैं।

  • व्यय सूची दिखाने के लिए ListView का उन्नत उपयोग।

  • प्रपत्र प्रोग्रामिंग।

  • SQLite डेटाबेस प्रोग्रामिंग हमारे खर्चों को संग्रहीत करने के लिए।

  • scoped_model हमारी प्रोग्रामिंग को आसान बनाने के लिए राज्य प्रबंधन।

आइए हम प्रोग्रामिंग शुरू करें expense_calculator आवेदन।

  • एंड्रॉइड स्टूडियो में एक नया फ़्लटर एप्लिकेशन, cost_calculator बनाएं।

  • 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 का उपयोग राज्य प्रबंधन के लिए किया जाता है।

    • intl का उपयोग दिनांक स्वरूपण के लिए किया जाता है।

  • एंड्रॉइड स्टूडियो निम्न चेतावनी प्रदर्शित करेगा कि pubspec.yaml अपडेट किया गया है।

  • निर्भरता विकल्प प्राप्त करें पर क्लिक करें। एंड्रॉइड स्टूडियो को इंटरनेट से पैकेज मिलेगा और एप्लिकेशन के लिए इसे ठीक से कॉन्फ़िगर करना होगा।

  • मौजूदा कोड main.dart में निकालें।

  • नई फ़ाइल जोड़ें, Expense.dart व्यय वर्ग बनाने के लिए। व्यय वर्ग में नीचे के गुण और विधियाँ होंगी।

    • property: id - SQLite डेटाबेस में एक व्यय प्रविष्टि का प्रतिनिधित्व करने के लिए अद्वितीय आईडी।

    • property: amount - राशि खर्च की गई।

    • property: date - राशि खर्च होने पर दिनांक।

    • property: category- श्रेणी उस क्षेत्र का प्रतिनिधित्व करती है जिसमें राशि खर्च की जाती है। जैसे भोजन, यात्रा, आदि;

    • formattedDate - दिनांक संपत्ति को प्रारूपित करने के लिए उपयोग किया जाता है

    • fromMap - व्यय वस्तु में डेटाबेस तालिका से संपत्ति तक के क्षेत्र को मैप करने और एक नई व्यय वस्तु बनाने के लिए उपयोग किया जाता है।

factory Expense.fromMap(Map<String, dynamic> data) { 
   return Expense( 
      data['id'], 
      data['amount'], 
      DateTime.parse(data['date']),    
      data['category'] 
   ); 
}
    • toMap - डार्ट मैप में व्यय वस्तु को परिवर्तित करने के लिए उपयोग किया जाता है, जिसे डेटाबेस प्रोग्रामिंग में आगे उपयोग किया जा सकता है

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 वर्ग बनाने के लिए नई फ़ाइल, डेटाबेस जोड़ें। SQLiteDbProvider वर्ग का उद्देश्य इस प्रकार है -

    • GetAllExpenses पद्धति का उपयोग करके डेटाबेस में उपलब्ध सभी खर्च प्राप्त करें। इसका उपयोग उपयोगकर्ता की सभी व्यय सूचनाओं को सूचीबद्ध करने के लिए किया जाएगा।

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; 
}
    • सम्मिलित विधि का उपयोग करके डेटाबेस में नई व्यय जानकारी जोड़ें। इसका उपयोग उपयोगकर्ता द्वारा नए व्यय प्रविष्टि को जोड़ने के लिए किया जाएगा।

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 (?, ?, ?, ?, ?)", [
                  2, "Pixel", "Pixel is the most feature phone ever", 800, "pixel.png"
               ]
            ); 
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  3, "Laptop", "Laptop is most productive development tool", 2000, "laptop.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  4, "Tablet", "Laptop is most productive development tool", 1500, "tablet.png"
               ]
            );
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  5, "Pendrive", "iPhone is the stylist phone ever", 100, "pendrive.png"
               ]
            ); 
            await db.execute(
               "INSERT INTO Product ('id', 'name', 'description', 'price', 'image') 
               values (?, ?, ?, ?, ?)", [
                  6, "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]);
   }
}
  • Here,

    • डेटाबेस SQLiteDbProvider वस्तु प्राप्त करने के लिए संपत्ति है।

    • initDB SQLite डेटाबेस को चुनने और खोलने के लिए उपयोग की जाने वाली एक विधि है।

  • ExpenseListModel बनाने के लिए एक नई फ़ाइल, ExpenseListModel.dart बनाएँ। मॉडल का उद्देश्य मेमोरी में उपयोगकर्ता के खर्चों की पूरी जानकारी और एप्लिकेशन के उपयोगकर्ता इंटरफ़ेस को अपडेट करना है जब भी उपयोगकर्ता का खर्च मेमोरी में बदलता है। यह scoped_model पैकेज से मॉडल वर्ग पर आधारित है। इसके निम्नलिखित गुण और विधियाँ हैं -

    • _items - खर्चों की निजी सूची।

    • आइटम - सूची में अप्रत्याशित या आकस्मिक परिवर्तन को रोकने के लिए _mems के लिए अनमॉडिफ़िबिलिटीस्ट व्यू <Expense> के लिए गेट्टर।

    • TotalExpense - आइटम्स वैरिएबल के आधार पर कुल खर्चों के लिए गेटटर।

double get totalExpense {
   double amount = 0.0; 
   for(var i = 0; i < _items.length; i++) { 
      amount = amount + _items[i].amount; 
   } 
   return amount; 
}
    • लोड - डेटाबेस से और _items चर में पूरा खर्च लोड करने के लिए उपयोग किया जाता है। यह UI को अपडेट करने के लिए InformListeners को भी कॉल करता है।

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 चर के साथ-साथ डेटाबेस में जोड़ने के लिए उपयोग किया जाता है। यह UI को अपडेट करने के लिए InformListeners को भी कॉल करता है।

void add(Expense item) {
   SQLiteDbProvider.db.insert(item).then((val) { 
      _items.add(val); notifyListeners(); 
   }); 
}
    • अद्यतन - डेटाबेस में और साथ ही _items चर में व्यय आइटम को अद्यतन करने के लिए उपयोग किया जाता है। यह UI को अपडेट करने के लिए InformListeners को भी कॉल करता है।

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(); 
}
    • हटाना - _items चर में और साथ ही डेटाबेस से मौजूदा व्यय मद को हटाने के लिए उपयोग किया जाता है। यह UI को अपडेट करने के लिए InformListeners को भी कॉल करता है।

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(),)
   );
}
  • Here,

    • व्यय वस्तु डेटाबेस से उपयोगकर्ता के सभी व्यय की जानकारी लोड करती है। इसके अलावा, जब एप्लिकेशन पहली बार खोला जाता है, तो यह उचित तालिकाओं के साथ आवश्यक डेटाबेस बनाएगा।

    • स्कोप्डमॉडल आवेदन के पूरे जीवन चक्र के दौरान खर्च की जानकारी प्रदान करता है और किसी भी समय आवेदन की स्थिति का रखरखाव सुनिश्चित करता है। यह हमें 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( 
                     // 2, 1000, DateTime.parse('2019-04-01 11:00:00'), 'Food')
                  ); 
                  // print(expenses.items.length); 
               },
               tooltip: 'Increment', child: Icon(Icons.add), ); 
            }
         )
      );
   }
}
  • Here,

    • ScopedModelDescendant का उपयोग व्यय मॉडल को सूची दृश्य और फ़्लोटिंगएशनबटन विजेट में पास करने के लिए किया जाता है।

    • ListView.separated और ListTile विजेट का उपयोग व्यय की जानकारी को सूचीबद्ध करने के लिए किया जाता है।

    • अस्वीकार्य विजेट का उपयोग स्वाइप जेस्चर का उपयोग करके व्यय प्रविष्टि को हटाने के लिए किया जाता है।

    • नेविगेटर का उपयोग व्यय प्रविष्टि के संपादित इंटरफ़ेस को खोलने के लिए किया जाता है। व्यय प्रविष्टि टैप करके इसे सक्रिय किया जा सकता है।

  • एक फॉर्मपेज विजेट बनाएं। फॉर्मपेज विजेट का उद्देश्य व्यय प्रविष्टि जोड़ना या अद्यतन करना है। यह व्यय प्रविष्टि सत्यापन भी संभालता है।

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)[- /.]
                              (0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$'; 
                           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'), 
                     ), 
                  ],
               ),
            ),
         ),
      );
   }
}
  • Here,

    • TextFormField का उपयोग फॉर्म प्रविष्टि बनाने के लिए किया जाता है।

    • TextFormField की वैध संपत्ति का उपयोग RegEx पैटर्न के साथ फार्म तत्व को मान्य करने के लिए किया जाता है।

    • _submit फ़ंक्शन का उपयोग डेटाबेस में खर्चों को जोड़ने या अपडेट करने के लिए व्यय वस्तु के साथ किया जाता है।

  • 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(
                           // 2, 1000, 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)[- /.]
                           (0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$'; 
                           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'), 
                     ),
                  ],
               ),
            ),
         ),
      );
   }
}
  • अब, एप्लिकेशन चलाएँ।

  • फ़्लोटिंग बटन का उपयोग करके नए खर्च जोड़ें।

  • व्यय प्रविष्टि को टैप करके मौजूदा खर्चों को संपादित करें।

  • किसी भी दिशा में व्यय प्रविष्टि स्वाइप करके मौजूदा खर्चों को हटा दें।

आवेदन के कुछ स्क्रीन शॉट्स इस प्रकार हैं -