diff --git a/AndroidManifest.xml b/AndroidManifest.xml
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -2,8 +2,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="it.reyboz.bustorino">
 
-
-
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@@ -14,13 +12,19 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
     <application
-        android:name=".BustoApp"
-        android:networkSecurityConfig="@xml/networks_security_config"
-        android:allowBackup="true"
-        android:icon="@mipmap/ic_launcher"
-        android:roundIcon="@mipmap/ic_launcher_round"
-        android:label="@string/app_name"
-        android:theme="@style/AppTheme.NoActionBar">
+            android:name=".BustoApp"
+            android:allowBackup="true"
+            android:icon="@mipmap/ic_launcher"
+            android:label="@string/app_name"
+            android:networkSecurityConfig="@xml/networks_security_config"
+            android:roundIcon="@mipmap/ic_launcher_round"
+            android:theme="@style/AppTheme.NoActionBar">
+
+        <activity
+                android:name=".ActivityExperiments"
+                android:label="@string/experiments"
+                android:theme="@style/MapTheme">
+        </activity>
         <activity
                 android:name=".ActivityPrincipal"
                 android:label="@string/app_name"
@@ -88,21 +92,20 @@
                     android:value=".ActivityMain"/>
         </activity>
         <activity
-            android:name=".ActivityMap"
-            android:label="@string/title_activity_map"
-            android:parentActivityName=".ActivityMain"
-            android:theme="@style/MapTheme">
+                android:name=".ActivityMap"
+                android:label="@string/title_activity_map"
+                android:parentActivityName=".ActivityMain"
+                android:theme="@style/MapTheme">
 
             <!-- API < 16: -->
             <meta-data
-                android:name="android.support.PARENT_ACTIVITY"
-                android:value=".ActivityMain"/>
+                    android:name="android.support.PARENT_ACTIVITY"
+                    android:value=".ActivityMain"/>
         </activity>
-
         <activity
                 android:name=".ActivityMain"
-                android:screenOrientation="portrait"
-                android:label="@string/app_name" >
+                android:label="@string/app_name"
+                android:screenOrientation="portrait">
         </activity>
 
         <provider
@@ -115,8 +118,7 @@
         <service
                 android:name=".data.DatabaseUpdateService"
                 android:exported="false">
-        </service>
-        <!-- Don't show the additional frame on samsung phones -->
+        </service> <!-- Don't show the additional frame on samsung phones -->
         <meta-data
                 android:name="com.samsung.android.icon_container.has_icon_container"
                 android:value="true"/>
@@ -132,4 +134,4 @@
         </activity>
     </application>
 
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -6,11 +6,9 @@
 
     }
 
-    dependencies {
-
-        classpath 'com.android.tools.build:gradle:4.1.3'
-    }
     ext {
+        //multidex
+        multidex_version = "2.0.1"
         //libraries versions
         fragment_version = "1.3.6"
         activity_version = "1.2.4"
@@ -21,8 +19,17 @@
         acra_version = "5.7.0"
         lifecycle_version = "2.3.1"
         arch_version = "2.1.0"
+        room_version = "2.3.0"
+        //kotlin
+        kotlin_version = '1.5.0'
+        coroutines_version = "1.5.0"
 
     }
+    dependencies {
+
+        classpath 'com.android.tools.build:gradle:4.1.3'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
 }
 allprojects {
     repositories {
@@ -34,6 +41,8 @@
 }
 
 apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
 
 android {
     compileSdkVersion 29
@@ -41,11 +50,12 @@
 
     defaultConfig {
         applicationId "it.reyboz.bustorino"
-        minSdkVersion 14
+        minSdkVersion 15
         targetSdkVersion 29
         versionCode 35
         versionName "1.15.4"
         vectorDrawables.useSupportLibrary = true
+        multiDexEnabled true
     }
 
     compileOptions {
@@ -91,8 +101,8 @@
         implementation "androidx.preference:preference:$preference_version"
         implementation "androidx.work:work-runtime:$work_version"
 
-
-        implementation "com.google.android.material:material:1.3.0"
+        implementation "com.google.android.material:material:1.4.0"
+        implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 
 
         implementation 'org.jsoup:jsoup:1.13.1'
@@ -113,5 +123,18 @@
         // Lifecycles only (without ViewModel or LiveData)
         implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
 
+        // 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"
     }
 }
+
+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"
+}
\ No newline at end of file
diff --git a/res/layout/activity_experiments.xml b/res/layout/activity_experiments.xml
new file mode 100644
--- /dev/null
+++ b/res/layout/activity_experiments.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:tools="http://schemas.android.com/tools"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        tools:context=".ActivityExperiments">
+
+    <Button
+            android:text="Download GTFS data"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:id="@+id/button"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            android:onClick="runExp"
+    />
+
+    <Button
+            android:text="Delete temporary GTFS file"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:id="@+id/deleteButton"
+            app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/button"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/menu/extra_menu_items.xml b/res/menu/principal_menu.xml
rename from res/menu/extra_menu_items.xml
rename to res/menu/principal_menu.xml
--- a/res/menu/extra_menu_items.xml
+++ b/res/menu/principal_menu.xml
@@ -21,4 +21,9 @@
             android:orderInCategory="8"
             android:title="@string/action_licence"
             app:showAsAction="never" />
+    <item
+            android:id="@+id/action_experiments"
+            android:title="@string/experiments"
+            android:orderInCategory="10"
+    />
 </menu>
\ 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
@@ -157,7 +157,8 @@
     <string name="storage_permission">storage</string>
     <string name="message_crash">The application has crashed because you encountered a bug.
         \nIf you want, you can help the developers by sending the crash report via email.
-        \nNote that no sensitive data is contained in the report, just small bits of info on your phone and app configuration/state.
+        \nNote that no sensitive data is contained in the report, just small bits of info on your phone and app
+        configuration/state.
     </string>
     <string name="acra_email_message">The application crashed and the crash report is in the attachments. Please
         describe what you were doing before the crash: \n
@@ -171,4 +172,6 @@
     <string name="donate_now">Buy us a coffee</string>
     <string name="map">Map</string>
     <string name="stop_search_view_title">Search by stop</string>
+
+
 </resources>
diff --git a/res/values/theme.xml b/res/values/theme.xml
--- a/res/values/theme.xml
+++ b/res/values/theme.xml
@@ -21,4 +21,6 @@
 
     <style name="preferenceTheme" parent="PreferenceThemeOverlay"></style>
 
+
+
 </resources>
\ No newline at end of file
diff --git a/res/xml/networks_security_config.xml b/res/xml/networks_security_config.xml
--- a/res/xml/networks_security_config.xml
+++ b/res/xml/networks_security_config.xml
@@ -3,6 +3,6 @@
     <domain-config cleartextTrafficPermitted="true">
         <domain includeSubdomains="true">5t.torino.it</domain>
         <domain includeSubdomains="true">gtt.to.it</domain>
