תחי ישראל - אין לנו ארץ אחרת

תחי ישראל -אין לנו ארץ אחרת

CRUD באפליקציית Flutter - מדריך שלישי : עדכון רשומה קיימת

מחבר:
בתאריך:

במדריכים הקודמים בסדרת מדריכי ה-Flutter למדנו כיצד לכתוב ולקרוא ממסד נתונים Firebase. במדריך זה נלמד כיצד לעדכן רשומות קיימות.

במדריכים קודמים השתמשנו בטופס כדי להוסיף פריט חדש לרשימה. גם במדריך זה נשתמש בטופס כדי להוסיף פריט וגם כדי לערוך אותו. אם במדריך הקודם מיקמנו את הטופס בתוך פופ אפ במדריך הנוכחי נקצה לטופס מסך משלו כדי שנוכל לתרגל את הנושא של ראוטים routes.

flutter update firebase real-time record

 

להורדת קוד אפליקציית Flutter לעבודה עם מסד הנתונים של Firebase

 

יצירת טופס העריכה בתוך מסך נפרד

נפתח קובץ חדש בתוך התיקייה screens בתוכה נשים את קובץ העריכה כמסך חדש:

lib/screens/car_edit_screen.dart

import 'package:flutter/material.dart';
 
class CarEditScreen extends StatefulWidget {
 const CarEditScreen({super.key});
 
 @override
 State<CarEditScreen> createState() => _CarEditScreenState();
}
 
class _CarEditScreenState extends State<CarEditScreen> {
   final _formKey = GlobalKey<FormState>();
 
   @override
   Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(
       title: const Text("Edit car"),
     ),
     body: Form(
       key: _formKey,
       child: Container(
           // form content goes here
       ),
     ),
   );
 }
}
  • המסך מכיל Scaffold כולל AppBar וטופס Form
  • כשיוצרים ווידג'ט Form צריך להוסיף לו GlobalKey בשביל וידוא אוטומטי של הטופס על ידי Flutter.

 

הטופס

נוסיף לטופס את השדה לעריכת שם המכונית:

lib/screens/car_edit_screen.dart

Form(
  key: _formKey,
  child: Container(
    padding: const EdgeInsets.all(10),
    child: Column(
      TextFormField(
        decoration: const InputDecoration(helperText: "Name"),
        textInputAction: TextInputAction.next,
        validator: (value) {
                 if (value == null || value.isEmpty) {
                   return 'Please enter name';
                 }
                 return null;
               },
         ),
    ),
  ),
),
  • השדה textInputAction מציג חץ ימינה על המקלדת שמעביר את הפוקוס לשדה הבא אחרי שמסיימים את מילוי השדה הנוכחי.
  • הוולידציה מוודאת שמילאו את השדה. אחרת מציגה הודעת שגיאה.
  • מיד נוסיף קונטרולר כדי שאפשר יהיה לעבוד עם המידע שהמשתמש מזין לטופס.

action button next in flutter form

נוסיף TextFormField מתחת לשדה למילוי שם המכונית בשביל למלא בו את המחיר:

lib/screens/car_edit_screen.dart

TextFormField(
  decoration: const InputDecoration(helperText: "Price"),
  textInputAction: TextInputAction.done,
  keyboardType:
                   const TextInputType.numberWithOptions(decimal: true),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter price';
    }
    return null;
   },
),
  • השדה textInputAction מציג וי במקלדת שלחיצה עליו סוגרת את המקלדת בניידים.
  • סוג המקלדת הינו מספרי הודות ל-keyboardType

action button done in keyboard with flutter code

נוסיף את הקונטרולרים כדי שנוכל לעבוד עם המידע אותו הזינו המשתמשים לטופס:

lib/screens/car_edit_screen.dart

class _CarEditScreenState extends State {
 final _formKey = GlobalKey<FormState>();
 
 final TextEditingController _editNameController = TextEditingController();
 final TextEditingController _editPriceController = TextEditingController();
 
  // the rest of the class code
}
  • הקונטרולרים שייכים לסוג TextEditingController. הוספנו שניים. אחד בשביל השם והשני בשביל המחיר. 
  • למרות שמשתמשים מזינים מספרים לשדה מחיר סוג הקונטרולר הוא טקסט. נפגוש את זה בהמשך.

חשוב להיפטר מהקונטרולרים ברגע שיוצאים מהמסך כדי למנוע memory leakage. את זה נעשה בתוך המתודה של ה-life cycle ששמה dispose שרצה כאשר הווידג'ט מוסר לצמיתות מה-widget tree ואין בו יותר צורך:

lib/screens/car_edit_screen.dart

@override
void dispose() {
   _editNameController.dispose();
   _editPriceController.dispose();
 
   super.dispose();
}

נוסיף את הקונטרולרים לשדות הטופס בתור controller:

TextFormField(
  controller: _editNameController,
  decoration: const InputDecoration(helperText: "Name"),
  textInputAction: TextInputAction.next,
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter name';
    }
    return null;
  },
),
TextFormField(
  controller: _editPriceController,
  decoration: const InputDecoration(helperText: "Price"),
  textInputAction: TextInputAction.done,
  keyboardType:
                   const TextInputType.numberWithOptions(decimal: true),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Please enter price';
    }
    return null;
  },
),

 

ניווט באפליקציית Flutter והעברת ארגומנטים בין דפים

נשתמש בארגומנטים של הניווט כדי להעביר לטופס את הערכים איתם הוא צריך לעבוד. בתוך המתודה build של הווידג'ט נשים את הקוד הבא שיקלוט את ה-key, מזהה הרשומה במסד הנתונים:

lib/screens/car_edit_screen.dart

final carId = ModalRoute.of(context)?.settings.arguments as String;
  • צריכים לעבוד בתוך ה-build בשביל ה-context.
  • המשתנה cardId הוא ה-key המזהה הייחודי של הרשומה (מכונית) במסד הנתונים.

נוסיף טבלת ראוטים routes לקובץ המרכזי של האפליקציה:

lib/main.dart

class MyApp extends StatelessWidget {
 const MyApp({super.key});
 @override
 Widget build(BuildContext context) {
   return MultiProvider(
     providers: [
       ChangeNotifierProvider(
         create: (context) => CarsProvider(),
       ),
     ],
     child: MaterialApp(
       title: 'My Garage App',
       theme: ThemeData(
         primarySwatch: Colors.blue,
       ),
       initialRoute: "/",
       routes: {
         "/": (context) => const HomeScreen(),
         "/car-edit": (context) => const CarEditScreen(),
       },
     ),
   );
 }
}
  • המפה routes מחזיקה את שמות הראוטים ולאן הם מפנים. לדוגמה, שם נתיב "/car-edit" מפנה לקלאס CarEditScreen
  • שם הנתיב "/" מפנה למסך הבית HomeScreen.
  • הראוט ממנו האפליקציה מתחילה הוא הנתיב של דף הבית.
initialRoute: "/"

נחזור למסך עריכת הטופס בו נקלוט את הפרמטרים המועברים על גבי הראוט.

אם נקבל key שאינו מחרוזת ריקה אז נשלוף את המידע ממסד הנתונים ונציג למשתמש כערכים בתוך הטופס.

lib/screens/car_edit_screen.dart

נוסיף את הקוד הבא מעל למתודה build:

final _localCarData =
     CarData(name: '', price: 0, isFavorite: false, date: null, active: true);
 
var _errorMsg = '';

lib/screens/car_edit_screen.dart

נוסיף את הקוד הבא בתוך המתודה build:

final carId = ModalRoute.of(context)?.settings.arguments as String;
 
   _editNameController.text = '';
   _editPriceController.text = '';
 
   // if key not empty
   if (carId != '') {
     // get the data about a record from the provider
     Provider.of<CarsProvider>(context, listen: false)
         .getByKey(carId)
         .then((CarData data) {
       // fill the CarData
       _localCarData.name = data.name;
       _localCarData.price = data.price;
       _localCarData.isFavorite = data.isFavorite;
 
       // populate the form fields with the retrieved data
       _editNameController.text = _localCarData.name.toString();
       _editPriceController.text = _localCarData.price.toString();
     }).catchError((errMsg) {
       // show error message in case of an error
       setState(() {
         _errorMsg = errMsg;
       });
     });
   }
  • אנחנו מעבירים למתודה getByKey את ה-carId ומקבלים אובייקט carData או מציגים הודעת שגיאה בכל מקרה אחר.
  • אנחנו ניגשים למתודה ב-provider באמצעות Provider.of. את הנושא של provider הסברתי במדריך על provider ב-Flutter.
  • כשהמידע חוזר מציבים את ערכיו לתוך אובייקט CarData מקומי, וגם לתוך הקונטרולרים.

