This is a Flutter project using Floor (package equivalent of Jetpack's Room). I have an entity with an auto increment id (note that this is pre null-safety code, in the answer I start now with post null safety snippet):
const String ACTIVITIES_TABLE_NAME = 'activities';
@Entity(
tableName: ACTIVITIES_TABLE_NAME,
indices: [
Index(value: ['start'])
],
)
class Activity {
@PrimaryKey(autoGenerate: true)
int id;
@ColumnInfo(name: 'device_name')
final String deviceName;
@ColumnInfo(name: 'device_id')
final String deviceId;
final int start;
Activity({
this.deviceName,
this.deviceId,
this.start,
});
}
I have this DAO:
@dao
abstract class ActivityDao {
@Query('SELECT * FROM $ACTIVITIES_TABLE_NAME ORDER BY start DESC')
Future<List<Activity>> findAllActivities();
@insert
Future<int> insertActivity(Activity activity);
}
In my app I recorded five activities so far, hunky-dory.
_database =
await $FloorAppDatabase.databaseBuilder('app_database.db').build();
...
_activity =
Activity(deviceName: device.name, deviceId: device.id.id, start: _lastRecord.millisecondsSinceEpoch);
final id = await _database.activityDao.insertActivity(_activity);
_activity.id = id;
Then in another view I list them, but there is a lurking problem.
final data = await _database.activityDao.findAllActivities();
When I query the entities with this dead simple method, all the five or so activities in my DB are returned with an id
field filled with null
. That makes the entity completely useless because any other operation I would like to perform on it fails due to lack of actual id. Am I doing something wrong?
I mostly have experience with MySQL, Postgres and non SQLite RDBMS. As I understand in SQLite every row has a unique rowid
, and by declaring my auto increment id
primary key field basically I alias that rowid? Whatever it is, I need the id. It cannot be null.
I'm debugging the guts of Floor.
Future<List<T>> queryList<T>(
final String sql, {
final List<dynamic> arguments,
@required final T Function(Map<String, dynamic>) mapper,
}) async {
final rows = await _database.rawQuery(sql, arguments);
return rows.map((row) => mapper(row)).toList();
}
At this point the rows
still have the ids filled in properly, although the entities in the rows are just a list of dynamic values. The rows.map
supposed to map them to the entity objects, and that cannot carry the id over for some reason? Can this be a Floor bug?
Ok, now I see that in the generated database.g.dart
the mapper does not have the id:
static final _activitiesMapper = (Map<String, dynamic> row) => Activity(
deviceName: row['device_name'] as String,
deviceId: row['device_id'] as String,
start: row['start'] as int);
That explains it why id is null
then. But this is generated code, how can I tell Floor I need the id? Or why should I tell it, it has to be there by default, who wouldn't want to know the primary key of an object?
Ok, so this is because I didn't have the the id
as a constructor parameter. I didn't have that because it's an auto increment field and I'm not the one who determines it. However without having it as a constructor argument, the generated code cannot pass it along with the mapper as a parameter, so it leaves it out from the mapper. So I added the id as a constructor argument.
Post null-safety version:
class Activity {
@PrimaryKey(autoGenerate: true)
int? id;
@ColumnInfo(name: 'device_name')
final String deviceName;
@ColumnInfo(name: 'device_id')
final String deviceId;
final int start;
final int end;
Activity({
this.id,
required this.deviceName,
required this.deviceId,
required this.start,
this.end: 0,
});
}
I also add this Floor GitHub issue here, it looks like in the future there might be an annotation: https://github.com/vitusortner/floor/issues/527
Pre null-safety version:
import 'package:meta/meta.dart';
class Activity {
@PrimaryKey(autoGenerate: true)
int id;
@ColumnInfo(name: 'device_name')
@required
final String deviceName;
@ColumnInfo(name: 'device_id')
@required
final String deviceId;
final int start;
Activity({
this.id: null,
this.deviceName,
this.deviceId,
this.start,
});
}
The compiler is a little iffy about the null, but after the flutter packages pub run build_runner build
the database.g.dart
's mappers have the id
as well.