Skip to content

Commit

Permalink
Create MkDocs website (#467)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitusortner authored Jan 1, 2021
1 parent 4a8456b commit 5af1d3e
Show file tree
Hide file tree
Showing 19 changed files with 829 additions and 0 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/deploy-website.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: deploy website

on:
release:
types: [ published ]
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.x
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force
13 changes: 13 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Architecture

The components for storing and accessing data are **Entity**, **Data Access Object (DAO)** and **Database**.

The first, Entity, represents a persistent class and thus a database table.
DAOs manage the access to Entities and take care of the mapping between in-memory objects and table rows.
Lastly, Database, is the central access point to the underlying SQLite database.
It holds the DAOs and, beyond that, takes care of initializing the database and its schema.
[Room](https://developer.android.com/topic/libraries/architecture/room) serves as the source of inspiration for this composition, because it allows creating a clean separation of the component's responsibilities.

The figure shows the relationship between Entity, DAO and Database.

![Floor Architecture](https://raw.githubusercontent.com/vitusortner/floor/develop/img/floor-architecture.png)
162 changes: 162 additions & 0 deletions docs/daos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Data Access Objects

These components are responsible for managing access to the underlying SQLite database and are defined as abstract classes with method signatures and query statements.
DAO classes can use inherited methods by implementing and extending classes while also using mixins.

```dart
@dao
abstract class PersonDao {
@Query('SELECT * FROM Person')
Future<List<Person>> findAllPersons();
@Query('SELECT * FROM Person WHERE id = :id')
Stream<Person> findPersonById(int id);
@insert
Future<void> insertPerson(Person person);
}
```

## Queries
Method signatures turn into query methods by adding the `@Query()` annotation with the query in parenthesis to them.
Be patient about the correctness of your SQL statements.
They are only partly validated while generating the code.
These queries have to return either a `Future` or a `Stream` of an entity or `void`.
Returning `Future<void>` comes in handy whenever you want to delete the full content of a table, for instance.
Some query method examples can be seen in the following.

```dart
@Query('SELECT * FROM Person WHERE id = :id')
Future<Person> findPersonById(int id);
@Query('SELECT * FROM Person WHERE id = :id AND name = :name')
Future<Person> findPersonByIdAndName(int id, String name);
@Query('SELECT * FROM Person')
Future<List<Person>> findAllPersons(); // select multiple items
@Query('SELECT * FROM Person')
Stream<List<Person>> findAllPersonsAsStream(); // stream return
@Query('DELETE FROM Person')
Future<void> deleteAllPersons(); // query without returning an entity
@Query('SELECT * FROM Person WHERE id IN (:ids)')
Future<List<Person>> findPersonsWithIds(List<int> ids); // query with IN clause
```

Query arguments, when using SQLite's `LIKE` operator, have to be supplied by the input of a method.
It's not possible to define a pattern matching argument like `%foo%` in the query itself.

```dart
// dao
@Query('SELECT * FROM Person WHERE name LIKE :name')
Future<List<Person>> findPersonsWithNamesLike(String name);
// usage
final name = '%foo%';
await dao.findPersonsWithNamesLike(name);
```

## Data Changes
Use the `@insert`, `@update` and `@delete` annotations for inserting and changing persistent data.
All these methods accept single or multiple entity instances.

`@insert` marks a method as an insertion method.
When using the capitalized `@Insert` you can specify a conflict strategy.
Else it just defaults to aborting the insert.
These methods can return a `Future` of either `void`, `int` or `List<int>`.
- `void` return nothing
- `int` return primary key of inserted item
- `List<int>` return primary keys of inserted items


`@update` marks a method as an update method.
When using the capitalized `@Update` you can specify a conflict strategy.
Else it just defaults to aborting the update.
These methods can return a `Future` of either `void` or `int`.
- `void` return nothing
- `int` return number of changed rows


`@delete` marks a method as a deletion method.
These methods can return a `Future` of either `void` or `int`.
- `void` return nothing
- `int` return number of deleted rows

```dart
// examples of changing multiple items with return
@insert
Future<List<int>> insertPersons(List<Person> person);
@update
Future<int> updatePersons(List<Person> person);
@delete
Future<int> deletePersons(List<Person> person);
```

## Streams
As already mentioned, queries cannot only return values once when called but also continuous streams of query results.
The returned streams keep you in sync with the changes happening in the database tables.
This feature plays well with the `StreamBuilder` widget which accepts a stream of values and rebuilds itself whenever there is a new emission.

These methods return broadcast streams and thus, can have multiple listeners.
```dart
// definition
@Query('SELECT * FROM Person')
Stream<List<Person>> findAllPersonsAsStream();
// usage
StreamBuilder<List<Person>>(
stream: dao.findAllPersonsAsStream(),
builder: (BuildContext context, AsyncSnapshot<List<Person>> snapshot) {
// do something with the values here
},
);
```

#### Limitations
- Only methods annotated with `@insert`, `@update` and `@delete` trigger `Stream` emissions.
Inserting data by using the `@Query()` annotation doesn't.
- It is now possible to return a `Stream` if the function queries a database view. But it will fire on **any**
`@update`, `@insert`, `@delete` events in the whole database, which can get quite taxing on the runtime. Please add it only if you know what you are doing!
This is mostly due to the complexity of detecting which entities are involved in a database view.
- Functions returning a stream of single items such as `Stream<Person>` do not emit when there is no query result.

## Transactions
Whenever you want to perform some operations in a transaction you have to add the `@transaction` annotation to the method.
It's also required to add the `async` modifier. These methods have to return a `Future`.

```dart
@transaction
Future<void> replacePersons(List<Person> persons) async {
await deleteAllPersons();
await insertPersons(persons);
}
```

## Inheritance
Data access object classes support inheritance as shown in the following.
There is no limit to inheritance levels and thus, each abstract parent can have another abstract parent.
Bear in mind that only abstract classes allow method signatures without an implementation body and thereby, make sure to position your to-be-inherited methods in an abstract class and extend this class with your DAO.

```dart
@dao
abstract class PersonDao extends AbstractDao<Person> {
@Query('SELECT * FROM Person WHERE id = :id')
Future<Person> findPersonById(int id);
}
abstract class AbstractDao<T> {
@insert
Future<void> insertItem(T item);
}
// usage
final person = Person(1, 'Simon');
await personDao.insertItem(person);
final result = await personDao.findPersonById(1);
```
38 changes: 38 additions & 0 deletions docs/database-views.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Database Views

If you want to define static `SELECT`-statements which return different types than your entities, your best option is to use `@DatabaseView`.
A database view can be understood as a virtual table, which can be queried like a real table.

A database view in floor is defined and used similarly to entities, with the main difference being that access is read-only, which means that update, insert and delete functions are not possible.
Similarly to entities, the class name is used if no `viewName` was set.

```dart
@DatabaseView('SELECT distinct(name) AS name FROM person', viewName: 'name')
class Name {
final String name;
Name(this.name);
}
```

Database views do not have any foreign/primary keys or indices. Instead, you should manually define indices which fit to your statement and put them into the `@Entity` annotation of the involved entities.

Setters, getters and static fields are automatically ignored (like in entities), you can specify additional fields to ignore by annotating them with `@ignore`.

After defining a database view in your code, you have to add it to your database by adding it to the `views` field of the `@Database` annotation:

```dart
@Database(version: 1, entities: [Person], views: [Name])
abstract class AppDatabase extends FloorDatabase {
// DAO getters
}
```

You can then query the view via a DAO function like an entity.

It is possible for DatabaseViews to inherit common fields from a base class, just like in entities.

#### Limitations
- It is now possible to return a `Stream` object from a DAO method which queries a database view. But it will fire on **any**
`@update`, `@insert`, `@delete` events in the whole database, which can get quite taxing on the runtime. Please add it only if you know what you are doing!
This is mostly due to the complexity of detecting which entities are involved in a database view.
Loading

0 comments on commit 5af1d3e

Please sign in to comment.