-
+        <domain includeSubdomains="true">comune.torino.it</domain>
     </domain-config>
 </network-security-config>
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityExperiments.java b/src/it/reyboz/bustorino/ActivityExperiments.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/ActivityExperiments.java
@@ -0,0 +1,141 @@
+/*
+	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;
+
+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.middleware.GeneralActivity;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+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 {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_experiments);
+        Button deleteButton = findViewById(R.id.deleteButton);
+        if(deleteButton!=null)
+            deleteButton.setOnClickListener(view -> {
+                File saveFile = new File(getFilesDir(), "gtfs_data.zip");
+                if(!saveFile.isDirectory() && saveFile.exists()){
+                    //delete the file
+                    if(saveFile.delete())
+                        Toast.makeText(this, "Gtfs zip deleted", Toast.LENGTH_SHORT).show();
+                    else
+                        Toast.makeText(this, "Cannot delete gtfs zip", Toast.LENGTH_SHORT).show();
+                } else
+                    Toast.makeText(this, "Gtfs data zip not present", Toast.LENGTH_SHORT).show();
+            });
+    }
+
+    public void runExp(View v){
+
+        final Context appContext = v.getContext().getApplicationContext();
+
+        Runnable run = new Runnable() {
+            @Override
+            public void run() {
+            final String DEBUG_TAG = "ExperimentsGTFS";
+            AtomicReference<Fetcher.Result> res = new AtomicReference<>();
+            //List<String> files = GtfsDataParser.readFilesList(res);
+            Date updateDate = GtfsDataParser.getLastGTFSUpdateDate(res);
+            Log.w(
+                    "ExperimentGTFS", "Last update date is " + updateDate//utils.joinList(files, "\n")
+            );
+            //Toast.makeText(v.getContext(), "Gtfs data already downloaded", Toast.LENGTH_SHORT).show();
+
+            File saveFile = new File(getFilesDir(), "gtfs_data.zip");
+            if (!saveFile.isDirectory() && saveFile.exists()) {
+                Log.w(DEBUG_TAG, "Zip exists: " + saveFile);
+                try (FileInputStream fileStream = new FileInputStream(saveFile)) {
+                    ZipInputStream stream = new ZipInputStream(fileStream);
+                    // now iterate through each item in the stream. The get next
+                    // entry call will return a ZipEntry for each file in the
+                    // stream
+                    ZipEntry entry;
+                    String line;
+                    final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+                    while ((entry = stream.getNextEntry()) != null) {
+                        String s = String.format(Locale.ENGLISH, "Entry: %s len %d added",
+                                entry.getName(),
+                                entry.getSize()
+                        );
+                        //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();
+                        GtfsDataParser.readCSVWithColumns(reader, tableName, v.getContext().getApplicationContext());
+
+
+                        // 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();
+                }
+                //saveFile.delete();
+            } else
+                try {
+                    //Toast.makeText(v.getContext(), "Downloading gtfs data", Toast.LENGTH_SHORT).show();
+
+                    networkTools.saveFileInCache(saveFile, new URL(GtfsDataParser.GTFS_ADDRESS));
+                    Log.w(DEBUG_TAG, "File saved");
+                } catch (MalformedURLException e) {
+                    e.printStackTrace();
+                }
+
+            }
+        };
+        ExecutorService executorService = Executors.newFixedThreadPool(2);
+        //Looper looper = new Looper(true);
+        //Handler handler = new Handler();
+        //handler.post(run);
+        executorService.execute(run);
+
+
+    }
+
+
+}
\ 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
@@ -1,3 +1,20 @@
+/*
+	BusTO - Arrival times for Turin public transport.
+    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;
 
 import android.Manifest;
@@ -23,6 +40,7 @@
 import androidx.fragment.app.Fragment;
 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;
@@ -165,22 +183,11 @@
             requestArrivalsForStopID(busStopID);
         }
         //Try (hopefully) database update
-        PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS)
-                .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
-                .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
-                        .build())
-                .build();
-        final WorkManager workManager = WorkManager.getInstance(this);
-
-        final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10);
-        if (version >= 0)
-            workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
-                    ExistingPeriodicWorkPolicy.KEEP, wr);
-        else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
-                ExistingPeriodicWorkPolicy.REPLACE, wr);
+        DatabaseUpdate.requestDBUpdateWithWork(this, false);
         /*
-        Set database update
+        Watch for database update
          */
+        final WorkManager workManager = WorkManager.getInstance(this);
         workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG)
                 .observe(this, workInfoList -> {
                     // If there are no matching work info, do nothing
@@ -300,7 +307,11 @@
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
-        getMenuInflater().inflate(R.menu.extra_menu_items, menu);
+        getMenuInflater().inflate(R.menu.principal_menu, menu);
+        MenuItem experimentsMenuItem = menu.findItem(R.id.action_experiments);
+        SharedPreferences shPr = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+        boolean exper_On = shPr.getBoolean(getString(R.string.pref_key_experimental), false);
+        experimentsMenuItem.setVisible(exper_On);
         return super.onCreateOptionsMenu(menu);
     }
 
@@ -569,6 +580,8 @@
                 case R.id.action_licence:
                     openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", activityContext);
                     return true;
+                case R.id.action_experiments:
+                    startActivity(new Intent(ActivityPrincipal.this, ActivityExperiments.class));
                 default:
             }
             return false;
diff --git a/src/it/reyboz/bustorino/BustoApp.java b/src/it/reyboz/bustorino/BustoApp.java
--- a/src/it/reyboz/bustorino/BustoApp.java
+++ b/src/it/reyboz/bustorino/BustoApp.java
@@ -1,8 +1,25 @@
+/*
+	BusTO - Arrival times for Turin public transport.
+    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;
 
-import android.app.Application;
 import android.content.Context;
 
+import androidx.multidex.MultiDexApplication;
 import org.acra.ACRA;
 import org.acra.BuildConfig;
 import org.acra.ReportField;
@@ -14,7 +31,7 @@
 import static org.acra.ReportField.*;
 
 
-public class BustoApp extends Application {
+public class BustoApp extends MultiDexApplication {
     private static final ReportField[] REPORT_FIELDS = {REPORT_ID, APP_VERSION_CODE, APP_VERSION_NAME,
             PACKAGE_NAME, PHONE_MODEL, BRAND, PRODUCT, ANDROID_VERSION, BUILD_CONFIG, CUSTOM_DATA,
             IS_SILENT, STACK_TRACE, INITIAL_CONFIGURATION, CRASH_CONFIGURATION, DISPLAY, USER_COMMENT,
diff --git a/src/it/reyboz/bustorino/backend/Fetcher.java b/src/it/reyboz/bustorino/backend/Fetcher.java
--- a/src/it/reyboz/bustorino/backend/Fetcher.java
+++ b/src/it/reyboz/bustorino/backend/Fetcher.java
@@ -31,6 +31,7 @@
      * QUERY_TOO_SHORT: input more characters and retry.
      */
     enum Result {
-        OK, CLIENT_OFFLINE, SERVER_ERROR, SETUP_ERROR,PARSER_ERROR, EMPTY_RESULT_SET, QUERY_TOO_SHORT,SERVER_ERROR_404
+        OK, CLIENT_OFFLINE, SERVER_ERROR, SETUP_ERROR,PARSER_ERROR, EMPTY_RESULT_SET, QUERY_TOO_SHORT, SERVER_ERROR_404,
+        CONNECTION_ERROR
     }
 }
