Skip to content

Commit

Permalink
Make Floor null-safe (#462)
Browse files Browse the repository at this point in the history
  • Loading branch information
vitusortner authored Mar 8, 2021
1 parent ee30e0a commit 296c783
Show file tree
Hide file tree
Showing 158 changed files with 2,009 additions and 1,312 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ jobs:
run: dartfmt -n --set-exit-if-changed .

- name: Run tests
if:
working-directory: floor_generator
run: pub run test_cov
# TODO #504 remove --no-sound-null-safety once all dependencies have been migrated
run: pub run test_cov --no-sound-null-safety

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:
working-directory: floor

- name: Run generator
run: flutter packages pub run build_runner build
run: flutter packages pub run build_runner build --delete-conflicting-outputs
working-directory: floor

- name: Analyze
Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

# 1.0.0-nullsafety.1

### Changes

* Migrate to stable Dart 2.12

# 1.0.0-nullsafety.0

### 🚀 Features

* Make floor null safe

# 0.19.1

### Changes
Expand Down Expand Up @@ -84,7 +96,8 @@

### ⚠️ Breaking Changes

**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel case.**
**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel
case.**

* Apply camel case to constants

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ The third dependency is `build_runner` which has to be included as a dev depende
dependencies:
flutter:
sdk: flutter
floor: ^0.19.1
floor: ^1.0.0-nullsafety

dev_dependencies:
floor_generator: ^0.19.1
build_runner: ^1.11.1
floor_generator: ^1.0.0-nullsafety
build_runner: ^1.11.5
```
### 2. Create an Entity
Expand Down Expand Up @@ -86,9 +86,9 @@ import 'package:floor/floor.dart';
abstract class PersonDao {
@Query('SELECT * FROM Person')
Future<List<Person>> findAllPersons();
@Query('SELECT * FROM Person WHERE id = :id')
Stream<Person> findPersonById(int id);
Stream<Person?> findPersonById(int id);
@insert
Future<void> insertPerson(Person person);
Expand Down
12 changes: 12 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 1.0.0-nullsafety.1

### Changes

* Migrate to stable Dart 2.12

## 1.0.0-nullsafety.0

### 🚀 Features

* Make floor null safe

## 0.19.1

### Changes
Expand Down
34 changes: 24 additions & 10 deletions docs/daos.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ abstract class PersonDao {
Future<List<Person>> findAllPersons();
@Query('SELECT * FROM Person WHERE id = :id')
Stream<Person> findPersonById(int id);
Stream<Person?> findPersonById(int id);
@insert
Future<void> insertPerson(Person person);
Expand All @@ -19,18 +19,22 @@ abstract class PersonDao {

## 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.
Be mindful about the correctness of your SQL statements as 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.

A function returning a single item will return `null` when no matching row is found.
Thereby, the function is required to return a nullable type.
For example `Person?`.
This way, we leave the handling of an absent row up to you and don't attempt to guess intention.

```dart
@Query('SELECT * FROM Person WHERE id = :id')
Future<Person> findPersonById(int id);
Future<Person?> findPersonById(int id);
@Query('SELECT * FROM Person WHERE id = :id AND name = :name')
Future<Person> findPersonByIdAndName(int id, String name);
Future<Person?> findPersonByIdAndName(int id, String name);
@Query('SELECT * FROM Person')
Future<List<Person>> findAllPersons(); // select multiple items
Expand Down Expand Up @@ -116,12 +120,23 @@ Future<int> deletePersons(List<Person> persons);
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.

A function returning a stream of single items will emit `null` when no matching row is found.
Thereby, it's necessary to make the function return a stream of a nullable type.
For example `Stream<Person?>`.
In case you're not interested in `null`s, you can simply use `Stream.where((value) => value != null)` to get rid of them.

```dart
// definition
@Query('SELECT * FROM Person')
Stream<List<Person>> findAllPersonsAsStream();
@dao
abstract class PersonDao {
@Query('SELECT * FROM Person WHERE id = :id')
Stream<Person?> findPersonByIdAsStream(int id);
@Query('SELECT * FROM Person')
Stream<List<Person>> findAllPersonsAsStream();
}
// usage
StreamBuilder<List<Person>>(
Expand All @@ -138,7 +153,6 @@ StreamBuilder<List<Person>>(
- 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.
Expand All @@ -161,7 +175,7 @@ Bear in mind that only abstract classes allow method signatures without an imple
@dao
abstract class PersonDao extends AbstractDao<Person> {
@Query('SELECT * FROM Person WHERE id = :id')
Future<Person> findPersonById(int id);
Future<Person?> findPersonById(int id);
}
abstract class AbstractDao<T> {
Expand Down
10 changes: 6 additions & 4 deletions docs/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ The value can be automatically generated by SQLite when `autoGenerate` is enable
For more information about primary keys and especially compound primary keys, refer to the [Primary Keys](#primary-keys) section.

`@ColumnInfo` enables custom mapping of single table columns.
With the annotation, it's possible to give columns a custom name and define if the column is able to store `null`.
With the annotation it's possible to give columns a custom name.
If you want a table's column to be nullable, mark the entity's field as nullable.
More information can be found in the [Null Safety](null-safety.md) section.

!!! attention
- Floor automatically uses the **first** constructor defined in the entity class for creating in-memory objects from database rows.
Expand All @@ -28,7 +30,7 @@ class Person {
@PrimaryKey(autoGenerate: true)
final int id;
@ColumnInfo(name: 'custom_name', nullable: false)
@ColumnInfo(name: 'custom_name')
final String name;
Person(this.id, this.name);
Expand Down Expand Up @@ -105,7 +107,7 @@ class Person {
@primaryKey
final int id;
@ColumnInfo(name: 'custom_name', nullable: false)
@ColumnInfo(name: 'custom_name')
final String name;
Person(this.id, this.name);
Expand Down Expand Up @@ -147,7 +149,7 @@ class BaseObject {
@PrimaryKey()
final int id;
@ColumnInfo(name: 'create_time', nullable: false)
@ColumnInfo(name: 'create_time')
final String createTime;
@ColumnInfo(name: 'update_time')
Expand Down
8 changes: 4 additions & 4 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ The third dependency is `build_runner` which has to be included as a dev depende
dependencies:
flutter:
sdk: flutter
floor: ^0.18.0
floor: ^1.0.0-nullsafety

dev_dependencies:
floor_generator: ^0.18.0
build_runner: ^1.10.3
floor_generator: ^1.0.0-nullsafety
build_runner: ^1.11.5
```
## 2. Create an Entity
Expand Down Expand Up @@ -65,7 +65,7 @@ abstract class PersonDao {
Future<List<Person>> findAllPersons();
@Query('SELECT * FROM Person WHERE id = :id')
Stream<Person> findPersonById(int id);
Stream<Person?> findPersonById(int id);
@insert
Future<void> insertPerson(Person person);
Expand Down
2 changes: 1 addition & 1 deletion docs/migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Person {
@PrimaryKey(autoGenerate: true)
final int id;
@ColumnInfo(name: 'custom_name', nullable: false)
@ColumnInfo(name: 'custom_name')
final String name;
final String nickname;
Expand Down
5 changes: 5 additions & 0 deletions docs/null-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Null Safety

Floor infers nullability of database columns directly from entity fields, as mentioned in the [Entities](entities.md) section.
When not explicitly making a field nullable by applying `?` to its type, a column cannot hold `NULL`.
For more information regarding `null`s as query results, see the [Queries](daos.md#queries) and [Streams](daos.md#streams) section.
4 changes: 2 additions & 2 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import 'entity/person.dart';
void main() {
group('database tests', () {
TestDatabase database;
PersonDao personDao;
late TestDatabase database;
late PersonDao personDao;
setUp(() async {
database = await $FloorTestDatabase
Expand Down
6 changes: 2 additions & 4 deletions docs/type-converters.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@ The implementation and usage of the mentioned `DateTime` to `int` converter is d
class DateTimeConverter extends TypeConverter<DateTime, int> {
@override
DateTime decode(int databaseValue) {
return databaseValue == null
? null
: DateTime.fromMillisecondsSinceEpoch(databaseValue);
return DateTime.fromMillisecondsSinceEpoch(databaseValue);
}
@override
int encode(DateTime value) {
return value == null ? null : value.millisecondsSinceEpoch;
return value.millisecondsSinceEpoch;
}
}
```
Expand Down
1 change: 1 addition & 0 deletions example/ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
1 change: 1 addition & 0 deletions example/ios/Flutter/Release.xcconfig
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
3 changes: 0 additions & 3 deletions example/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,6 @@
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
Expand Down Expand Up @@ -382,7 +381,6 @@
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
Expand Down Expand Up @@ -436,7 +434,6 @@
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 296c783

Please sign in to comment.