feat(transporthelper): code cleanup, add toMaps, implement m-n tables

- remove final keywords everywhere for now cause I don't think I need
  them yet for any specific purpose
- add toMap() functions
- add database.insert() functions for classes and relational tables when
  classes use list that reference other classes
This commit is contained in:
fbachus
2026-06-23 02:37:22 +02:00
parent 7f6c7beb59
commit b67d835f38
+160 -105
View File
@@ -37,120 +37,106 @@ class DbHelper {
Station.initStationTable(); Station.initStationTable();
Vehicle.initVehicleTable(); Vehicle.initVehicleTable();
Departure.initDepartureTable(); Departure.initDepartureTable();
Route.updateHighestId();
} }
} }
/// this class covers two database tables: "route" and "routesegments"
/// while route will for now only hold an id, routesegments will store the
/// m-n/ 1-n relations between routes and segments - the segments contain the most
/// relevant info, while routesegments stores which route the segments belong to
/// and in which order they make up a route
class Route { class Route {
static int highestId = 0; // TODO: add last_viewed, last_modified and such
static const String tableName = "route"; int? id;
int id;
List<Segment> segments = List.empty(); List<Segment> segments = List.empty();
Duration duration = Duration(minutes: 0); Duration duration = Duration(minutes: 0);
Route({ Route({
required this.id, this.id,
required this.segments, required this.segments,
required this.duration, required this.duration,
}); });
/// toMap for Route but it's just route ids for now
Map<String, Object?> toMapRoute() {
return {"id": id};
}
/// toMap for routesegments relational table
List<Map<String, Object?>> toMapRouteSegments() {
//asMap() apparently enumerates
List<Map<String, Object?>> tmp = List.empty();
for (final (segmentIdx, segment) in segments.indexed) {
tmp.add({
"route": id,
"segmentid": segment.id,
"segmentorder": segmentIdx,
});
}
return tmp;
}
// from trip API request // from trip API request
// TODO: how to connect this best with the database for read/write? // TODO: how to connect this best with the database for read/write?
factory Route.fromJson(Map<String, dynamic> json) { factory Route.fromJson(Map<String, dynamic> json) {
Route newRoute = Route( Route newRoute = Route(
id: highestId,
segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)), segments: json["LegList"]["Leg"].map((leg) => Segment.fromJson(leg)),
duration: duration:
json["LegList"]["Leg"][-1]["Origin"]["time"] - json["LegList"]["Leg"][-1]["Origin"]["time"] -
json["LegList"]["Leg"][0]["Origin"]["time"], json["LegList"]["Leg"][0]["Origin"]["time"],
); );
highestId += 1;
return newRoute; return newRoute;
} }
// rowids will give us new ids for every route-segment connection, but routeid
// is needed as well, so how do we create these - query highest existing routeid? I think so
// TODO: get highest routeid in route table (fast)
static void initRouteTable() async { static void initRouteTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS routesegments( database.execute("""CREATE TABLE IF NOT EXISTS route(
id TEXT, id INTEGER PRIMARY KEY AUTOINCREMENT,
segmentid INTEGER,
segmentorder INTEGER, -- TODO: implement sorting
FOREIGN KEY(segmentid) REFERENCES segment(id)
); );
"""); """);
} }
static void updateHighestId() async { static void initRouteSegmentsTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
int newHigh = await getHighestId(); String segment = Segment.tableName;
highestId = newHigh + 1; database.execute("""CREATE TABLE IF NOT EXISTS routesegments(
id TEXT PRIMARY KEY AUTOINCREMENT,
route INTEGER,
segment INTEGER,
segmentorder INTEGER, -- TODO: implement sorting
FOREIGN KEY(route) REFERENCES route(id),
FOREIGN KEY(segment) REFERENCES $segment(id)
);
""");
} }
/* TODO: better separation between production db and test db void writeRouteToTable() async {
* they share the highestId field regardless, which wont do
* so is an attribut for database needed?
*/
/// parse the highest id from the database to continue with ongoing ids but
/// create the db entry after the class, for now
// -- this might be making it more complicated than it needs to be
static Future<int> getHighestId() async {
Database database = DbHelper.db; Database database = DbHelper.db;
try { database.insert(
var tmp = await database.query( "route",
"routesegments", toMapRoute(),
columns: ["id"], conflictAlgorithm: ConflictAlgorithm.replace,
distinct: true, );
orderBy: "id",
whereArgs: ["id"],
limit: 1,
);
if (tmp.isEmpty) {
return 0;
}
if (tmp[0].isEmpty) {
return 0;
}
// entry "routeid" should definitely exist
assert(tmp[0]["id"] != null);
// Source - https://stackoverflow.com/a/52632172
// Posted by Günter Zöchbauer, modified by community. See post 'Timeline' for change history
// Retrieved 2026-06-08, License - CC BY-SA 4.0
T? cast<T>(x) => x is T ? x : null;
// --
//doing this in two steps go deal with nullability
String highestId = cast<String>(tmp[0]["routeid"]) ?? "0";
return int.parse(highestId);
} catch (e) {
print('Error: Could not get id: $e'); // TODO: use logging
}
return 0;
} }
void writeNewToTable(Route route) async { void writeRouteSegmentsToTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
for (var i = 0; i < route.segments.length; i++) { var routeSegments = toMapRouteSegments();
database.insert("routesegments", { for (var i = 0; i < routeSegments.length; i++) {
"id": route.id, database.insert("routesegments", routeSegments[i]);
"segmentid": route.segments[i].id,
"segmentorder": i,
});
} }
} }
} }
class Segment { class Segment {
final int id = 0; static const String tableName = "segment";
final Station startPoint;
final Station endPoint; int? id;
final DateTime startTime; Station startPoint;
final DateTime endTime; Station endPoint;
final Vehicle vehicle; DateTime startTime;
DateTime endTime;
Vehicle vehicle;
Segment({ Segment({
required this.startPoint, required this.startPoint,
@@ -160,6 +146,16 @@ class Segment {
required this.vehicle, required this.vehicle,
}); });
Map<String, Object?> toMap() {
return {
"startPoint": startPoint.id,
"endpoint": endPoint.id,
"starttime": startTime,
"endtime": endTime,
"vehicle": vehicle.id,
};
}
factory Segment.fromJson(Map<String, dynamic> json) { factory Segment.fromJson(Map<String, dynamic> json) {
Database database = DbHelper.db; Database database = DbHelper.db;
Segment tmp = Segment( Segment tmp = Segment(
@@ -175,37 +171,35 @@ class Segment {
static void initSegmentTable() async { static void initSegmentTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
String station = Station.tableName;
String vehicle = Vehicle.tableName;
database.execute(""" database.execute("""
CREATE TABLE IF NOT EXISTS segment( CREATE TABLE IF NOT EXISTS $tableName(
startpoint TEXT, startpoint TEXT,
endpoint TEXT, endpoint TEXT,
starttime TEXT, starttime TEXT,
endtime TEXT, endtime TEXT,
vehicle INTEGER, vehicle INTEGER,
FOREIGN KEY(startpoint) REFERENCES station(stationid), FOREIGN KEY(startpoint) REFERENCES $station(stationid),
FOREIGN KEY(endpoint) REFERENCES station(stationid), FOREIGN KEY(endpoint) REFERENCES $station(stationid),
FOREIGN KEY(vehicle) REFERENCES vehicle(vehicleid) FOREIGN KEY(vehicle) REFERENCES $vehicle(vehicleid)
); );
"""); """);
} }
Future<int> writeNewToTable() async { Future<int> writeNewToTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
int id = await database.insert("segment", { int id = await database.insert(tableName, toMap());
"startpoint": startPoint.id,
"endpoint": startPoint.id,
"startTime": startTime,
"endTime": endTime,
"vehicle": vehicle.id,
});
return id; return id;
} }
} }
class Station { class Station {
final String id; static const String tableName = "station";
final String name;
List<Line>? transportLines; // could be normalized away String id;
String name;
List<Line> transportLines; // could be normalized away
Station({ Station({
required this.id, required this.id,
@@ -213,6 +207,18 @@ class Station {
required this.transportLines, //TODO: make extra db table required this.transportLines, //TODO: make extra db table
}); });
Map<String, Object?> toMap() {
return {"id": id, "name": name};
}
List<Map<String, Object?>> toMapLines() {
return transportLines
.map(
(line) => {"stationid": id, "lineid": line},
)
.toList();
}
factory Station.fromJson(Map<String, dynamic> json) => Station( factory Station.fromJson(Map<String, dynamic> json) => Station(
id: json["id"] as String? ?? json["extId"] as String, id: json["id"] as String? ?? json["extId"] as String,
name: json["name"], name: json["name"],
@@ -226,22 +232,43 @@ class Station {
static void initStationTable() async { static void initStationTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS station( database.execute("""CREATE TABLE IF NOT EXISTS station(
id TEXT PRIMARY KEY, stationid TEXT PRIMARY KEY,
name TEXT name TEXT
); );
"""); """);
} }
static void initStationLinesTable() async {
Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS stationlines(
id INT PRIMARY KEY AUTOINCREMENT,
stationid TEXT,
lineid TEXT,
FOREIGN KEY (stationid) REFERENCES station(stationid),
FOREIGN KEY (lineid) REFERENCES line(lineid)
);
""");
}
} }
class Vehicle { class Vehicle {
final String id; static const String tableName = "vehicle";
final Line line;
String id;
Line line;
Vehicle({ Vehicle({
required this.id, required this.id,
required this.line, required this.line,
}); });
Map<String, Object?> toMap() {
return {
"id": id,
"line": line.id,
};
}
// we can use the "product" class from within any JSON response // we can use the "product" class from within any JSON response
// (within hafas API, on the appropriate level) // (within hafas API, on the appropriate level)
factory Vehicle.fromJson(Map<String, dynamic> json) => Vehicle( factory Vehicle.fromJson(Map<String, dynamic> json) => Vehicle(
@@ -251,21 +278,24 @@ class Vehicle {
static void initVehicleTable() async { static void initVehicleTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS vehicle( String line = Line.tableName;
database.execute("""CREATE TABLE IF NOT EXISTS $tableName(
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
line TEXT, line TEXT,
FOREIGN KEY(line) REFERENCES line(lineid) FOREIGN KEY(line) REFERENCES $line(lineid)
); );
"""); """);
} }
} }
class Line { class Line {
final String id; static const String tableName = "line";
String id;
Station? destination; Station? destination;
Station? direction; Station? direction;
final VehicleType mode; VehicleType mode;
final String name; String name;
Color? fgColor; Color? fgColor;
Color? bgColor; Color? bgColor;
@@ -277,6 +307,18 @@ class Line {
this.bgColor, this.bgColor,
}); });
Map<String, Object?> toMap() {
return {
"lineid": id,
"destination": destination,
"direction": direction,
"mode": mode,
"name": name,
"fgcolor": fgColor,
"bgcolor": bgColor,
};
}
// TODO: EXPLAIN!!!!!!!!!!!! // TODO: EXPLAIN!!!!!!!!!!!!
static const _vehicleTypeAsCatCode = [ static const _vehicleTypeAsCatCode = [
VehicleType.metro, VehicleType.metro,
@@ -298,7 +340,7 @@ class Line {
); );
static void initLineTable() async { static void initLineTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS line( database.execute("""CREATE TABLE IF NOT EXISTS $tableName(
lineid TEXT PRIMARY KEY, lineid TEXT PRIMARY KEY,
destination TEXT, destination TEXT,
direction TEXT, direction TEXT,
@@ -312,10 +354,12 @@ class Line {
} }
class Departure { class Departure {
static const String tableName = "departure";
String? id; String? id;
final Vehicle vehicle; Vehicle vehicle;
final Station station; Station station;
final DateTime departureTime; DateTime departureTime;
Departure({ Departure({
required this.vehicle, required this.vehicle,
@@ -323,24 +367,35 @@ class Departure {
required this.departureTime, required this.departureTime,
}); });
Map<String, Object?> toMap() {
return {
"id": id,
"vehicle": vehicle,
"station": station,
"departuretime": departureTime,
};
}
static void initDepartureTable() async { static void initDepartureTable() async {
Database database = DbHelper.db; Database database = DbHelper.db;
database.execute("""CREATE TABLE IF NOT EXISTS departure( String vehicle = Vehicle.tableName;
String station = Station.tableName;
database.execute("""CREATE TABLE IF NOT EXISTS $tableName(
id INTEGER, id INTEGER,
vehicle TEXT, vehicle TEXT,
station TEXT, station TEXT,
departuretime TEXT, departuretime TEXT,
FOREIGN KEY(vehicle) REFERENCES vehicle(id), FOREIGN KEY(vehicle) REFERENCES $vehicle(id),
FOREIGN KEY(station) REFERENCES station(id) FOREIGN KEY(station) REFERENCES $station(id)
); );
"""); """);
} }
} }
class Color { class Color {
final int r; int r;
final int g; int g;
final int b; int b;
Color({ Color({
required this.r, required this.r,
@@ -362,4 +417,4 @@ enum VehicleType {
subway, subway,
regionalTrain, regionalTrain,
PLACEHOLDER, PLACEHOLDER,
} }