ذخیره کردن اطلاعات در فلاتر با دیتابیس Hive
راه های مختلفی برای ذخیره سازی اطلاعات در یک اپلیکیشن وجود دارد که در مطالب قبلی با آنها نیز آشنا شده ایم مثل استفاده از دیتابیس SQLite و یا ذخیره سازی به وسیله SharedPreferences اما در این مطلب ابزار جدیدی به اسم Hive این کار انجام خواهیم داد.
Hive یک دیتابیس سبک, سریع و از نوع NoSQL در زبان دارت و فلاتر می باشد.
در این مقاله انجام عملیات CRUD را با استفاده از این پکیج قرار است انجام دهیم.
آماده سازی پروژه
در این پروژه از Provider استفاده میکنیم که اگر با آن آشنا نیستید بهتر است اول درباره آن مطالعه کنید تا به مشکل نخورید همچنین با طراحی رابط کاربری نیز باید آشنا باشید.
برای اینکه سرعت بیشتری در کار داشته باشیم پروژه ابتدایی آموزش را از انتهای صفحه دانلود کنید. در این پروژه بخش Provider و رابط کاربری ایجاد شده است و مانند تصویر زیر در اختیار شماست.
بعد از دانلود کردن پروژه نیاز به اضافه کردن پکیج های مختلفی داریم که در بخش زیر میتونید مشاهده کنید.
hive: ^1.4.1+1
provider: ^4.3.1
path_provider: ^1.6.5
hive_generator: ^0.5.0
پکیج hive_generator را در بخش dev dependencies قرار دهید.
پیاده سازی Hive
قبل از اینکه از hive استفاده کنید باید در بخش main آن را تنظیم کنید.
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart' as pathProvider;
Future<void> main() async {
Directory directory = await pathProvider.getApplicationDocumentsDirectory();
Hive.init(directory.path);
runApp(MyApp());
}
با این کار مسیر ذخیره سازی اطلاعات را مشخص میکنیم.
Hive Boxes
زمانی که با Hive کار میکنید اطلاعات به شکل جدول و ستون ذخیره نمیشوند چون گفتیم که دیتابیس ما از نوع No SQL است.
در اینجا مفهومی داریم به نام Box که اطلاعات درون این Box یا جعبه ها ذخیره می شوند.
این جعبه را میتونید مانند یک پوشه در کامپیوتر تصور کنید.
قبل از اینکه بتونید اطلاعات را بخوانید یا بنویسید باید ابتدا این Box را باز کنید که این کار با دستور await Hive.openBox(‘name’) قابل انجام است.
برای دسترسی به نمونه ای از جعبه ای که قبلا باز شده است هم میتونید از دستور Hive.box(‘name’) استفاده کنید.
معرفی TypeAdapters
در این نوع دیتابیس امکان ذخیره سازی انواع داده های معمول مثل String, int, Map, List, DateTime را دارید.
اما ممکن است بخواهید یک کلاس مدل از داده های خود داشته باشید و سریعتر اطلاعات را ذخیره و بازیابی کنید. در این حالت باید از TypeAdapters استفاده کنید که کار دیکد و انکد کردن اشیای کلاس را انجام میدهد.
در مسیری که مایل هستید یک فایل جدید برای کلاس مدل ایجاد کنید, در اینجا اسم فایل را inventory_model قرار داده ایم.
در این فایل کلاس خودمون و با استفاده از annotations های مخصوص TypeAdapters ایجاد میکنیم تا فایل های متناسب با کلاس ما به شکل خودکار ساخته شود.
import 'package:hive/hive.dart';
part 'inventory_model.g.dart';
@HiveType()
class Inventory {
@HiveField(0)
final String name;
@HiveField(1)
final String description;
Inventory({this.name, this.description});
}
برای اینکه از کلاس خودتون بتونید در دیتابیس استفاده کنید باید قبل از اسم کلاس از برچسب یا انوتیشن @HiveType() استفاده کنید.
سپس هر فیلد از کلاس را که قصد دارید داخل دیتابیس قرار بگیرد با انوتیشن @HiveField(index) مشخص میکنید و یک ایندکس که عددی صحیح و منحصر به فرد است را برای آن قرار میدهید.
حالا برای ساخته شدن ابزارهای موردنیاز دستور زیر را در ترمینال وارد کنید.
$ flutter packages pub run build_runner build
در این قسمت شاید با خطایی مثل Missing concrete implementation of ‘getter TypeAdapter.typeId’ روبرو شوید.
برای حل این مشکل کافیست یک متد get برای فیلد typeId بنویسید.
@override
int get typeId => 0;
بعد از رفع خطا زمان رجیستر کردن اداپتر در متد main فرا رسیده.
Hive.registerAdapter(InventoryAdapter());
runApp(MyApp());
ذخیره اطلاعات
برای ذخیره کردن اطلاعات یک متد برای اینکار در کلاس ChangeNotifier مینویسیم.
addItem(Inventory item) async {
var box = await Hive.openBox<Inventory>('inventory');
box.add(item);
notifyListeners();
}
در اینجا ابتدا همانطور که قبل تر گفتیم ابتدا یک box را باز میکنیم و سپس از متد add استفاده میکنیم تا اطلاعات را به آن اضافه کنیم.
برای اینکار میتونید از متد put() هم استفاده کنید اما تفاوت این روش با متد قبلی استفاده از یک کلید برای ذخیره سازی است.
box.put(‘key’, item) در حالی که متد add فقط یک ایندکس به شکل خودکار برای هر سری از اطلاعات در نظر میگیرد.
برای ذخیره سازی همچنین میتونید از متد box.putAll({‘key’: value}) نیز استفاده کنید که یک مپ را به عنوان ورودی دریافت میکند و یا متد box.addAll( ) که پارامتر ورودی آن لیست است.
برای استفاده عملی از کد زیر میتوانید استفاده کنید.
inputItemDialog(BuildContext context, String action, [int index]) {
var inventoryDb = Provider.of<HomeModel>(context, listen: false);
showDialog(
context: context,
builder: (context) {
return Dialog(
child: Container(
padding: EdgeInsets.only(
left: 15,
right: 15,
top: 40,
),
height: 45.height,
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Text(
action == 'add' ? 'Add Item' : 'Update Item',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: nameController,
validator: (value) {
if (value.isEmpty) {
return 'Item name cannot be empty';
}
return null;
},
decoration: InputDecoration(
labelText: 'Item name',
),
),
SizedBox(
height: 40,
),
TextFormField(
controller: descriptionController,
validator: (value) {
if (value.isEmpty) {
return 'Item description cannot be empty';
}
return null;
},
decoration: InputDecoration(
labelText: 'Item description',
),
),
SizedBox(
height: 40,
),
RaisedButton(
onPressed: () async {
if (_formKey.currentState.validate()) {
if (action == 'add') {
await inventoryDb.addItem(Inventory(
name: nameController.text,
description: descriptionController.text,
));
} else {
}
nameController.clear();
descriptionController.clear();
inventoryDb.getInventory();
Navigator.pop(context);
}
},
color: Colors.green[600],
child: Text(
action == 'add' ? 'Add' : 'update',
style: TextStyle(
fontSize: 16,
color: Colors.white,
),
),
),
SizedBox(
height: 20,
)
],
),
),
),
),
);
},
);
}
خواندن اطلاعات
سه روش مختلف برای خواندن اطلاعات ذخیره شده وجود دارد.
- box.get(‘key): دریافت اطلاعات مربوط به یک کلید
- box.getAt(index): دریافت اطلاعات مربوط به یک ایندکس
- box.values: شامل تمام اطلاعات موجود در باکس
متد جدیدی به کلاسمون اضافه میکنیم برای خواندن اطلاعات به شکل زیر:
List _inventoryList = <Inventory>[];
List get inventoryList => _inventoryList;
getItem() async {
final box = await Hive.openBox<Inventory>('inventory');
_inventoryList = box.values.toList();
notifyListeners();
}
برای استفاده از این متد در رابط کاربری پروژمون کد زیر را در بالای متد build قرار دهید.
context.watch<HomeModel>().getItem();
ListView.builder(
itemCount: model.inventoryList.length,
itemBuilder: (context, index) {
Inventory inv = model.inventoryList[index];
return Container(
margin: EdgeInsets.symmetric(
vertical: 2.height,
horizontal: 4.5.width,
),
height: 13.height,
decoration: BoxDecoration(
color: Colors.white,
),
child: Row(
children: <Widget>[
Container(
margin: EdgeInsets.only(left: 2.width),
width: 16.width,
decoration: BoxDecoration(
color: Colors.green[600],
shape: BoxShape.circle,
),
child: Center(
child: Icon(
Icons.home,
color: Colors.white,
),
),
),
Padding(
padding: EdgeInsets.symmetric(
vertical: 3.height,
horizontal: 3.width,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
inv.name,
style: TextStyle(
color: Colors.green[600],
fontWeight: FontWeight.w600,
fontSize: 4.5.text,
),
),
SizedBox(
height: 1.height,
),
Text(
inv.description,
style: TextStyle(
color: Colors.grey,
fontSize: 4.text,
),
),
],
),
),
Padding(
padding: EdgeInsets.only(left: 8.width),
child: PopupMenuButton(
onSelected: (item) {
switch (item) {
case 'update':
nameController.text = inv.name;
descriptionController.text =
inv.description;
inputItemDialog(context, 'update', index);
break;
case 'delete':
model.deleteAt(index);
}
},
itemBuilder: (context) {
return [
PopupMenuItem(
value: 'update',
child: Text('Update'),
),
PopupMenuItem(
value: 'delete',
child: Text('Delete'),
),
];
},
),
),
],
),
);
},
)
آپدیت اطلاعات
در لیستی که داریم که با کلیک روی هر آیتم منوی پاپ آپی باز میشود که گزینه آپدیت دارد.
برای بروزرسانی مقادیر ذخیره شده از دو متد زیر میتونید کمک بگیرید.
- box.put(‘key’, newValue)
- box.putAt(index, item)
مثل دفعات قبلی متد مخصوص این کار را نیز به کلاس اضافه میکنیم.
updateItem(int index, Inventory inventory) {
final box = Hive.box<Inventory>('inventory');
box.putAt(index, inventory);
notifyListeners();
}
حذف آیتم
پاک کردن اطلاعات در Hive تقریبا مثل اضافه کردن اطلاعات می باشد.
- box.delete(‘key)
- box.deleteAt(index)
- box.add()
- box.deleteAll(keys)
همچنین یک متد deleteFromDisk هم وجود دارد که فایل box را پاک میکند.
deleteItem(int index) {
final box = Hive.box<Inventory>(_inventoryBox);
box.deleteAt(index);
getInventory();
notifyListeners();
}
Lazy Boxes
اگر در پروژه موبایل خود نیاز به ذخیره حجم زیاد اطلاعات در دیتابیس دارید بهتر است که از lazyBox ها استفاده کنید. در این نوع از ذخیره سازی تمام اطلاعات همزمان از روی دیسک خوانده نمیشود.
final lazyBox = await Hive.openLazyBox('name');
var value = await lazyBox.get('key');
مابقی موارد در این نوع ذخیره سازی مشابه موارد گفته شده می باشد.
مطالب زیر را حتما مطالعه کنید
آموزش پیاده سازی معماری تمیز در برنامه نویسی فلاتر Clean Architecture
آموزش پیاده سازی لینت Lint در برنامه نویسی فلاتر
آموزش الگوی تزریق وابستگی در فلاتر Dependency Injection
کتاب های آموزش برنامه نویسی فلاتر + دانلود PDF
آموزش نصب فلاتر و رفع خطاهای رایج ساخت پروژه + ویدیو
آموزش استفاده از نقشه در فلاتر
6 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
چه تفاوت هایی از نظر عملکردی با SQlite داره
تفاوت عملیات write:

لطفاً این رو هم اضافه بفرمایید که nosql هست و قابلیت کوئریهای پیچیده زدن رو نداره. برای دادههای جدولی و ساختارمند مناسب نیست.
سلام, بله پاراگراف اول به Nosql بودن اشاره شده با تشکر
فایل خراب بود 😐
قابلیت join زدن چند جدول(باکس) و اعمال شرط ، در این روش هست ؟