בשביל להגיש את הטופס לוחצים על כפתור submit אשר מפעיל 2 מתודות שונות בהתאם למידע בטופס. אם הפריט קיים במסד הנתונים מופעלת המתודה update של ה-provider. אם הפריט חדש, ולא קיים במסד הנתונים, מופעלת המתודה add.

 

המתודות בהם נשתמש להוספה ועדכון של רשומות בתוך קלאס ה-provider

את המתודה add בה אנו משתמשים להוספת פריטים לרשימה ורישום במסד הנתונים הוספנו כבר בעבר לקלאס CarsProvider:

lib/providers/cars_provider.dart

Future<bool> add(String name, double price) async {
   try {
     final Map<String, dynamic> data = {
       "name": name.toString(),
       "price": price,
       "isFavorite": false,
       "date": DateTime.now().millisecondsSinceEpoch,
       "active": true,
     };
     await _db.child(carsPath).push().set(data);
     return true;
   } catch (err) {
     return false;
   }
}

נוסיף את המתודה update המשמשת לעדכון רשומה במסד הנתונים. המתודה מקבלת את ה-key המזהה הייחודי של הרשומה במסד הנתונים ואת ה-CarData המידע אותו מעוניינים לעדכן:

lib/providers/cars_provider.dart

Future<bool> update(CarData item, String key) async {
   try {
     final Map<String, dynamic> data = {
       "name": item.name,
       "price": item.price,
       "date": DateTime.now().millisecondsSinceEpoch,
       "isFavorite": item.isFavorite
     };
     _db.child(carsPath).child(key).update(data);
     return true;
   } catch (err) {
     return false;
   }
 }
  • המתודה מחזירה ערך בוליאני true או false בהתאם להצלחה בעדכון הרשומה במסד הנתונים.

 

נחזור לטופס

לחיצה על כפתור ההגשה של הטופס תקרא לאחת הפונקציות, לעדכון או להוספה של רשומה, תלוי אם ה-key מזהה הרשומה קיים או ריק:

lib/screens/car_edit_screen.dart

ElevatedButton(
  onPressed: () {
    if (_formKey.currentState!.validate()) {
      _localCarData.name =
                     _editNameController.text.trim().toString();
      _localCarData.price =
                       double.parse(_editPriceController.text.trim());
 
       if (carId != '') {
         Provider.of<CarsProvider>(context, listen: false)
           .update(_localCarData, carId);
                        
        } else {
          Provider.of<CarsProvider>(context, listen: false)
            .add(_localCarData.name as String,
                             _localCarData.price as double);
        }
      }
    },
  },
  child: const Text("Save"),
),
  • Flutter מוודא את הטופס לפני שהוא מעביר את המידע לטיפול בפונקציות של ההוספה או עדכון.
  • הגרסה לעיל היא מאוד מפושטת כי אנחנו רוצים לדעת אם הפעולה הצליחה וגם לספק חיווי למשתמש.

נפרט את העבודה עם כל אחת מהמתודות של הוספה ועדכון רשומה.

הקוד להוספת רשומה למסד הנתונים:

lib/screens/car_edit_screen.dart

Provider.of<CarsProvider>(context, listen: false)
  .add(_localCarData.name as String, _localCarData.price as double)
  .then((success) {
    if (success) {
      // navigate back
      if (!mounted) return;
      Navigator.of(context).pop();
    } else {
      _errorMsg = 'Problem adding a new car';
    }
  },
);
  • המתודה add בצד ה-provider מחזירה ערך בוליאני שלו אנו קוראים success. במקרה של כשלון, תופיע הודעת שגיאה. במקרה של הצלחה המשתמש יחזור לדף ממנו הגיע באמצעות:
Navigator.of(context).pop();

הקוד לעדכון רשומה במסד הנתונים:

lib/screens/car_edit_screen.dart

Provider.of<CarsProvider>(context, listen: false)
  .update(_localCarData, carId)
  .then((success) {
     if (success) {
     // message to user                        
ScaffoldMessenger.of(context).hideCurrentSnackBar();
       ScaffoldMessenger.of(context).showSnackBar(
       const SnackBar(
         backgroundColor: Colors.green,
         content: Text('Successfully updated the car',
         style: TextStyle(
           color: Colors.black,
           fontWeight: FontWeight.w700)),
         duration: Duration(seconds: 5)),
        );
 
        // navigate back
        if (!mounted) return;
     Navigator.of(context).pop();
      } else {
        _errorMsg = 'Problem updating the car';
      }
    },
);
  • המתודה update בצד ה-provider מחזירה ערך בוליאני success. במקרה של כשלון, תופיע הודעת שגיאה. במקרה של הצלחה יופיע SnackBar כולל הודעה על הצלחת הפעולה והמשתמש יחזור לדף ממנו הגיע.

 

