دیتابیس Hive در فلاتر به همراه مثال

با سلام و وقت بخیر خدمت شما عزیزانتوی این آموزش قصد داریم دیتابیس Hive رو در فلاتر با استفاده از یک برنامه ساده که نام شخص و نام کشور محل زندگی اون فرد رو در دیتابیس ذخیره میکنه و به ما نمایش میده رو شرح بدیم. ما توی این مثال ۴ عمل اصلی CRUD(Create,Read,Upadate,delete) رو در دیتابیس به طور مختصر در قالب ساخت اپ توضیح میدیم.قبل از اینکه وارد جزئیات اصلی دیتابیس Hive بشیم در مورد انواع دیتابیس،تفاوت هاشون و چرایی انتخاب Hive به طور مختصر توضیح میدیم و بعد وارد توضیحات دیتابیس Hive میشیم.ذخیره کردن داده در به صورت محلی یا Local یکی از مفاهیم پایه ای و مهم در بسیاری از اپ ها هست. برای مثال ذخیره کردن ادرس و اطلاعات یک مشتری برنامه سفارش غذای آنلاین یا مثلا ذخیره کردن مقدار امتیاز کسب شده که هر شخص در یک بازی موبایلی کسب میکنه یا مقدار متغییری که نمایش میده کاربر دارک مود یا لایت مود رو در اپلیکیشن انتخاب کرده همگی این ها نیاز به یک دیتابیس داخلی دارن و چندین کاربرد دیگه که در اپ های مختلف مشاهده کردین.با این مثال ها متوجه اهمیت وجود دیتابیس در اپلیکیشن ها شدیم.فلاتر گزینه های مختلفی برای ذخیره سازی داده به صورت محلی در اختیار ما گذاشته که برنامه نویس بر اساس کابرد اپلیکیشنی که درحال توسعه اون هست میتونه بین این گزینه ها یکی رو انتخاب کنه برای مثال دیتابیس Shared_Preferences پکیج مناسبی برای ذخیره ساختارهای کوچک Key-Value در فلاتر هست که یک مقدار رو به عنوان کلید میگیره و یک مقدار رو به عنوان مقدار اون کلید که مثلا برای ذخیره مقدار روشن یا خاموش بودن دارک مود میتونه گزینه خوبی باشه.یکی دیگه از گزینه ها دیتابیس Sqflite هست که پکیجی هست بر پایه Sqlite که در فلاتر استفاده میشه. این دیتابیس انتخاب خوبی هست برای ذخیره کردن داده ها در جداولی که دیتاها در اون جداول با هم ارتباط دارن و در ارتباطاتشون پیچیدگی زیادی وجود داره و ما در چنین مواقعی از این دیتابیس استفاده میکنیم.ولی اگر ما میخوایم که از دیتابیسی استفاده کنیم که پیچیدگی کد نویسی کمتری داشته باشه و سریع و امن باشه و وابستگی نیتیو نیاز نباشه و بتونیم حتی در فلاتر وب هم از اون استفاده کنیم ،دیتابیس Hive گزینه خیلی خوبی برای ما هست.چرا دیتابیس Hive?با این گزینه های در دسترس چرا ما از دیتابیس Hive استفاده کنیم؟چون دیتابیس سبکی هست از لحاظ حجمی و راه حل سریعی هست برای استفاده از ذخیره کردن ساختار Key-Value در اپلیکیشن و اینکه Cross-Platform هست و ما میتونیم همزمان اون رو در(فلاتر موبایل،وب و دستکتاپ) استفاده کنیم که کراس پلتفرم بودنش مزیتی هست نسبت به دیتابیس Sqflite در فلاتر.و اینکه دیتابیس hive از نوع NOSQL دیتابیس برای فلاتر هست که نیازی به نوشتن دستورات sql ای در فلاتر نیست و به طور اتوماتیک با استفاده از انوتیشن ها ساختارهارو برای ما میسازه.در تصویر پایین میتونید مشاهده کنید میزان سرعت خواندن و نوشتن Hive رو نسبت به بقیه دیتابیس هادر بالا اهمیت دیتابیس در اپلیکیشن ها و مزیت Hive نسبت به بقیه دیتابیس ها برای ما روشن شد و حالا بریم سراغ استفاده از دیتابیس.نکته:دیتابیس های مختلف دیگه ای هم وجود داره و شما میتونید توی گوگل در مورد انواع اون ها و تفاوت هاشون مطالعه کنید وتصمیم بگیرید که کدوم رو استفاده کنید،و ما سعی کردیم شناخته شده ها رو معرفی کنیم.با من همراه باشید.در تصویر پایین خروجی اپلیکشنی که قرار هست با هم بسازیم رو مشاهده میکنید.ما یک پروژه جدید فلاتر میسازیم به نام hive_demo و در pubspec.yaml پروژه دو تا پکیج زیر رو اضافه میکنیم.dependencies: hive: ^2.1.0 hive_flutter: ^1.1.0شما میتونید برای به روز بودن ورژن پکیج ها از وبسایت Pub.dev اخرین نسخه رو به پروژتون اضافه کنید.و محتوای زیر رو به جای فایل Main.dart جایگزین کنید.import 'package:flutter/material.dart'; main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Hive Demo', theme: ThemeData( primarySwatch: Colors.purple, ), debugShowCheckedModeBanner: false, home: InfoScreen(), ); } }همون طور که مشاهده میکنید با در قسمت home صفحه InfoScreen رو قرار دادیم که شما میتونید یک صفحه با همین نام بسازید برای نمایش اطلاعات کاربر،ولی قبل از اینکه وارد پیاده سازی قسمت های مختلف برنامه بشیم بهتره یه خورده در مورد مفاهیم دیتابیس Hive توضیح بدیم که درک بهتری داشته باشیم و بعد قسمت های مختلف برنامه رو پیاده سازی کنیم.مفهوم Box در دیتابیس Hiveدیتابیس Hive از یک مفهومی به اسم Boxes برای ذخیره کردن دیتا در دیتابیس استفاده میکنه، اگه قبلا با دیتابیس Sql کار کرده باشین با مفهوم Table در اون آشنا هستین و یک Box مفهومی شبیه به Table هست،با این تفاوت که Box ها فاقد ساختار دقیقی هستن،‌Box ها انعطاف پذیر هستن و اون ها فقط میتونن ارتباطات ساده بین دیتاها رو هندل کنن.قبل از اینکه دسترسی پیدا کنیم به داده های ذخیره شده یک جعبه ما باید اون Box یا جعبه رو باز کنیم،این کار کل محتوای داخل جعبه رو در مموری بارگزاری میکنه و باعث میشه خیلی راحت و سریع به محتوای داخل جعبه قابل دسترسی باشند.این دو خط پایین یک مثال ساده از این هست که چطور جعبه ای به نام peopleBox رو باز میکنیم و پارامتر name این جعبه رو دریافت میکنیم.var box = await Hive.openBox('peopleBox'); String name = box.get('name');غیر از ‌Box های معمولی ما مدل های مختلفی از box ها داریم که شامل Lazy box و Encrypted box میشه: کاربرد Lazy box:همون طور که گفتیم جعبه های معمولی داده های مارو از حافظه محلی در مموری بارگیری میکنن و جعبه ما باز میمونه،اما وقتی داده های سنگینی داشته باشیم این راه خوبی برای دسترسی به داده ها نیست و حجم زیادی از داده وارد مموری ما بشه ممکنه باعث وجود خطا در برنامه بشه و حافظه زیادی رو اشغال کنه،به این خاطر ما از Lazy box استفاده میکنیم.در Lazy box ، فقط کلید ها یا Key برای خواندن و ذخیره کردن داخل مموری بارگزاری میشن، و ما میتونیم با استفاده از کلید داده مورد نظرمون رو از داخل جعبه فراخوانی کنیم.دوخط زیر رو مشاهده کنید به این طریق میتونیم از Lazy box استفاده کنیم.var lazyBox = await Hive.openLazyBox('hugePeopleBox');String name = await lazyBox.get('name');ما برای دسترسی به محتویات داخل جعبه های معمولی نیاز به استفاده از مفهوم await نداریم ولی در Lazy box باید استفاده کنیم به خاطر اینکه داده ها و محتوای ما درحال حاضر در مموری نیستند و فقط با استفاده از کلیدها قابل دسترسی هستند.ذخیره کردن داده های حساس با استفاده از Encrypted boxشما ممکنه بخواید داده حساسی از کاربر رو داخل دیتابیستون ذخیره کنید که به راحتی قابل دسترسی نباشه اینجا هست که مفهوم Encrypted ‌‌‌Box به ما کمک میکنه که این کارو انجام بدیم.دیتابیس Hive از مفهوم رمزگذاری AES-256 در خارج از Box یا جعبه پشتیبانی میکنه همراه با یک تابع کمکی برای تولید یک کلید رمزگذاری با استفاده از الگوریتم Fortuna.برای ذخیره کردن ایمن کلیدرمزگزاری شده بهتره از پکیج flutter_secure_storage استفاده کنید.اینجا یک مثال از ساخت و بازکردن یک Encrypted Box رو مشاهده میکنید.const secureStorage = FlutterSecureStorage(); final encryprionKey = await secureStorage.read(key: 'key'); if (encryprionKey == null) { final key = Hive.generateSecureKey(); await secureStorage.write( key: 'key', value: base64UrlEncode(key), ); } final key = await secureStorage.read(key: 'key'); final encryptionKey = base64Url.decode(key!); print('Encryption key: $encryptionKey'); await Hive.openBox( 'securedBox', encryptionCipher: HiveAesCipher(encryptionKey), );برای واکشی و ذخیره دیتا ها در جعبه شما میتونید از متدهای زیر استفاده کنید.final encryptedBox = Hive.box('securedBox'); _getData() { setState(() { data = encryptedBox.get('secret'); }); log('Fetched data'); } _putData() async { await encryptedBox.put('secret', 'Test secret key'); log('Stored data'); }برای مشاهده مثال کامل با استفاده از این نوع جعبه شما میتونید روی این لینک کلیک کنید.خوب الان که مفاهیم کلی دیتابیس رو درک کردیم بریم به سراغ ادامه مثال:قبل از اینکه عملیات CRUD رو توضیح بدیم باید دیتابیس خودمون رو initialize کنیم و یک ‌Box یا جعبه رو باز کنیم که این جعبه برای ذخیره داده های ما استفاده میشه.دیتابیس باید قبل از اینکه هر جعبه ای باز بشه، initialize بشه،بهترین مکان در تابع main برنامه هست.دستورات زیر رو مشاهده کنید.main() async { // Initialize hive await Hive.initFlutter(); runApp(MyApp()); }فقط باید حواستون باشه که تابع ما باید asynchronous باشه و از await قبل از init کردن استفاده کنیم.ما در این برنامه فقط یک جعبه داریم اگر که شما قصد دارید از جعبه های مختلفی استفاده کنید باید مواظب باشید حتما قبل از استفاده اون جعبه رو باز کنید.main() async { // Initialize hive await Hive.initFlutter(); // Open the peopleBox await Hive.openBox('peopleBox'); runApp(MyApp()); }خوب الان همه چی اماده شده که ما عملیات CRUD رو که مخفف ساختن،خواندن،اپدیت،حذف کردن داده هست رو پیاده کنیم.عملیات CRUD در اپلیکیشن:ما عملیات های خودمون رو در داخل صفحه Info Screen که قبلا در موردش صحبت کردیم که یک صفحه StateFul Widget هست تعریف میکنیم.ساختار صفحه ما به شکل زیر هست:import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; class InfoScreen extends StatefulWidget { @override _InfoScreenState createState() => _InfoScreenState(); } class _InfoScreenState extends State<InfoScreen> { late final Box box; @override void initState() { super.initState(); // Get reference to an already opened box box = Hive.box('peopleBox'); } @override void dispose() { // Closes all Hive boxes Hive.close(); super.dispose(); } @override Widget build(BuildContext context) { return Container(); } }کد بالا رو که نگاه کنید ما جعبه خودمون رو در متد initState در داخل یک متغیر به اسم box فراخوانی میکنیم.(جعبه ای که قبلا ساختیم رو در اینجا فقط فراخوانی میکنیم) و ما باید بعد از اتمام کارمون با جعبه و قبل از بستن اپلیکیشن ببندیم که ما در متد dispose دیتابیس خودمون رو میبندیم که باعث مشکلات حافظه نشه...خوب بریم چندتا متد تعریف کنیم برای عملیات CRUD در اپلیکیشنمون:class _InfoScreenState extends State<InfoScreen> { late final Box box; _addInfo() async { // Add info to people box } _getInfo() { // Get info from people box } _updateInfo() { // Update info of people box } _deleteInfo() { // Delete info from people box } // ... }خوب ما یک Ui خیلی ساده با استفاده از کدهای زیر میسازیم برای تست و بررسی اینکه عملیات های ما به درستی کار میکنه یا نه؟!class _InfoScreenState extends State<InfoScreen> { // ... @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('People Info'), ), body: Center( child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ ElevatedButton( onPressed: _addInfo, child: Text('Add'), ), ElevatedButton( onPressed: _getInfo, child: Text('Get'), ), ElevatedButton( onPressed: _updateInfo, child: Text('Update'), ), ElevatedButton( onPressed: _deleteInfo, child: Text('Delete'), ), ], ), ), ); } }خروجی کد ما به این شکل هست:ذخیره کردن دیتا در Hive :برای این که ما بتونیم داده در داخل دیتابیس و باکسمون ذخیره کنیم باید اون متدی که قبلا تعریف کردیم برای فراخوانی دیتابیسمون به اسم box رو به همراه متد Put به شکل زیر استفاده کنیم،این متد زوج های کلید مقدار یا همون زوج key_value رو میپذیره.// Add info to people box _addInfo() async { // Storing key-value pair box.put('name', 'John'); box.put('country', 'Italy'); print('Info added to box!'); }خوب ما متد add رو به شکل بالا مقدار دهی میکنیم،مقدار اول میشه نام کلید که در اولی name هست و رشته دوم میشه مقدار name که John هست و خط بعدیش هم که کشور و نام اون رو میگیره.همچنین Hive کلید های integer ای رو هم ساپورت میکنه و میتونیم از کلیدهای auto-incrementing هم استفاده کنیم که خیلی کاربردی هست زمانی که ذخیره کنیم چندین مقداررو شبیه یک لیست و بخوایم با استفاده از یک شاخص خاص اون مقدار رو از داخل دیتابیس یا باکسمون بازیابی کنیم از این روش استفاده میکنیم.برای روشن تر شدن مطلب کد زیر رو مشاهده کنید.box.add('Linda'); // index 0, key 0box.add('Dan'); // index 1, key 1دریافت دیتا از Box :برای خوندن دیتا از جعبه ما از متد get روی متغییری که قبلا ساختیم به اسم box استفاده میکنیم که در قسمت put هم مشاهده کردین// Read info from people box _getInfo() { var name = box.get('name'); var country = box.get('country'); print('Info retrieved from box: $name ($country)'); }نکته اگه از auto-incrementing values یا مقادیری که به صورت اتوماتیک و افزایشی مقدار دهی میشن استفاده کنیم میتونیم با استفاده از index اون رو فراخوانی کنیم به شکل زیر:box.getAt(0); // retrieves the value with index 0box.getAt(1); // retrieves the value with index 1اپدیت کردن دیتا:برای اپدیت کردن داده ما میتونیم از همون متود put که برای اضافه کردن داده از اون استفاده کردیم،استفاده کنیم که وقتی در داخل اون key های قبلی مقادیر جدید قرار میدیم،جایگزین قبلی میشه،کد زیر رو مشاهده کنید:// Update info of people box _updateInfo() { box.put('name', 'Mike'); box.put('country', 'United States'); print('Info updated in box!'); }مقدار اسم و کشور رو به Mike و UnitedState تغییر دادیم.جایگزین مقادیر قبلی میکنه.نکته: اگر از auto-incrementing values استفاده کرده باشیم، باید از متد putAt به شکل زیر استفاده کنیم،این متد مقادیر با یک شاخصه خاص مثلا index 0 رو اپدیت میکنه به شکل زیر:box.putAt(0, 'Jenifer');حذف کردن دیتا در جعبه:برای حذف کردن داده میتونیم از متد delete به شکل زیر استفاده کنیم:// Delete info from people box _deleteInfo() { box.delete('name'); box.delete('country'); print('Info deleted from box!'); }بعد از حذف اگر بخوایم مشخصه حذف شده رو برگردونیم یا بهتره بگیم با get مقدارش رو بخونیم به ما null بر میگردونه.نکته : اگر از auto-incrementing values استفاده کرده باشیم باید به شکل زیر اون رو حذف کنیم:box.deleteAt(0);استفاده از اشیاء شخصی سازی شده با استفاده از TypeAdapter :اگرمتوجه تیتر بالا نشدید ،ادامه مطلب رو بخونید متوجه موضوع میشین(واقعا پیدا کردن معادل فارسی بعضی از کلمات خیلی سخته D:)به طور معمول hive از همه ی primitive type ها نظیر List,Map,DateTime,Uint8List اما گاهی اوقات نیاز هست دیتا مدل های شخصی سازی شده خودمون رو داشته باشیم که باعث میشه مدیریت داده ها آسون تر بشه،برای این کار میتونیم از TypeAdapter ها استفاده کنیم.تایپ آداپترها هم میتونن به صورت دستی تولید بشن و هم به صورت اتوماتیک.بهتره به صورت اتوماتیک این کار رو انجام بدیم که باعث باعث میشه مشکلات احتمالی که وقتی دستی انجام میدیم به حداقل برسه و همچنین روش خیلی سریعتری هست.کلاس مدل خودمون رو به شکل زیر تعریف میکنیم:class Person { final String name; final String country; Person({ required this.name, required this.country, }); }ساخت Hive Adapter :ابتدا نیاز هست چند تا dependencies رو برای ساخت TypeAdapter به فایل pubspec.yaml خودمون اضافه کنیم.dev_dependencies: hive_generator: ^1.1.2 build_runner: ^2.1.8و بعد از اضافه کردن dependencies ها مثل کدهای زیر انوتیشن ها رو به هر متغییرمون اضافه میکنیم.import 'package:hive/hive.dart'; part 'people.g.dart'; @HiveType(typeId: 1) class People { @HiveField(0) final String name; @HiveField(1) final String country; People({ required this.name, required this.country, }); }در قسمت بالا با استفاده از کلمه کلیدی part و اضافه کردن .g در وسط نام کلاس مدلمون مشخص میکنیم که از این کلاس مدل ما باید به صورت اتوماتیک تایپ اداپتر ساخته بشه.انوتیشن ها هم از نامشون مشخصه مثلا گفتیم فیلد جعبه ما اولیش نام هست دومیش نام کشور.خوب بعد از اینکه کلاس بالا رو ساختیم با استفاده از دستور زیر در ترمینال به طور اتوماتیک کلاس رو برای ما میسازه:flutter packages pub run build_runner buildرجیستر کردن TypeAdapter :باید قبل از اینکه جعبه رو باز کنیم TypeAdapter امون رو رجیستر کنیم وگرنه به ارور بر میخوریم و این کار رو به شکل زیر در متد main انجام میدیم.main() async { // Initialize hive await Hive.initFlutter(); // Registering the adapter Hive.registerAdapter(PersonAdapter()); // Opening the box await Hive.openBox('peopleBox'); runApp(MyApp()); }بعد از اتمام این کار بریم برای ساخت اپلیکیشن نهایی :ما سه تا صفحه داریم به اسم:۱- صفحه add یا AddScreen برای اضافه و ذخیره کردن داده در دیتابیس۲- صفحه Info یا InfoScreen برای نمایش و حذف داده های موجود در دیتابیس3- صفحه Update یا UpdateScreen برای اپدیت کردن داده در دیتابیسنیاز نیست که فایل main برنامه رو تغییر بدیم و تمام کارهای برنامه رو در همین صفحات انجام میدیم.صفحه Add:صفحه add ما شامل دوتا ورودی برای نام شخص و نام کشور هست و یک دکمه در پایین صفحه برای اضافه کردن داده های ورودی به دیتابیس مثل شکل زیر:کد صفحه بدین صورت هست:class AddScreen extends StatefulWidget { @override _AddScreenState createState() => _AddScreenState(); } class _AddScreenState extends State<AddScreen> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text('Add Info'), ), body: Padding( padding: const EdgeInsets.all(16.0), child: AddPersonForm(), ), ); } }همون طور که در داخل فرزند Padding کد بالا مشاهده میکنید متد AddPersonForm ما ویجت اصلی هست زمانی که Ui ساخته میشه و همچنین شامل عملگرهای ذخیره سازیHive هست که ساختار پایه این متد به شکل زیر هست.class AddPersonForm extends StatefulWidget { const AddPersonForm({Key? key}) : super(key: key); @override _AddPersonFormState createState() => _AddPersonFormState(); } class _AddPersonFormState extends State<AddPersonForm> { late final Box box; @override void initState() { super.initState(); // Get reference to an already opened box box = Hive.box('peopleBox'); } @override Widget build(BuildContext context) { return Container(); } }ما مثل مثال قبل در متد initstate رفرنس جعبه باز شده خودمون رو در متغیر box میریزیم و حالا ما باید یک GlobalKey برای فرم و جند تا کنترلر برای ادیت کردن تکست ها تعریف کنیم مثل شکل زیر:class _AddPersonFormState extends State<AddPersonForm> { final _nameController = TextEditingController(); final _countryController = TextEditingController(); final _personFormKey = GlobalKey<FormState>(); // ... }در کدهای زیر هم متدهایی برای ذخیره کردن دیتا در هایو و اضافه کردن یک اعتبارسنج برای متن هامون تعریف میکنیم:class _AddPersonFormState extends State<AddPersonForm> { // ... // Add info to people box _addInfo() async { Person newPerson = Person( name: _nameController.text, country: _countryController.text, ); box.add(newPerson); print('Info added to box!'); } String? _fieldValidator(String? value) { if (value == null || value.isEmpty) { return 'Field can\'t be empty'; } return null; } // ... } و کد Ui برنامه ما هم به شکل زیر هست:class _AddPersonFormState extends State<AddPersonForm> { // ... @override Widget build(BuildContext context) { return Form( key: _personFormKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Name'), TextFormField( controller: _nameController, validator: _fieldValidator, ), SizedBox(height: 24.0), Text('Home Country'), TextFormField( controller: _countryController, validator: _fieldValidator, ), Spacer(), Padding( padding: const EdgeInsets.fromLTRB(8.0, 0.0, 8.0, 24.0), child: Container( width: double.maxFinite, height: 50, child: ElevatedButton( onPressed: () { if (_personFormKey.currentState!.validate()) { _addInfo(); Navigator.of(context).pop(); } }, child: Text('Add'), ), ), ), ], ), ); } }صفحه Update :صفحه Update هم دقیقا در ظاهر مثل صفحه Add هست با این تفاوت ما به این صفحه شی از کلاس Person پاس میدیم که مقادیر قبلی که در دیتابیس هستن رو نمایش بده و ما بتونیم اون رو تغییر بدیم.کد زیر برای صفحه Update :class UpdateScreen extends StatefulWidget { final int index; final Person person; const UpdateScreen({ required this.index, required this.person, }); @override _UpdateScreenState createState() => _UpdateScreenState(); } class _UpdateScreenState extends State<UpdateScreen> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( title: Text('Update Info'), ), body: Padding( padding: const EdgeInsets.all(16.0), child: UpdatePersonForm( index: widget.index, person: widget.person, ), ), ); } }تنها تفاوت در در متد UpdatePersonForm نسبت به Add وجود متدهایی برای اپدیت مقادیر موجود در دیتابیس ما هست.در کد زیر مشاهده میکنید:class _UpdatePersonFormState extends State<UpdatePersonForm> { late final _nameController; late final _countryController; late final Box box; // ... // Update info of people box _updateInfo() { Person newPerson = Person( name: _nameController.text, country: _countryController.text, ); box.putAt(widget.index, newPerson); print('Info updated in box!'); }صفحه Info :صفحه info ما شامل اطلاعات شخص ذخیره شده در داخل دیتابیس هست.درواقع همون عملیات خواندن یا read در دیتابیس مثل شکل زیر:ما از یک ویجت به اسم ValueListenableBuilder استفاده میکنیم که وقتی رفرش میشه صفحه که داده ای در دیتابیس تغییر کنه.صفحه ما شامل چندین عملکرد هست:۱- ضربه زدن به آیکون دیلیت برای حذف کردن اطلاعات از دیتابیس۲- ضربه زدن به هر آیتم برای آپدیت اون و سومی هم ضربه زدن به floating action button ما برای اضافه کردن اطلاعاتکد نهایی این صفحه ما:class InfoScreen extends StatefulWidget { @override _InfoScreenState createState() => _InfoScreenState(); } class _InfoScreenState extends State<InfoScreen> { late final Box contactBox; // Delete info from people box _deleteInfo(int index) { contactBox.deleteAt(index); print('Item deleted from box at index: $index'); } @override void initState() { super.initState(); // Get reference to an already opened box contactBox = Hive.box('peopleBox'); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('People Info'), ), floatingActionButton: FloatingActionButton( onPressed: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => AddScreen(), ), ), child: Icon(Icons.add), ), body: ValueListenableBuilder( valueListenable: contactBox.listenable(), builder: (context, Box box, widget) { if (box.isEmpty) { return Center( child: Text('Empty'), ); } else { return ListView.builder( itemCount: box.length, itemBuilder: (context, index) { var currentBox = box; var personData = currentBox.getAt(index)!; return InkWell( onTap: () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => UpdateScreen( index: index, person: personData, ), ), ), child: ListTile( title: Text(personData.name), subtitle: Text(personData.country), trailing: IconButton( onPressed: () => _deleteInfo(index), icon: Icon( Icons.delete, color: Colors.red, ), ), ), ); }, ); } }, ), ); } }خوب تبریک میگم ما تونستیم با استفاده از دیتابیس Hive یه اپ ساده که اطلاعات شخص رو شامل نام و نام کشور اون شخص هست رو میگیره و ذخیره میکنه بسازیم. و ما تونستیم هم اون اطلاعات رو اضافه کنیم هم دیلیت کنیم هم اپدیت کنیم و هم اون رو از حافظه بخونیم.(چهارعمل اصلی دیتابیس)تا جای ممکن سعی کردیم تو این مقاله هم درمورد مفاهیم دیتابیس Hive توضیح بدیم و هم کدهارو ولی هرجایی از کد یا هر مفهمومی ناآشنا بود خیلی راحت میتونید صرفا اون قسمت رو درگوگل سرچ کنید و در موردش اطلاع پیدا کنید.منبع این مقاله آموزشی ما هم بلاگ سایت logrocket هست که مقالات ارزشمند دیگه رو هم در مورد فلاتر میتونین اینجا پیدا کنید و بخونید.سورس نهایی کد رو هم از اینجا از گیت هاب نویسنده اصلی این مقاله آموزشی میتونید دانلود کنید.امیدوارم که این مقاله آموزشی مورد پسندتون قرار گرفته باشه و خوشحال میشم نظرات شمارو در رابطه با این مقاله بدونم.و ممنون از شما که تا اخر همراه من بودین.پ ن: ممنون از پیام زاهدی عزیز که گفتن خیلی پکیج hive استفاده نمیشه و میشه گفت منسوخ شده و بهتره از پکیج isar برای دیتابیس استفاده کنیم.موفق باشیدیاعلی