diff --git a/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json
new file mode 100644
--- /dev/null
+++ b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json
@@ -0,0 +1,464 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "5c633aea20ff416df784e00a939d7ae5",
+    "entities": [
+      {
+        "tableName": "gtfs_calendar_dates",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `date` TEXT NOT NULL, `exception_type` INTEGER NOT NULL, PRIMARY KEY(`service_id`, `date`), FOREIGN KEY(`service_id`) REFERENCES `gtfs_calendar`(`service_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "serviceID",
+            "columnName": "service_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exceptionType",
+            "columnName": "exception_type",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "service_id",
+            "date"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "gtfs_calendar",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "service_id"
+            ],
+            "referencedColumns": [
+              "service_id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "stops_gtfs",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stop_id` INTEGER NOT NULL, `stop_code` TEXT NOT NULL, `stop_name` TEXT NOT NULL, `stop_desc` TEXT NOT NULL, `stop_lat` REAL NOT NULL, `stop_lon` REAL NOT NULL, `wheelchair_boarding` TEXT NOT NULL, PRIMARY KEY(`stop_id`))",
+        "fields": [
+          {
+            "fieldPath": "internalID",
+            "columnName": "stop_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gttStopID",
+            "columnName": "stop_code",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stopName",
+            "columnName": "stop_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gttPlaceName",
+            "columnName": "stop_desc",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "latitude",
+            "columnName": "stop_lat",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "longitude",
+            "columnName": "stop_lon",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "wheelchair",
+            "columnName": "wheelchair_boarding",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "stop_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "gtfs_calendar",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `monday` INTEGER NOT NULL, `tuesday` INTEGER NOT NULL, `wednesday` INTEGER NOT NULL, `thursday` INTEGER NOT NULL, `friday` INTEGER NOT NULL, `saturday` INTEGER NOT NULL, `sunday` INTEGER NOT NULL, `start_date` TEXT NOT NULL, `end_date` TEXT NOT NULL, PRIMARY KEY(`service_id`))",
+        "fields": [
+          {
+            "fieldPath": "serviceID",
+            "columnName": "service_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onMonday",
+            "columnName": "monday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onTuesday",
+            "columnName": "tuesday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onWednesday",
+            "columnName": "wednesday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onThursday",
+            "columnName": "thursday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onFriday",
+            "columnName": "friday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onSaturday",
+            "columnName": "saturday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onSunday",
+            "columnName": "sunday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "startDate",
+            "columnName": "start_date",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "endDate",
+            "columnName": "end_date",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "service_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "routes_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_type` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, `route_sort_order` INTEGER NOT NULL, PRIMARY KEY(`route_id`))",
+        "fields": [
+          {
+            "fieldPath": "ID",
+            "columnName": "route_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "agencyID",
+            "columnName": "agency_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "route_short_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "longName",
+            "columnName": "route_long_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "route_desc",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "type",
+            "columnName": "route_type",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "color",
+            "columnName": "route_color",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "textColor",
+            "columnName": "route_text_color",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "sortOrder",
+            "columnName": "route_sort_order",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "route_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "gtfs_stop_times",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trip_id` TEXT NOT NULL, `arrival_time` TEXT NOT NULL, `departure_time` TEXT NOT NULL, `stop_id` INTEGER NOT NULL, `stop_sequence` INTEGER NOT NULL, PRIMARY KEY(`trip_id`, `stop_id`), FOREIGN KEY(`stop_id`) REFERENCES `stops_gtfs`(`stop_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`trip_id`) REFERENCES `gtfs_trips`(`trip_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "tripID",
+            "columnName": "trip_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "arrivalTime",
+            "columnName": "arrival_time",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "departureTime",
+            "columnName": "departure_time",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stopID",
+            "columnName": "stop_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stopSequence",
+            "columnName": "stop_sequence",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "trip_id",
+            "stop_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_gtfs_stop_times_stop_id",
+            "unique": false,
+            "columnNames": [
+              "stop_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_stop_times_stop_id` ON `${TABLE_NAME}` (`stop_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "stops_gtfs",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "stop_id"
+            ],
+            "referencedColumns": [
+              "stop_id"
+            ]
+          },
+          {
+            "table": "gtfs_trips",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "trip_id"
+            ],
+            "referencedColumns": [
+              "trip_id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "gtfs_trips",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `service_id` TEXT NOT NULL, `trip_id` TEXT NOT NULL, `trip_headsign` TEXT NOT NULL, `direction_id` INTEGER NOT NULL, `block_id` TEXT NOT NULL, `shape_id` TEXT NOT NULL, `wheelchair_accessible` INTEGER NOT NULL, `limited_route` INTEGER NOT NULL, PRIMARY KEY(`trip_id`), FOREIGN KEY(`route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "routeID",
+            "columnName": "route_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceID",
+            "columnName": "service_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "tripID",
+            "columnName": "trip_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "tripHeadsign",
+            "columnName": "trip_headsign",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "directionID",
+            "columnName": "direction_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "blockID",
+            "columnName": "block_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shapeID",
+            "columnName": "shape_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isWheelchairAccess",
+            "columnName": "wheelchair_accessible",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isLimitedRoute",
+            "columnName": "limited_route",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "trip_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_gtfs_trips_route_id",
+            "unique": false,
+            "columnNames": [
+              "route_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_trips_route_id` ON `${TABLE_NAME}` (`route_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "routes_table",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "route_id"
+            ],
+            "referencedColumns": [
+              "route_id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "gtfs_shapes",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`shape_id` TEXT NOT NULL, `shape_pt_lat` REAL NOT NULL, `shape_pt_lon` REAL NOT NULL, `shape_pt_sequence` INTEGER NOT NULL, PRIMARY KEY(`shape_id`, `shape_pt_sequence`))",
+        "fields": [
+          {
+            "fieldPath": "shapeID",
+            "columnName": "shape_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointLat",
+            "columnName": "shape_pt_lat",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointLon",
+            "columnName": "shape_pt_lon",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointSequence",
+            "columnName": "shape_pt_sequence",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "shape_id",
+            "shape_pt_sequence"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5c633aea20ff416df784e00a939d7ae5')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json
new file mode 100644
--- /dev/null
+++ b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json
@@ -0,0 +1,648 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "6d2aa826894d1e6b1429678e13b65433",
+    "entities": [
+      {
+        "tableName": "gtfs_feeds",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`feed_id` TEXT NOT NULL, PRIMARY KEY(`feed_id`))",
+        "fields": [
+          {
+            "fieldPath": "gtfsId",
+            "columnName": "feed_id",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "feed_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "gtfs_agencies",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`gtfs_id` TEXT NOT NULL, `ag_name` TEXT NOT NULL, `ag_url` TEXT NOT NULL, `fare_url` TEXT, `phone` TEXT, `feed_id` TEXT, PRIMARY KEY(`gtfs_id`))",
+        "fields": [
+          {
+            "fieldPath": "gtfsId",
+            "columnName": "gtfs_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "ag_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "url",
+            "columnName": "ag_url",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "fareUrl",
+            "columnName": "fare_url",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "phone",
+            "columnName": "phone",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "feed.gtfsId",
+            "columnName": "feed_id",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "gtfs_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "gtfs_calendar_dates",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `date` TEXT NOT NULL, `exception_type` INTEGER NOT NULL, PRIMARY KEY(`service_id`, `date`), FOREIGN KEY(`service_id`) REFERENCES `gtfs_calendar`(`service_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "serviceID",
+            "columnName": "service_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "date",
+            "columnName": "date",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "exceptionType",
+            "columnName": "exception_type",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "service_id",
+            "date"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "gtfs_calendar",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "service_id"
+            ],
+            "referencedColumns": [
+              "service_id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "stops_gtfs",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stop_id` INTEGER NOT NULL, `stop_code` TEXT NOT NULL, `stop_name` TEXT NOT NULL, `stop_desc` TEXT NOT NULL, `stop_lat` REAL NOT NULL, `stop_lon` REAL NOT NULL, `wheelchair_boarding` TEXT NOT NULL, PRIMARY KEY(`stop_id`))",
+        "fields": [
+          {
+            "fieldPath": "internalID",
+            "columnName": "stop_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gttStopID",
+            "columnName": "stop_code",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stopName",
+            "columnName": "stop_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "gttPlaceName",
+            "columnName": "stop_desc",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "latitude",
+            "columnName": "stop_lat",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "longitude",
+            "columnName": "stop_lon",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "wheelchair",
+            "columnName": "wheelchair_boarding",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "stop_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "gtfs_calendar",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `monday` INTEGER NOT NULL, `tuesday` INTEGER NOT NULL, `wednesday` INTEGER NOT NULL, `thursday` INTEGER NOT NULL, `friday` INTEGER NOT NULL, `saturday` INTEGER NOT NULL, `sunday` INTEGER NOT NULL, `start_date` TEXT NOT NULL, `end_date` TEXT NOT NULL, PRIMARY KEY(`service_id`))",
+        "fields": [
+          {
+            "fieldPath": "serviceID",
+            "columnName": "service_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onMonday",
+            "columnName": "monday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onTuesday",
+            "columnName": "tuesday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onWednesday",
+            "columnName": "wednesday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onThursday",
+            "columnName": "thursday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onFriday",
+            "columnName": "friday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onSaturday",
+            "columnName": "saturday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "onSunday",
+            "columnName": "sunday",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "startDate",
+            "columnName": "start_date",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "endDate",
+            "columnName": "end_date",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "service_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "routes_table",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_mode` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, PRIMARY KEY(`route_id`))",
+        "fields": [
+          {
+            "fieldPath": "gtfsId",
+            "columnName": "route_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "agencyID",
+            "columnName": "agency_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shortName",
+            "columnName": "route_short_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "longName",
+            "columnName": "route_long_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "description",
+            "columnName": "route_desc",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "mode",
+            "columnName": "route_mode",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "color",
+            "columnName": "route_color",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "textColor",
+            "columnName": "route_text_color",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "route_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "gtfs_stop_times",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trip_id` TEXT NOT NULL, `arrival_time` TEXT NOT NULL, `departure_time` TEXT NOT NULL, `stop_id` INTEGER NOT NULL, `stop_sequence` INTEGER NOT NULL, PRIMARY KEY(`trip_id`, `stop_id`), FOREIGN KEY(`stop_id`) REFERENCES `stops_gtfs`(`stop_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`trip_id`) REFERENCES `gtfs_trips`(`trip_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "tripID",
+            "columnName": "trip_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "arrivalTime",
+            "columnName": "arrival_time",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "departureTime",
+            "columnName": "departure_time",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stopID",
+            "columnName": "stop_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stopSequence",
+            "columnName": "stop_sequence",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "trip_id",
+            "stop_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_gtfs_stop_times_stop_id",
+            "unique": false,
+            "columnNames": [
+              "stop_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_stop_times_stop_id` ON `${TABLE_NAME}` (`stop_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "stops_gtfs",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "stop_id"
+            ],
+            "referencedColumns": [
+              "stop_id"
+            ]
+          },
+          {
+            "table": "gtfs_trips",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "trip_id"
+            ],
+            "referencedColumns": [
+              "trip_id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "gtfs_trips",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `service_id` TEXT NOT NULL, `trip_id` TEXT NOT NULL, `trip_headsign` TEXT NOT NULL, `direction_id` INTEGER NOT NULL, `block_id` TEXT NOT NULL, `shape_id` TEXT NOT NULL, `wheelchair_accessible` INTEGER NOT NULL, `limited_route` INTEGER NOT NULL, PRIMARY KEY(`trip_id`), FOREIGN KEY(`route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "routeID",
+            "columnName": "route_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "serviceID",
+            "columnName": "service_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "tripID",
+            "columnName": "trip_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "tripHeadsign",
+            "columnName": "trip_headsign",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "directionID",
+            "columnName": "direction_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "blockID",
+            "columnName": "block_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "shapeID",
+            "columnName": "shape_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isWheelchairAccess",
+            "columnName": "wheelchair_accessible",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "isLimitedRoute",
+            "columnName": "limited_route",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "trip_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_gtfs_trips_route_id",
+            "unique": false,
+            "columnNames": [
+              "route_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_trips_route_id` ON `${TABLE_NAME}` (`route_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "routes_table",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "route_id"
+            ],
+            "referencedColumns": [
+              "route_id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "gtfs_shapes",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`shape_id` TEXT NOT NULL, `shape_pt_lat` REAL NOT NULL, `shape_pt_lon` REAL NOT NULL, `shape_pt_sequence` INTEGER NOT NULL, PRIMARY KEY(`shape_id`, `shape_pt_sequence`))",
+        "fields": [
+          {
+            "fieldPath": "shapeID",
+            "columnName": "shape_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointLat",
+            "columnName": "shape_pt_lat",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointLon",
+            "columnName": "shape_pt_lon",
+            "affinity": "REAL",
+            "notNull": true
+          },
+          {
+            "fieldPath": "pointSequence",
+            "columnName": "shape_pt_sequence",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "shape_id",
+            "shape_pt_sequence"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "mato_patterns",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pattern_name` TEXT NOT NULL, `pattern_code` TEXT NOT NULL, `pattern_hash` TEXT NOT NULL, `pattern_direction_id` INTEGER NOT NULL, `pattern_route_id` TEXT NOT NULL, `pattern_headsign` TEXT, `pattern_polyline` TEXT NOT NULL, `pattern_polylength` INTEGER NOT NULL, PRIMARY KEY(`pattern_code`), FOREIGN KEY(`pattern_route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "pattern_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "code",
+            "columnName": "pattern_code",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "semanticHash",
+            "columnName": "pattern_hash",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "directionId",
+            "columnName": "pattern_direction_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "routeGtfsId",
+            "columnName": "pattern_route_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "headsign",
+            "columnName": "pattern_headsign",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "patternGeometryPoly",
+            "columnName": "pattern_polyline",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "patternGeometryLength",
+            "columnName": "pattern_polylength",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "pattern_code"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "routes_table",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "pattern_route_id"
+            ],
+            "referencedColumns": [
+              "route_id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "patterns_stops",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pattern_gtfs_id` TEXT NOT NULL, `stop_gtfs_id` TEXT NOT NULL, `stop_order` INTEGER NOT NULL, PRIMARY KEY(`pattern_gtfs_id`, `stop_gtfs_id`, `stop_order`), FOREIGN KEY(`pattern_gtfs_id`) REFERENCES `mato_patterns`(`pattern_code`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "patternId",
+            "columnName": "pattern_gtfs_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "stopGtfsId",
+            "columnName": "stop_gtfs_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "order",
+            "columnName": "stop_order",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "pattern_gtfs_id",
+            "stop_gtfs_id",
+            "stop_order"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "mato_patterns",
+            "onDelete": "CASCADE",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "pattern_gtfs_id"
+            ],
+            "referencedColumns": [
+              "pattern_code"
+            ]
+          }
+        ]
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6d2aa826894d1e6b1429678e13b65433')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -7,6 +7,8 @@
     }
 
     ext {
+        androidXTestVersion = "1.4.0"
+
         //multidex
         multidex_version = "2.0.1"
         //libraries versions
@@ -57,6 +59,12 @@
         versionName "1.16.3"
         vectorDrawables.useSupportLibrary = true
         multiDexEnabled true
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments = ["room.schemaLocation": "$projectDir/assets/schemas/".toString()]
+            }
+        }
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
 
     compileOptions {
@@ -65,6 +73,8 @@
     }
 
     sourceSets {
+        androidTest.assets.srcDirs += files("$projectDir/assets/schemas/".toString())
+
         main {
             manifest.srcFile 'AndroidManifest.xml'
             java.srcDirs = ['src']
@@ -93,7 +103,7 @@
 
     dependencies {
         //new libraries
-        implementation "androidx.fragment:fragment:$fragment_version"
+        implementation "androidx.fragment:fragment-ktx:$fragment_version"
         implementation "androidx.activity:activity:$activity_version"
         implementation "androidx.annotation:annotation:1.3.0"
         implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
@@ -121,25 +131,45 @@
         // ViewModel
         implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
         // LiveData
-        implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
+        implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
         // Lifecycles only (without ViewModel or LiveData)
-        implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
+        implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
+        // Legacy
+        implementation 'androidx.legacy:legacy-support-v4:1.0.0'
 
         // Room components
         implementation "androidx.room:room-ktx:$room_version"
         kapt "androidx.room:room-compiler:$room_version"
-        androidTestImplementation "androidx.room:room-testing:$room_version"
         //multidex - we need this to build the app
         implementation "androidx.multidex:multidex:$multidex_version"
 
         implementation 'de.siegmar:fastcsv:2.0.0'
 
+        testImplementation 'junit:junit:4.12'
+        implementation 'junit:junit:4.12'
+
+        implementation "androidx.test.ext:junit:1.1.3"
+        implementation "androidx.test:core:$androidXTestVersion"
+        implementation "androidx.test:runner:$androidXTestVersion"
+        implementation "androidx.room:room-testing:$room_version"
+
+        androidTestImplementation "androidx.test.ext:junit:1.1.3"
+        androidTestImplementation "androidx.test:core:$androidXTestVersion"
+        androidTestImplementation "androidx.test:runner:$androidXTestVersion"
+        androidTestImplementation "androidx.test:rules:$androidXTestVersion"
+        androidTestImplementation "androidx.room:room-testing:$room_version"
+
+
+
+
     }
 }
 
 dependencies {
 
+
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
     api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
     api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+
 }
diff --git a/res/drawable/ic_moving.xml b/res/drawable/ic_moving.xml
new file mode 100644
--- /dev/null
+++ b/res/drawable/ic_moving.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M6.15,36.2 L3.85,33.9 14.9,22.85Q16.55,21.2 18.9,21.2Q21.25,21.2 22.9,22.85L25.2,25.15Q25.9,25.85 26.85,25.85Q27.8,25.85 28.55,25.15L38.55,15.15H32.9V11.85H44.15V23.15H40.9V17.5L30.85,27.5Q29.2,29.15 26.85,29.15Q24.5,29.15 22.85,27.5L20.5,25.15Q19.8,24.5 18.825,24.5Q17.85,24.5 17.2,25.15Z"/>
+</vector>
diff --git a/res/drawable/ic_moving_emph.xml b/res/drawable/ic_moving_emph.xml
new file mode 100644
--- /dev/null
+++ b/res/drawable/ic_moving_emph.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M6.25,36.9 L3.25,33.9 14.65,22.5Q16.5,20.7 19.025,20.7Q21.55,20.7 23.35,22.5L25.65,24.8Q26.2,25.35 26.975,25.35Q27.75,25.35 28.3,24.8L37.45,15.65H32.5V11.35H44.8V23.65H40.5V18.7L31.3,27.85Q29.5,29.7 26.95,29.7Q24.4,29.7 22.6,27.85L20.25,25.5Q19.7,24.95 18.95,24.95Q18.2,24.95 17.65,25.5Z"/>
+</vector>
diff --git a/res/layout/arrivals_nearby_card.xml b/res/layout/arrivals_nearby_card.xml
--- a/res/layout/arrivals_nearby_card.xml
+++ b/res/layout/arrivals_nearby_card.xml
@@ -27,16 +27,23 @@
                   android:layout_margin="10dp"
                   android:textStyle="normal" android:layout_marginRight="20dp" android:layout_marginEnd="20dp"
                   android:layout_marginBottom="20dp"/>
-        <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
-                  android:id="@+id/lineDirectionTextView"
-                  android:text="piazza statuto" android:textSize="20sp"
-                  android:textAppearance="?android:attr/textAppearanceLarge"
-                  android:layout_toLeftOf="@id/arrivalsDistanceTextView"
-                  android:layout_toRightOf="@id/lineNameTextView"
-                  android:textStyle="normal" android:layout_marginRight="6dp" android:layout_marginEnd="6dp"
-                  android:layout_alignBaseline="@id/lineNameTextView"
-                  android:layout_marginLeft="10dp" android:layout_marginTop="10dp"
-                  android:layout_marginStart="10dp"/>
+
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:id="@+id/lineDirectionTextView"
+                android:text="piazza statuto"
+                android:textSize="20sp"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:layout_toLeftOf="@id/arrivalsDistanceTextView"
+                android:layout_toRightOf="@id/lineNameTextView"
+                android:textStyle="normal"
+                android:layout_marginRight="6dp"
+                android:layout_marginEnd="6dp"
+                android:layout_alignBaseline="@id/lineNameTextView"
+                android:layout_marginLeft="10dp"
+                android:layout_marginTop="5dp"
+                android:layout_marginStart="10dp" />
         <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
                   android:layout_alignBaseline="@id/arrivalsTimeTextView"
                   android:textAppearance="?android:attr/textAppearanceMedium"
diff --git a/res/layout/bus_stop_line_elmt.xml b/res/layout/bus_stop_line_elmt.xml
new file mode 100644
--- /dev/null
+++ b/res/layout/bus_stop_line_elmt.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                                                   xmlns:app="http://schemas.android.com/apk/res-auto"
+                                                   xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+                                                   android:layout_height="wrap_content">
+
+    <TextView
+            android:id="@+id/busStopID"
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+
+            android:layout_centerVertical="true"
+            android:background="@drawable/bus_stop_background"
+            android:gravity="center"
+            android:textColor="@color/grey_100"
+            android:textSize="20sp"
+            android:layout_margin="-1dp"
+            app:layout_constraintStart_toStartOf="parent"
+            android:layout_marginLeft="14dp"
+            android:layout_marginStart="14dp"
+                app:layout_constraintTop_toBottomOf="@+id/topStub"/>
+    <View
+            android:layout_width="8dp"
+            android:layout_height="16dp" android:id="@+id/topStub"
+            android:background="@color/blue_500"
+
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintStart_toStartOf="@id/busStopID"
+            android:layout_marginLeft="25dp"
+            android:layout_marginStart="25dp"/>
+    <View
+            android:layout_width="8dp"
+            android:layout_height="0dp" android:id="@+id/bottomStub"
+            android:background="@color/blue_500"
+
+            app:layout_constraintTop_toBottomOf="@id/busStopID"
+            app:layout_constraintStart_toStartOf="@id/busStopID"
+            app:layout_constraintBottom_toBottomOf="parent"
+            android:layout_marginLeft="25dp"
+            android:layout_marginStart="25dp"/>
+
+    <TextView
+            android:id="@+id/busStopName"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="@color/blue_500"
+            app:layout_constraintStart_toEndOf="@id/busStopID"
+            app:layout_constraintTop_toTopOf="@id/busStopID"
+            android:layout_marginStart="10dp"
+            android:layout_marginLeft="10dp"
+            android:layout_marginTop="0dp"
+            android:layout_marginEnd="10dp"
+
+            android:layout_marginRight="10dp"
+            app:layout_constraintEnd_toEndOf="parent"/>
+
+    <TextView
+            android:id="@+id/busStopLocality"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="16dp"
+            android:layout_marginEnd="16dp"
+            app:layout_constraintTop_toBottomOf="@id/busStopName"
+            app:layout_constraintStart_toStartOf="@id/busStopName"
+            android:layout_marginStart="3dp"
+            android:layout_marginLeft="3dp"
+            android:layout_marginTop="4dp"
+
+            android:textColor="@color/grey_600"
+            android:textAppearance="?android:attr/textAppearanceMedium" app:layout_constraintEnd_toEndOf="parent"/>
+
+    <TextView
+            android:id="@+id/routesThatStopHere"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/busStopLocality"
+            android:layout_marginRight="25dp"
+            android:layout_marginEnd="25dp"
+            android:gravity="center_vertical"
+
+            app:layout_constraintStart_toStartOf="@id/busStopName"
+            android:layout_marginStart="10dp"
+            android:layout_marginLeft="10dp"
+
+
+            app:layout_constraintTop_toBottomOf="@id/busStopLocality"
+            android:layout_marginTop="5dp"
+
+
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@color/grey_600" app:layout_constraintEnd_toEndOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/entry_bus_stop.xml b/res/layout/entry_bus_stop.xml
--- a/res/layout/entry_bus_stop.xml
+++ b/res/layout/entry_bus_stop.xml
@@ -6,68 +6,69 @@
     android:paddingBottom="8dip" >
 
     <TextView
-        android:id="@+id/busStopName"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="16sp"
-        android:layout_marginStart="16sp"
-        android:layout_marginRight="16sp"
-        android:layout_marginEnd="16sp"
-        android:layout_marginTop="2sp"
-        android:layout_alignParentTop="true"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentStart="true"
-        android:layout_toLeftOf="@+id/busStopID"
-        android:layout_toStartOf="@+id/busStopID"
-        android:textAppearance="?android:attr/textAppearanceMedium"
-        android:textColor="@color/blue_500"/>
+            android:id="@+id/busStopName"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="16sp"
+            android:layout_marginStart="16sp"
+            android:layout_marginRight="16sp"
+            android:layout_marginEnd="16sp"
+            android:layout_marginTop="2sp"
+            android:layout_alignParentTop="true"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:layout_toLeftOf="@+id/busStopID"
+            android:layout_toStartOf="@+id/busStopID"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="@color/blue_500"/>
 
     <TextView
-        android:id="@+id/busStopLocality"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginLeft="16sp"
-        android:layout_marginStart="16sp"
-        android:layout_marginRight="16sp"
-        android:layout_marginEnd="16sp"
-        android:layout_below="@id/busStopName"
-        android:layout_toLeftOf="@+id/busStopID"
-        android:layout_toStartOf="@+id/busStopID"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentStart="true"
-        android:textColor="@color/grey_600"
-        android:textAppearance="?android:attr/textAppearanceMedium"/>
+            android:id="@+id/busStopLocality"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="16sp"
+            android:layout_marginStart="16sp"
+            android:layout_marginRight="16sp"
+            android:layout_marginEnd="16sp"
+            android:layout_below="@id/busStopName"
+            android:layout_toLeftOf="@+id/busStopID"
+            android:layout_toStartOf="@+id/busStopID"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:textColor="@color/grey_600"
+            android:textAppearance="?android:attr/textAppearanceMedium"/>
 
     <TextView
-        android:id="@+id/routesThatStopHere"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/busStopLocality"
-        android:layout_marginLeft="16sp"
-        android:layout_marginStart="16sp"
-        android:layout_marginRight="16sp"
-        android:layout_marginEnd="16sp"
-        android:gravity="center_vertical"
-        android:layout_toLeftOf="@id/busStopID"
-        android:layout_alignParentLeft="true"
-        android:layout_alignParentStart="true"
-        android:textAppearance="?android:attr/textAppearanceSmall"
-        android:textColor="@color/grey_600"/>
+            android:id="@+id/routesThatStopHere"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/busStopLocality"
+            android:layout_marginLeft="16sp"
+            android:layout_marginStart="16sp"
+            android:layout_marginRight="16sp"
+            android:layout_marginEnd="16sp"
+            android:gravity="center_vertical"
+            android:layout_toLeftOf="@id/busStopID"
+            android:layout_toStartOf="@id/busStopID"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="@color/grey_600"/>
 
 
     <TextView
-        android:id="@+id/busStopID"
-        android:layout_width="70dip"
-        android:layout_height="70dip"
-        android:layout_marginRight="16sp"
-        android:layout_marginEnd="16sp"
-        android:layout_alignParentRight="true"
-        android:layout_alignParentEnd="true"
-        android:layout_alignTop="@id/busStopName"
-        android:layout_centerVertical="true"
-        android:background="@drawable/bus_stop_background"
-        android:gravity="center"
-        android:textColor="@color/grey_100"
-        android:textSize="21sp"/>
+            android:id="@+id/busStopID"
+            android:layout_width="70dip"
+            android:layout_height="70dip"
+            android:layout_marginRight="16sp"
+            android:layout_marginEnd="16sp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentEnd="true"
+            android:layout_alignTop="@id/busStopName"
+            android:layout_centerVertical="true"
+            android:background="@drawable/bus_stop_background"
+            android:gravity="center"
+            android:textColor="@color/grey_100"
+            android:textSize="21sp"/>
 
 </RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/fragment_lines.xml b/res/layout/fragment_lines.xml
new file mode 100644
--- /dev/null
+++ b/res/layout/fragment_lines.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context=".fragments.LinesFragment">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+        <Spinner
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:id="@+id/linesSpinner"
+                app:layout_constraintTop_toTopOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/linesLabel"
+                android:layout_marginLeft="16dp"
+                android:layout_marginStart="16dp"
+                android:layout_marginTop="8dp"
+
+        />
+
+        <Spinner
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:id="@+id/patternsSpinner"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="@+id/linesSpinner"
+                app:layout_constraintTop_toBottomOf="@id/routeDescriptionTextView"
+                android:layout_marginTop="16dp" />
+
+        <TextView
+                android:text="@string/line"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:id="@+id/linesLabel"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                android:layout_marginTop="8dp"
+                android:layout_marginLeft="16dp"
+                android:layout_marginStart="16dp"
+                android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+                app:layout_constraintBottom_toBottomOf="@+id/linesSpinner"
+
+                android:gravity="center_vertical"
+
+        />
+
+        <TextView
+                android:text="Descr"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:id="@+id/routeDescriptionTextView"
+                app:layout_constraintStart_toStartOf="@id/linesSpinner"
+                app:layout_constraintTop_toBottomOf="@id/linesSpinner"
+                app:layout_constraintEnd_toEndOf="parent"
+
+                android:layout_marginTop="8dp"
+                android:layout_marginLeft="16dp"
+                android:layout_marginStart="16dp"
+                android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+
+                android:gravity="center_vertical"
+
+                android:layout_marginRight="16dp" android:layout_marginEnd="16dp"/>
+        <View
+                android:id="@+id/divider"
+                android:layout_width="match_parent"
+                android:layout_height="1dp"
+                android:background="?android:attr/listDivider"
+                app:layout_constraintTop_toBottomOf="@id/patternsSpinner"
+                android:layout_marginTop="8dp"/>
+        <androidx.recyclerview.widget.RecyclerView
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:id="@+id/patternStopsRecyclerView"
+                app:layout_constraintTop_toBottomOf="@+id/divider"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintVertical_bias="0.0"
+                android:layout_marginBottom="8dp"
+                android:layout_marginTop="0dp"
+                android:layout_margin="4dp"
+                app:layout_constraintHorizontal_bias="0.0" />
+        <!--
+        <TextView
+                android:text="TextView"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:id="@+id/textView2"
+                app:layout_constraintTop_toBottomOf="@+id/linesSpinner"
+                android:layout_marginTop="16dp"
+                app:layout_constraintStart_toStartOf="parent"
+                android:layout_marginLeft="16dp"
+                android:layout_marginStart="16dp" />
+                -->
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/menu/drawer_main.xml b/res/menu/drawer_main.xml
--- a/res/menu/drawer_main.xml
+++ b/res/menu/drawer_main.xml
@@ -14,6 +14,10 @@
             android:id="@+id/nav_favorites_item"
             android:icon="@drawable/ic_star_filled_white"
             android:title="@string/nav_favorites_text" />
+        <item android:id="@+id/nav_lines_item"
+              android:icon="@drawable/ic_moving_emph"
+            android:title="@string/lines"
+        />
     </group>
 
     <item android:id="@+id/drawer_action_settings"
diff --git a/res/menu/menu_line_item.xml b/res/menu/menu_line_item.xml
new file mode 100644
--- /dev/null
+++ b/res/menu/menu_line_item.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+            android:id="@+id/action_show_arrivals"
+            android:title="@string/show_arrivals"/>
+    <item
+            android:id="@+id/action_view_on_map"
+            android:title="@string/action_view_on_map"/>
+</menu>
\ No newline at end of file
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -14,7 +14,11 @@
     <string name="no_arrivals_stop">Nessun passaggio trovato alla fermata</string>
     <string name="parsing_error">Errore di lettura del sito 5T/GTT (dannato sito!)</string>
     <string name="passages">Fermata: %1$s</string>
-    <string name="lines">Linee: %1$s</string>
+    <string name="line">Linea</string>
+    <string name="lines">Linee</string>
+
+    <string name="line_fill">Linea: %1$s</string>
+    <string name="lines_fill">Linee: %1$s</string>
     <string name="results">Scegli la fermata…</string>
     <string name="no_passages">Nessun passaggio</string>
     <string name="no_qrcode">Nessun QR code</string>
@@ -95,7 +99,10 @@
     <string name="position_searching_message">Ricerca della posizione in corso&#8230;</string>
     <string name="no_stops_nearby">Nessuna fermata nei dintorni</string>
     <string name="main_menu_pref">Preferenze</string>
-    <string name="database_update_message">Aggiornamento del database&#8230;</string>
+    <string name="database_update_msg_inapp">Aggiornamento del database&#8230;</string>
+    <string name="database_update_msg_notif">Aggiornamento del database</string>
+    <string name="database_update_req">Aggiornamento database forzato</string>
+    <string name="database_update_req_descr">Tocca per aggiornare ora il database</string>
     <string name="pref_num_elements">Numero minimo di fermate</string>
     <string name="num_stops_nearby_not_number">Il numero di fermate da ricercare non è valido</string>
     <string name="invalid_number">Valore errato, inserisci un numero</string>
@@ -140,6 +147,10 @@
 
 
     <string name="default_notification_channel_description">Canale unico delle notifiche</string>
+    <string name="database_notification_channel">Database</string>
+    <string name="database_notification_channel_desc">Informazioni sul database (aggiornamento)</string>
+
+
     <string name="too_many_permission_asks">Chiesto troppe volte per il permesso %1$s</string>
     <string name="permission_storage_maps_msg">Non si può usare questa funzionalità senza il permesso di archivio</string>
     <string name="storage_permission">di archivio</string>
@@ -160,6 +171,10 @@
     <string name="app_version">Versione app</string>
     <string name="arrival_times">Orari di arrivo</string>
 
+    <!--  Preferences -->
+    <string name="requesting_db_update">Richiesto aggiornamento del database</string>
+
+
 
     <string name="pref_directions_capitalize">Mostra direzioni in maiuscolo</string>
     <string-array name="directions_capitalize">
@@ -167,4 +182,7 @@
         <item>Tutto in maiuscolo</item>
         <item>Solo la prima lettera maiuscola</item>
     </string-array>
+
+    <!-- lines -->
+    <string name="long_press_for_options">Tocca a lungo per le opzioni</string>
 </resources>
diff --git a/res/values/keys.xml b/res/values/keys.xml
--- a/res/values/keys.xml
+++ b/res/values/keys.xml
@@ -2,4 +2,5 @@
 <resources xmlns:tools="http://schemas.android.com/tools"
            tools:ignore="MissingTranslation">
     <string name="pref_layout">layout_pref</string>
+    <string name="pref_update_db_now">pref_update_db_now</string>
 </resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -22,7 +22,10 @@
     </string> <!-- TODO: carry out experiments to determine the best wording for this message and publish a paper with the findings -->
     <string name="passages">Arrivals at: %1$s</string>
     <string name="results">Choose the bus stop…</string>
-    <string name="lines">Lines: %1$s</string>
+    <string name="line">Line</string>
+    <string name="lines">Lines</string>
+    <string name="lines_fill">Lines: %1$s</string>
+    <string name="line_fill">Line: %1$s</string>
     <string name="no_passages">No timetable found</string>
     <string name="no_qrcode">No QR code</string>
     <string name="internal_error">Unexpected internal error, cannot extract data from GTT/5T website</string>
@@ -126,7 +129,10 @@
 
     <string name="enable_position_message_map">Allow access to position to show it on the map</string>
     <string name="enableGpsText">Please enable GPS</string>
-    <string name="database_update_message">Database update in progress&#8230;</string>
+    <string name="database_update_msg_inapp">Database update in progress&#8230;</string>
+    <string name="database_update_msg_notif">Updating the database</string>
+    <string name="database_update_req">Force database update</string>
+    <string name="database_update_req_descr">Touch to update the app database now</string>
     <string name="bus_arriving_at">is arriving at</string>
     <string name="arrivals_card_at_the_stop">at the stop</string>
     <string name="two_strings_format" translatable="false">%1$s - %2$s</string>
@@ -156,6 +162,9 @@
     -->
     <string name="default_notification_channel" translatable="false">Default</string>
     <string name="default_notification_channel_description">Default channel for notifications</string>
+    <string name="database_notification_channel">Database</string>
+    <string name="database_notification_channel_desc">Notifications on the update of the database</string>
+
     <string name="too_many_permission_asks">Asked for %1$s permission too many times</string>
     <string name="permission_storage_maps_msg">Cannot use the map with the storage permission!</string>
     <string name="storage_permission">storage</string>
@@ -176,6 +185,7 @@
     <string name="donate_now">Buy us a coffee</string>
     <string name="map">Map</string>
     <string name="stop_search_view_title">Search by stop</string>
+    <string name="requesting_db_update">Launching database update</string>
 
 
     <!-- preferences -->
@@ -191,4 +201,7 @@
         <item>CAPITALIZE_FIRST</item>
 
     </array>
+    
+    <!-- lines -->
+    <string name="long_press_for_options">Long press for options</string>
 </resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -58,4 +58,12 @@
             android:key="@string/pref_arrival_times_capit"
         />
     </androidx.preference.PreferenceCategory>
+
+    <androidx.preference.PreferenceCategory android:title="Database">
+            <androidx.preference.Preference
+                android:title="@string/database_update_req"
+                android:summary="@string/database_update_req_descr"
+                android:key="pref_db_update_now"
+            />
+    </androidx.preference.PreferenceCategory>
 </androidx.preference.PreferenceScreen>
diff --git a/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java b/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
new file mode 100644
--- /dev/null
+++ b/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
@@ -0,0 +1,53 @@
+package it.reyboz.bustorino.data.gtfs;
+
+import androidx.room.Room;
+import androidx.room.migration.Migration;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
+
+//@RunWith(AndroidJUnit4.class)
+public class GtfsDBMigrationsTest {
+    private static final String TEST_DB = "migration-test";
+
+    @Rule
+    public MigrationTestHelper helper;
+
+    public GtfsDBMigrationsTest() {
+        helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
+                GtfsDatabase.class.getCanonicalName(),
+                new FrameworkSQLiteOpenHelperFactory());
+    }
+
+    @Test
+    public void migrateAll() throws IOException {
+        // Create earliest version of the database.
+        SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
+        db.close();
+
+        // Open latest version of the database. Room will validate the schema
+        // once all migrations execute.
+        GtfsDatabase appDb = Room.databaseBuilder(
+                InstrumentationRegistry.getInstrumentation().getTargetContext(),
+                GtfsDatabase.class,
+                TEST_DB)
+                .addMigrations(ALL_MIGRATIONS).build();
+        appDb.getOpenHelper().getWritableDatabase();
+        appDb.close();
+    }
+
+    // Array of all migrations
+    private static final Migration[] ALL_MIGRATIONS = new Migration[]{
+            GtfsDatabase.Companion.getMIGRATION_1_2()};
+}
+
diff --git a/src/it/reyboz/bustorino/ActivityExperiments.java b/src/it/reyboz/bustorino/ActivityExperiments.java
--- a/src/it/reyboz/bustorino/ActivityExperiments.java
+++ b/src/it/reyboz/bustorino/ActivityExperiments.java
@@ -18,21 +18,16 @@
 package it.reyboz.bustorino;
 
 import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 import android.widget.Toast;
-import androidx.appcompat.app.AppCompatActivity;
 import android.os.Bundle;
 import it.reyboz.bustorino.backend.Fetcher;
 import it.reyboz.bustorino.backend.gtfs.GtfsDataParser;
 import it.reyboz.bustorino.backend.networkTools;
-import it.reyboz.bustorino.backend.utils;
 import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
-import it.reyboz.bustorino.data.gtfs.StaticGtfsDao;
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao;
 import it.reyboz.bustorino.middleware.GeneralActivity;
 
 import java.io.*;
@@ -44,7 +39,6 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
 
 public class ActivityExperiments extends GeneralActivity {
 
@@ -91,7 +85,7 @@
                     "ExperimentGTFS", "Last update date is " + updateDate//utils.joinList(files, "\n")
             );
             //Toast.makeText(v.getContext(), "Gtfs data already downloaded", Toast.LENGTH_SHORT).show();
-                StaticGtfsDao dao = GtfsDatabase.Companion.getGtfsDatabase(appContext).gtfsDao();
+                GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(appContext).gtfsDao();
                 Log.d(DEBUG_TAG, String.valueOf(dao));
                 dao.deleteAllStopTimes();
 
@@ -106,10 +100,12 @@
                     // now iterate through each item in the stream. The get next
                     // entry call will return a ZipEntry for each file in the
                     // stream
+                    /*
                     Enumeration<? extends ZipEntry> entries = zipFile.entries();
                     ZipEntry entry;
                     String line;
                     //final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+
                     HashSet<ZipEntry> readLater = new HashSet<>();
                     while(entries.hasMoreElements()){
                         entry = entries.nextElement();
@@ -123,30 +119,7 @@
                     for(ZipEntry laterEntry: readLater){
                         GtfsDataParser.readGtfsZipEntry(laterEntry, zipFile, v.getContext().getApplicationContext());
                     }
-                    //Toast.makeText(appContext, "D", Toast.LENGTH_SHORT).show();
-                    /*
-                    while ((entry = stream.getNextEntry()) != null) {
-                        String s = String.format(Locale.ENGLISH, "Entry: %s len %d added",
-                                entry.getName(),
-                                entry.getSize()
-                        );
-                        if(entry.getName().contains("stop_times.")){
-                            //skip and do later
-
-                        }
-                        //Toast.makeText(v.getContext(), "File: " + entry.getName(), Toast.LENGTH_SHORT).show();
-                        Log.d(DEBUG_TAG, s);
-                        //read data in table
-                        final String tableName = entry.getName().split("\\.")[0].trim();
-
-
 
-                        // Once we get the entry from the stream, the stream is
-                        // positioned read to read the raw data, and we keep
-                        // reading until read returns 0 or less.
-                        //result.add(entry.getName());
-                    }
-                    stream.close();
                      */
                 } catch (IOException e) {
                     e.printStackTrace();
@@ -165,32 +138,18 @@
             }
         };
 
-        Toast.makeText(this, "Launching, no result will show", Toast.LENGTH_SHORT).show();
+        Toast.makeText(this, "Test disabled", Toast.LENGTH_SHORT).show();
         //Looper looper = new Looper(true);
         //Handler handler = new Handler();
         //handler.post(run);
-        executorService.execute(run);
+        //executorService.execute(run);
 
 
     }
 
     public void deleteDatabase(View v){
-        final Context con = getApplicationContext().getApplicationContext();
-        Toast.makeText(this, "Deleting GTFS DB contents, wait a few seconds", Toast.LENGTH_SHORT).show();
-        Runnable deleteDB = new Runnable() {
-            @Override
-            public void run() {
-                StaticGtfsDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
-                Log.d(DEBUG_TAG, String.valueOf(dao));
-                dao.deleteAllStopTimes();
-                dao.deleteAllTrips();
-                dao.deleteAllRoutes();
-                dao.deleteAllStops();
-                dao.deleteAllServices();
-                Log.d(DEBUG_TAG, "Deleted stuff");
-            }
-        };
+        //final Context con = getApplicationContext().getApplicationContext();
+        Toast.makeText(this, "Deleting GTFS DB contents isn't allowed anymore", Toast.LENGTH_SHORT).show();
 
-        executorService.execute(deleteDB);
     }
 }
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java
--- a/src/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/src/it/reyboz/bustorino/ActivityPrincipal.java
@@ -41,11 +41,6 @@
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentTransaction;
 import androidx.preference.PreferenceManager;
-import androidx.work.BackoffPolicy;
-import androidx.work.Constraints;
-import androidx.work.ExistingPeriodicWorkPolicy;
-import androidx.work.NetworkType;
-import androidx.work.PeriodicWorkRequest;
 import androidx.work.WorkInfo;
 import androidx.work.WorkManager;
 
@@ -53,7 +48,6 @@
 import com.google.android.material.snackbar.Snackbar;
 
 import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
 
 import it.reyboz.bustorino.backend.Stop;
 import it.reyboz.bustorino.data.DBUpdateWorker;
@@ -179,7 +173,7 @@
             requestArrivalsForStopID(busStopID);
         }
         //Try (hopefully) database update
-        DatabaseUpdate.requestDBUpdateWithWork(this, false);
+        DatabaseUpdate.requestDBUpdateWithWork(this, false, false);
         /*
         Watch for database update
          */
@@ -239,8 +233,8 @@
                         //get Fragment
                         FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
                         FavoritesFragment fragment = FavoritesFragment.newInstance();
-                        ft.replace(R.id.mainActContentFrame,fragment, TAG_FAVORITES);
-                        ft.addToBackStack(null);
+                        ft.replace(R.id.mainActContentFrame,fragment, TAG_FAVORITES)
+                            .addToBackStack("main");
                         ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                         ft.commit();
                         return true;
@@ -266,6 +260,22 @@
                                 Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show();
                         }
                         return true;
+                    } else if (menuItem.getItemId() == R.id.nav_lines_item) {
+                        closeDrawerIfOpen();
+                        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+                        Fragment f = getSupportFragmentManager().findFragmentByTag(LinesFragment.FRAGMENT_TAG);
+                        if(f!=null){
+                            ft.replace(R.id.mainActContentFrame, f, LinesFragment.FRAGMENT_TAG);
+                        }else{
+                            //use new method
+                            ft.replace(R.id.mainActContentFrame,LinesFragment.class,null,LinesFragment.FRAGMENT_TAG);
+                        }
+
+                                ft.setReorderingAllowed(true)
+                                .addToBackStack("lines")
+                                .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+                                .commit();
+                        return true;
                     }
                     //selectDrawerItem(menuItem);
                     Log.d(DEBUG_TAG, "pressed item "+menuItem);
@@ -389,7 +399,7 @@
         }
         if (baseView == null) baseView = findViewById(R.id.mainActContentFrame);
         if (baseView == null) Log.e(DEBUG_TAG, "baseView null for default snackbar, probably exploding now");
-        snackbar = Snackbar.make(baseView, R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
+        snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE);
         snackbar.show();
     }
 
@@ -500,6 +510,10 @@
                 titleResId=R.string.app_name_full;
                 mNavView.setCheckedItem(R.id.nav_arrivals);
                 break;
+            case LINES:
+                titleResId=R.string.lines;
+                mNavView.setCheckedItem(R.id.nav_lines_item);
+                break;
             default:
                 titleResId = 0;
         }
diff --git a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
--- a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
@@ -18,10 +18,12 @@
 package it.reyboz.bustorino.adapters;
 
 import android.content.Context;
+import android.content.SharedPreferences;
 import android.location.Location;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.util.Pair;
+import androidx.preference.PreferenceManager;
 import androidx.recyclerview.widget.RecyclerView;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -37,15 +39,18 @@
 
 import java.util.*;
 
-public class ArrivalsStopAdapter extends RecyclerView.Adapter<ArrivalsStopAdapter.ViewHolder> {
+public class ArrivalsStopAdapter extends RecyclerView.Adapter<ArrivalsStopAdapter.ViewHolder> implements SharedPreferences.OnSharedPreferenceChangeListener {
     private final static int layoutRes = R.layout.arrivals_nearby_card;
     //private List<Stop> stops;
     private @Nullable Location userPosition;
     private FragmentListenerMain listener;
-    private List< Pair<Stop, Route> > routesPairList = new ArrayList<>();
+    private List< Pair<Stop, Route> > routesPairList;
     private final Context context;
     //Maximum number of stops to keep
     private final int MAX_STOPS = 20; //TODO: make it programmable
+    private String KEY_CAPITALIZE;
+    private NameCapitalize capit;
+
 
     public ArrivalsStopAdapter(@Nullable List< Pair<Stop, Route> > routesPairList, FragmentListenerMain fragmentListener, Context con, @Nullable Location pos) {
         listener  = fragmentListener;
@@ -55,10 +60,16 @@
         resetListAndPosition();
         // if(paline!=null)
         //resetRoutesPairList(paline);
+        KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit);
+        SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context);
+        defSharPref.registerOnSharedPreferenceChangeListener(this);
+        String capitalizeKey = defSharPref.getString(KEY_CAPITALIZE, "");
+        this.capit = NameCapitalize.getCapitalize(capitalizeKey);
     }
 
 
 
+    @NonNull
     @Override
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
@@ -85,7 +96,7 @@
             //final String routeName = String.format(context.getResources().getString(R.string.two_strings_format),r.getNameForDisplay(),r.destinazione);
             if (r!=null) {
                 holder.lineNameTextView.setText(r.getNameForDisplay());
-                holder.lineDirectionTextView.setText(r.destinazione);
+                holder.lineDirectionTextView.setText(NameCapitalize.capitalizePass(r.destinazione, capit));
                 holder.arrivalsTextView.setText(r.getPassaggiToString(0,2,true));
             } else {
                 holder.lineNameTextView.setVisibility(View.INVISIBLE);
@@ -117,6 +128,17 @@
         return routesPairList.size();
     }
 
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        if(key.equals(KEY_CAPITALIZE)){
+            String k = sharedPreferences.getString(KEY_CAPITALIZE, "");
+            capit = NameCapitalize.getCapitalize(k);
+
+            notifyDataSetChanged();
+
+        }
+    }
+
     class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener  {
         TextView lineNameTextView;
         TextView lineDirectionTextView;
diff --git a/src/it/reyboz/bustorino/adapters/NameCapitalize.java b/src/it/reyboz/bustorino/adapters/NameCapitalize.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/adapters/NameCapitalize.java
@@ -0,0 +1,44 @@
+package it.reyboz.bustorino.adapters;
+
+import java.util.Locale;
+
+import it.reyboz.bustorino.backend.utils;
+
+public enum NameCapitalize {
+    DO_NOTHING, ALL, FIRST;
+    public static NameCapitalize getCapitalize(String capitalize){
+
+        switch (capitalize.trim()){
+            case "KEEP":
+                return NameCapitalize.DO_NOTHING;
+            case "CAPITALIZE_ALL":
+                return NameCapitalize.ALL;
+
+            case "CAPITALIZE_FIRST":
+                return NameCapitalize.FIRST;
+        }
+        return  NameCapitalize.DO_NOTHING;
+    }
+
+    /**
+     * Parse the output
+     * @param input the input string
+     * @param capitalize the capitalize value
+     * @return parsed string
+     */
+    public static String capitalizePass(String input, NameCapitalize capitalize){
+        String dest = input;
+        switch (capitalize){
+            case ALL:
+                dest = input.toUpperCase(Locale.ROOT);
+                break;
+            case FIRST:
+                dest = utils.toTitleCase(input, true);
+                break;
+            case DO_NOTHING:
+            default:
+
+        }
+        return dest;
+    }
+}
diff --git a/src/it/reyboz/bustorino/adapters/StopAdapter.java b/src/it/reyboz/bustorino/adapters/StopAdapter.java
--- a/src/it/reyboz/bustorino/adapters/StopAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/StopAdapter.java
@@ -35,7 +35,7 @@
  */
 public class StopAdapter extends ArrayAdapter<Stop> {
     private LayoutInflater li;
-    private static int row_layout = R.layout.entry_bus_stop;
+    private static final int row_layout = R.layout.entry_bus_stop;
     private static final int busIcon = R.drawable.bus;
     private static final int trainIcon = R.drawable.subway;
     private static final int tramIcon = R.drawable.tram;
diff --git a/src/it/reyboz/bustorino/adapters/AdapterListener.java b/src/it/reyboz/bustorino/adapters/StopAdapterListener.java
rename from src/it/reyboz/bustorino/adapters/AdapterListener.java
rename to src/it/reyboz/bustorino/adapters/StopAdapterListener.java
--- a/src/it/reyboz/bustorino/adapters/AdapterListener.java
+++ b/src/it/reyboz/bustorino/adapters/StopAdapterListener.java
@@ -19,6 +19,8 @@
 
 import it.reyboz.bustorino.backend.Stop;
 
-public interface AdapterListener {
+public interface StopAdapterListener {
     void onTappedStop(Stop stop);
+
+    boolean onLongPressOnStop(Stop stop);
 }
diff --git a/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java
--- a/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java
@@ -35,13 +35,17 @@
 
 public class StopRecyclerAdapter extends RecyclerView.Adapter<StopRecyclerAdapter.ViewHolder> {
     private List<Stop> stops;
-    private static final int row_layout = R.layout.entry_bus_stop;
+    private static final int ITEM_LAYOUT_FAVORITES = R.layout.entry_bus_stop;
+    private static final int ITEM_LAYOUT_LINES = R.layout.bus_stop_line_elmt;
     private static final int busIcon = R.drawable.bus;
     private static final int trainIcon = R.drawable.subway;
     private static final int tramIcon = R.drawable.tram;
     private static final int cityIcon = R.drawable.city;
 
-    private AdapterListener listener;
+    private NameCapitalize capitalizeLocation = NameCapitalize.DO_NOTHING;
+    private final Use usedFor;
+
+    private final StopAdapterListener listener;
     private int position;
 
 
@@ -52,14 +56,29 @@
         //TextView busLineVehicleIcon;
         TextView busStopLinesTextView;
         TextView busStopLocaLityTextView;
+
+        View topStub, bottomStub;
         Stop mStop;
 
-        public ViewHolder(@NonNull View itemView, AdapterListener listener) {
+        int menuResID=R.menu.menu_favourites_entry;
+
+        public ViewHolder(@NonNull View itemView, StopAdapterListener listener, Use usedFor) {
             super(itemView);
-            busStopIDTextView = (TextView) itemView.findViewById(R.id.busStopID);
-            busStopNameTextView = (TextView) itemView.findViewById(R.id.busStopName);
-            busStopLinesTextView = (TextView) itemView.findViewById(R.id.routesThatStopHere);
-            busStopLocaLityTextView = (TextView) itemView.findViewById(R.id.busStopLocality);
+            busStopIDTextView = itemView.findViewById(R.id.busStopID);
+            busStopNameTextView = itemView.findViewById(R.id.busStopName);
+            busStopLinesTextView = itemView.findViewById(R.id.routesThatStopHere);
+            busStopLocaLityTextView = itemView.findViewById(R.id.busStopLocality);
+            switch (usedFor){
+                case LINES:
+                    topStub = itemView.findViewById(R.id.topStub);
+                    bottomStub = itemView.findViewById(R.id.bottomStub);
+                    menuResID = R.menu.menu_line_item;
+                    break;
+                case FAVORITES:
+                default:
+                    topStub = null;
+                    bottomStub = null;
+            }
 
             mStop = new Stop("");
 
@@ -71,13 +90,29 @@
         @Override
         public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
             MenuInflater inflater = new MenuInflater(view.getContext());
-            inflater.inflate(R.menu.menu_favourites_entry, contextMenu);
+            inflater.inflate(menuResID, contextMenu);
         }
     }
 
-    public StopRecyclerAdapter(List<Stop> stops,AdapterListener listener) {
+    public StopRecyclerAdapter(List<Stop> stops, StopAdapterListener listener, Use usedFor) {
         this.stops = stops;
         this.listener = listener;
+        this.usedFor = usedFor;
+    }
+    public StopRecyclerAdapter(List<Stop> stops, StopAdapterListener listener, Use usedFor, NameCapitalize locationCapit) {
+        this.stops = stops;
+        this.listener = listener;
+        this.usedFor = usedFor;
+        this.capitalizeLocation = locationCapit;
+    }
+
+    public NameCapitalize getCapitalizeLocation() {
+        return capitalizeLocation;
+    }
+
+    public void setCapitalizeLocation(NameCapitalize capitalizeLocation) {
+        this.capitalizeLocation = capitalizeLocation;
+        notifyDataSetChanged();
     }
 
     public void setStops(List<Stop> stops){
@@ -100,10 +135,20 @@
     @NonNull
     @Override
     public StopRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+        final int layoutID;
+        switch (usedFor){
+            case LINES:
+                layoutID = ITEM_LAYOUT_LINES;
+                break;
+            case FAVORITES:
+            default:
+                layoutID = ITEM_LAYOUT_FAVORITES;
+
+        }
         View view = LayoutInflater.from(parent.getContext())
-                .inflate(row_layout, parent, false);
+                .inflate(layoutID, parent, false);
 
-        return  new StopRecyclerAdapter.ViewHolder(view, listener);
+        return  new StopRecyclerAdapter.ViewHolder(view, listener, this.usedFor);
     }
 
     @Override
@@ -114,11 +159,11 @@
 
     @Override
     public void onBindViewHolder(@NonNull StopRecyclerAdapter.ViewHolder vh, int position) {
-        Log.d("StopRecyclerAdapter", "Called for position "+position);
+        //Log.d("StopRecyclerAdapter", "Called for position "+position);
         Stop stop = stops.get(position);
         vh.busStopIDTextView.setText(stop.ID);
         vh.mStop = stop;
-        Log.d("StopRecyclerAdapter", "Stop: "+stop.ID);
+        //Log.d("StopRecyclerAdapter", "Stop: "+stop.ID);
 
         // NOTE: intentionally ignoring stop username in search results: if it's in the favorites, why are you searching for it?
         vh.busStopNameTextView.setText(stop.getStopDisplayName());
@@ -154,21 +199,34 @@
         if (stop.location == null) {
             vh.busStopLocaLityTextView.setVisibility(View.GONE);
         } else {
-            vh.busStopLocaLityTextView.setText(stop.location);
+            vh.busStopLocaLityTextView.setText(NameCapitalize.capitalizePass(stop.location, capitalizeLocation));
             vh.busStopLocaLityTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern
         }
         //trick to set the position
-        vh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
-            @Override
-            public boolean onLongClick(View view) {
-                setPosition(vh.getAdapterPosition());
-                return false;
-            }
+        vh.itemView.setOnLongClickListener(view -> {
+            setPosition(vh.getAdapterPosition());
+            return false;
         });
+        if(this.usedFor == Use.LINES){
+
+            //vh.menuResID;
+            vh.bottomStub.setVisibility(View.VISIBLE);
+            vh.topStub.setVisibility(View.VISIBLE);
+            if(position == 0) {
+                vh.topStub.setVisibility(View.GONE);
+            }
+            else if (position == stops.size()-1) {
+                vh.bottomStub.setVisibility(View.GONE);
+            }
+        }
     }
 
     @Override
     public int getItemCount() {
         return stops.size();
     }
+
+    public enum  Use{
+        FAVORITES, LINES
+    }
 }
diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
--- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
+++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
@@ -291,7 +291,8 @@
                 Stop s = new Stop(stopID, stopName,
                         null,location,t,Arrays.asList(lines),
                         Double.parseDouble(currentStop.getString("lat")),
-                        Double.parseDouble(currentStop.getString("lng")));
+                        Double.parseDouble(currentStop.getString("lng")),
+                        null);
                 if(placeName!=null)
                 s.setAbsurdGTTPlaceName(placeName);
                 stopslist.add(s);
diff --git a/src/it/reyboz/bustorino/backend/Notifications.java b/src/it/reyboz/bustorino/backend/Notifications.java
--- a/src/it/reyboz/bustorino/backend/Notifications.java
+++ b/src/it/reyboz/bustorino/backend/Notifications.java
@@ -8,6 +8,7 @@
 
 public class Notifications {
     public static final String DEFAULT_CHANNEL_ID ="Default";
+    public static final String DB_UPDATE_CHANNELS_ID ="Database Update";
 
     public static void createDefaultNotificationChannel(Context context) {
         // Create the NotificationChannel, but only on API 26+ because
diff --git a/src/it/reyboz/bustorino/backend/Palina.java b/src/it/reyboz/bustorino/backend/Palina.java
--- a/src/it/reyboz/bustorino/backend/Palina.java
+++ b/src/it/reyboz/bustorino/backend/Palina.java
@@ -48,13 +48,13 @@
 
     public Palina(Stop s){
         super(s.ID,s.getStopDefaultName(),s.getStopUserName(),s.location,s.type,
-                s.getRoutesThatStopHere(),s.getLatitude(),s.getLongitude());
+                s.getRoutesThatStopHere(),s.getLatitude(),s.getLongitude(), null);
     }
 
     public Palina(@NonNull String ID, @Nullable String name, @Nullable String userName,
                   @Nullable String location,
-                  @Nullable Double lat, @Nullable Double lon) {
-        super(ID, name, userName, location, null, null, lat, lon);
+                  @Nullable Double lat, @Nullable Double lon, @Nullable String gtfsID) {
+        super(ID, name, userName, location, null, null, lat, lon, gtfsID);
     }
 
     public Palina(@Nullable String name, @NonNull String ID, @Nullable String location, @Nullable Route.Type type, @Nullable List<String> routesThatStopHere) {
diff --git a/src/it/reyboz/bustorino/backend/Result.java b/src/it/reyboz/bustorino/backend/Result.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/Result.java
@@ -0,0 +1,38 @@
+package it.reyboz.bustorino.backend;
+
+import androidx.annotation.Nullable;
+
+
+public class Result<T> {
+    @Nullable
+    public final T result;
+
+    @Nullable
+    public final Exception exception;
+    public boolean isSuccess() {
+        return exception == null;
+    }
+
+
+    public static <T> Result<T> success(@Nullable T result) {
+        return new Result<>(result);
+    }
+
+    /**
+     * Returns a failed response
+     */
+    public static <T> Result<T> failure(Exception error) {
+        return new Result<>(error);
+    }
+
+    private Result(@Nullable T result) {
+        this.result = result;
+        this.exception = null;
+    }
+
+
+    private Result(Exception error) {
+        this.result = null;
+        this.exception = error;
+    }
+}
diff --git a/src/it/reyboz/bustorino/backend/Stop.java b/src/it/reyboz/bustorino/backend/Stop.java
--- a/src/it/reyboz/bustorino/backend/Stop.java
+++ b/src/it/reyboz/bustorino/backend/Stop.java
@@ -80,7 +80,9 @@
     /**
      * Constructor that sets EVERYTHING.
      */
-    public Stop(@NonNull String ID, @Nullable String name, @Nullable String userName, @Nullable String location, @Nullable Route.Type type, @Nullable List<String> routesThatStopHere, @Nullable Double lat, @Nullable Double lon) {
+    public Stop(@NonNull String ID, @Nullable String name, @Nullable String userName,
+                @Nullable String location, @Nullable Route.Type type, @Nullable List<String> routesThatStopHere,
+                @Nullable Double lat, @Nullable Double lon, @Nullable String gtfsID) {
         this.ID = ID;
         this.name = name;
         this.username = userName;
@@ -89,6 +91,7 @@
         this.routesThatStopHere = routesThatStopHere;
         this.lat = lat;
         this.lon = lon;
+        this.gtfsID = gtfsID;
     }
 
 
diff --git a/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java b/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.backend.gtfs;
+
+import org.osmdroid.util.GeoPoint;
+
+import java.util.ArrayList;
+
+public final class PolylineParser {
+    /**
+     * Decode a Google polyline
+     * Thanks to https://stackoverflow.com/questions/9341020/how-to-decode-googles-polyline-algorithm
+     * @param encodedPolyline the encoded polyline in a string
+     * @param initial_capacity for the list
+     * @return the list of points correspoding to the polyline
+     */
+    public static ArrayList<GeoPoint> decodePolyline(String encodedPolyline, int initial_capacity) {
+        ArrayList<GeoPoint> points = new ArrayList<>(initial_capacity);
+        int truck = 0;
+        int carriage_q = 0;
+        int longit=0, latit=0;
+        boolean is_lat=true;
+        for (int x = 0, xx = encodedPolyline.length(); x < xx; ++x) {
+            int i = encodedPolyline.charAt(x);
+            i -= 63;
+            int _5_bits = i << (32 - 5) >>> (32 - 5);
+            truck |= _5_bits << carriage_q;
+            carriage_q += 5;
+            boolean is_last = (i & (1 << 5)) == 0;
+            if (is_last) {
+                boolean is_negative = (truck & 1) == 1;
+                truck >>>= 1;
+                if (is_negative) {
+                    truck = ~truck;
+                }
+                if (is_lat){
+                    latit += truck;
+                    is_lat = false;
+                } else{
+                    longit += truck;
+                    points.add(new GeoPoint((double)latit/1e5,(double)longit/1e5));
+                    is_lat=true;
+                }
+                carriage_q = 0;
+                truck = 0;
+            }
+        }
+        return points;
+    }
+}
diff --git a/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java b/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
--- a/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
+++ b/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
@@ -31,10 +31,7 @@
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
 
 import it.reyboz.bustorino.backend.Fetcher;
@@ -54,7 +51,7 @@
                               AtomicReference<Fetcher.Result> res,
                               Response.Listener<Palina> listener,
                               @Nullable Response.ErrorListener errorListener) {
-        super(MatoAPIFetcher.QueryType.ARRIVALS, listener, errorListener);
+        super(MatoQueries.QueryType.ARRIVALS, listener, errorListener);
         this.stopName = stopName;
         this.startingTime = startingTime;
         this.timeRange = timeRange;
diff --git a/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java b/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
--- a/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
+++ b/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
@@ -12,9 +12,9 @@
     private static final String API_URL="https://mapi.5t.torino.it/routing/v1/routers/mat/index/graphql";
 
     protected final Response.Listener<T> listener;
-    private final MatoAPIFetcher.QueryType type;
+    protected final MatoQueries.QueryType type;
     public MapiVolleyRequest(
-            MatoAPIFetcher.QueryType type,
+            MatoQueries.QueryType type,
             Response.Listener<T> listener,
             @Nullable Response.ErrorListener errorListener) {
         super(Method.POST, API_URL, errorListener);
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
--- a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
+++ b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
@@ -19,15 +19,23 @@
 
 import android.content.Context
 import android.util.Log
+import com.android.volley.DefaultRetryPolicy
 import com.android.volley.toolbox.RequestFuture
 import it.reyboz.bustorino.BuildConfig
 import it.reyboz.bustorino.backend.*
+import it.reyboz.bustorino.data.gtfs.GtfsAgency
+import it.reyboz.bustorino.data.gtfs.GtfsFeed
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+import org.json.JSONArray
+import org.json.JSONException
 import org.json.JSONObject
 import java.util.*
 import java.util.concurrent.ExecutionException
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.TimeoutException
 import java.util.concurrent.atomic.AtomicReference
+import kotlin.collections.ArrayList
 
 
 open class MatoAPIFetcher(val minNumPassaggi: Int) : ArrivalsFetcher {
@@ -58,7 +66,7 @@
                 return Palina(stopID)
             }
             val requestQueue = NetworkVolleyManager.getInstance(appContext).requestQueue
-            request.setTag(getVolleyReqTag(QueryType.ARRIVALS))
+            request.setTag(getVolleyReqTag(MatoQueries.QueryType.ARRIVALS))
             requestQueue.add(request)
 
             try {
@@ -105,10 +113,15 @@
             "DNT" to "1",
             "Host" to "mapi.5t.torino.it")
 
-        fun getVolleyReqTag(type: QueryType): String{
+        private val longRetryPolicy = DefaultRetryPolicy(10000,5,DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)
+
+        fun getVolleyReqTag(type: MatoQueries.QueryType): String{
             return when (type){
-                QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops"
-                QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals"
+                MatoQueries.QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops"
+                MatoQueries.QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals"
+                MatoQueries.QueryType.FEEDS -> VOLLEY_TAG +"_Feeds"
+                MatoQueries.QueryType.ROUTES -> VOLLEY_TAG +"_AllRoutes"
+                MatoQueries.QueryType.PATTERNS_FOR_ROUTES -> VOLLEY_TAG + "_PatternsForRoute"
             }
         }
 
@@ -120,14 +133,15 @@
             val future = RequestFuture.newFuture<List<Palina>>()
 
             val request = VolleyAllStopsRequest(future, future)
-            request.tag = getVolleyReqTag(QueryType.ALL_STOPS)
+            request.tag = getVolleyReqTag(MatoQueries.QueryType.ALL_STOPS)
+            request.retryPolicy = longRetryPolicy
 
             requestQueue.add(request)
 
             var palinaList:List<Palina> = mutableListOf()
 
             try {
-                palinaList = future.get(60, TimeUnit.SECONDS)
+                palinaList = future.get(120, TimeUnit.SECONDS)
 
                 res?.set(Fetcher.Result.OK)
             }catch (e: InterruptedException) {
@@ -172,10 +186,9 @@
             val palina = Palina(
                 jsonStop.getString("code"),
                 jsonStop.getString("name"),
-                null, null, latitude, longitude
+                null, null, latitude, longitude,
+                jsonStop.getString("gtfsId")
             )
-            palina.gtfsID = jsonStop.getString("gtfsId")
-
             val routesStoppingJSON = jsonStop.getJSONArray("routes")
             val baseRoutes = mutableListOf<Route>()
             // get all the possible routes
@@ -259,10 +272,159 @@
             return  data
         }
 
-    }
 
-    enum class QueryType {
-        ARRIVALS, ALL_STOPS
+        fun getFeedsAndAgencies(context: Context, res: AtomicReference<Fetcher.Result>?):
+                Pair<List<GtfsFeed>, ArrayList<GtfsAgency>> {
+            val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+            val future = RequestFuture.newFuture<JSONObject>()
+
+            val request = MatoVolleyJSONRequest(MatoQueries.QueryType.FEEDS, JSONObject(), future, future)
+            request.setRetryPolicy(longRetryPolicy)
+            request.tag = getVolleyReqTag(MatoQueries.QueryType.FEEDS)
+
+            requestQueue.add(request)
+
+            val feeds = ArrayList<GtfsFeed>()
+            val agencies = ArrayList<GtfsAgency>()
+            var outObj = ""
+            try {
+                val resObj = future.get(120,TimeUnit.SECONDS)
+                outObj = resObj.toString(1)
+                val feedsJSON = resObj.getJSONArray("feeds")
+                for (i in 0 until feedsJSON.length()){
+                    val resTup = ResponseParsing.parseFeedJSON(feedsJSON.getJSONObject(i))
+                    feeds.add(resTup.first)
+
+                    agencies.addAll(resTup.second)
+                }
+
+
+            } catch (e: InterruptedException) {
+                e.printStackTrace()
+                res?.set(Fetcher.Result.PARSER_ERROR)
+            } catch (e: ExecutionException) {
+                e.printStackTrace()
+                res?.set(Fetcher.Result.SERVER_ERROR)
+            } catch (e: TimeoutException) {
+                res?.set(Fetcher.Result.CONNECTION_ERROR)
+                e.printStackTrace()
+            } catch (e: JSONException){
+                e.printStackTrace()
+                res?.set(Fetcher.Result.PARSER_ERROR)
+                Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+            }
+            return Pair(feeds,agencies)
+
+        }
+        fun getRoutes(context: Context, res: AtomicReference<Fetcher.Result>?):
+                ArrayList<GtfsRoute>{
+            val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+            val future = RequestFuture.newFuture<JSONObject>()
+
+            val params = JSONObject()
+            params.put("feeds","gtt")
+
+            val request = MatoVolleyJSONRequest(MatoQueries.QueryType.ROUTES, params, future, future)
+            request.tag = getVolleyReqTag(MatoQueries.QueryType.ROUTES)
+            request.retryPolicy = longRetryPolicy
+
+            requestQueue.add(request)
+
+            val routes = ArrayList<GtfsRoute>()
+            var outObj = ""
+            try {
+                val resObj = future.get(120,TimeUnit.SECONDS)
+                outObj = resObj.toString(1)
+                val routesJSON = resObj.getJSONArray("routes")
+                for (i in 0 until routesJSON.length()){
+                    val route = ResponseParsing.parseRouteJSON(routesJSON.getJSONObject(i))
+                    routes.add(route)
+                }
+
+
+            } catch (e: InterruptedException) {
+                e.printStackTrace()
+                res?.set(Fetcher.Result.PARSER_ERROR)
+            } catch (e: ExecutionException) {
+                e.printStackTrace()
+                res?.set(Fetcher.Result.SERVER_ERROR)
+            } catch (e: TimeoutException) {
+                res?.set(Fetcher.Result.CONNECTION_ERROR)
+                e.printStackTrace()
+            } catch (e: JSONException){
+                e.printStackTrace()
+                res?.set(Fetcher.Result.PARSER_ERROR)
+                Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+            }
+            return routes
+
+        }
+        fun getPatternsWithStops(context: Context, routesGTFSIds: ArrayList<String>, res: AtomicReference<Fetcher.Result>?): ArrayList<MatoPattern>{
+            val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+
+            val future = RequestFuture.newFuture<JSONObject>()
+
+            val params = JSONObject()
+            for (r in routesGTFSIds){
+                if(r.isEmpty()) routesGTFSIds.remove(r)
+            }
+            val routes = JSONArray(routesGTFSIds)
+
+            params.put("routes",routes)
+
+            val request = MatoVolleyJSONRequest(MatoQueries.QueryType.PATTERNS_FOR_ROUTES, params, future, future)
+            request.retryPolicy = longRetryPolicy
+            request.tag = getVolleyReqTag(MatoQueries.QueryType.PATTERNS_FOR_ROUTES)
+
+            requestQueue.add(request)
+
+            val patterns = ArrayList<MatoPattern>()
+            //var outObj = ""
+            try {
+                val resObj = future.get(60,TimeUnit.SECONDS)
+                //outObj = resObj.toString(1)
+                val routesJSON = resObj.getJSONArray("routes")
+                for (i in 0 until routesJSON.length()){
+                    val patternList = ResponseParsing.parseRoutePatternsStopsJSON(routesJSON.getJSONObject(i))
+                    patterns.addAll(patternList)
+                }
+
+
+            } catch (e: InterruptedException) {
+                e.printStackTrace()
+                res?.set(Fetcher.Result.PARSER_ERROR)
+            } catch (e: ExecutionException) {
+                e.printStackTrace()
+                res?.set(Fetcher.Result.SERVER_ERROR)
+            } catch (e: TimeoutException) {
+                res?.set(Fetcher.Result.CONNECTION_ERROR)
+                e.printStackTrace()
+            } catch (e: JSONException){
+                e.printStackTrace()
+                res?.set(Fetcher.Result.PARSER_ERROR)
+                //Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+            }
+            /*
+            var numRequests = 0
+            for(routeName in routesGTFSIds){
+                if (!routeName.isEmpty()) numRequests++
+            }
+            val countDownForRequests = CountDownLatch(numRequests)
+            val lockSave = ReentrantLock()
+            //val countDownFor
+            for (routeName in routesGTFSIds){
+                val pars = JSONObject()
+                pars.put("")
+
+            }
+            val goodResponseListener = Response.Listener<JSONObject> {  }
+            val errorResponseListener = Response.ErrorListener {  }
+             */
+
+            return patterns
+        }
+
+
     }
 
 }
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt b/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
--- a/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
+++ b/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
@@ -86,5 +86,81 @@
           }
         }
         """
+
+        const val ALL_FEEDS="""
+        query AllFeeds{
+            feeds{
+                feedId
+                agencies{
+                  gtfsId
+                  name
+                  url
+                  fareUrl
+                  phone
+                }
+           }
+        }
+        """
+
+        const val ROUTES_BY_FEED="""
+        query AllRoutes(${'$'}feeds: [String]){
+          routes(feeds: ${'$'}feeds) {
+            agency{
+              gtfsId
+            }
+            gtfsId
+            shortName
+            longName
+            type
+            desc
+            color
+            textColor
+          }
+        }
+        """
+
+        const val ROUTES_WITH_PATTERNS="""
+            query RoutesWithPatterns(${'$'}routes: [String]) {
+              routes(ids: ${'$'}routes) {
+                gtfsId
+                shortName
+                longName
+                type
+                
+                patterns{
+                  name
+                  code
+                  semanticHash
+                  directionId
+                  headsign
+                  stops{
+                    gtfsId
+                    lat
+                    lon
+                  }
+                  patternGeometry{
+                    length
+                    points
+                  }
+                  
+                }
+              }
+            }
+        """
+
+        fun getNameAndRequest(type: QueryType): Pair<String, String>{
+            return  when (type){
+                QueryType.FEEDS -> Pair("AllFeeds", ALL_FEEDS)
+                QueryType.ALL_STOPS -> Pair("AllStops", ALL_STOPS_BY_FEEDS)
+                QueryType.ARRIVALS -> Pair("AllStopsDirect", QUERY_ARRIVALS)
+                QueryType.ROUTES -> Pair("AllRoutes", ROUTES_BY_FEED)
+                QueryType.PATTERNS_FOR_ROUTES -> Pair("RoutesWithPatterns", ROUTES_WITH_PATTERNS)
+            }
+        }
     }
+
+    enum class QueryType {
+        ARRIVALS, ALL_STOPS, FEEDS, ROUTES, PATTERNS_FOR_ROUTES
+    }
+
 }
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt b/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.backend.mato
+
+import android.util.Log
+import com.android.volley.NetworkResponse
+import com.android.volley.Response
+import com.android.volley.VolleyError
+import com.android.volley.toolbox.HttpHeaderParser
+import org.json.JSONException
+import org.json.JSONObject
+
+class MatoVolleyJSONRequest(type: MatoQueries.QueryType,
+                            val variables: JSONObject,
+                            listener: Response.Listener<JSONObject>,
+                            errorListener: Response.ErrorListener?)
+    : MapiVolleyRequest<JSONObject>(type, listener, errorListener) {
+    protected val requestName:String
+    protected val requestQuery:String
+    init {
+        val dd = MatoQueries.getNameAndRequest(type)
+        requestName = dd.first
+        requestQuery = dd.second
+    }
+
+    override fun getBody(): ByteArray {
+
+        val data = MatoAPIFetcher.makeRequestParameters(requestName, variables, requestQuery)
+
+        return data.toString().toByteArray()
+    }
+
+    override fun parseNetworkResponse(response: NetworkResponse?): Response<JSONObject> {
+        if (response==null)
+            return Response.error(VolleyError("Null response"))
+        else if(response.statusCode != 200)
+            return Response.error(VolleyError("Response not ready, status "+response.statusCode))
+        val obj:JSONObject
+        try {
+            obj = JSONObject(String(response.data)).getJSONObject("data")
+        }catch (ex: JSONException){
+            Log.e("BusTO-VolleyJSON","Cannot parse response as JSON")
+            ex.printStackTrace()
+            return Response.error(VolleyError("Error parsing JSON"))
+        }
+
+        return Response.success(obj, HttpHeaderParser.parseCacheHeaders(response))
+    }
+
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt b/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
@@ -0,0 +1,120 @@
+/*
+	BusTO  - Backend components
+    Copyright (C) 2022 Fabio Mazza
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.backend.mato
+
+import it.reyboz.bustorino.data.gtfs.*
+import org.json.JSONObject
+
+abstract class ResponseParsing{
+
+    companion object{
+        fun parseAgencyJSON(jsonObject: JSONObject): GtfsAgency {
+            return GtfsAgency(
+                jsonObject.getString("gtfsId"),
+                jsonObject.getString("name"),
+                jsonObject.getString("url"),
+                jsonObject.getString("fareUrl"),
+                jsonObject.getString("phone"),
+                null
+            )
+        }
+
+        /**
+         * Parse a feed request json, containing the GTFS agencies it is served by
+         */
+        fun parseFeedJSON(jsonObject: JSONObject): Pair<GtfsFeed, ArrayList<GtfsAgency>> {
+
+            val agencies = ArrayList<GtfsAgency>()
+            val feed = GtfsFeed(jsonObject.getString("feedId"))
+            val oo = jsonObject.getJSONArray("agencies")
+            agencies.ensureCapacity(oo.length())
+            for (i in 0 until oo.length()){
+                val agObj = oo.getJSONObject(i)
+
+                agencies.add(
+                    GtfsAgency(
+                        agObj.getString("gtfsId"),
+                        agObj.getString("name"),
+                        agObj.getString("url"),
+                        agObj.getString("fareUrl"),
+                        agObj.getString("phone"),
+                        feed
+                    )
+                )
+            }
+            return Pair(feed, agencies)
+        }
+
+        fun parseRouteJSON(jsonObject: JSONObject): GtfsRoute {
+
+            val agencyJSON = jsonObject.getJSONObject("agency")
+            val agencyId = agencyJSON.getString("gtfsId")
+
+
+            return GtfsRoute(
+                jsonObject.getString("gtfsId"),
+                agencyId,
+                jsonObject.getString("shortName"),
+                jsonObject.getString("longName"),
+                jsonObject.getString("desc"),
+                GtfsMode.getByValue(jsonObject.getInt("type"))!!,
+                jsonObject.getString("color"),
+                jsonObject.getString("textColor")
+
+            )
+        }
+
+        /**
+         * Parse a route pattern from the JSON response of the MaTO server
+         */
+        fun parseRoutePatternsStopsJSON(jsonObject: JSONObject) : ArrayList<MatoPattern>{
+            val routeGtfsId = jsonObject.getString("gtfsId")
+
+            val patternsJSON = jsonObject.getJSONArray("patterns")
+            val patternsOut = ArrayList<MatoPattern>(patternsJSON.length())
+            var mPatternJSON: JSONObject
+            for(i in 0 until patternsJSON.length()){
+                mPatternJSON = patternsJSON.getJSONObject(i)
+
+                val stopsJSON = mPatternJSON.getJSONArray("stops")
+
+                val stopsCodes = ArrayList<String>(stopsJSON.length())
+                for(k in 0 until stopsJSON.length()){
+                    stopsCodes.add(
+                        stopsJSON.getJSONObject(k).getString("gtfsId")
+                    )
+                }
+
+                val geometry = mPatternJSON.getJSONObject("patternGeometry")
+                val numGeo = geometry.getInt("length")
+                val polyline = geometry.getString("points")
+
+                patternsOut.add(
+                    MatoPattern(
+                        mPatternJSON.getString("name"), mPatternJSON.getString("code"),
+                        mPatternJSON.getString("semanticHash"), mPatternJSON.getInt("directionId"),
+                        routeGtfsId,mPatternJSON.getString("headsign"), polyline, numGeo, stopsCodes
+                    )
+                )
+            }
+            return patternsOut
+        }
+
+
+    }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt b/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
--- a/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
+++ b/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
@@ -31,7 +31,7 @@
     listener: Response.Listener<List<Palina>>,
     errorListener: Response.ErrorListener,
 ) : MapiVolleyRequest<List<Palina>>(
-    MatoAPIFetcher.QueryType.ALL_STOPS,listener, errorListener) {
+    MatoQueries.QueryType.ALL_STOPS,listener, errorListener) {
     private val FEEDS = JSONArray()
     init {
 
@@ -73,7 +73,7 @@
         return Response.success(palinas, HttpHeaderParser.parseCacheHeaders(response))
     }
     companion object{
-        val FEEDS_STR = arrayOf("gtt")
+        //val FEEDS_STR = arrayOf("gtt")
 
     }
 }
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java
--- a/src/it/reyboz/bustorino/backend/utils.java
+++ b/src/it/reyboz/bustorino/backend/utils.java
@@ -15,11 +15,15 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import it.reyboz.bustorino.backend.mato.MatoAPIFetcher;
 
 public abstract class utils {
     private static final double EarthRadius = 6371e3;
+
+
     public static Double measuredistanceBetween(double lat1,double long1,double lat2,double long2){
         final double phi1 = Math.toRadians(lat1);
         final double phi2 = Math.toRadians(lat2);
@@ -107,21 +111,58 @@
         }
         return busStopID;
     }
+    final static Pattern ROMAN_PATTERN =  Pattern.compile(
+            "^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$");
+    private static boolean isRomanNumber(String str){
+        if(str.isEmpty()) return false;
+        final Matcher matcher = ROMAN_PATTERN.matcher(str);
+        return matcher.find();
+    }
 
     public static String toTitleCase(String givenString, boolean lowercaseRest) {
-        String[] arr = givenString.split(" ");
+        String[] arr = givenString.trim().split(" ");
         StringBuilder sb = new StringBuilder();
         //Log.d("BusTO chars", "String parsing: "+givenString+" in array: "+ Arrays.toString(arr));
-        for (int i = 0; i < arr.length; i++) {
-            if (arr[i].length() > 1) {
-                sb.append(Character.toUpperCase(arr[i].charAt(0)));
+        for (String s : arr) {
+            if (s.length() > 0) {
+                String[] allsubs = s.split("\\.");
+
+                boolean addPoint = s.contains(".");
+                /*if (s.contains(".lli")|| s.contains(".LLI")) //Fratelli
+                {
+                DOESN'T ALWAYS WORK
+                    addPoint = false;
+                    allsubs = new String[]{s};
+                }*/
+                boolean first = true;
+                for (String subs : allsubs) {
+                    if(first) first=false;
+                    else {
+                        if (addPoint) sb.append(".");
+                        sb.append(" ");
+                    }
+                    if(isRomanNumber(subs)){
+                        //add and skip the rest
+                        sb.append(subs);
+                        continue;
+                    }
+                    sb.append(Character.toUpperCase(subs.charAt(0)));
+                    if (lowercaseRest)
+                        sb.append(subs.substring(1).toLowerCase(Locale.ROOT));
+                    else
+                        sb.append(subs.substring(1));
+
+                }
+                sb.append(" ");
+                /*sb.append(Character.toUpperCase(arr[i].charAt(0)));
                 if (lowercaseRest)
                     sb.append(arr[i].substring(1).toLowerCase(Locale.ROOT));
                 else
                     sb.append(arr[i].substring(1));
                 sb.append(" ");
-            }
-            else sb.append(arr[i]);
+
+                 */
+            } else sb.append(s);
         }
         return sb.toString().trim();
     }
diff --git a/src/it/reyboz/bustorino/data/AppDataProvider.java b/src/it/reyboz/bustorino/data/AppDataProvider.java
--- a/src/it/reyboz/bustorino/data/AppDataProvider.java
+++ b/src/it/reyboz/bustorino/data/AppDataProvider.java
@@ -153,7 +153,7 @@
                         Log.d("InsBranchWithProvider","line: "+c.getString(c.getColumnIndex(LinesTable.COLUMN_NAME))+"\n"
                         +c.getString(c.getColumnIndex(LinesTable.COLUMN_DESCRIPTION)));
                     }*/
-                    lineid = c.getInt(c.getColumnIndex(NextGenDB.Contract.LinesTable._ID));
+                    lineid = c.getInt(c.getColumnIndexOrThrow(NextGenDB.Contract.LinesTable._ID));
                     c.close();
                 }
                 values.remove(NextGenDB.Contract.LinesTable.COLUMN_NAME);
@@ -191,7 +191,7 @@
     @Override
     public boolean onCreate() {
         con = getContext();
-        appDBHelper = new NextGenDB(getContext());
+        appDBHelper = NextGenDB.getInstance(getContext());
         userDBHelper = new UserDB(getContext());
         if(con!=null) {
             preferences = new DBStatusManager(con,null);
diff --git a/src/it/reyboz/bustorino/data/AppRepository.java b/src/it/reyboz/bustorino/data/AppRepository.java
deleted file mode 100644
--- a/src/it/reyboz/bustorino/data/AppRepository.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-	BusTO - Data components
-    Copyright (C) 2021 Fabio Mazza
-
-    This program is free software: you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation, either version 3 of the License, or
-    (at your option) any later version.
-
-    This program is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-package it.reyboz.bustorino.data;
-
-public class AppRepository {
-
-
-}
diff --git a/src/it/reyboz/bustorino/data/DBUpdateWorker.java b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
--- a/src/it/reyboz/bustorino/data/DBUpdateWorker.java
+++ b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
@@ -47,13 +47,13 @@
     public static final int SUCCESS_NO_ACTION_NEEDED = 9;
     public static final int SUCCESS_UPDATE_DONE = 1;
 
-    private final int notifi_ID=62341;
+    private final static int NOTIFIC_ID =32198;
 
     public static final String FORCED_UPDATE = "FORCED-UPDATE";
 
     public static final String DEBUG_TAG = "Busto-UpdateWorker";
 
-    private static final long UPDATE_MIN_DELAY= 3*7*24*3600; //3 weeks
+    private static final long UPDATE_MIN_DELAY= 9*24*3600; //9 days
 
 
     public DBUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
@@ -66,7 +66,14 @@
     public Result doWork() {
         //register Notification channel
         final Context con = getApplicationContext();
-        Notifications.createDefaultNotificationChannel(con);
+        //Notifications.createDefaultNotificationChannel(con);
+        //Use the new notification channels
+        Notifications.createNotificationChannel(con,con.getString(R.string.database_notification_channel),
+                con.getString(R.string.database_notification_channel_desc), NotificationManagerCompat.IMPORTANCE_LOW,
+                Notifications.DB_UPDATE_CHANNELS_ID
+            );
+        final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
+        final int notification_ID = 32198;
         final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
         final int current_DB_version = shPr.getInt(DatabaseUpdate.DB_VERSION_KEY,-10);
 
@@ -77,7 +84,17 @@
         final long lastDBUpdateTime = shPr.getLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, 0);
         long currentTime = System.currentTimeMillis()/1000;
 
-        final int notificationID = showNotification();
+        //showNotification(notificationManager, notification_ID);
+        final NotificationCompat.Builder builder = new NotificationCompat.Builder(con,
+                Notifications.DB_UPDATE_CHANNELS_ID)
+                .setContentTitle(con.getString(R.string.database_update_msg_notif))
+                .setProgress(0,0,true)
+                .setPriority(NotificationCompat.PRIORITY_LOW);
+        builder.setSmallIcon(R.drawable.ic_bus_orange);
+
+
+        notificationManager.notify(notification_ID,builder.build());
+
         Log.d(DEBUG_TAG, "Have previous version: "+current_DB_version +" and new version "+new_DB_version);
         Log.d(DEBUG_TAG, "Update compulsory: "+isUpdateCompulsory);
         /*
@@ -95,7 +112,7 @@
         if (!(current_DB_version < new_DB_version || currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY )
                 && !isUpdateCompulsory) {
             //don't need to update
-            cancelNotification(notificationID);
+            cancelNotification(notification_ID);
             return ListenableWorker.Result.success(new Data.Builder().
                     putInt(SUCCESS_REASON_KEY, SUCCESS_NO_ACTION_NEEDED).build());
         }
@@ -106,8 +123,7 @@
         DatabaseUpdate.setDBUpdatingFlag(con, shPr,false);
 
         if (resultUpdate != DatabaseUpdate.Result.DONE){
-            Fetcher.Result result = resultAtomicReference.get();
-
+            //Fetcher.Result result = resultAtomicReference.get();
             final Data.Builder dataBuilder = new Data.Builder();
             switch (resultUpdate){
                 case ERROR_STOPS_DOWNLOAD:
@@ -117,7 +133,7 @@
                     dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_LINES);
                     break;
             }
-            cancelNotification(notificationID);
+            cancelNotification(notification_ID);
             return ListenableWorker.Result.failure(dataBuilder.build());
         }
         Log.d(DEBUG_TAG, "Update finished successfully!");
@@ -127,7 +143,7 @@
         currentTime = System.currentTimeMillis()/1000;
         editor.putLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, currentTime);
         editor.apply();
-        cancelNotification(notificationID);
+        cancelNotification(notification_ID);
 
         return ListenableWorker.Result.success(new Data.Builder().putInt(SUCCESS_REASON_KEY, SUCCESS_UPDATE_DONE).build());
     }
@@ -144,19 +160,21 @@
                 .build();
     }
 
-
-    private int showNotification(){
-        final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), Notifications.DEFAULT_CHANNEL_ID)
+    /*
+    private int showNotification(@NonNull final NotificationManagerCompat notificManager, final int notification_ID,
+                                 final String channel_ID){
+        final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channel_ID)
                 .setContentTitle("Libre BusTO - Updating Database")
                 .setProgress(0,0,true)
                 .setPriority(NotificationCompat.PRIORITY_LOW);
         builder.setSmallIcon(R.drawable.ic_bus_orange);
-        final NotificationManagerCompat notifcManager = NotificationManagerCompat.from(getApplicationContext());
-        final  int notification_ID = 32198;
-        notifcManager.notify(notification_ID,builder.build());
+
+
+        notificManager.notify(notification_ID,builder.build());
 
         return notification_ID;
     }
+     */
 
     private void cancelNotification(int notificationID){
         final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
diff --git a/src/it/reyboz/bustorino/data/DatabaseUpdate.java b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
--- a/src/it/reyboz/bustorino/data/DatabaseUpdate.java
+++ b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
@@ -22,22 +22,31 @@
 import android.content.SharedPreferences;
 import android.database.sqlite.SQLiteDatabase;
 import android.util.Log;
-import androidx.core.content.ContextCompat;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.Observer;
 import androidx.work.*;
 import it.reyboz.bustorino.R;
 import it.reyboz.bustorino.backend.Fetcher;
 import it.reyboz.bustorino.backend.FiveTAPIFetcher;
 import it.reyboz.bustorino.backend.Palina;
 import it.reyboz.bustorino.backend.Route;
-import it.reyboz.bustorino.backend.Stop;
 import it.reyboz.bustorino.backend.mato.MatoAPIFetcher;
+import it.reyboz.bustorino.data.gtfs.GtfsAgency;
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao;
+import it.reyboz.bustorino.data.gtfs.GtfsFeed;
+import it.reyboz.bustorino.data.gtfs.GtfsRoute;
+import it.reyboz.bustorino.data.gtfs.MatoPattern;
+import it.reyboz.bustorino.data.gtfs.PatternStop;
+import kotlin.Pair;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -59,131 +68,189 @@
             DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD
         }
 
-        /**
-         * Request the server the version of the database
-         * @return the version of the DB, or an error code
-         */
-        public static int getNewVersion(){
-            AtomicReference<Fetcher.Result> gres = new AtomicReference<>();
-            String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
-            if(networkRequest == null){
-                return VERSION_UNAVAILABLE;
-            }
+    /**
+     * Request the server the version of the database
+     * @return the version of the DB, or an error code
+     */
+    public static int getNewVersion(){
+        AtomicReference<Fetcher.Result> gres = new AtomicReference<>();
+        String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
+        if(networkRequest == null){
+            return VERSION_UNAVAILABLE;
+        }
 
-            try {
-                JSONObject resp = new JSONObject(networkRequest);
-                return resp.getInt("id");
-            } catch (JSONException e) {
-                e.printStackTrace();
-                Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
-                return JSON_PARSING_ERROR;
-            }
+        try {
+            JSONObject resp = new JSONObject(networkRequest);
+            return resp.getInt("id");
+        } catch (JSONException e) {
+            e.printStackTrace();
+            Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
+            return JSON_PARSING_ERROR;
         }
-        /**
-         * Run the DB Update
-         * @param con a context
-         * @param gres a result reference
-         * @return result of the update
-         */
-        public static Result performDBUpdate(Context con, AtomicReference<Fetcher.Result> gres) {
+    }
+    private static boolean updateGTFSAgencies(Context con, AtomicReference<Fetcher.Result> res){
 
-            final FiveTAPIFetcher f = new FiveTAPIFetcher();
-            /*
-            final ArrayList<Stop> stops = f.getAllStopsFromGTT(gres);
-            //final ArrayList<ContentProviderOperation> cpOp = new ArrayList<>();
+        final GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
 
-            if (gres.get() != Fetcher.Result.OK) {
-                Log.w(DEBUG_TAG, "Something went wrong downloading");
-                return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+        final Pair<List<GtfsFeed>, ArrayList<GtfsAgency>> respair = MatoAPIFetcher.Companion.getFeedsAndAgencies(
+                con, res
+        );
 
-            }
+        dao.insertAgenciesWithFeeds(respair.getFirst(), respair.getSecond());
 
-             */
-            final NextGenDB dbHelp = new NextGenDB(con.getApplicationContext());
-            final SQLiteDatabase db = dbHelp.getWritableDatabase();
+        return true;
+    }
+    private static boolean updateGTFSRoutes(Context con, AtomicReference<Fetcher.Result> res){
 
-            final List<Palina> palinasMatoAPI = MatoAPIFetcher.Companion.getAllStopsGTT(con, gres);
-            if (gres.get() != Fetcher.Result.OK) {
-                Log.w(DEBUG_TAG, "Something went wrong downloading");
-                return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+        final GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
 
-            }
-            //TODO: Get the type of stop from the lines
-            //Empty the needed tables
-            db.beginTransaction();
-            //db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
-            //db.delete(LinesTable.TABLE_NAME,null,null);
-
-            //put new data
-            long startTime = System.currentTimeMillis();
-
-            Log.d(DEBUG_TAG, "Inserting " + palinasMatoAPI.size() + " stops");
-            for (final Palina p : palinasMatoAPI) {
-                final ContentValues cv = new ContentValues();
-
-                cv.put(NextGenDB.Contract.StopsTable.COL_ID, p.ID);
-                cv.put(NextGenDB.Contract.StopsTable.COL_NAME, p.getStopDefaultName());
-                if (p.location != null)
-                    cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, p.location);
-                cv.put(NextGenDB.Contract.StopsTable.COL_LAT, p.getLatitude());
-                cv.put(NextGenDB.Contract.StopsTable.COL_LONG, p.getLongitude());
-                if (p.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
-                cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, p.routesThatStopHereToString());
-                if (p.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, p.type.getCode());
-                if (p.gtfsID != null) cv.put(NextGenDB.Contract.StopsTable.COL_GTFS_ID, p.gtfsID);
-                //Log.d(DEBUG_TAG,cv.toString());
-                //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
-                //valuesArr[i] = cv;
-                db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
+        final List<GtfsRoute> routes= MatoAPIFetcher.Companion.getRoutes(
+                con, res
+        );
+        dao.insertRoutes(routes);
+        if(res.get()!= Fetcher.Result.OK){
+            return false;
+        }
+        final ArrayList<String> gtfsRoutesIDs = new ArrayList<>(routes.size());
+        for(GtfsRoute r: routes){
+            gtfsRoutesIDs.add(r.getGtfsId());
+        }
+        long t0 = System.currentTimeMillis();
+        final ArrayList<MatoPattern> patterns = MatoAPIFetcher.Companion.getPatternsWithStops(con,gtfsRoutesIDs,res);
+        long tend = System.currentTimeMillis() - t0;
+        Log.d(DEBUG_TAG, "Downloaded patterns in "+tend+" ms");
+        if(res.get()!=Fetcher.Result.OK){
+            Log.e(DEBUG_TAG, "Something went wrong downloading patterns");
+            return false;
+        }
 
+        final ArrayList<PatternStop> patternStops = new ArrayList<>(patterns.size());
+        for(MatoPattern p: patterns){
+            final ArrayList<String> stopsIDs = p.getStopsGtfsIDs();
+            for (int i=0; i<stopsIDs.size(); i++){
+                patternStops.add(new PatternStop(p.getCode(),stopsIDs.get(i), i));
             }
-            db.setTransactionSuccessful();
-            db.endTransaction();
-            long endTime = System.currentTimeMillis();
-            Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
+        }
+        dao.insertPatterns(patterns);
+        dao.insertPatternStops(patternStops);
 
-            final ArrayList<Route> routes = f.getAllLinesFromGTT(gres);
+        return true;
+    }
 
-            if (routes == null) {
-                Log.w(DEBUG_TAG, "Something went wrong downloading the lines");
-                dbHelp.close();
-                return DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD;
 
-            }
+    /**
+     * Run the DB Update
+     * @param con a context
+     * @param gres a result reference
+     * @return result of the update
+     */
+    public static Result performDBUpdate(Context con, AtomicReference<Fetcher.Result> gres) {
 
-            db.beginTransaction();
-            startTime = System.currentTimeMillis();
-            for (Route r : routes) {
-                final ContentValues cv = new ContentValues();
-                cv.put(NextGenDB.Contract.LinesTable.COLUMN_NAME, r.getName());
-                switch (r.type) {
-                    case BUS:
-                        cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "URBANO");
-                        break;
-                    case RAILWAY:
-                        cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "FERROVIA");
-                        break;
-                    case LONG_DISTANCE_BUS:
-                        cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "EXTRA");
-                        break;
-                }
-                cv.put(NextGenDB.Contract.LinesTable.COLUMN_DESCRIPTION, r.description);
-
-                //db.insert(LinesTable.TABLE_NAME,null,cv);
-                int rows = db.update(NextGenDB.Contract.LinesTable.TABLE_NAME, cv, NextGenDB.Contract.LinesTable.COLUMN_NAME + " = ?", new String[]{r.getName()});
-                if (rows < 1) { //we haven't changed anything
-                    db.insert(NextGenDB.Contract.LinesTable.TABLE_NAME, null, cv);
-                }
-            }
-            db.setTransactionSuccessful();
-            db.endTransaction();
-            endTime = System.currentTimeMillis();
-            Log.d(DEBUG_TAG, "Inserting lines took: " + ((double) (endTime - startTime) / 1000) + " s");
+        final FiveTAPIFetcher f = new FiveTAPIFetcher();
+
+        final NextGenDB dbHelp = NextGenDB.getInstance(con.getApplicationContext());
+        final SQLiteDatabase db = dbHelp.getWritableDatabase();
+
+        final List<Palina> palinasMatoAPI = MatoAPIFetcher.Companion.getAllStopsGTT(con, gres);
+        if (gres.get() != Fetcher.Result.OK) {
+            Log.w(DEBUG_TAG, "Something went wrong downloading");
+            return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+
+        }
+        //TODO: Get the type of stop from the lines
+        //Empty the needed tables
+        db.beginTransaction();
+        //db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
+        //db.delete(LinesTable.TABLE_NAME,null,null);
+
+        //put new data
+        long startTime = System.currentTimeMillis();
+
+        Log.d(DEBUG_TAG, "Inserting " + palinasMatoAPI.size() + " stops");
+        for (final Palina p : palinasMatoAPI) {
+            final ContentValues cv = new ContentValues();
+
+            cv.put(NextGenDB.Contract.StopsTable.COL_ID, p.ID);
+            cv.put(NextGenDB.Contract.StopsTable.COL_NAME, p.getStopDefaultName());
+            if (p.location != null)
+                cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, p.location);
+            cv.put(NextGenDB.Contract.StopsTable.COL_LAT, p.getLatitude());
+            cv.put(NextGenDB.Contract.StopsTable.COL_LONG, p.getLongitude());
+            if (p.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
+            cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, p.routesThatStopHereToString());
+            if (p.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, p.type.getCode());
+            if (p.gtfsID != null) cv.put(NextGenDB.Contract.StopsTable.COL_GTFS_ID, p.gtfsID);
+            //Log.d(DEBUG_TAG,cv.toString());
+            //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
+            //valuesArr[i] = cv;
+            db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
+
+        }
+        db.setTransactionSuccessful();
+        db.endTransaction();
+        long endTime = System.currentTimeMillis();
+        Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
+
+        // GTFS data fetching
+        AtomicReference<Fetcher.Result> gtfsRes = new AtomicReference<>(Fetcher.Result.OK);
+        updateGTFSAgencies(con, gtfsRes);
+        if (gtfsRes.get()!= Fetcher.Result.OK){
+            Log.w(DEBUG_TAG, "Could not insert the feeds and agencies stuff");
+        } else{
+            Log.d(DEBUG_TAG, "Done downloading agencies");
+        }
+        gtfsRes.set(Fetcher.Result.OK);
+        updateGTFSRoutes(con,gtfsRes);
+        if (gtfsRes.get()!= Fetcher.Result.OK){
+            Log.w(DEBUG_TAG, "Could not insert the routes into DB");
+        } else{
+            Log.d(DEBUG_TAG, "Done downloading routes from MaTO");
+        }
+        /*
+        final ArrayList<Route> routes = f.getAllLinesFromGTT(gres);
+
+        if (routes == null) {
+            Log.w(DEBUG_TAG, "Something went wrong downloading the lines");
             dbHelp.close();
+            return DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD;
 
-            return DatabaseUpdate.Result.DONE;
         }
 
+        db.beginTransaction();
+        startTime = System.currentTimeMillis();
+        for (Route r : routes) {
+            final ContentValues cv = new ContentValues();
+            cv.put(NextGenDB.Contract.LinesTable.COLUMN_NAME, r.getName());
+            switch (r.type) {
+                case BUS:
+                    cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "URBANO");
+                    break;
+                case RAILWAY:
+                    cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "FERROVIA");
+                    break;
+                case LONG_DISTANCE_BUS:
+                    cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "EXTRA");
+                    break;
+            }
+            cv.put(NextGenDB.Contract.LinesTable.COLUMN_DESCRIPTION, r.description);
+
+            //db.insert(LinesTable.TABLE_NAME,null,cv);
+            int rows = db.update(NextGenDB.Contract.LinesTable.TABLE_NAME, cv, NextGenDB.Contract.LinesTable.COLUMN_NAME + " = ?", new String[]{r.getName()});
+            if (rows < 1) { //we haven't changed anything
+                db.insert(NextGenDB.Contract.LinesTable.TABLE_NAME, null, cv);
+            }
+        }
+        db.setTransactionSuccessful();
+        db.endTransaction();
+        endTime = System.currentTimeMillis();
+        Log.d(DEBUG_TAG, "Inserting lines took: " + ((double) (endTime - startTime) / 1000) + " s");
+
+         */
+        dbHelp.close();
+
+        return DatabaseUpdate.Result.DONE;
+    }
+
     public static boolean setDBUpdatingFlag(Context con, boolean value){
         final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
         return setDBUpdatingFlag(con, shPr, value);
@@ -199,17 +266,21 @@
      * @param con the context to use
      * @param forced if you want to force the request to go now
      */
-    public static void requestDBUpdateWithWork(Context con, boolean forced){
+    public static void requestDBUpdateWithWork(Context con,boolean restart, boolean forced){
         final SharedPreferences theShPr = PreferencesHolder.getMainSharedPreferences(con);
         final WorkManager workManager = WorkManager.getInstance(con);
+        final Data reqData = new Data.Builder()
+                .putBoolean(DBUpdateWorker.FORCED_UPDATE, forced).build();
+
         PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 7, TimeUnit.DAYS)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
                 .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
                         .build())
+                .setInputData(reqData)
                 .build();
         final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10);
         final long lastDBUpdateTime = theShPr.getLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, -10);
-        if ((version >= 0 || lastDBUpdateTime >=0) && !forced)
+        if ((version >= 0 || lastDBUpdateTime >=0) && !restart)
             workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
                     ExistingPeriodicWorkPolicy.KEEP, wr);
         else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
@@ -221,4 +292,12 @@
         TODO
     }
      */
+
+    public static void watchUpdateWorkStatus(Context context, @NonNull LifecycleOwner lifecycleOwner,
+                                             @NonNull Observer<? super List<WorkInfo>> observer) {
+        WorkManager workManager = WorkManager.getInstance(context);
+        workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG).observe(
+                lifecycleOwner, observer
+        );
+    }
 }
diff --git a/src/it/reyboz/bustorino/data/GtfsRepository.kt b/src/it/reyboz/bustorino/data/GtfsRepository.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/GtfsRepository.kt
@@ -0,0 +1,36 @@
+package it.reyboz.bustorino.data
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
+
+class GtfsRepository(
+        val gtfsDao: GtfsDBDao
+) {
+
+
+    fun getLinesLiveDataForFeed(feed: String): LiveData<List<GtfsRoute>>{
+        //return withContext(Dispatchers.IO){
+            return gtfsDao.getRoutesForFeed(feed)
+        //}
+    }
+    fun getPatternsForRouteID(routeID: String): LiveData<List<MatoPattern>>{
+        return if(routeID.isNotEmpty())
+            gtfsDao.getPatternsByRouteID(routeID)
+        else
+            MutableLiveData(listOf())
+    }
+
+    /**
+     * Get the patterns with the stops lists (gtfsIDs only)
+     */
+    fun getPatternsWithStopsForRouteID(routeID: String): LiveData<List<MatoPatternWithStops>>{
+        return if(routeID.isNotEmpty())
+            gtfsDao.getPatternsWithStopsByRouteID(routeID)
+        else
+            MutableLiveData(listOf())
+    }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/NextGenDB.java b/src/it/reyboz/bustorino/data/NextGenDB.java
--- a/src/it/reyboz/bustorino/data/NextGenDB.java
+++ b/src/it/reyboz/bustorino/data/NextGenDB.java
@@ -85,15 +85,24 @@
             StopsTable.COL_LAT + " <= ? AND "+ StopsTable.COL_LONG +
             " >= ? AND "+ StopsTable.COL_LONG + " <= ?";
 
+    public static final String QUERY_FROM_GTFS_ID_IN_TO_COMPLETE= StopsTable.COL_GTFS_ID +" IN ";
+
     public static String QUERY_WHERE_ID = StopsTable.COL_ID+" = ?";
 
 
     private final Context appContext;
+    private static NextGenDB INSTANCE;
 
-    public NextGenDB(Context context) {
+    private NextGenDB(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
         appContext = context.getApplicationContext();
     }
+    public static NextGenDB getInstance(Context context) {
+        if (INSTANCE == null){
+            INSTANCE = new NextGenDB(context);
+        }
+        return INSTANCE;
+    }
 
 
     @Override
@@ -124,7 +133,7 @@
             db.execSQL(SQL_CREATE_BRANCH_TABLE);
             db.execSQL(SQL_CREATE_CONNECTIONS_TABLE);
 
-            DatabaseUpdate.requestDBUpdateWithWork(appContext, true);
+            DatabaseUpdate.requestDBUpdateWithWork(appContext, true, true);
         }
         if(oldVersion < 3 && newVersion == 3){
             Log.d("BusTO-Database", "Running upgrades for version 3");
@@ -194,6 +203,53 @@
         return stops;
     }
 
+    /**
+     * Query stops in the database having these IDs
+     * REMEMBER TO CLOSE THE DB CONNECTION AFTERWARDS
+     * @param bustoDB readable database instance
+     * @param gtfsIDs gtfs IDs to query
+     * @return list of stops
+     */
+    public static synchronized ArrayList<Stop> queryAllStopsWithGtfsIDs(SQLiteDatabase bustoDB, List<String> gtfsIDs){
+        final ArrayList<Stop> stops = new ArrayList<>();
+
+        if(bustoDB == null){
+            Log.e(DEBUG_TAG, "Asked query for IDs but database is null");
+            return stops;
+        } else if (gtfsIDs == null || gtfsIDs.isEmpty()) {
+            return stops;
+        }
+
+        final StringBuilder builder = new StringBuilder(QUERY_FROM_GTFS_ID_IN_TO_COMPLETE);
+        boolean first = true;
+        builder.append(" ( ");
+        for(int i=0; i< gtfsIDs.size(); i++){
+            if(first){
+                first = false;
+            } else{
+                builder.append(", ");
+            }
+            builder.append("?");//.append("\"").append(id).append("\"");
+        }
+        builder.append(") ");
+        final String whereClause = builder.toString();
+
+        final String[] idsQuery = gtfsIDs.toArray(new String[0]);
+
+        try {
+            final Cursor result = bustoDB.query(StopsTable.TABLE_NAME,QUERY_COLUMN_stops_all, whereClause,
+                    idsQuery,
+                    null, null, null);
+            stops.addAll(getStopsFromCursorAllFields(result));
+            result.close();
+        } catch(SQLiteException e) {
+            Log.e(DEBUG_TAG, "SQLiteException occurred");
+            e.printStackTrace();
+
+        }
+        return stops;
+    }
+
     /**
      * Get the list of stop in the query, with all the possible fields {NextGenDB.QUERY_COLUMN_stops_all}
      * @param result cursor from query
@@ -205,6 +261,7 @@
         final int colLocation = result.getColumnIndex(StopsTable.COL_LOCATION);
         final int colType = result.getColumnIndex(StopsTable.COL_TYPE);
         final int colLat = result.getColumnIndex(StopsTable.COL_LAT);
+        final int colGtfsID = result.getColumnIndex(StopsTable.COL_GTFS_ID);
         final int colLon = result.getColumnIndex(StopsTable.COL_LONG);
         final int colLines = result.getColumnIndex(StopsTable.COL_LINES_STOPPING);
 
@@ -215,9 +272,15 @@
         while(result.moveToNext()) {
 
             final String stopID = result.getString(colID).trim();
-            final Route.Type type;
-            if(result.getString(colType) == null) type = Route.Type.BUS;
-            else type = Route.getTypeFromSymbol(result.getString(colType));
+            Route.Type type;
+            //if(result.getString(colType) == null) type = Route.Type.BUS;
+            //else type = Route.getTypeFromSymbol(result.getString(colType));
+            //if(result.getInt(colType) == null) type = Route.Type.BUS;
+            try{
+                type =  Route.Type.fromCode(result.getInt(colType));
+            } catch (Exception e){
+                type = Route.Type.BUS;
+            }
             String lines = result.getString(colLines).trim();
 
             String locationSometimesEmpty = result.getString(colLocation);
@@ -227,11 +290,39 @@
 
             stops.add(new Stop(stopID, result.getString(colName), null,
                     locationSometimesEmpty, type, splitLinesString(lines),
-                    result.getDouble(colLat), result.getDouble(colLon))
+                    result.getDouble(colLat), result.getDouble(colLon),
+                    result.getString(colGtfsID))
             );
         }
         return stops;
     }
+    /*
+    static ArrayList<Stop> createStopListFromCursor(Cursor data){
+        ArrayList<Stop> stopList = new ArrayList<>();
+        final int col_id = data.getColumnIndex(StopsTable.COL_ID);
+        final int latInd = data.getColumnIndex(StopsTable.COL_LAT);
+        final int lonInd = data.getColumnIndex(StopsTable.COL_LONG);
+        final int nameindex = data.getColumnIndex(StopsTable.COL_NAME);
+        final int typeIndex = data.getColumnIndex(StopsTable.COL_TYPE);
+        final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
+
+        data.moveToFirst();
+        for(int i=0; i<data.getCount();i++){
+            String[] routes = data.getString(linesIndex).split(",");
+
+            stopList.add(new Stop(data.getString(col_id),data.getString(nameindex),null,null,
+                            Route.Type.fromCode(data.getInt(typeIndex)),
+                            Arrays.asList(routes), //the routes should be compact, not normalized yet
+                            data.getDouble(latInd),data.getDouble(lonInd)
+                    )
+            );
+            //Log.d("NearbyStopsFragment","Got stop with id "+data.getString(col_id)+
+            //" and name "+data.getString(nameindex));
+            data.moveToNext();
+        }
+        return stopList;
+    }
+     */
 
     /**
      * Insert batch content, already prepared as
diff --git a/src/it/reyboz/bustorino/data/OldDataRepository.java b/src/it/reyboz/bustorino/data/OldDataRepository.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/OldDataRepository.java
@@ -0,0 +1,58 @@
+/*
+	BusTO - Data components
+    Copyright (C) 2021 Fabio Mazza
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data;
+
+import android.database.sqlite.SQLiteDatabase;
+import it.reyboz.bustorino.backend.Result;
+import it.reyboz.bustorino.backend.Stop;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class OldDataRepository {
+
+    private final Executor executor;
+    private final NextGenDB nextGenDB;
+
+    public OldDataRepository(Executor executor, final NextGenDB nextGenDB) {
+        this.executor = executor;
+        this.nextGenDB = nextGenDB;
+    }
+
+    public void requestStopsWithGtfsIDs(final List<String> gtfsIDs,
+                                        final Callback<List<Stop>> callback){
+        executor.execute(() -> {
+
+            try {
+                //final NextGenDB dbHelper = new NextGenDB(context);
+                final SQLiteDatabase db = nextGenDB.getReadableDatabase();
+
+                final List<Stop> stops = NextGenDB.queryAllStopsWithGtfsIDs(db, gtfsIDs);
+                //Result<List<Stop>> result = Result.success;
+
+                callback.onComplete(Result.success(stops));
+            } catch (Exception e){
+                callback.onComplete(Result.failure(e));
+            }
+        });
+    }
+
+    public interface Callback<T>{
+        void onComplete(Result<T> result);
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
--- a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
@@ -19,13 +19,12 @@
 
 import android.content.Context
 import android.util.Log
-import java.util.ArrayList
 
 class CsvTableInserter(
     val tableName: String, context: Context
 ) {
     private val database: GtfsDatabase = GtfsDatabase.getGtfsDatabase(context)
-    private val dao: StaticGtfsDao = database.gtfsDao()
+    private val databaseDao: GtfsDBDao = database.gtfsDao()
 
     private val elementsList: MutableList< in GtfsTable> = mutableListOf()
 
@@ -35,12 +34,12 @@
     private var countInsert = 0
     init {
         if(tableName == "stop_times") {
-            stopsIDsPresent = dao.getAllStopsIDs().toHashSet()
-            tripsIDsPresent = dao.getAllTripsIDs().toHashSet()
+            stopsIDsPresent = databaseDao.getAllStopsIDs().toHashSet()
+            tripsIDsPresent = databaseDao.getAllTripsIDs().toHashSet()
             Log.d(DEBUG_TAG, "num stop IDs present: "+ stopsIDsPresent!!.size)
             Log.d(DEBUG_TAG, "num trips IDs present: "+ tripsIDsPresent!!.size)
         } else if(tableName == "routes"){
-            dao.deleteAllRoutes()
+            databaseDao.deleteAllRoutes()
         }
     }
 
@@ -77,7 +76,7 @@
             //have to insert
 
             if (tableName == "routes")
-                dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+                databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
             else
                 insertDataInDatabase()
 
@@ -90,21 +89,21 @@
         countInsert += elementsList.size
         when(tableName){
             "stops" -> {
-                dao.insertStops(elementsList.filterIsInstance<GtfsStop>())
+                databaseDao.insertStops(elementsList.filterIsInstance<GtfsStop>())
             }
-            "routes" -> dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
-            "calendar" -> dao.insertServices(elementsList.filterIsInstance<GtfsService>())
-            "calendar_dates" -> dao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
-            "trips" -> dao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
-            "stop_times"-> dao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
-            "shapes" -> dao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
+            "routes" -> databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+            "calendar" -> databaseDao.insertServices(elementsList.filterIsInstance<GtfsService>())
+            "calendar_dates" -> databaseDao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
+            "trips" -> databaseDao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
+            "stop_times"-> databaseDao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
+            "shapes" -> databaseDao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
 
         }
         ///if(elementsList.size < MAX_ELEMENTS)
     }
     fun finishInsert(){
         insertDataInDatabase()
-        Log.d(DEBUG_TAG, "Inserted "+countInsert+" elements from "+tableName);
+        Log.d(DEBUG_TAG, "Inserted $countInsert elements from $tableName")
     }
 
     companion object{
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
@@ -0,0 +1,55 @@
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = GtfsAgency.TABLE_NAME)
+data class GtfsAgency(
+        @PrimaryKey
+        @ColumnInfo(name = COL_GTFS_ID)
+        val gtfsId: String,
+        @ColumnInfo(name = COL_NAME)
+        val name: String,
+        @ColumnInfo(name = COL_URL)
+        val url: String,
+        @ColumnInfo(name = COL_FAREURL)
+        val fareUrl: String?,
+        @ColumnInfo(name = COL_PHONE)
+        val phone: String?,
+        @Embedded var feed: GtfsFeed?
+): GtfsTable{
+    constructor(valuesByColumn: Map<String,String>) : this(
+            valuesByColumn[COL_GTFS_ID]!!,
+            valuesByColumn[COL_NAME]!!,
+            valuesByColumn[COL_URL]!!,
+            valuesByColumn[COL_FAREURL],
+            valuesByColumn[COL_PHONE],
+        null
+    )
+
+    companion object{
+        const val TABLE_NAME="gtfs_agencies"
+
+        const val COL_GTFS_ID="gtfs_id"
+        const val COL_NAME="ag_name"
+        const val COL_URL="ag_url"
+        const val COL_FAREURL = "fare_url"
+        const val COL_PHONE = "phone"
+
+        val COLUMNS = arrayOf(
+                COL_GTFS_ID,
+                COL_NAME,
+                COL_URL,
+                COL_FAREURL,
+                COL_PHONE
+        )
+        const val CREATE_SQL =
+                "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID  )"
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
rename from src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
rename to src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
--- a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
@@ -21,8 +21,8 @@
 import androidx.room.*
 
 @Dao
-interface StaticGtfsDao {
-    @Query("SELECT * FROM "+GtfsRoute.DB_TABLE+" ORDER BY "+GtfsRoute.COL_SORT_ORDER)
+interface GtfsDBDao {
+    @Query("SELECT * FROM "+GtfsRoute.DB_TABLE)
     fun getAllRoutes() : LiveData<List<GtfsRoute>>
 
     @Query("SELECT "+GtfsTrip.COL_TRIP_ID+" FROM "+GtfsTrip.DB_TABLE)
@@ -40,14 +40,32 @@
     )
     fun getShapeByID(shapeID: String) : LiveData<List<GtfsShape>>
 
+
+    @Query("SELECT * FROM ${GtfsRoute.DB_TABLE} WHERE ${GtfsRoute.COL_AGENCY_ID} LIKE :agencyID")
+    fun getRoutesByAgency(agencyID:String) : LiveData<List<GtfsRoute>>
+
+    @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeID")
+    fun getPatternsByRouteID(routeID: String): LiveData<List<MatoPattern>>
+
+    @Query("SELECT * FROM ${PatternStop.TABLE_NAME} WHERE ${PatternStop.COL_PATTERN_ID} LIKE :patternGtfsID")
+    fun getStopsByPatternID(patternGtfsID: String): LiveData<List<PatternStop>>
+
+    @Transaction
+    @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeID")
+    fun getPatternsWithStopsByRouteID(routeID: String): LiveData<List<MatoPatternWithStops>>
+
+    fun getRoutesForFeed(feed:String): LiveData<List<GtfsRoute>>{
+        val agencyID = "${feed}:%"
+        return getRoutesByAgency(agencyID)
+    }
     @Transaction
     fun clearAndInsertRoutes(routes: List<GtfsRoute>){
         deleteAllRoutes()
         insertRoutes(routes)
     }
-
+    @Transaction
     @Insert(onConflict = OnConflictStrategy.REPLACE)
-    fun insertRoutes(users: List<GtfsRoute>)
+    fun insertRoutes(routes: List<GtfsRoute>)
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     fun insertStops(stops: List<GtfsStop>)
     @Insert(onConflict = OnConflictStrategy.REPLACE)
@@ -87,4 +105,24 @@
     @Query("DELETE FROM "+GtfsService.DB_TABLE)
     fun deleteAllServices()
 
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertFeeds(feeds: List<GtfsFeed>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertAgencies(agencies: List<GtfsAgency>)
+
+    @Transaction
+    fun insertAgenciesWithFeeds(feeds: List<GtfsFeed>, agencies: List<GtfsAgency>){
+        insertFeeds(feeds)
+        insertAgencies(agencies)
+    }
+    //patterns
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertPatterns(patterns: List<MatoPattern>)
+
+    @Query("SELECT * FROM "+MatoPattern.TABLE_NAME+
+            " WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeGtfsId")
+    fun getPatternsForRouteID(routeGtfsId: String) : List<MatoPattern>
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertPatternStops(patternStops: List<PatternStop>)
 }
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
--- a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
@@ -18,24 +18,30 @@
 package it.reyboz.bustorino.data.gtfs
 
 import android.content.Context
+import android.util.Log
 import androidx.room.*
+import androidx.room.migration.Migration
 
 @Database(
     entities = [
+        GtfsFeed::class,
+        GtfsAgency::class,
         GtfsServiceDate::class,
         GtfsStop::class,
         GtfsService::class,
         GtfsRoute::class,
         GtfsStopTime::class,
         GtfsTrip::class,
-        GtfsShape::class],
+        GtfsShape::class,
+        MatoPattern::class,
+        PatternStop::class
+               ],
     version = GtfsDatabase.VERSION,
-    exportSchema = false,
 )
 @TypeConverters(Converters::class)
-public abstract class GtfsDatabase : RoomDatabase() {
+abstract class GtfsDatabase : RoomDatabase() {
 
-    abstract fun gtfsDao() : StaticGtfsDao
+    abstract fun gtfsDao() : GtfsDBDao
 
     companion object{
         @Volatile
@@ -44,14 +50,36 @@
         fun getGtfsDatabase(context: Context): GtfsDatabase{
             return INSTANCE ?: synchronized(this){
                 val instance = Room.databaseBuilder(context.applicationContext,
-                GtfsDatabase::class.java,
-                "gtfs_database").build()
+                    GtfsDatabase::class.java,
+                    "gtfs_database")
+                        .addMigrations(MIGRATION_1_2)
+                        .build()
                 INSTANCE = instance
                 instance
             }
         }
 
-        const val VERSION = 1
+        const val VERSION = 2
         const val FOREIGNKEY_ONDELETE = ForeignKey.CASCADE
+
+        val MIGRATION_1_2 = Migration(1,2) {
+            Log.d("BusTO-Database", "Upgrading from version 1 to version 2 the Room Database")
+            //create table for feeds
+            it.execSQL("CREATE TABLE IF NOT EXISTS `gtfs_feeds` (`feed_id` TEXT NOT NULL, PRIMARY KEY(`feed_id`))")
+            //create table gtfs_agencies
+            it.execSQL("CREATE TABLE IF NOT EXISTS `gtfs_agencies` (`gtfs_id` TEXT NOT NULL, `ag_name` TEXT NOT NULL, `ag_url` TEXT NOT NULL, `fare_url` TEXT, `phone` TEXT, `feed_id` TEXT, PRIMARY KEY(`gtfs_id`))")
+
+            //recreate routes
+            it.execSQL("DROP TABLE IF EXISTS `routes_table`")
+            it.execSQL("CREATE TABLE IF NOT EXISTS `routes_table` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_mode` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, PRIMARY KEY(`route_id`))")
+
+            //create patterns and stops
+            it.execSQL("CREATE TABLE IF NOT EXISTS `mato_patterns` (`pattern_name` TEXT NOT NULL, `pattern_code` TEXT NOT NULL, `pattern_hash` TEXT NOT NULL, `pattern_direction_id` INTEGER NOT NULL, `pattern_route_id` TEXT NOT NULL, `pattern_headsign` TEXT, `pattern_polyline` TEXT NOT NULL, `pattern_polylength` INTEGER NOT NULL, PRIMARY KEY(`pattern_code`), FOREIGN KEY(`pattern_route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
+            it.execSQL("CREATE TABLE IF NOT EXISTS `patterns_stops` (`pattern_gtfs_id` TEXT NOT NULL, `stop_gtfs_id` TEXT NOT NULL, `stop_order` INTEGER NOT NULL, PRIMARY KEY(`pattern_gtfs_id`, `stop_gtfs_id`, `stop_order`), FOREIGN KEY(`pattern_gtfs_id`) REFERENCES `mato_patterns`(`pattern_code`) ON UPDATE NO ACTION ON DELETE CASCADE )")
+
+
+        }
+
+
     }
 }
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
@@ -0,0 +1,50 @@
+/*
+	BusTO - Data components
+    Copyright (C) 2022 Fabio Mazza
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = GtfsFeed.TABLE_NAME)
+data class GtfsFeed(
+        @PrimaryKey
+        @ColumnInfo(name = COL_GTFS_ID)
+        val gtfsId: String,
+): GtfsTable{
+    constructor(valuesByColumn: Map<String,String>) : this(
+            valuesByColumn[COL_GTFS_ID]!!,
+    )
+
+    companion object{
+        const val TABLE_NAME="gtfs_feeds"
+
+        const val COL_GTFS_ID="feed_id"
+
+
+        val COLUMNS = arrayOf(
+                COL_GTFS_ID,
+        )
+        const val CREATE_SQL =
+                "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID  )"
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
@@ -0,0 +1,19 @@
+package it.reyboz.bustorino.data.gtfs
+
+enum class GtfsMode(val intType: Int) {
+    TRAM(0),
+    SUBWAY(1),
+    RAIL(2),
+    BUS(3),
+    FERRY(4),
+    CABLE_TRAM(5),
+    GONDOLA(6),
+    FUNICULAR(7),
+    TROLLEYBUS(11),
+    MONORAIL(12);
+
+    companion object {
+        private val VALUES = values()
+        fun getByValue(value: Int) = VALUES.firstOrNull { it.intType == value }
+    }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
--- a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
@@ -23,26 +23,25 @@
 
 @Entity(tableName=GtfsRoute.DB_TABLE)
 data class GtfsRoute(
-    @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
-    val ID: String,
-    @ColumnInfo(name = "agency_id")
+        @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
+    val gtfsId: String,
+        @ColumnInfo(name = COL_AGENCY_ID)
     val agencyID: String,
-    @ColumnInfo(name = "route_short_name")
+        @ColumnInfo(name = "route_short_name")
     val shortName: String,
-    @ColumnInfo(name = "route_long_name")
+        @ColumnInfo(name = "route_long_name")
     val longName: String,
-    @ColumnInfo(name = "route_desc")
+        @ColumnInfo(name = "route_desc")
     val description: String,
-    @ColumnInfo(name ="route_type")
-    val type: String,
+        @ColumnInfo(name = COL_MODE)
+    val mode: GtfsMode,
     //@ColumnInfo(name ="route_url")
     //val url: String,
-    @ColumnInfo(name ="route_color")
+        @ColumnInfo(name = COL_COLOR)
     val color: String,
-    @ColumnInfo(name ="route_text_color")
+        @ColumnInfo(name = COL_TEXT_COLOR)
     val textColor: String,
-    @ColumnInfo(name = COL_SORT_ORDER)
-    val sortOrder: Int
+
 ): GtfsTable {
 
     constructor(valuesByColumn: Map<String,String>) : this(
@@ -51,18 +50,21 @@
         valuesByColumn["route_short_name"]!!,
         valuesByColumn["route_long_name"]!!,
         valuesByColumn["route_desc"]!!,
-        valuesByColumn["route_type"]!!,
-        valuesByColumn["route_color"]!!,
-        valuesByColumn["route_text_color"]!!,
-        valuesByColumn[COL_SORT_ORDER]?.toInt()!!
+        valuesByColumn["route_type"]?.toInt()?.let { GtfsMode.getByValue(it) }!!,
+        valuesByColumn[COL_COLOR]!!,
+        valuesByColumn[COL_TEXT_COLOR]!!,
     )
     companion object {
         const val DB_TABLE: String="routes_table"
         const val COL_SORT_ORDER: String="route_sort_order"
+        const val COL_AGENCY_ID = "agency_id"
         const val COL_ROUTE_ID = "route_id"
+        const val COL_MODE ="route_mode"
+        const val COL_COLOR="route_color"
+        const val COL_TEXT_COLOR="route_text_color"
 
         val COLUMNS = arrayOf(COL_ROUTE_ID,
-            "agency_id",
+            COL_AGENCY_ID,
             "route_short_name",
             "route_long_name",
             "route_desc",
@@ -71,6 +73,8 @@
             "route_text_color",
             COL_SORT_ORDER
         )
+
+        //const val CREATE_SQL = ""
     }
 
     override fun getColumns(): Array<String> {
diff --git a/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
@@ -0,0 +1,140 @@
+/*
+	BusTO - Data components
+    Copyright (C) 2022 Fabio Mazza
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.*
+import it.reyboz.bustorino.backend.Stop
+
+@Entity(tableName = MatoPattern.TABLE_NAME,
+        foreignKeys = [
+            ForeignKey(entity = GtfsRoute::class,
+                    parentColumns = [GtfsRoute.COL_ROUTE_ID],
+                    childColumns = [MatoPattern.COL_ROUTE_ID],
+                    onDelete = ForeignKey.CASCADE,
+            )
+        ]
+)
+data class MatoPattern(
+    @ColumnInfo(name= COL_NAME)
+    val name: String,
+    @ColumnInfo(name= COL_CODE)
+    @PrimaryKey
+    val code: String,
+    @ColumnInfo(name= COL_SEMANTIC_HASH)
+    val semanticHash: String,
+    @ColumnInfo(name= COL_DIRECTION_ID)
+    val directionId: Int,
+    @ColumnInfo(name= COL_ROUTE_ID)
+    val routeGtfsId: String,
+    @ColumnInfo(name= COL_HEADSIGN)
+    var headsign: String?,
+    @ColumnInfo(name= COL_GEOMETRY_POLY)
+    val patternGeometryPoly: String,
+    @ColumnInfo(name= COL_GEOMETRY_LENGTH)
+    val patternGeometryLength: Int,
+    @Ignore
+    val stopsGtfsIDs: ArrayList<String>
+
+):GtfsTable{
+
+    @Ignore
+    val servingStops= ArrayList<Stop>(4)
+    constructor(
+        name: String, code:String,
+        semanticHash: String, directionId: Int,
+        routeGtfsId: String, headsign: String?,
+        patternGeometryPoly: String, patternGeometryLength: Int
+    ): this(name, code, semanticHash, directionId, routeGtfsId, headsign, patternGeometryPoly, patternGeometryLength, ArrayList<String>(4))
+
+    companion object{
+        const val TABLE_NAME="mato_patterns"
+
+        const val COL_NAME="pattern_name"
+        const val COL_CODE="pattern_code"
+        const val COL_ROUTE_ID="pattern_route_id"
+        const val COL_SEMANTIC_HASH="pattern_hash"
+        const val COL_DIRECTION_ID="pattern_direction_id"
+        const val COL_HEADSIGN="pattern_headsign"
+        const val COL_GEOMETRY_POLY="pattern_polyline"
+        const val COL_GEOMETRY_LENGTH="pattern_polylength"
+
+        val COLUMNS = arrayOf(
+            COL_NAME,
+            COL_CODE,
+            COL_ROUTE_ID,
+            COL_SEMANTIC_HASH,
+            COL_DIRECTION_ID,
+            COL_HEADSIGN,
+            COL_GEOMETRY_POLY,
+            COL_GEOMETRY_LENGTH
+        )
+    }
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
+
+//DO NOT USE EMBEDDED!!! -> copies all data
+
+@Entity(tableName=PatternStop.TABLE_NAME,
+    primaryKeys = [
+        PatternStop.COL_PATTERN_ID,
+        PatternStop.COL_STOP_GTFS,
+        PatternStop.COL_ORDER
+    ],
+    foreignKeys = [
+        ForeignKey(entity = MatoPattern::class,
+                parentColumns = [MatoPattern.COL_CODE],
+                childColumns = [PatternStop.COL_PATTERN_ID],
+                onDelete = ForeignKey.CASCADE
+        )
+    ]
+)
+data class PatternStop(
+    @ColumnInfo(name= COL_PATTERN_ID)
+    val patternId: String,
+    @ColumnInfo(name=COL_STOP_GTFS)
+    val stopGtfsId: String,
+    @ColumnInfo(name=COL_ORDER)
+    val order: Int,
+){
+    companion object{
+        const val TABLE_NAME="patterns_stops"
+
+        const val COL_PATTERN_ID="pattern_gtfs_id"
+        const val COL_STOP_GTFS="stop_gtfs_id"
+        const val COL_ORDER="stop_order"
+    }
+}
+
+data class MatoPatternWithStops(
+    @Embedded val pattern: MatoPattern,
+    @Relation(
+        parentColumn = MatoPattern.COL_CODE,
+        entityColumn = PatternStop.COL_PATTERN_ID,
+
+        )
+    var stopsIndices: List<PatternStop>)
+    {
+
+
+    init {
+        stopsIndices = stopsIndices.sortedBy { p-> p.order }
+
+    }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
--- a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
+++ b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
@@ -19,7 +19,6 @@
 
 import android.app.AlertDialog;
 import android.content.Context;
-import android.content.DialogInterface;
 import android.os.Bundle;
 import android.util.Log;
 import android.view.ContextMenu;
@@ -44,9 +43,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
 import it.reyboz.bustorino.*;
-import it.reyboz.bustorino.adapters.AdapterListener;
+import it.reyboz.bustorino.adapters.StopAdapterListener;
 import it.reyboz.bustorino.adapters.StopRecyclerAdapter;
 import it.reyboz.bustorino.backend.Stop;
 import it.reyboz.bustorino.data.FavoritesViewModel;
@@ -64,11 +62,17 @@
 
     public static final String FRAGMENT_TAG = "BusTOFavFragment";
 
-    private final AdapterListener adapterListener = new AdapterListener() {
+    private final StopAdapterListener adapterListener = new StopAdapterListener() {
         @Override
         public void onTappedStop(Stop stop) {
             mListener.requestArrivalsForStopID(stop.ID);
         }
+
+        @Override
+        public boolean onLongPressOnStop(Stop stop) {
+            Log.d("BusTO-FavoritesFrag", "LongPressOnStop");
+            return true;
+        }
     };
 
 
@@ -123,6 +127,7 @@
 
         angeryBusImageView = root.findViewById(R.id.angeryBusImageView);
         favoriteTipTextView = root.findViewById(R.id.favoriteTipTextView);
+        //register for the context menu
         registerForContextMenu(favoriteRecyclerView);
 
         FavoritesViewModel model = new ViewModelProvider(this).get(FavoritesViewModel.class);
@@ -150,12 +155,13 @@
     }
     /*
     This method is apparently NOT CALLED ANYMORE
+    Called on Android 6
      */
     @Override
     public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v,
                                     ContextMenu.ContextMenuInfo menuInfo) {
         super.onCreateContextMenu(menu, v, menuInfo);
-        Log.d("Favorites Fragment", "Creating context menu on "+v);
+        Log.d("Favorites Fragment", "Creating context menu ");
         if (v.getId() == R.id.favoritesRecyclerView) {
             // if we aren't attached to activity, return null
             if (getActivity()==null) return;
@@ -244,7 +250,7 @@
          * redrwaing everything.
          */
         // Show results
-        favoriteRecyclerView.setAdapter(new StopRecyclerAdapter(busStops,adapterListener));
+        favoriteRecyclerView.setAdapter(new StopRecyclerAdapter(busStops,adapterListener, StopRecyclerAdapter.Use.FAVORITES));
     }
 
     public void showBusStopUsernameInputDialog(final Stop busStop) {
diff --git a/src/it/reyboz/bustorino/fragments/FragmentKind.java b/src/it/reyboz/bustorino/fragments/FragmentKind.java
--- a/src/it/reyboz/bustorino/fragments/FragmentKind.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentKind.java
@@ -18,5 +18,6 @@
 package it.reyboz.bustorino.fragments;
 
 public enum FragmentKind {
-    STOPS,ARRIVALS,FAVORITES,NEARBY_STOPS,NEARBY_ARRIVALS, MAP, MAIN_SCREEN_FRAGMENT
+    STOPS,ARRIVALS,FAVORITES,NEARBY_STOPS,NEARBY_ARRIVALS, MAP, MAIN_SCREEN_FRAGMENT,
+    LINES
 }
diff --git a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
--- a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
@@ -17,6 +17,9 @@
  */
 package it.reyboz.bustorino.fragments;
 
+/**
+ * This interface is for the subfragments
+ */
 public interface FragmentListenerMain extends CommonFragmentListener {
 
     void toggleSpinner(boolean state);
diff --git a/src/it/reyboz/bustorino/fragments/LinesFragment.kt b/src/it/reyboz/bustorino/fragments/LinesFragment.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/LinesFragment.kt
@@ -0,0 +1,289 @@
+/*
+	BusTO  - Fragments components
+    Copyright (C) 2022 Fabio Mazza
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.util.Log
+import android.view.*
+import android.widget.*
+import android.widget.AdapterView.INVALID_POSITION
+import android.widget.AdapterView.OnItemSelectedListener
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.adapters.NameCapitalize
+import it.reyboz.bustorino.adapters.StopAdapterListener
+import it.reyboz.bustorino.adapters.StopRecyclerAdapter
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
+import it.reyboz.bustorino.data.gtfs.PatternStop
+import it.reyboz.bustorino.util.LinesNameSorter
+
+class LinesFragment : ScreenBaseFragment() {
+
+    companion object {
+        fun newInstance(){
+            val fragment = LinesFragment()
+        }
+        const val DEBUG_TAG="BusTO-LinesFragment"
+        const val FRAGMENT_TAG="LinesFragment"
+    }
+
+
+    private lateinit var viewModel: LinesViewModel
+
+    private lateinit var linesSpinner: Spinner
+    private lateinit var patternsSpinner: Spinner
+
+    private lateinit var currentRoutes: List<GtfsRoute>
+    private lateinit var currentPatterns: List<MatoPatternWithStops>
+    private lateinit var currentPatternStops: List<PatternStop>
+
+    private lateinit var routeDescriptionTextView: TextView
+    private lateinit var stopsRecyclerView: RecyclerView
+
+    private var linesAdapter: ArrayAdapter<String>? = null
+    private var patternsAdapter: ArrayAdapter<String>? = null
+    private var mListener: CommonFragmentListener? = null
+
+    private val linesNameSorter = LinesNameSorter()
+    private val linesComparator = Comparator<GtfsRoute> { a,b ->
+        return@Comparator linesNameSorter.compare(a.shortName, b.shortName)
+    }
+
+    private val adapterListener = object : StopAdapterListener {
+        override fun onTappedStop(stop: Stop?) {
+            //var r = ""
+            //stop?.let { r= it.stopDisplayName.toString() }
+            Toast.makeText(context,R.string.long_press_for_options,Toast.LENGTH_SHORT).show()
+        }
+
+        override fun onLongPressOnStop(stop: Stop?): Boolean {
+            Log.d("BusTO-LinesFrag", "LongPressOnStop")
+            return true
+        }
+    }
+
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+                              savedInstanceState: Bundle?): View? {
+        val rootView =  inflater.inflate(R.layout.fragment_lines, container, false)
+
+        linesSpinner = rootView.findViewById(R.id.linesSpinner)
+        patternsSpinner = rootView.findViewById(R.id.patternsSpinner)
+
+        routeDescriptionTextView = rootView.findViewById(R.id.routeDescriptionTextView)
+        stopsRecyclerView = rootView.findViewById(R.id.patternStopsRecyclerView)
+
+        /*
+            Stop busStop = (Stop) parent.getItemAtPosition(position);
+
+            if(mListener!=null){
+                mListener.requestArrivalsForStopID(busStop.ID);
+            }
+
+        });
+
+         */
+        val llManager = LinearLayoutManager(context)
+        llManager.orientation = LinearLayoutManager.VERTICAL
+
+        stopsRecyclerView.layoutManager = llManager
+        //allow the context menu to be opened
+        registerForContextMenu(stopsRecyclerView)
+
+        if(context!=null) {
+            patternsAdapter = ArrayAdapter(context!!, android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
+            patternsSpinner.adapter = patternsAdapter
+            linesAdapter = ArrayAdapter(context!!, android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
+            linesSpinner.adapter = linesAdapter
+
+
+            linesSpinner.onItemSelectedListener = object: OnItemSelectedListener{
+                override fun onItemSelected(p0: AdapterView<*>?, p1: View?, pos: Int, p3: Long) {
+                    val selRoute = currentRoutes.get(pos)
+
+                    routeDescriptionTextView.text = selRoute.longName
+
+                    viewModel.setRouteIDQuery(selRoute.gtfsId)
+
+                }
+
+                override fun onNothingSelected(p0: AdapterView<*>?) {
+
+                }
+            }
+
+            patternsSpinner.onItemSelectedListener = object : OnItemSelectedListener{
+                override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
+                    val patternWithStops = currentPatterns.get(position)
+
+                    setPatternAndReqStops(patternWithStops)
+
+                }
+
+                override fun onNothingSelected(p0: AdapterView<*>?) {
+                }
+            }
+        }
+
+        return rootView
+    }
+
+    override fun onAttach(context: Context) {
+        super.onAttach(context)
+        if(context is CommonFragmentListener)
+            mListener = context
+        else throw RuntimeException(context.toString()
+                + " must implement CommonFragmentListener")
+    }
+
+    override fun onResume() {
+        super.onResume()
+        mListener?.readyGUIfor(FragmentKind.LINES)
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        viewModel = ViewModelProvider(this).get(LinesViewModel::class.java)
+
+        //val lines = viewModel.();
+        viewModel.routesGTTLiveData.observe(this) {
+            setRoutes(it)
+        }
+
+        viewModel.patternsWithStopsByRouteLiveData.observe(this){
+            patterns ->
+            run {
+                currentPatterns = patterns.sortedBy { p->"${p.pattern.directionId} - ${p.pattern.headsign}" }
+                patternsAdapter?.let {
+                    it.clear()
+                    it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
+                    it.notifyDataSetChanged()
+                }
+
+                val  pos = patternsSpinner.selectedItemPosition
+                if(pos!= INVALID_POSITION){
+                    setPatternAndReqStops(currentPatterns[pos])
+                }
+
+            }
+        }
+
+        viewModel.stopsForPatternLiveData.observe(this){stops->
+            Log.d("BusTO-LinesFragment", "Setting stops from DB")
+            setCurrentStops(stops)
+        }
+
+    }
+
+    override fun getBaseViewForSnackBar(): View? {
+        return null
+    }
+
+    private fun setRoutes(routes: List<GtfsRoute>){
+        currentRoutes = routes.sortedWith<GtfsRoute>(linesComparator)
+
+        linesAdapter?.clear()
+        linesAdapter?.addAll(currentRoutes.map { r -> r.shortName })
+        linesAdapter?.notifyDataSetChanged()
+    }
+
+    private fun setCurrentStops(stops: List<Stop>){
+
+        val orderBy = currentPatternStops.withIndex().associate{it.value.stopGtfsId to it.index}
+        val stopsSorted = stops.sortedBy { s -> orderBy[s.gtfsID] }
+        val numStops = stopsSorted.size
+        Log.d(DEBUG_TAG, "RecyclerView adapter is: ${stopsRecyclerView.adapter}")
+
+        var setNewAdapter = true;
+        if(stopsRecyclerView.adapter is StopRecyclerAdapter){
+            val adapter = stopsRecyclerView.adapter as StopRecyclerAdapter
+            if(adapter.stops.size == stopsSorted.size && (adapter.stops.get(0).gtfsID == stopsSorted.get(0).gtfsID)
+                && (adapter.stops.get(numStops-1).gtfsID == stopsSorted.get(numStops-1).gtfsID)
+            ){
+                Log.d(DEBUG_TAG, "Found same stops on recyclerview")
+                setNewAdapter = false
+            }
+            /*else {
+                Log.d(DEBUG_TAG, "Found adapter on recyclerview, but not the same stops")
+                adapter.stops = stopsSorted
+                adapter.notifyDataSetChanged()
+            }*/
+
+        }
+        if(setNewAdapter){
+            stopsRecyclerView.adapter = StopRecyclerAdapter(
+                stopsSorted, adapterListener, StopRecyclerAdapter.Use.LINES,
+                NameCapitalize.FIRST
+            )
+        }
+
+
+    }
+
+    private fun setPatternAndReqStops(patternWithStops: MatoPatternWithStops){
+        Log.d(DEBUG_TAG, "Requesting stops for pattern ${patternWithStops.pattern.code}")
+        currentPatternStops = patternWithStops.stopsIndices.sortedBy { i-> i.order }
+
+        viewModel.requestStopsForPatternWithStops(patternWithStops)
+    }
+
+    override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
+        super.onCreateContextMenu(menu, v, menuInfo)
+        Log.d("BusTO-LinesFragment", "Creating context menu ")
+
+
+        if (v.id == R.id.patternStopsRecyclerView) {
+            // if we aren't attached to activity, return null
+            if (activity == null) return
+            val inflater = activity!!.menuInflater
+            inflater.inflate(R.menu.menu_line_item, menu)
+
+
+        }
+    }
+
+
+    override fun onContextItemSelected(item: MenuItem): Boolean {
+        val info = item.getMenuInfo();
+
+        if (stopsRecyclerView.getAdapter() !is StopRecyclerAdapter) return false
+        val adapter =stopsRecyclerView.adapter as StopRecyclerAdapter
+        val stop = adapter.stops.get(adapter.getPosition())
+
+        val acId = item.itemId
+        if(acId == R.id.action_view_on_map){
+            // view on the map
+            if ((stop.latitude == null) or (stop.longitude == null) or (mListener == null) ) {
+                Toast.makeText(context, R.string.cannot_show_on_map_no_position, Toast.LENGTH_SHORT).show()
+                return true
+            }
+            mListener!!.showMapCenteredOnStop(stop)
+            return true
+        } else if (acId == R.id.action_show_arrivals){
+            mListener?.requestArrivalsForStopID(stop.ID);
+            return true
+        }
+        return false
+    }
+
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/LinesViewModel.kt b/src/it/reyboz/bustorino/fragments/LinesViewModel.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/LinesViewModel.kt
@@ -0,0 +1,77 @@
+package it.reyboz.bustorino.fragments
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.*
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.data.GtfsRepository
+import it.reyboz.bustorino.data.NextGenDB
+import it.reyboz.bustorino.data.OldDataRepository
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
+import java.util.concurrent.Executors
+
+class LinesViewModel(application: Application) : AndroidViewModel(application) {
+
+    private val gtfsRepo: GtfsRepository
+    private val oldRepo: OldDataRepository
+    //val patternsByRouteLiveData: LiveData<List<MatoPattern>>
+
+    private val routeIDToSearch = MutableLiveData<String>()
+
+    val stopsForPatternLiveData = MutableLiveData<List<Stop>>()
+    val executor = Executors.newFixedThreadPool(2)
+
+    init {
+        val gtfsDao = GtfsDatabase.getGtfsDatabase(application).gtfsDao()
+        gtfsRepo = GtfsRepository(gtfsDao)
+
+        oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
+
+    }
+
+    val routesGTTLiveData: LiveData<List<GtfsRoute>> by lazy{
+        gtfsRepo.getLinesLiveDataForFeed("gtt")
+    }
+    val patternsWithStopsByRouteLiveData = routeIDToSearch.switchMap {
+        gtfsRepo.getPatternsWithStopsForRouteID(it)
+
+    }
+    val routesName: LiveData<List<String>> = Transformations.map(routesGTTLiveData) {
+        it.map { route -> route.longName }
+    }
+
+    fun setRouteIDQuery(routeID: String){
+        routeIDToSearch.value = routeID
+    }
+
+    fun requestStopsForGTFSIDs(gtfsIDs: List<String>){
+        oldRepo.requestStopsWithGtfsIDs(gtfsIDs) {
+            if (it.isSuccess) {
+                stopsForPatternLiveData.postValue(it.result)
+            } else {
+                Log.e("BusTO-LinesVM", "Got error on callback with stops for gtfsID")
+                it.exception?.printStackTrace()
+            }
+        }
+    }
+
+    fun requestStopsForPatternWithStops(patternStops: MatoPatternWithStops){
+        val gtfsIDs = ArrayList<String>()
+        for(pat in patternStops.stopsIndices){
+            gtfsIDs.add(pat.stopGtfsId)
+        }
+        requestStopsForGTFSIDs(gtfsIDs)
+    }
+
+
+    /*fun getLinesGTT(): MutableLiveData<List<GtfsRoute>> {
+        val routesData = MutableLiveData<List<GtfsRoute>>()
+            viewModelScope.launch {
+                 val routes=gtfsRepo.getLinesForFeed("gtt")
+                routesData.postValue(routes)
+            }
+        return routesData
+    }*/
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/MapFragment.java b/src/it/reyboz/bustorino/fragments/MapFragment.java
--- a/src/it/reyboz/bustorino/fragments/MapFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MapFragment.java
@@ -610,7 +610,7 @@
             final BoundingBoxLimit limit = limits[0];
             //Log.d(DEBUG_TAG, "Async Stop Fetcher started working");
 
-            NextGenDB dbHelper = new NextGenDB(fragmentWeakReference.get().getContext());
+            NextGenDB dbHelper = NextGenDB.getInstance(fragmentWeakReference.get().getContext());
             ArrayList<Stop> stops = dbHelper.queryAllInsideMapView(limit.latitFrom, limit.latitTo,
                     limit.longFrom, limit.latitTo);
             dbHelper.close();
diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
--- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -28,6 +28,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
 import androidx.loader.app.LoaderManager;
 import androidx.loader.content.CursorLoader;
 import androidx.loader.content.Loader;
@@ -36,6 +37,8 @@
 import androidx.appcompat.widget.AppCompatButton;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.work.WorkInfo;
+
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -49,6 +52,9 @@
 import it.reyboz.bustorino.adapters.ArrivalsStopAdapter;
 import it.reyboz.bustorino.backend.*;
 import it.reyboz.bustorino.backend.FiveTAPIFetcher.QueryType;
+import it.reyboz.bustorino.backend.mato.MapiArrivalRequest;
+import it.reyboz.bustorino.data.DatabaseUpdate;
+import it.reyboz.bustorino.data.NextGenDB;
 import it.reyboz.bustorino.middleware.AppLocationManager;
 import it.reyboz.bustorino.data.AppDataProvider;
 import it.reyboz.bustorino.data.NextGenDB.Contract.*;
@@ -62,8 +68,7 @@
 
     private FragmentListenerMain mListener;
     private FragmentLocationListener fragmentLocationListener;
-    private final String[] PROJECTION = {StopsTable.COL_ID,StopsTable.COL_LAT,StopsTable.COL_LONG,
-            StopsTable.COL_NAME,StopsTable.COL_TYPE,StopsTable.COL_LINES_STOPPING};
+
     private final static String DEBUG_TAG = "NearbyStopsFragment";
     private final static String FRAGMENT_TYPE_KEY = "FragmentType";
     public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20;
@@ -78,7 +83,6 @@
 
     private SquareStopAdapter dataAdapter;
     private AutoFitGridLayoutManager gridLayoutManager;
-    boolean canStartDBQuery = true;
     private Location lastReceivedLocation = null;
     private ProgressBar circlingProgressBar,flatProgressBar;
     private int distance;
@@ -100,6 +104,10 @@
     private ArrivalsManager arrivalsManager = null;
     private ArrivalsStopAdapter arrivalsStopAdapter = null;
 
+    private boolean dbUpdateRunning = false;
+
+    private ArrayList<Stop> currentNearbyStops = new ArrayList<>();
+
     public NearbyStopsFragment() {
         // Required empty public constructor
     }
@@ -129,10 +137,10 @@
         }
         locManager = AppLocationManager.getInstance(getContext());
         fragmentLocationListener = new FragmentLocationListener(this);
-        globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences),Context.MODE_PRIVATE);
-
-
-        globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
+        if (getContext()!=null) {
+            globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE);
+            globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
+        }
 
 
     }
@@ -153,51 +161,28 @@
         titleTextView = root.findViewById(R.id.titleTextView);
         switchButton = root.findViewById(R.id.switchButton);
 
-        preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
+        scrollListener = new CommonScrollListener(mListener,false);
+        switchButton.setOnClickListener(v -> switchFragmentType());
+        Log.d(DEBUG_TAG, "onCreateView");
+
+        DatabaseUpdate.watchUpdateWorkStatus(getContext(), this, new Observer<List<WorkInfo>>() {
             @Override
-            public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-                Log.d(DEBUG_TAG,"Key "+key+" was changed");
-                if(key.equals(getString(R.string.databaseUpdatingPref))){
-                    if(!sharedPreferences.getBoolean(getString(R.string.databaseUpdatingPref),true)){
-                        canStartDBQuery = true;
-                        Log.d(DEBUG_TAG,"The database has finished updating, can start update now");
-                    }
+            public void onChanged(List<WorkInfo> workInfos) {
+                if(workInfos.isEmpty()) return;
+
+                WorkInfo wi = workInfos.get(0);
+                if (wi.getState() == WorkInfo.State.RUNNING && locManager.isRequesterRegistered(fragmentLocationListener)) {
+                    locManager.removeLocationRequestFor(fragmentLocationListener);
+                    dbUpdateRunning = true;
+                } else if(!locManager.isRequesterRegistered(fragmentLocationListener)){
+                    locManager.addLocationRequestFor(fragmentLocationListener);
+                    dbUpdateRunning = false;
                 }
             }
-        };
-        scrollListener = new CommonScrollListener(mListener,false);
-        switchButton.setOnClickListener(v -> {
-           switchFragmentType();
         });
-        Log.d(DEBUG_TAG, "onCreateView");
         return root;
     }
 
-    protected ArrayList<Stop> createStopListFromCursor(Cursor data){
-        ArrayList<Stop> stopList = new ArrayList<>();
-        final int col_id = data.getColumnIndex(StopsTable.COL_ID);
-        final int latInd = data.getColumnIndex(StopsTable.COL_LAT);
-        final int lonInd = data.getColumnIndex(StopsTable.COL_LONG);
-        final int nameindex = data.getColumnIndex(StopsTable.COL_NAME);
-        final int typeIndex = data.getColumnIndex(StopsTable.COL_TYPE);
-        final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
-
-        data.moveToFirst();
-        for(int i=0; i<data.getCount();i++){
-            String[] routes = data.getString(linesIndex).split(",");
-
-            stopList.add(new Stop(data.getString(col_id),data.getString(nameindex),null,null,
-                            Route.Type.fromCode(data.getInt(typeIndex)),
-                            Arrays.asList(routes), //the routes should be compact, not normalized yet
-                            data.getDouble(latInd),data.getDouble(lonInd)
-                    )
-            );
-            //Log.d("NearbyStopsFragment","Got stop with id "+data.getString(col_id)+
-            //" and name "+data.getString(nameindex));
-            data.moveToNext();
-        }
-        return stopList;
-    }
 
     /**
      * Use this method to set the fragment type
@@ -220,7 +205,7 @@
 
 
     @Override
-    public void onAttach(Context context) {
+    public void onAttach(@NonNull Context context) {
         super.onAttach(context);
         /// TODO: RISOLVERE PROBLEMA: il context qui e' l'Activity non il Fragment
         if (context instanceof FragmentListenerMain) {
@@ -235,7 +220,6 @@
     @Override
     public void onPause() {
         super.onPause();
-        canStartDBQuery = false;
 
         gridRecyclerView.setAdapter(null);
         locManager.removeLocationRequestFor(fragmentLocationListener);
@@ -245,9 +229,9 @@
     @Override
     public void onResume() {
         super.onResume();
-        canStartDBQuery = !globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
         try{
-            if(canStartDBQuery) locManager.addLocationRequestFor(fragmentLocationListener);
+            if(!dbUpdateRunning && !locManager.isRequesterRegistered(fragmentLocationListener))
+                    locManager.addLocationRequestFor(fragmentLocationListener);
         } catch (SecurityException ex){
             //ignored
             //try another location provider
@@ -314,14 +298,15 @@
     @Override
     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
         //BUILD URI
-        lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
+        if (args!=null)
+            lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
         Uri.Builder builder =  new Uri.Builder();
         builder.scheme("content").authority(AppDataProvider.AUTHORITY)
                 .appendPath("stops").appendPath("location")
                 .appendPath(String.valueOf(lastReceivedLocation.getLatitude()))
                 .appendPath(String.valueOf(lastReceivedLocation.getLongitude()))
                 .appendPath(String.valueOf(distance)); //distance
-        CursorLoader cl = new CursorLoader(getContext(),builder.build(),PROJECTION,null,null,null);
+        CursorLoader cl = new CursorLoader(getContext(),builder.build(),NextGenDB.QUERY_COLUMN_stops_all,null,null,null);
         cl.setUpdateThrottle(2000);
         return cl;
     }
@@ -331,50 +316,58 @@
     public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
         if (0 > MAX_DISTANCE) throw new AssertionError();
         //Cursor might be null
-        if(cursor==null){
-            Log.e(DEBUG_TAG,"Null cursor, something really wrong happened");
+        if (cursor == null) {
+            Log.e(DEBUG_TAG, "Null cursor, something really wrong happened");
             return;
         }
-        Log.d(DEBUG_TAG, "Num stops found: "+cursor.getCount()+", Current distance: "+distance);
+        Log.d(DEBUG_TAG, "Num stops found: " + cursor.getCount() + ", Current distance: " + distance);
 
-        if(!isDBUpdating() && (cursor.getCount()<MIN_NUM_STOPS && distance<=MAX_DISTANCE)){
-            distance = distance*2;
+        if (!dbUpdateRunning && (cursor.getCount() < MIN_NUM_STOPS && distance <= MAX_DISTANCE)) {
+            distance = distance * 2;
             Bundle d = new Bundle();
-            d.putParcelable(BUNDLE_LOCATION,lastReceivedLocation);
-            getLoaderManager().restartLoader(LOADER_ID,d,this);
+            d.putParcelable(BUNDLE_LOCATION, lastReceivedLocation);
+            getLoaderManager().restartLoader(LOADER_ID, d, this);
             //Log.d(DEBUG_TAG, "Doubling distance now!");
             return;
         }
-        Log.d("LoadFromCursor","Number of nearby stops: "+cursor.getCount());
+        Log.d("LoadFromCursor", "Number of nearby stops: " + cursor.getCount());
         ////////
+        if(cursor.getCount()>0)
+            currentNearbyStops = NextGenDB.getStopsFromCursorAllFields(cursor);
 
-        if(cursor.getCount()>0) {
-            ArrayList<Stop> stopList = createStopListFromCursor(cursor);
-            double minDistance = Double.POSITIVE_INFINITY;
-            for(Stop s: stopList){
-                minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation));
-            }
+        showCurrentStops();
+    }
 
+    /**
+     * Display the stops, or run new set of requests for arrivals
+     */
+    private void showCurrentStops(){
+        if (currentNearbyStops.isEmpty()) {
+            setNoStopsLayout();
+            return;
+        }
 
-            //quick trial to hopefully always get the stops in the correct order
-            Collections.sort(stopList,new StopSorterByDistance(lastReceivedLocation));
-            switch (fragment_type){
-                case TYPE_STOPS:
-                    showStopsInRecycler(stopList);
-                    break;
-                case TYPE_ARRIVALS:
-                    arrivalsManager = new ArrivalsManager(stopList);
-                    flatProgressBar.setVisibility(View.VISIBLE);
-                    flatProgressBar.setProgress(0);
-                    flatProgressBar.setIndeterminate(false);
-                    //for the moment, be satisfied with only one location
-                    //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
-                    break;
-                default:
-            }
+        double minDistance = Double.POSITIVE_INFINITY;
+        for(Stop s: currentNearbyStops){
+            minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation));
+        }
 
-        } else {
-            setNoStopsLayout();
+
+        //quick trial to hopefully always get the stops in the correct order
+        Collections.sort(currentNearbyStops,new StopSorterByDistance(lastReceivedLocation));
+        switch (fragment_type){
+            case TYPE_STOPS:
+                showStopsInRecycler(currentNearbyStops);
+                break;
+            case TYPE_ARRIVALS:
+                arrivalsManager = new ArrivalsManager(currentNearbyStops);
+                flatProgressBar.setVisibility(View.VISIBLE);
+                flatProgressBar.setProgress(0);
+                flatProgressBar.setIndeterminate(false);
+                //for the moment, be satisfied with only one location
+                //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
+                break;
+            default:
         }
 
     }
@@ -411,15 +404,12 @@
                 gridRecyclerView.setAdapter(arrivalsStopAdapter);
         }
         fragmentLocationListener.lastUpdateTime = -1;
-        locManager.removeLocationRequestFor(fragmentLocationListener);
-        locManager.addLocationRequestFor(fragmentLocationListener);
+        //locManager.removeLocationRequestFor(fragmentLocationListener);
+        //locManager.addLocationRequestFor(fragmentLocationListener);
+        showCurrentStops();
     }
 
     //useful methods
-    protected boolean isDBUpdating(){
-        return globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
-    }
-
 
     /////// GUI METHODS ////////
     private void showStopsInRecycler(List<Stop> stops){
@@ -454,7 +444,8 @@
             if(p.queryAllRoutes().size() == 0) continue;
             for(Route r: p.queryAllRoutes()){
                 //if there are no routes, should not do anything
-                routesPairList.add(new Pair<>(p,r));
+                if (r.passaggi != null && !r.passaggi.isEmpty())
+                    routesPairList.add(new Pair<>(p,r));
             }
         }
         if (getContext()==null){
@@ -493,31 +484,30 @@
         messageTextView.setVisibility(View.GONE);
     }
 
-    class ArrivalsManager implements FiveTAPIVolleyRequest.ResponseListener, Response.ErrorListener{
-        final HashMap<String,Palina> mStops;
-        final Map<String,List<Route>> routesToAdd = new HashMap<>();
+    class ArrivalsManager implements Response.Listener<Palina>, Response.ErrorListener{
+        final HashMap<String,Palina> palinasDone = new HashMap<>();
+        //final Map<String,List<Route>> routesToAdd = new HashMap<>();
         final static String REQUEST_TAG = "NearbyArrivals";
-        private final QueryType[] types = {QueryType.ARRIVALS,QueryType.DETAILS};
         final NetworkVolleyManager volleyManager;
         int activeRequestCount = 0,reqErrorCount = 0, reqSuccessCount=0;
 
         ArrivalsManager(List<Stop> stops){
-            mStops = new HashMap<>();
             volleyManager = NetworkVolleyManager.getInstance(getContext());
 
             int MAX_ARRIVAL_STOPS = 35;
+            Date currentDate = new Date();
+            int timeRange = 3600;
+            int departures = 10;
+            int numreq = 0;
             for(Stop s: stops.subList(0,Math.min(stops.size(), MAX_ARRIVAL_STOPS))){
-                mStops.put(s.ID,new Palina(s));
-                for(QueryType t: types) {
-                    final FiveTAPIVolleyRequest req = FiveTAPIVolleyRequest.getNewRequest(t, s.ID, this, this);
-                    if (req != null) {
-                        req.setTag(REQUEST_TAG);
-                        volleyManager.addToRequestQueue(req);
-                        activeRequestCount++;
-                    }
-                }
+
+                final MapiArrivalRequest req = new MapiArrivalRequest(s.ID, currentDate, timeRange, departures, this, this);
+                req.setTag(REQUEST_TAG);
+                volleyManager.addToRequestQueue(req);
+                activeRequestCount++;
+                numreq++;
             }
-            flatProgressBar.setMax(activeRequestCount);
+            flatProgressBar.setMax(numreq);
         }
 
 
@@ -546,40 +536,19 @@
         }
 
         @Override
-        public void onResponse(Palina result, QueryType type) {
+        public void onResponse(Palina result) {
             //counter for requests
             activeRequestCount--;
             reqSuccessCount++;
-
-
-            final Palina palinaInMap = mStops.get(result.ID);
+            //final Palina palinaInMap = palinasDone.get(result.ID);
             //palina cannot be null here
             //sorry for the brutal crash when it happens
-            if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
-            //necessary to split the Arrivals and Details cases
-            switch (type){
-                case ARRIVALS:
-                    palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
-                    final List<Route> possibleRoutes = routesToAdd.get(result.ID);
-                    if(possibleRoutes!=null) {
-                        palinaInMap.addInfoFromRoutes(possibleRoutes);
-                        routesToAdd.remove(result.ID);
-                    }
-                break;
-                case DETAILS:
-                    if(palinaInMap.queryAllRoutes().size()>0){
-                        //merge the branches
-                        palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
-                    } else {
-                        routesToAdd.put(result.ID,result.queryAllRoutes());
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException("Wrong QueryType in onResponse");
-            }
-
+            //if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
+            //add the palina to the successful one
+            //TODO: Avoid redoing everything every time a new Result arrives
+            palinasDone.put(result.ID, result);
             final ArrayList<Palina> outList = new ArrayList<>();
-            for(Palina p: mStops.values()){
+            for(Palina p: palinasDone.values()){
                 final List<Route> routes = p.queryAllRoutes();
                 if(routes!=null && routes.size()>0) outList.add(p);
             }
@@ -613,14 +582,14 @@
         public void onLocationChanged(Location location) {
             //set adapter
             float accuracy = location.getAccuracy();
-            if(accuracy<60 && canStartDBQuery) {
+            if(accuracy<60 && !dbUpdateRunning) {
                 distance = 20;
                 final Bundle msgBundle = new Bundle();
                 msgBundle.putParcelable(BUNDLE_LOCATION,location);
                 getLoaderManager().restartLoader(LOADER_ID,msgBundle,callbacks);
             }
             lastUpdateTime = System.currentTimeMillis();
-            Log.d("BusTO:NearPositListen","can start loader "+ canStartDBQuery);
+            Log.d("BusTO:NearPositListen","can start loader "+ !dbUpdateRunning);
         }
 
         @Override
diff --git a/src/it/reyboz/bustorino/fragments/SettingsFragment.java b/src/it/reyboz/bustorino/fragments/SettingsFragment.java
--- a/src/it/reyboz/bustorino/fragments/SettingsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/SettingsFragment.java
@@ -25,10 +25,13 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Toast;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.preference.*;
+import androidx.room.Database;
 import it.reyboz.bustorino.R;
+import it.reyboz.bustorino.data.DatabaseUpdate;
 
 import java.lang.ref.WeakReference;
 
@@ -63,6 +66,25 @@
 
         //ListPreference preference = findPreference(R.string.arrival_times)
 
+        Preference dbUpdateNow = findPreference("pref_db_update_now");
+        if (dbUpdateNow!=null)
+        dbUpdateNow.setOnPreferenceClickListener(
+                new Preference.OnPreferenceClickListener() {
+                    @Override
+                    public boolean onPreferenceClick(@NonNull Preference preference) {
+                        //trigger update
+                        if(getContext()!=null) {
+                            DatabaseUpdate.requestDBUpdateWithWork(getContext().getApplicationContext(), true, true);
+                            Toast.makeText(getContext(),R.string.requesting_db_update,Toast.LENGTH_SHORT).show();
+                            return true;
+                        }
+                        return false;
+                    }
+                }
+        );
+        else {
+            Log.e("BusTO-Preferences", "Cannot find db update preference");
+        }
 
     }
 
diff --git a/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java b/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
--- a/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
+++ b/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
@@ -250,7 +250,7 @@
 
         @Override
         public void run() {
-            final NextGenDB nextGenDB = new NextGenDB(context);
+            final NextGenDB nextGenDB = NextGenDB.getInstance(context);
             //ContentValues[] values = new ContentValues[routesToInsert.size()];
             ArrayList<ContentValues> branchesValues = new ArrayList<>(routesToInsert.size()*4);
             ArrayList<ContentValues> connectionsVals = new ArrayList<>(routesToInsert.size()*4);
diff --git a/src/it/reyboz/bustorino/util/LinesNameSorter.java b/src/it/reyboz/bustorino/util/LinesNameSorter.java
--- a/src/it/reyboz/bustorino/util/LinesNameSorter.java
+++ b/src/it/reyboz/bustorino/util/LinesNameSorter.java
@@ -23,19 +23,93 @@
 public class LinesNameSorter implements Comparator<String> {
     @Override
     public int compare(String name1, String name2) {
+        name1 = name1.trim();
+        name2 = name2.trim();
 
+        /*
         if(name1.length()>name2.length()) return 1;
         if(name1.length()==name2.length()) {
+
+
             try{
                 int num1 = Integer.parseInt(name1.trim());
                 int num2 = Integer.parseInt(name2.trim());
                 return num1-num2;
             } catch (NumberFormatException  ex){
                 //Log.d("BUSTO Compare lines","Cannot compare lines "+name1+" and "+name2);
+                //return name1.compareTo(name2);
+                //One of them is not a line
+                String trim1 = name1.substring(0, name1.length() - 1).trim();
+                String trim2 = name2.substring(0, name2.length()-1).trim();
+                if(isInteger(trim1)){ //cut away the last part
+                    //this means it's a line
+                    return compare(trim1, name2);
+                } else if(isInteger(trim2)){
+                    return compare(name1,trim2);
+                }
+                return name1.compareTo(name2);
+            }
+        }**/
+        //One of them is not
+        int num1 = -1;
+        if(isInteger(name1)) num1 = Integer.parseInt(name1);
+        int num2 = -1;
+        if (isInteger(name2)) num2 = Integer.parseInt(name2);
+
+        if(num1 >= 0 && num2 >=0){
+            //we're very happy
+            return (num1-num2)*10;
+        } else if (num1>=0) {
+            //name2 is not fully integer
+            final String name2sub = name2.substring(0, name2.length()-1).trim();
+            char lastchar = name2.charAt(name2.length()-1);
+            if(isInteger(name2sub)){
+                num2 = Integer.parseInt(name2sub);
+                int diff = (num1-num2)*10;
+                return diff - incrementFromLastChar(lastchar);
+            } else{
+                //failed
+                return name1.compareTo(name2);
+            }
+        } else if (num2>=0) {
+            //name1 is not fully integer
+            final String name1sub = name1.substring(0, name1.length()-1).trim();
+            char lastchar = name1.charAt(name1.length()-1);
+            if (isInteger(name1sub)){
+                num1 = Integer.parseInt(name1sub);
+                int diff = (num1-num2)*10;
+                return diff + incrementFromLastChar(lastchar);
+            } else {
                 return name1.compareTo(name2);
             }
         }
-        return -1;
+        //last case
+        return name1.compareTo(name2);
 
     }
+
+    public static boolean isInteger(String strNum) {
+        if (strNum == null) {
+            return false;
+        }
+        try {
+            int d = Integer.parseInt(strNum);
+        } catch (NumberFormatException nfe) {
+            return false;
+        }
+        return true;
+    }
+    private static int incrementFromLastChar(char lastchar){
+        switch (lastchar){
+            case 'B':
+            case 'b':
+            case '/':
+                return 1;
+            case 'n':
+            case 'N':
+                return 3;
+            default:
+                return 6;
+        }
+    }
 }