flora_orm 2.0.5 flora_orm: ^2.0.5 copied to clipboard
Flutter plugin for ORM in front of storage like sqflite and shared_preferences
flora_orm #
Database ORM (Object-Relational Mapping) for Flutter.
The ORM supports:
- shared_preferences - All platforms support
- sqflite - iOS/Android/MacOS support
- sqflite_common_ffi - on disk - iOS/Android/MacOS/Linux/Windows support
- sqflite_common_ffi - in memory - iOS/Android/MacOS/Linux/Windows support
Getting Started #
To get started, you need to add flora_orm
to your project. Follow the steps below:
-
Open the terminal in your project root. You can do this by pressing
Alt+F12
in Android Studio orCtrl+`
in VS Code. -
Run the following command:
flutter pub add flora_orm
This command will add a line to your package's pubspec.yaml
file and run an implicit flutter pub get
.
The added line will look like this:
dependencies:
flora_orm:
Usage example #
Import flora_orm.dart
import 'package:flora_orm/flora_orm.dart';
Initializing #
To use flora_orm
, you need to create entity classes that satisfy the following:
- Naming conversion is
{entity_name}.entity.dart
. For exampleuser.entity.dart
(recommended). - You must add 2
part
s to the top of the entity file:{entity_name}.entity.g.dart
and{entity_name}.entity.migrations.dart
. - You must annotate the class with
@entity
(or@OrmEntity()
for granular control). - Your entity class must extend
Entity<{YourEntityName}, {YourEntityName}Meta> with _{YourEntityName}Mixin, {YourEntityName}Migrations
.
Example Entity
import 'package:flora_orm/flora_orm.dart';
part 'user.entity.g.dart';
part 'user.entity.migrations.dart';
@OrmEntity(tableName: 'user')
class UserEntity extends Entity<UserEntity, UserEntityMeta>
with _UserEntityMixin, UserEntityMigrations {
const UserEntity({
super.id,
super.createdAt,
super.updatedAt,
this.claims,
this.uid,
this.email,
this.phoneNumber,
this.displayName,
this.photoURL,
this.provider,
});
@override
@column
final List<String>? claims;
@override
@column
final String? uid;
@override
@column
final String? email;
@override
@column
final String? phoneNumber;
@override
@column
final String? displayName;
@override
@OrmColumn(isEnum: true)
final OAuthProvider? provider;
@override
@column
final String? photoURL;
}
enum OAuthProvider { google, apple, facebook }
Once you have created or updated your entity files, open terminal and run the following from the root directory of your project:
dart run build_runner build
OrmManager #
You need an instance of OrmManager
to interact with the storage.
Create an instance of OrmManager
as early as possible.
We recommend registering it as a singleton during app start-up using get_it or any DI you prefer.
For example, in your void main()
function before runApp()
, you can have the following:
final ormManager = OrmManager(
/// update this version number whenever you update your entities
/// such as adding new properties/fields.
dbVersion: 1,
/// dbEngine defaults to DbEngine.sqflite so you can remove this line if
// you want to use the default engine
dbEngine: DbEngine.sqflite,
dbName: 'your_db_name_here.db',
tables: <Entity>[
/// instatiate all your entities that must be saved in db here
const UserEntity(),
],
);
GetIt.I.registerSingleton(ormManager);
To keep your code clean, we recommend you have the above code in a seperate file. For example in src/orm.init.dart
The dbEngine
value defaults to DbEngine.sqflite
, and may be one of the following:
inMemory,
sqfliteCommon,
sqflite,
sharedPreferences,
However, not all engines are available on all platforms. Here is a breakdown of each platform and supported engines:
Andoid: all (we recommend sqflite)
iOS: all (we recommend sqflite)
macOS: all (we recommend sqflite)
Linux: inMemory, sqfliteCommon, sharedPreferences (defaults to sqfliteCommon)
Windows: inMemory, sqfliteCommon, sharedPreferences (defaults to sqfliteCommon)
web: sharedPreferences (defaults to sharedPreferences)
If you provide a dbEngine
value not supported by a platform, then the default for that platform is used.
Once your OrmManager
is set, you can use it from anywhere in your code. If you are using get_it for example, you can get your storage
instance as:
final orm = GetIt.I<OrmManager>();
final {EntityType}Orm storage = orm.getStorage(/* Instance of your Entity here */);
For example, to get storage
for UserEntity
:
final orm = GetIt.I<OrmManager>();
final UserEntityOrm storage = orm.getStorage(const UserEntity())
IMPORTANT: You NEED to specify type (e.g UserEntityOrm
above) for you to get ColumnDefition
s on your Filter
s later. The type class is auto-generated when you run dart run build_runner build
CRUD operations #
CRUD - Create #
Will throw error if record with same id
already exists:
final entity = await storage.insert(
UserEntity(id: 'user1',
displayName: 'Test User',
));
We recommend using uuid for generating ids.
You can insertOrUpdate
instead, which will update record if it exists:
final entity = await storage.insertOrUpdate(
UserEntity(id: 'user1',
displayName: 'Test User',
));
You can insert more than one record at a time:
final entities = await storage.insertList([
UserEntity(id: 'user1',
displayName: 'Test User'),
...,
]);
An equivalent for insertOrUpdate for more that one record exists:
final entities = await storage.insertOrUpdateList([
UserEntity(id: 'user1',
displayName: 'Test User'),
...,
]);
CRUD - Read #
Get single record:
final entity = await storage.firstWhereOrNull(...);
More than one record:
final entities = await storage.where(...);
CRUD - Update #
You can use the insertOrUpdate options as explained before, which will insert records
if they do not exist. But, if all you want is to strictly update existing records, then:
final updatedCount = await storage.update(where: ...);
CRUD - Delete #
final deletedCount = await storage.delete(where: ...);
The Filter
function #
Most of the queries will need a where
parameter which is a function that must return a Filter
.
The function has a parameter t
which is meta description of your properties as ColumnDefinition
s.
Here are some examples:
Get UserEntity
with id = 'user1'
final user = await storage.firstWhereOrNull(
where: (t) => Filter(
t.id,
value: 'user1',
),
);
Delete all UserEntity
s with uid != null
await storage.delete(
where: (t) => Filter(
t.uid,
condition: OrmCondition.notNull,
),
);
Get all UserEntity
s with rating >= 20
final users = await storage.where(
where: (t) => Filter(
t.rating,
condition: OrmCondition.greaterThanOrEqual,
value: 20,
),
);
Get all UserEntity
s with rating between 10 and 100
final users = await storage.where(
where: (t) => Filter(
t.rating,
condition: OrmCondition.between,
value: 10,
secondaryValue: 100,
),
);
Chaining and grouping filters #
You can have complex filters that meet your needs.
Use utility functions such as startGroup()
, endGroup()
, filter()
and()
, and or()
.
filter()
and()
, and or()
also have parameters openGroup
and closeGroup
to simplify the grouping so that you may not need startGroup()
and endGroup()
. However, we recommend using startGroup()
and endGroup()
since they are easy to read and understand their effects.
Think of grouping as opening and closing brackets, and putting the operations in-between the openGroup
...closeGroup
into those brackets.
In the example below, the last or()
and and()
filters will be grouped into (...)
.
Example:
final users = await storage.where(
where: (t) => Filter.startGroup()
.filter(
t.displayName,
condition: OrmCondition.like,
value: '%flu%',
)
.and(
t.rating,
value: 10,
)
.endGroup()
.or(
openGroup: true,
t.displayName,
value: 'Loveable',
)
.and(
t.rating,
value: 11002,
closeGroup: true,
),
);
startGroup()
must usually be followed by filter()
before chaining additional filters. Remember to endGroup()
/closeGroup
.
Migrations - Changes to Entity classes and Database updates #
If you update any of your Entity
classes, you need to run dart run build_runner build
again.
If you add/remove @column
or any annotated item in your Entity
classes then increment OrmManager
's dbVersion
and add the migrations in the respective {entity_name}.entity.migrations.dart
files.
The simplest way to migrate is either to drop and recreate the entity table (losing all data in that table), or specifying the added columns:
Example UserEntity
migration: (the file itself is auto-generated the first time you run dart run build_runner build
):
mixin UserEntityMigrations on Entity<UserEntity, UserEntityMeta> {
@override
bool recreateTableAt(int newVersion) {
return switch (newVersion) {
/// when dbVersion = 3, drop and recreate table
3 => true,
_ => false,
};
}
@override
List<ColumnDefinition> addColumnsAt(int newVersion) {
return switch (newVersion) {
/// Here we are saying we added property
/// named provider when we set dbVersion = 2.
/// All [@column] properties in your entity class
/// are available in [meta] object as [ColumnDefinition]s
2 => [meta.provider],
_ => [],
};
}
}
In {entity_name}.entity.migrations.dart
You can also override downgradeTable()
and additionalUpgradeQueries()
, returning queries that must be run during that operation.
You can also override onUpgradeComplete
and onDowngradeComplete
to return custom queries that will be run after completion of upgrade/downgrade.
There is also onCreateComplete
which you can return queries that will be run the first time the database is created.
As a reminder, when you update your entity files, run:
dart run build_runner build
Supported data types #
- String
- bool
- int
- double
- DateTime
- enums (needs
@OrmColumn(isEnum: true)
to be specified) - Custom classes (Objects) - they need to have factory
fromMap(map)
and functiontoMap()
- Lists of above types (e.g
List<String>
)
All entity classes will already have toMap implemented. You need to define the factory fromMap(map)
if you want to have the class as a column in another entity.
For convenience, you can call the load()
function that will do the rest.
Example factory for UserEntity
:
@OrmEntity(tableName: 'user')
class UserEntity extends Entity<UserEntit... {
/// default constructor here
factory UserEntity.fromMap(map) {
return const UserEntity().load(map);
}
/// rest of the class here
}