diff --git a/src/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java b/src/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/gtfs/GtfsDataParser.java
@@ -0,0 +1,256 @@
+package it.reyboz.bustorino.backend.gtfs;
+
+import android.content.Context;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import it.reyboz.bustorino.backend.Fetcher;
+import it.reyboz.bustorino.backend.networkTools;
+import it.reyboz.bustorino.data.gtfs.CsvTableInserter;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+abstract public class GtfsDataParser {
+    public static final String GTFS_ADDRESS="https://www.gtt.to.it/open_data/gtt_gtfs.zip";
+    public static final String GTFS_PAGE_ADDRESS="http://aperto.comune.torino.it/dataset/feed-gtfs-trasporti-gtt";
+
+    private static final String DEBUG_TAG = "BusTO-GTFSDataParser";
+
+    private static final Pattern quotePattern = Pattern.compile("^\\s*\"((?:[^\"]|(?:\"\"))*?)\"\\s*,");
+
+    /**
+     * First trial for a function to download the zip
+     * @param res Fetcher.result
+     * @return the list of files inside the ziè
+     */
+    public static ArrayList<String> readFilesList(AtomicReference<Fetcher.Result> res){
+
+        HttpURLConnection urlConnection;
+        InputStream in;
+        ArrayList<String> result = new ArrayList<>();
+        try {
+            final URL gtfsUrl = new URL(GTFS_ADDRESS);
+            urlConnection = (HttpURLConnection) gtfsUrl.openConnection();
+        } catch(IOException e) {
+            //e.printStackTrace();
+            res.set(Fetcher.Result.SERVER_ERROR); // even when offline, urlConnection works fine. WHY.
+            return null;
+        }
+        urlConnection.setConnectTimeout(4000);
+        urlConnection.setReadTimeout(50*1000);
+
+        try {
+            in = urlConnection.getInputStream();
+        } catch (Exception e) {
+            try {
+                if(urlConnection.getResponseCode()==404)
+                    res.set(Fetcher.Result.SERVER_ERROR_404);
+            } catch (IOException e2) {
+                e2.printStackTrace();
+            }
+            return null;
+
+        }
+        try (ZipInputStream stream = new ZipInputStream(in)) {
+
+            // now iterate through each item in the stream. The get next
+            // entry call will return a ZipEntry for each file in the
+            // stream
+            ZipEntry entry;
+            while ((entry = stream.getNextEntry()) != null) {
+                String s = String.format(Locale.ENGLISH, "Entry: %s len %d added",
+                        entry.getName(),
+                        entry.getSize()
+                );
+                System.out.println(s);
+
+                // 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());
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        // we must always close the zip file.
+        return result;
+    }
+
+    public static Date getLastGTFSUpdateDate(AtomicReference<Fetcher.Result> res) {
+        URL theURL;
+        final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.ENGLISH);
+        //final Date baseDate = dateFormat.parse("1970-00-00T00:00:00+0000");
+        final Date nullDate = new Date(0);
+        try{
+            theURL = new URL(GTFS_PAGE_ADDRESS);
+        } catch (IOException ex){
+            Log.e(DEBUG_TAG, "Fixed URL is null, this is a real issue");
+            return nullDate;
+        }
+        res.set(Fetcher.Result.OK);
+        final String fullPageDOM = networkTools.getDOM(theURL, res);
+        if(fullPageDOM== null){
+            //Something wrong happend
+            Log.e(DEBUG_TAG, "Cannot get URL");
+            return nullDate;
+        }
+        res.set(Fetcher.Result.OK);
+        Document doc = Jsoup.parse(fullPageDOM);
+
+        Elements sections = doc.select("section.additional-info");
+        Date finalDate = new Date(0);
+        for (Element sec: sections){
+            Element head = sec.select("h3").first();
+            String headTitle = head.text();
+            if(!headTitle.trim().toLowerCase(Locale.ITALIAN).equals("informazioni supplementari"))
+                continue;
+
+            for (Element row: sec.select("tr")){
+                if(!row.selectFirst("th").text().trim()
+                        .toLowerCase(Locale.ITALIAN).equals("ultimo aggiornamento"))
+                    continue;
+
+                Attributes spanAttributes = row.selectFirst("td > span").attributes();
+                String dateAsString = spanAttributes.get("data-datetime");
+                try {
+                    finalDate = dateFormat.parse(dateAsString);
+                    return finalDate;
+                }catch (ParseException ex){
+                    Log.e(DEBUG_TAG, "Wrong date for the last update of GTFS Data: "+dateAsString);
+                    res.set(Fetcher.Result.PARSER_ERROR);
+                    ex.printStackTrace();
+                }
+                break;
+            }
+        }
+        res.set(Fetcher.Result.PARSER_ERROR);
+        return finalDate;
+
+    }
+
+    public static void readCSVWithColumns(BufferedReader reader, String tableName, Context con) throws IOException {
+
+        //String[] elements;
+        List<String> lineElements;
+
+        String line;
+
+        final String header = reader.readLine();
+        if (header == null){
+            throw new IOException();
+        }
+
+        //elements = header.split("\n")[0].split(",");
+        //System.out.println(Arrays.toString(elements));
+
+        lineElements = readCsvLine(header);
+
+
+        final HashMap<Integer,String> columnMap = new HashMap<>();
+
+        final CsvTableInserter inserter = new CsvTableInserter(tableName,con);
+
+        for (int i=0; i< lineElements.size(); i++){
+            //columnMap.put(i, fixStringIfItHasQuotes(elements[i].trim()) );
+            columnMap.put(i, lineElements.get(i).trim() );
+
+        }
+        Log.d(DEBUG_TAG, "Columns for the file: "+columnMap);
+        boolean first = true;
+        while((line = reader.readLine())!=null){
+            //there is a line of data
+            //elements = line.split("\n")[0].split(",");
+            if(first) Log.d(DEBUG_TAG, "Element line: "+line);
+            lineElements = readCsvLine(line);
+
+            final Map<String,String> rowsMap = getColumnsAsString(lineElements.toArray(new String[0]), columnMap);
+            if (first){
+                Log.d(DEBUG_TAG, " in map:"+rowsMap);
+                first=false;
+            }
+            inserter.addElement(rowsMap);
+        }
+
+        //commit data
+        inserter.insertDataInDatabase();
+    }
+    @NonNull
+    private static Map<String,String> getColumnsAsString(@NonNull String[] lineElements, Map<Integer,String> colsIndices)
+    {
+        final HashMap<String,String>  theMap = new HashMap<>();
+        for(int l=0; l<lineElements.length; l++){
+            if(!colsIndices.containsKey(l))
+                continue;
+            //theMap.put(colsIndices.get(l), fixStringIfItHasQuotes(lineElements[l].trim()));
+            theMap.put(colsIndices.get(l), lineElements[l].trim());
+        }
+        return theMap;
+    }
+
+    private static String fixStringIfItHasQuotes(String item) {
+        if(item.length()==0){
+            return item;
+        }
+        final String[] elements=item.split("\"");
+        /*
+        Log.d(DEBUG_TAG,"Splitting quotes length:"+elements.length);
+        for (int i=0; i<elements.length; i++){
+            Log.d(DEBUG_TAG,"Elements: "+i+" "+elements[i]);
+        }
+         */
+        if(elements.length>1){
+            //if(elements.length<3) throw new IllegalArgumentException("Malformed string");
+            return elements[1];
+        } else if(elements.length > 0)
+            return elements[0];
+        else
+            return item;
+    }
+    //https://stackoverflow.com/questions/7800494/parse-csv-with-double-quote-in-some-cases#7800519
+    public static List<String> readCsvLine(String line) throws IllegalArgumentException
+    {
+
+        List<String> list = new ArrayList<String>();
+        line += ",";
+
+        for (int x = 0; x < line.length(); x++)
+        {
+            String s = line.substring(x);
+            if (s.trim().startsWith("\""))
+            {
+                Matcher m = quotePattern.matcher(s);
+                if (!m.find()) {
+                    Log.e(DEBUG_TAG, "Cannot find pattern, "+s+" , line: "+line);
+                    throw new IllegalArgumentException("CSV is malformed");
+                }
+                list.add(m.group(1).replace("\"\"", "\""));
+                x += m.end() - 1;
+            }
+            else
+            {
+                int y = s.indexOf(",");
+                if (y == -1)
+                    throw new IllegalArgumentException("CSV is malformed");
+                list.add(s.substring(0, y));
+                x += y;
+            }
+        }
+        return list;
+    }
+}
diff --git a/src/it/reyboz/bustorino/backend/networkTools.java b/src/it/reyboz/bustorino/backend/networkTools.java
--- a/src/it/reyboz/bustorino/backend/networkTools.java
+++ b/src/it/reyboz/bustorino/backend/networkTools.java
@@ -18,22 +18,20 @@
 
 package it.reyboz.bustorino.backend;
 
+import android.content.Context;
 import androidx.annotation.Nullable;
 import android.util.Log;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.io.*;
 import java.net.HttpURLConnection;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.Map;
 import java.util.Scanner;
 import java.util.concurrent.atomic.AtomicReference;
 
 public abstract class networkTools {
-    static String getDOM(final URL url, final AtomicReference<Fetcher.Result> res) {
+    public static String getDOM(final URL url, final AtomicReference<Fetcher.Result> res) {
         //Log.d("asyncwget", "Catching URL in background: " + uri[0]);
         HttpURLConnection urlConnection;
         StringBuilder result = null;
@@ -70,6 +68,52 @@
         res.set(Fetcher.Result.PARSER_ERROR); // will be set to "OK" later, this is a safety net in case StringBuilder returns null, the website returns an HTTP 204 or something like that.
         return result.toString();
     }
+
+    public static Fetcher.Result saveFileInCache(File outputFile, URL url) {
+        HttpURLConnection urlConnection;
+        try {
+            urlConnection = (HttpURLConnection) url.openConnection();
+        } catch (IOException e) {
+            //e.printStackTrace();
+            return Fetcher.Result.CONNECTION_ERROR;
+        }
+        urlConnection.setConnectTimeout(4000);
+        urlConnection.setReadTimeout(50 * 1000);
+
+        Log.d("BusTO net Tools", "Download file "+url);
+        try (InputStream inputStream = urlConnection.getInputStream()) {
+            //File outputFile = new File(con.getFilesDir(), fileName);
+            //BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
+            FileOutputStream outputStream = new FileOutputStream(outputFile);
+            byte buffer[] = new byte[16384];
+            boolean inProgress = true;
+            while(inProgress){
+                int numread = inputStream.read(buffer);
+                inProgress = (numread > 0);
+                if(inProgress) outputStream.write(buffer, 0, numread);
+            }
+            outputStream.close();
+            //while (bufferedInputStream.available())
+        } catch (IOException e) {
+            e.printStackTrace();
+            try {
+                final Fetcher.Result  res;
+                if(urlConnection.getResponseCode()==404)
+                    res= Fetcher.Result.SERVER_ERROR_404;
+                else if(urlConnection.getResponseCode()!=200)
+                    res= Fetcher.Result.SERVER_ERROR;
+                else res= Fetcher.Result.PARSER_ERROR;
+                urlConnection.disconnect();
+                return res;
+            } catch (IOException ioException) {
+                ioException.printStackTrace();
+                urlConnection.disconnect();
+                return Fetcher.Result.PARSER_ERROR;
+            }
+        }
+        urlConnection.disconnect();
+        return Fetcher.Result.OK;
+    }
     @Nullable
     static String queryURL(URL url, AtomicReference<Fetcher.Result> res){
         return queryURL(url,res,null);
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
@@ -8,10 +8,12 @@
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.View;
+import androidx.annotation.Nullable;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Arrays;
+import java.util.List;
 
 public abstract class utils {
     private static final double EarthRadius = 6371e3;
@@ -153,4 +155,17 @@
         return "Trace too Short.";
     }
      */
+    public static String joinList(@Nullable List<String> dat, String separator){
+        StringBuilder sb = new StringBuilder();
+        if(dat==null || dat.size()==0)
+            return "";
+        else if(dat.size()==1)
+            return dat.get(0);
+        sb.append(dat.get(0));
+        for (int i=1; i<dat.size(); i++){
+            sb.append(separator);
+            sb.append(dat.get(i));
+        }
+        return sb.toString();
+    }
 }
diff --git a/src/it/reyboz/bustorino/data/AppRepository.java b/src/it/reyboz/bustorino/data/AppRepository.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/AppRepository.java
@@ -0,0 +1,23 @@
+/*
+	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
@@ -1,3 +1,20 @@
+/*
+	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.annotation.SuppressLint;
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
@@ -1,3 +1,20 @@
+/*
+	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.content.ContentValues;
@@ -5,6 +22,8 @@
 import android.content.SharedPreferences;
 import android.database.sqlite.SQLiteDatabase;
 import android.util.Log;
+import androidx.core.content.ContextCompat;
+import androidx.work.*;
 import it.reyboz.bustorino.R;
 import it.reyboz.bustorino.backend.Fetcher;
 import it.reyboz.bustorino.backend.FiveTAPIFetcher;
@@ -14,6 +33,7 @@
 import org.json.JSONObject;
 
 import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static android.content.Context.MODE_PRIVATE;
@@ -155,4 +175,25 @@
         editor.putBoolean(con.getString(R.string.databaseUpdatingPref),value);
         return editor.commit();
     }
+
+    /**
+     * Request update using workmanager framework
+     * @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){
+        final SharedPreferences theShPr = PreferencesHolder.getMainSharedPreferences(con);
+        final WorkManager workManager = WorkManager.getInstance(con);
+        PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS)
+                .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
+                .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
+                        .build())
+                .build();
+        final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10);
+        if (version >= 0 && !forced)
+            workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
+                    ExistingPeriodicWorkPolicy.KEEP, wr);
+        else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
+                ExistingPeriodicWorkPolicy.REPLACE, wr);
+    }
 }
diff --git a/src/it/reyboz/bustorino/data/FavoritesLiveData.java b/src/it/reyboz/bustorino/data/FavoritesLiveData.java
--- a/src/it/reyboz/bustorino/data/FavoritesLiveData.java
+++ b/src/it/reyboz/bustorino/data/FavoritesLiveData.java
@@ -1,3 +1,20 @@
+/*
+	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;
 
 
diff --git a/src/it/reyboz/bustorino/data/GTTInfoInject.java b/src/it/reyboz/bustorino/data/GTTInfoInject.java
--- a/src/it/reyboz/bustorino/data/GTTInfoInject.java
+++ b/src/it/reyboz/bustorino/data/GTTInfoInject.java
@@ -1,3 +1,20 @@
+/*
+	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 java.util.Locale;
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
@@ -123,7 +123,7 @@
             db.execSQL(SQL_CREATE_BRANCH_TABLE);
             db.execSQL(SQL_CREATE_CONNECTIONS_TABLE);
 
-            DatabaseUpdateService.startDBUpdate(appContext,0,true);
+            DatabaseUpdate.requestDBUpdateWithWork(appContext, true);
         }
     }
 
diff --git a/src/it/reyboz/bustorino/data/PreferencesHolder.java b/src/it/reyboz/bustorino/data/PreferencesHolder.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/PreferencesHolder.java
@@ -0,0 +1,34 @@
+/*
+	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.content.Context;
+import android.content.SharedPreferences;
+import it.reyboz.bustorino.R;
+
+import static android.content.Context.MODE_PRIVATE;
+
+/**
+ * Static class for commonly used SharedPreference operations
+ */
+public abstract class PreferencesHolder {
+
+    public static SharedPreferences getMainSharedPreferences(Context context){
+        return context.getSharedPreferences(context.getString(R.string.mainSharedPreferences), MODE_PRIVATE);
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/Converters.kt b/src/it/reyboz/bustorino/data/gtfs/Converters.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/Converters.kt
@@ -0,0 +1,103 @@
+/*
+	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.gtfs
+
+import androidx.room.TypeConverter
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * Class to convert values for objects into
+ * the needed columns
+ *
+ * handled automatically by Room with TypeConverter
+ */
+class Converters {
+
+
+    @TypeConverter
+    fun fromString(value: String?): Date? {
+        return dateFromFmtString(value)
+    }
+
+    @TypeConverter
+    fun dateToString(date: Date?): String? {
+        return date?.let { stringFormat.format(it)}
+    }
+
+    @TypeConverter
+    fun exceptionToInt(type: GtfsServiceDate.ExceptionType?): Int? {
+        return type?.value
+    }
+    @TypeConverter
+    fun fromInt(value: Int?): GtfsServiceDate.ExceptionType? {
+        return value?.let { GtfsServiceDate.ExceptionType.getByValue(it) }
+    }
+
+    companion object{
+        const val DATE_FMT_STRING = "yyyyMMdd"
+        val stringFormat = SimpleDateFormat(DATE_FMT_STRING, Locale.US)
+
+        fun fromStringNum(string:  String?): Boolean?{
+            string?.let { if (it.trim() == "1")
+                return true
+            else if(it.trim() == "0")
+                return false
+            else throw Exception("Cannot convert $string to numeric value") }
+            return null
+
+        }
+        fun fromStringNum(string:  String?, defaultVal: Boolean): Boolean{
+            string?.let { if (it.trim() == "1")
+                return true
+            else if(it.trim() == "0")
+                return false
+            else return defaultVal }
+            return defaultVal
+
+        }
+        fun dateFromFmtString(value: String?): Date?{
+            return value?.let {
+
+                stringFormat.parse(it)
+            }
+        }
+
+        fun wheelchairFromString(string:  String?): GtfsStop.WheelchairAccess?{
+            string?.let { if (it.trim() == "1")
+                return GtfsStop.WheelchairAccess.SOMETIMES
+            else if(it.trim() == "0")
+                return GtfsStop.WheelchairAccess.UNKNOWN
+            else if(it.trim() == "2")
+                return GtfsStop.WheelchairAccess.IMPOSSIBLE
+            else //throw Exception("Cannot convert $string to wheelchair access") }
+            return GtfsStop.WheelchairAccess.UNKNOWN
+
+        }
+            return null
+        }
+        @TypeConverter
+        fun wheelchairToInt(access:GtfsStop.WheelchairAccess): Int{
+           return  access.value;
+        }
+        @TypeConverter
+        fun wheelchairFromInt(value: Int): GtfsStop.WheelchairAccess{
+            return  GtfsStop.WheelchairAccess.getByValue(value)?: GtfsStop.WheelchairAccess.UNKNOWN
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
@@ -0,0 +1,82 @@
+/*
+	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.gtfs
+
+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 elementsList: MutableList< in GtfsTable> = mutableListOf()
+
+    fun addElement(csvLineElements: Map<String,String>) {
+
+        when(tableName){
+            "stops" ->
+                elementsList.add(GtfsStop(csvLineElements))
+            "routes" ->
+                elementsList.add(GtfsRoute(csvLineElements))
+            "calendar" ->
+                elementsList.add(GtfsService(csvLineElements))
+            "calendar_dates" ->
+                elementsList.add(GtfsServiceDate(csvLineElements))
+            "trips" ->
+                elementsList.add(GtfsTrip(csvLineElements))
+            "shapes" ->
+                elementsList.add(GtfsShape(csvLineElements))
+            "stop_times" ->
+                elementsList.add(GtfsStopTime(csvLineElements))
+
+
+        }
+        if(elementsList.size >= MAX_ELEMENTS){
+            //have to insert
+            Log.d(DEBUG_TAG, "Inserting first batch of elements now, list size: "+elementsList.size)
+            if (tableName == "routes")
+                dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+            else
+                insertDataInDatabase()
+
+            elementsList.clear()
+
+        }
+    }
+    fun insertDataInDatabase(){
+        when(tableName){
+            "stops" -> dao.updateStops(elementsList.filterIsInstance<GtfsStop>())
+            "routes" -> dao.clearAndInsertRoutes(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>())
+
+        }
+    }
+
+    companion object{
+        val MAX_ELEMENTS = 5000
+
+        val DEBUG_TAG="BusTO - TableInserter"
+    }
+}
\ 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
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
@@ -0,0 +1,57 @@
+/*
+	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.gtfs
+
+import android.content.Context
+import androidx.room.*
+
+@Database(
+    entities = [
+        GtfsServiceDate::class,
+        GtfsStop::class,
+        GtfsService::class,
+        GtfsRoute::class,
+        GtfsStopTime::class,
+        GtfsTrip::class,
+        GtfsShape::class],
+    version = GtfsDatabase.VERSION,
+    exportSchema = false,
+)
+@TypeConverters(Converters::class)
+public abstract class GtfsDatabase : RoomDatabase() {
+
+    abstract fun gtfsDao() : StaticGtfsDao
+
+    companion object{
+        @Volatile
+        private var INSTANCE: GtfsDatabase? =null
+
+        fun getGtfsDatabase(context: Context): GtfsDatabase{
+            return INSTANCE ?: synchronized(this){
+                val instance = Room.databaseBuilder(context.applicationContext,
+                GtfsDatabase::class.java,
+                "gtfs_database").build()
+                INSTANCE = instance
+                instance
+            }
+        }
+
+        const val VERSION = 1
+        const val FOREIGNKEY_ONDELETE = ForeignKey.NO_ACTION
+    }
+}
\ 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
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
@@ -0,0 +1,79 @@
+/*
+	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.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName=GtfsRoute.DB_TABLE)
+data class GtfsRoute(
+    @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
+    val ID: String,
+    @ColumnInfo(name = "agency_id")
+    val agencyID: String,
+    @ColumnInfo(name = "route_short_name")
+    val shortName: String,
+    @ColumnInfo(name = "route_long_name")
+    val longName: String,
+    @ColumnInfo(name = "route_desc")
+    val description: String,
+    @ColumnInfo(name ="route_type")
+    val type: String,
+    //@ColumnInfo(name ="route_url")
+    //val url: String,
+    @ColumnInfo(name ="route_color")
+    val color: String,
+    @ColumnInfo(name ="route_text_color")
+    val textColor: String,
+    @ColumnInfo(name = COL_SORT_ORDER)
+    val sortOrder: Int
+): GtfsTable {
+
+    constructor(valuesByColumn: Map<String,String>) : this(
+        valuesByColumn[COL_ROUTE_ID]!!,
+        valuesByColumn["agency_id"]!!,
+        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()!!
+    )
+    companion object {
+        const val DB_TABLE: String="routes_table"
+        const val COL_SORT_ORDER: String="route_sort_order"
+        const val COL_ROUTE_ID = "route_id"
+
+        val COLUMNS = arrayOf(COL_ROUTE_ID,
+            "agency_id",
+            "route_short_name",
+            "route_long_name",
+            "route_desc",
+            "route_type",
+            "route_color",
+            "route_text_color",
+            COL_SORT_ORDER
+        )
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsService.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsService.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsService.kt
@@ -0,0 +1,94 @@
+/*
+	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.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import java.util.Date
+
+@Entity(tableName = GtfsService.DB_TABLE)
+data class GtfsService(
+    @PrimaryKey
+    @ColumnInfo(name = COL_SERVICE_ID)
+    val serviceID: String,
+    @ColumnInfo(name = COL_MONDAY)
+    val onMonday: Boolean,
+    @ColumnInfo(name = COL_TUESDAY)
+    val onTuesday: Boolean,
+    @ColumnInfo(name = COL_WEDNESDAY)
+    val onWednesday: Boolean,
+    @ColumnInfo(name = COL_THURSDAY)
+    val onThursday: Boolean,
+    @ColumnInfo(name = COL_FRIDAY)
+    val onFriday: Boolean,
+    @ColumnInfo(name = COL_SATURDAY)
+    val onSaturday: Boolean,
+    @ColumnInfo(name = COL_SUNDAY)
+    val onSunday: Boolean,
+    @ColumnInfo(name = COL_START_DATE)
+    val startDate: Date,
+    @ColumnInfo(name = COL_END_DATE)
+    val endDate: Date,
+): GtfsTable {
+
+    constructor(valuesByColumn: Map<String,String>) : this(
+        valuesByColumn[COL_SERVICE_ID]!!,
+        Converters.fromStringNum(valuesByColumn[COL_MONDAY])!!,
+        Converters.fromStringNum(valuesByColumn[COL_TUESDAY])!!,
+        Converters.fromStringNum(valuesByColumn[COL_WEDNESDAY])!!,
+        Converters.fromStringNum(valuesByColumn[COL_THURSDAY])!!,
+        Converters.fromStringNum(valuesByColumn[COL_FRIDAY])!!,
+        Converters.fromStringNum(valuesByColumn[COL_SATURDAY])!!,
+        Converters.fromStringNum(valuesByColumn[COL_SUNDAY])!!,
+        Converters.dateFromFmtString(valuesByColumn[COL_START_DATE])!!,
+        Converters.dateFromFmtString(valuesByColumn[COL_END_DATE])!!
+    )
+    companion object{
+        const val DB_TABLE="gtfs_calendar"
+        const val COL_SERVICE_ID="service_id"
+        const val COL_MONDAY="monday"
+        const val COL_TUESDAY="tuesday"
+        const val COL_WEDNESDAY="wednesday"
+        const val COL_THURSDAY="thursday"
+        const val COL_FRIDAY="friday"
+        const val COL_SATURDAY="saturday"
+        const val COL_SUNDAY="sunday"
+        const val COL_START_DATE="start_date"
+
+        const val COL_END_DATE="end_date"
+
+        val COLUMNS = arrayOf(
+            COL_SERVICE_ID,
+            COL_MONDAY,
+            COL_TUESDAY,
+            COL_WEDNESDAY,
+            COL_THURSDAY,
+            COL_FRIDAY,
+            COL_SATURDAY,
+            COL_SUNDAY,
+            COL_START_DATE
+        )
+    }
+
+
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsServiceDate.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsServiceDate.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsServiceDate.kt
@@ -0,0 +1,72 @@
+/*
+	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.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import java.util.Date
+
+
+@Entity(
+    tableName = GtfsServiceDate.DB_TABLE,
+    primaryKeys = [GtfsServiceDate.COL_SERVICE_ID, GtfsServiceDate.COL_DATE],
+    foreignKeys =  [ForeignKey(entity = GtfsService::class,
+        parentColumns = [GtfsService.COL_SERVICE_ID],
+        childColumns = [GtfsServiceDate.COL_SERVICE_ID],
+        onDelete = GtfsDatabase.FOREIGNKEY_ONDELETE)]
+)
+data class GtfsServiceDate(
+    @ColumnInfo(name= COL_SERVICE_ID)
+    val serviceID: String,
+    @ColumnInfo(name=COL_DATE)
+    val date: Date,
+    @ColumnInfo(name=COL_EXCEPTION)
+    val exceptionType: ExceptionType,
+): GtfsTable {
+    companion object{
+        const val DB_TABLE="gtfs_calendar_dates"
+        const val COL_SERVICE_ID="service_id"
+        const val COL_DATE="date"
+        const val COL_EXCEPTION="exception_type"
+
+        val COLUMNS = arrayOf(COL_SERVICE_ID, COL_DATE, COL_SERVICE_ID)
+
+        val converter = Converters()
+
+    }
+    constructor(valuesByColumn: Map<String,String>) : this(
+        valuesByColumn[COL_SERVICE_ID]!!,
+        converter.fromString(valuesByColumn[COL_DATE])!!,
+        valuesByColumn[COL_EXCEPTION]?.let { ExceptionType.getByValue(it.toInt()) }!!
+    )
+    enum class ExceptionType(val value: Int){
+        ADDED(1),
+        REMOVED(2);
+
+        companion object {
+            private val VALUES = values()
+            fun getByValue(value: Int) = VALUES.firstOrNull { it.value == value }
+        }
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
+
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsShape.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsShape.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsShape.kt
@@ -0,0 +1,61 @@
+/*
+	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.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+
+@Entity(tableName = GtfsShape.DB_TABLE,
+primaryKeys = [GtfsShape.COL_SHAPE_ID, GtfsShape.COL_POINT_SEQ])
+data class GtfsShape(
+    @ColumnInfo(name = COL_SHAPE_ID)
+    val shapeID: String,
+    @ColumnInfo(name = COL_POINT_LAT)
+    val pointLat: Double,
+    @ColumnInfo(name = COL_POINT_LON)
+    val pointLon: Double,
+    @ColumnInfo(name = COL_POINT_SEQ)
+    val pointSequence: Int,
+): GtfsTable {
+
+    constructor(valuesByColumn: Map<String,String>) : this(
+        valuesByColumn[COL_SHAPE_ID]!!,
+        valuesByColumn[COL_POINT_LAT]?.toDoubleOrNull()!!,
+        valuesByColumn[COL_POINT_LON]?.toDoubleOrNull()!!,
+        valuesByColumn[COL_POINT_SEQ]?.toIntOrNull()!!
+    )
+
+    companion object{
+        const val DB_TABLE="gtfs_shapes"
+        const val COL_SHAPE_ID = "shape_id"
+        const val COL_POINT_LAT="shape_pt_lat"
+        const val COL_POINT_LON="shape_pt_lon"
+        const val COL_POINT_SEQ="shape_pt_sequence"
+
+        val COLUMNS= arrayOf(
+            COL_SHAPE_ID,
+            COL_POINT_LAT,
+            COL_POINT_LON,
+            COL_POINT_SEQ
+        )
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsStop.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsStop.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsStop.kt
@@ -0,0 +1,90 @@
+/*
+	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.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = GtfsStop.DB_TABLE)
+data class GtfsStop(
+    @PrimaryKey
+    @ColumnInfo(name= COL_STOP_ID)
+    val internalID: Int,
+    @ColumnInfo(name= COL_STOP_CODE)
+    val gttStopID: String,
+    @ColumnInfo(name= COL_STOP_NAME)
+    val stopName: String,
+    @ColumnInfo(name= COL_GTT_PLACE)
+    val gttPlaceName: String,
+    @ColumnInfo(name= COL_LATITUDE)
+    val latitude: Double,
+    @ColumnInfo(name= COL_LONGITUDE)
+    val longitude: Double,
+    //@ColumnInfo(name="zone_id")
+    //val zoneID: Int,
+    @ColumnInfo(name= COL_WHEELCHAIR)
+    val wheelchair: WheelchairAccess,
+): GtfsTable {
+
+    constructor(valuesByColumn: Map<String,String>) : this(
+        valuesByColumn[COL_STOP_ID]?.toIntOrNull()!!,
+        valuesByColumn[COL_STOP_CODE]!!,
+        valuesByColumn[COL_STOP_NAME]!!,
+        valuesByColumn[COL_GTT_PLACE]!!,
+        valuesByColumn[COL_LATITUDE]?.toDoubleOrNull()!!,
+        valuesByColumn[COL_LONGITUDE]?.toDoubleOrNull()!!,
+        //valuesByColumn["zone_id"]?.toIntOrNull()!!,
+        Converters.wheelchairFromString(valuesByColumn[COL_WHEELCHAIR])!!
+    )
+    companion object{
+        const val DB_TABLE="stops_gtfs"
+        const val COL_STOP_CODE="stop_code"
+        const val COL_STOP_ID = "stop_id"
+        const val COL_GTT_PLACE="stop_desc"
+        const val COL_STOP_NAME="stop_name"
+        const val COL_LATITUDE="stop_lat"
+        const val COL_LONGITUDE="stop_lon"
+        const val COL_WHEELCHAIR="wheelchair_boarding"
+        val COLUMNS = arrayOf(
+            COL_STOP_CODE,
+            COL_STOP_ID,
+            COL_GTT_PLACE,
+            COL_STOP_NAME,
+            COL_LATITUDE,
+            COL_LONGITUDE,
+            //"zone_id",
+            COL_WHEELCHAIR
+        )
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+
+    enum class WheelchairAccess(val value: Int){
+        UNKNOWN(0),
+        SOMETIMES(1),
+        IMPOSSIBLE(2);
+
+        companion object {
+            private val VALUES = values()
+            fun getByValue(value: Int) = VALUES.firstOrNull { it.value == value }
+        }
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsStopTime.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsStopTime.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsStopTime.kt
@@ -0,0 +1,78 @@
+/*
+	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.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+
+@Entity(tableName = GtfsStopTime.DB_TABLE,
+    primaryKeys = [GtfsStopTime.COL_TRIP_ID, GtfsStopTime.COL_STOP_ID],
+    foreignKeys = [
+        ForeignKey(entity = GtfsStop::class,
+            parentColumns = [GtfsStop.COL_STOP_ID],
+            childColumns = [GtfsStopTime.COL_STOP_ID],
+            onDelete = GtfsDatabase.FOREIGNKEY_ONDELETE),
+        ForeignKey(entity = GtfsTrip::class,
+            parentColumns = [GtfsTrip.COL_TRIP_ID],
+            childColumns = [GtfsStopTime.COL_TRIP_ID],
+            onDelete = GtfsDatabase.FOREIGNKEY_ONDELETE),
+    ],
+    indices = [Index(GtfsStopTime.COL_STOP_ID)]
+)
+data class GtfsStopTime(
+    @ColumnInfo(name= COL_TRIP_ID)
+    val tripID: String,
+    @ColumnInfo(name= COL_ARRIVAL_TIME)
+    val arrivalTime: String,
+    @ColumnInfo(name= COL_DEPARTURE_TIME)
+    val departureTime:String,
+    @ColumnInfo(name= COL_STOP_ID)
+    val stopID: Int,
+    @ColumnInfo(name= COL_STOP_SEQUENCE)
+    val stopSequence: Int,
+): GtfsTable {
+    constructor(valuesByColumn: Map<String,String>) : this(
+        valuesByColumn[COL_TRIP_ID]!!,
+        valuesByColumn[COL_ARRIVAL_TIME]!!,
+        valuesByColumn[COL_DEPARTURE_TIME]!!,
+        valuesByColumn[COL_STOP_ID]?.toIntOrNull()!!,
+        valuesByColumn[COL_STOP_SEQUENCE]?.toIntOrNull()!!
+    )
+    companion object{
+        const val DB_TABLE="gtfs_stop_times"
+        const val COL_TRIP_ID="trip_id"
+        const val COL_ARRIVAL_TIME="arrival_time"
+        const val COL_DEPARTURE_TIME="departure_time"
+        const val COL_STOP_ID="stop_id"
+        const val COL_STOP_SEQUENCE="stop_sequence"
+
+        val COLUMNS = arrayOf(
+            COL_TRIP_ID,
+            COL_ARRIVAL_TIME,
+            COL_DEPARTURE_TIME,
+            COL_STOP_ID,
+            COL_STOP_SEQUENCE
+        )
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsTable.java b/src/it/reyboz/bustorino/data/gtfs/GtfsTable.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsTable.java
@@ -0,0 +1,24 @@
+/*
+	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.gtfs;
+
+public interface GtfsTable {
+
+    String[] getColumns();
+
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsTrip.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsTrip.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsTrip.kt
@@ -0,0 +1,107 @@
+/*
+	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.gtfs
+
+import androidx.room.*
+
+@Entity(tableName = GtfsTrip.DB_TABLE,
+    foreignKeys=[
+        ForeignKey(entity = GtfsRoute::class,
+            parentColumns = [GtfsRoute.COL_ROUTE_ID],
+            childColumns = [GtfsTrip.COL_ROUTE_ID],
+            onDelete = GtfsDatabase.FOREIGNKEY_ONDELETE),
+        // The service_id: ID referencing calendar.service_id or calendar_dates.service_id
+        /*
+        ForeignKey(entity = GtfsService::class,
+            parentColumns = [GtfsService.COL_SERVICE_ID],
+            childColumns = [GtfsTrips.COL_SERVICE_ID],
+            onDelete = GtfsDatabase.FOREIGNKEY_ONDELETE),
+         */
+    ],
+    indices = [Index(GtfsTrip.COL_ROUTE_ID)]
+)
+data class GtfsTrip(
+    @ColumnInfo(name = COL_ROUTE_ID )
+    val routeID: String,
+    @ColumnInfo(name = COL_SERVICE_ID)
+    val serviceID: String,
+    @PrimaryKey
+    @ColumnInfo(name = COL_TRIP_ID)
+    val tripID: String,
+    @ColumnInfo(name = COL_HEADSIGN)
+    val tripHeadsign: String,
+    @ColumnInfo(name = COL_DIRECTION_ID)
+    val directionID: Int,
+    @ColumnInfo(name = COL_BLOCK_ID)
+    val blockID: String,
+    @ColumnInfo(name = COL_SHAPE_ID)
+    val shapeID: String,
+    @ColumnInfo(name = COL_WHEELCHAIR)
+    val isWheelchairAccess: Boolean,
+    @ColumnInfo(name = COL_LIMITED_R)
+    val isLimitedRoute: Boolean,
+
+): GtfsTable {
+
+    constructor(valuesByColumn: Map<String,String>) : this(
+        valuesByColumn[COL_ROUTE_ID]!!,
+        valuesByColumn[COL_SERVICE_ID]!!,
+        valuesByColumn[COL_TRIP_ID]!!,
+        valuesByColumn[COL_HEADSIGN]!!,
+        valuesByColumn[COL_DIRECTION_ID]?.toIntOrNull()?: 0,
+        valuesByColumn[COL_BLOCK_ID]!!,
+        valuesByColumn[COL_SHAPE_ID]!!,
+        Converters.fromStringNum(valuesByColumn[COL_WHEELCHAIR], false),
+        Converters.fromStringNum(valuesByColumn[COL_LIMITED_R], false)
+    )
+
+    companion object{
+        const val DB_TABLE="gtfs_trips"
+        const val COL_ROUTE_ID="route_id"
+        const val COL_SERVICE_ID="service_id"
+        const val COL_TRIP_ID = "trip_id"
+        const val COL_HEADSIGN="trip_headsign"
+        //const val COL_SHORT_NAME="trip_short_name",
+        const val COL_DIRECTION_ID="direction_id"
+        const val COL_BLOCK_ID="block_id"
+        const val COL_SHAPE_ID = "shape_id"
+        const val COL_WHEELCHAIR="wheelchair_accessible"
+        const val COL_LIMITED_R="limited_route"
+
+        val COLUMNS= arrayOf(
+            COL_ROUTE_ID,
+            COL_SERVICE_ID,
+            COL_TRIP_ID,
+            COL_HEADSIGN,
+            COL_DIRECTION_ID,
+            COL_BLOCK_ID,
+            COL_SHAPE_ID,
+            COL_WHEELCHAIR,
+            COL_LIMITED_R
+        )
+        /*
+        open fun fromContentValues(values: ContentValues) {
+            val tripItem = GtfsTrips();
+        }
+         */
+    }
+
+    override fun getColumns(): Array<String> {
+        return COLUMNS
+    }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt b/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
@@ -0,0 +1,77 @@
+/*
+	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.gtfs
+
+import androidx.lifecycle.LiveData
+import androidx.room.*
+
+@Dao
+interface StaticGtfsDao {
+    @Query("SELECT * FROM "+GtfsRoute.DB_TABLE+" ORDER BY "+GtfsRoute.COL_SORT_ORDER)
+    fun getAllRoutes() : LiveData<List<GtfsRoute>>
+
+    @Query("SELECT * FROM "+GtfsStop.DB_TABLE+" WHERE "+GtfsStop.COL_STOP_CODE+" LIKE :queryID")
+    fun getStopByStopID(queryID: String): LiveData<List<GtfsStop>>
+
+    @Query("SELECT * FROM "+GtfsShape.DB_TABLE+
+            " WHERE "+GtfsShape.COL_SHAPE_ID+" LIKE :shapeID"+
+            " ORDER BY "+GtfsShape.COL_POINT_SEQ+ " ASC"
+    )
+    fun getShapeByID(shapeID: String) : LiveData<List<GtfsShape>>
+
+    @Transaction
+    fun clearAndInsertRoutes(routes: List<GtfsRoute>){
+        deleteAllRoutes()
+        insertRoutes(routes)
+    }
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertRoutes(users: List<GtfsRoute>)
+    @Insert
+    fun insertStops(stops: List<GtfsStop>)
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertCalendarServices(services: List<GtfsService>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertShapes(shapes: List<GtfsShape>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertDates(dates: List<GtfsServiceDate>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertServices(services: List<GtfsService>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertTrips(trips: List<GtfsTrip>)
+
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun insertStopTimes(stopTimes: List<GtfsStopTime>)
+
+    @Query("DELETE FROM "+GtfsRoute.DB_TABLE)
+    fun deleteAllRoutes()
+    @Query("DELETE FROM "+GtfsStop.DB_TABLE)
+    fun deleteAllStops()
+    @Update(onConflict = OnConflictStrategy.REPLACE)
+    fun updateShapes(shapes: List<GtfsShape>) : Int
+
+    @Transaction
+    fun updateStops(stops: List<GtfsStop>){
+        deleteAllStops()
+        insertStops(stops)
+    }
+}
\ 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
@@ -1,3 +1,20 @@
+/*
+	BusTO  - Fragments 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.fragments;
 
 import android.app.AlertDialog;
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
@@ -1,3 +1,21 @@
+/*
+	BusTO  - Fragments components
+    Copyright (C) 2020 Andrea Ugo
+    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.fragments;
 
 import android.Manifest;
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
@@ -328,15 +328,16 @@
 
 
     @Override
-    public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
+    public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
         if (0 > MAX_DISTANCE) throw new AssertionError();
         //Cursor might be null
-        Log.d(DEBUG_TAG, "Num stops found: "+data.getCount()+", Current distance: "+distance);
-        if(data==null){
+        if(cursor==null){
             Log.e(DEBUG_TAG,"Null cursor, something really wrong happened");
             return;
         }
-        if(!isDBUpdating() && (data.getCount()<MIN_NUM_STOPS && distance<=MAX_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;
             Bundle d = new Bundle();
             d.putParcelable(BUNDLE_LOCATION,lastReceivedLocation);
@@ -344,11 +345,11 @@
             //Log.d(DEBUG_TAG, "Doubling distance now!");
             return;
         }
-        Log.d("LoadFromCursor","Number of nearby stops: "+data.getCount());
+        Log.d("LoadFromCursor","Number of nearby stops: "+cursor.getCount());
         ////////
 
-        if(data.getCount()>0) {
-            ArrayList<Stop> stopList = createStopListFromCursor(data);
+        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));
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
@@ -1,3 +1,20 @@
+/*
+	BusTO  - Fragments components
+    Copyright (C) 2020 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;
diff --git a/src/it/reyboz/bustorino/map/CustomInfoWindow.java b/src/it/reyboz/bustorino/map/CustomInfoWindow.java
--- a/src/it/reyboz/bustorino/map/CustomInfoWindow.java
+++ b/src/it/reyboz/bustorino/map/CustomInfoWindow.java
@@ -1,3 +1,21 @@
+/*
+	BusTO - Map components
+	Copyright (C) 2020 Andrea Ugo
+    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.map;
 
 import android.annotation.SuppressLint;
diff --git a/src/it/reyboz/bustorino/map/LocationOverlay.java b/src/it/reyboz/bustorino/map/LocationOverlay.java
--- a/src/it/reyboz/bustorino/map/LocationOverlay.java
+++ b/src/it/reyboz/bustorino/map/LocationOverlay.java
@@ -1,5 +1,5 @@
 /*
-	BusTO (middleware)
+	BusTO - Map components
     Copyright (C) 2021 Fabio Mazza
 
     This program is free software: you can redistribute it and/or modify
diff --git a/src/it/reyboz/bustorino/middleware/GeneralActivity.java b/src/it/reyboz/bustorino/middleware/GeneralActivity.java
--- a/src/it/reyboz/bustorino/middleware/GeneralActivity.java
+++ b/src/it/reyboz/bustorino/middleware/GeneralActivity.java
@@ -25,6 +25,7 @@
 
 import it.reyboz.bustorino.R;
 import it.reyboz.bustorino.backend.utils;
+import it.reyboz.bustorino.data.PreferencesHolder;
 
 /**
  * Activity class that contains all the generally useful methods
@@ -58,7 +59,7 @@
     }
 
     protected SharedPreferences getMainSharedPreferences(){
-        return getSharedPreferences(getString(R.string.mainSharedPreferences),MODE_PRIVATE);
+        return PreferencesHolder.getMainSharedPreferences(this);
     }
     public void hideKeyboard() {
         View view = getCurrentFocus();