עדכון הרשומות בדף הבית בזמן אמת

דבר נוסף שאנחנו צריכים לעדכן הוא שלחיצה על הלב תגרום לשינוי הערך הבוליאני isFavorite של הפריט.

הלב נמצא בתוך הווידג'ט CarCardWidget אשר מקבל פרמטר את המידע אודות פריטי הרשימה כאובייקט Car עם היווסדו.

בזכות המידע שיש לנו האם פריט הרשימה מוגדר isFavorite אנחנו יכולים לשנות את מראה הווידג'ט:

lib/widgets/car_card_widget.dart

IconButton(
  icon: car.carData.isFavorite == false
    ? const Icon(Icons.favorite_border)
    : const Icon(Icons.favorite),
    color: Colors.red,
    onPressed: () {
      // update isFavorite
    },
),

display of isFavorite state in a flutter app via a heart icon full or empty

בתגובה ללחיצה של המשתמש על האייקון נהפוך את המצב של isFavorite (מ-true ל-false או הפוך) וערך זה הוא מה שנזין לפונקציה המעדכנת update אשר ב-provider:

lib/widgets/car_card_widget.dart

IconButton(
  icon: car.carData.isFavorite == false
    ? const Icon(Icons.favorite_border)
    : const Icon(Icons.favorite),
    color: Colors.red,
    onPressed: () {
      // update isFavorite
      final localCarData = car.carData;
 
       localCarData.isFavorite =
                     localCarData.isFavorite == true ? false : true;
         Provider.of<CarsProvider>(context, listen: false)
             .update(localCarData, car.key.toString());
    },
),

 

מדריכים נוספים בסדרת מדריכי ה- Flutter שעשויים לעניין אותך

CRUD באפליקציית Flutter - מדריך שני : קריאה ממסד הנתונים

CRUD באפליקציית Flutter - מדריך ראשון : כתיבה למסד נתונים

הקמת אפליקציית Flutter פשוטה על גבי מסד נתונים Firebase

 

לכל המדריכים בסדרת ה- Flutter

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

 

המדריכים באתר עוסקים בנושאי תכנות ופיתוח אישי. הקוד שמוצג משמש להדגמה ולצרכי לימוד. התוכן והקוד המוצגים באתר נבדקו בקפידה ונמצאו תקינים. אבל ייתכן ששימוש במערכות שונות, דוגמת דפדפן או מערכת הפעלה שונה ולאור השינויים הטכנולוגיים התכופים בעולם שבו אנו חיים יגרום לתוצאות שונות מהמצופה. בכל מקרה, אין בעל האתר נושא באחריות לכל שיבוש או שימוש לא אחראי בתכנים הלימודיים באתר.

למרות האמור לעיל, ומתוך רצון טוב, אם נתקלת בקשיים ביישום הקוד באתר מפאת מה שנראה לך כשגיאה או כחוסר עקביות נא להשאיר תגובה עם פירוט הבעיה באזור התגובות בתחתית המדריכים. זה יכול לעזור למשתמשים אחרים שנתקלו באותה בעיה ואם אני רואה שהבעיה עקרונית אני עשוי לערוך התאמה במדריך או להסיר אותו כדי להימנע מהטעיית הציבור.

שימו לב! הסקריפטים במדריכים מיועדים למטרות לימוד בלבד. כשאתם עובדים על הפרויקטים שלכם אתם צריכים להשתמש בספריות וסביבות פיתוח מוכחות, מהירות ובטוחות.

המשתמש באתר צריך להיות מודע לכך שאם וכאשר הוא מפתח קוד בשביל פרויקט הוא חייב לשים לב ולהשתמש בסביבת הפיתוח המתאימה ביותר, הבטוחה ביותר, היעילה ביותר וכמובן שהוא צריך לבדוק את הקוד בהיבטים של יעילות ואבטחה. מי אמר שלהיות מפתח זו עבודה קלה ?

השימוש שלך באתר מהווה ראייה להסכמתך עם הכללים והתקנות שנוסחו בהסכם תנאי השימוש.

הוסף תגובה חדשה

 

 

ענה על השאלה הפשוטה הבאה כתנאי להוספת תגובה:

מהם שלוש רשויות השלטון בישראל?