25 Nov 2016 10 Nov 2017
Σε αυτό το μάθημα θα αναλύσουμε τη λειτουργία των Βάσεων Δεδομένων και συγκεκριμένα της SQLite.
Το σύστημά μας θα αλλάξει αρκετά, καθώς θα προστεθούν πολλές νέες κλάσεις που σχετίζονται με τη ΒΔ. Πρώτα θα ορίσουμε τους πίνακες της ΒΔ, έπειτα θα γράψουμε κώδικα για να δημιουργούνται μέσω Java και έπειτα θα κάνουμε δοκιμαστικές εισαγωγές και ελέγχους δεδομένων απο και προς τη ΒΔ. Επειδή η λειτουργικότητα των ΒΔ είναι δύσκολο να δοκιμαστεί σε κινητό λόγω του απαραίτητου User Interface, ο καλύτερος τρόπος είναι να γίνει μέσω Unit Testing. Θα πούμε 2 λόγιο για το Unit Testing στη συνέχεια.
Οι μονάδες που θα προστεθούν στο σύστημα φαίνονται στην παρακάτω εικόνα:
Για την αποθήκευση δεδομένων το Android μας παρέχει 2 βασικούς πυλώνες:
- Το σύστημα αρχείων (File Storage) πχ εκεί αποθηκεύονται τα Shared Preferences
- Μια μίνι-έκδοση βάσης δεδομένων που λέγεται SQLite
Φυσικά το 2ο είναι πολύ πιο πολύπλοκο στην υλοποίηση, αλλά παρέχει σαφή και αποδοτική οργάνωση δεδομένων (+γρηγορη αναζήτηση)
Με τη χρήση του SQLite μέσα απο τον κώδικα μπορούμε εύκολα να δημιουργήσουμε πίνακες, να εισάγουμε και να διαγράψουμε δεδομένα.
Ξεκινάμε την εισαγωγή του WeatherContract (DBContract)
Πρόκειται για τη διασύνδεση των πινάκων με τον κώδικα. Μια κλάση contract περιλαμβάνει έναν σύνολο static final Strings καθένα απο τα οποία περιγράφει το όνομα ενός πίνακα και της μιας στήλης στη ΒΔ
public class WeatherContract {
/*
Inner class that defines the table contents of the location table
Students: This is where you will add the strings. (Similar to what has been
done for WeatherEntry)
*/
public static final class LocationEntry implements BaseColumns {
public static final String TABLE_NAME = "location";
public static final String COLUMN_LOCATION_SETTING = "location_setting";
public static final String COLUMN_CITY_NAME = "city_name";
public static final String COLUMN_COORD_LAT = "coord_lat";
public static final String COLUMN_COORD_LONG = "coord_long";
}
}
Για να αποφασίσουμε τι δεδομένα πρέπει να κρατήσουμε, μελετούμε το wireframe για την πρόβλεψη μιας ημέρας:
αποφασίζουμε λοιπόν πως το πίνακας weather
πρέπει να έχει τα εξής πεδία:
- Record ID
- Weather Condition ID
- Min Temp
- Max Temp
- Wind speed
- Humidity
- Date
- Wind direction
- Pressure
Ο Weather Location θα πρέπει επίσης να περιλαμβάνει και την πληροφορία της τοποθεσίας
Info: Επεξήγηση ακεραιότητας αναφορών
Τί χρειάζονται αυτές οι κλάσεις;
Επειδή η λειτουργικότητα των ΒΔ είναι δύσκολο να δοκιμαστεί σε κινητό λόγω του απαραίτητου User Interface, ο καλύτερος τρόπος είναι να γίνει μέσω Unit Testing.
Με τα Unit Tests εκτελείται δοκιμαστικά ο κώδικας με σκοπό να επαληθευτεί η σωστή του λειτουργία. Αφού εκτελεστεί ένα κομμάτι κώδικα (συνήθως το σπάμε σε μεθόδους) ελέγχεται αν η τιμή μιας μεταβλητής είναι η αναμενόμενη, αν ένας πίνακας είναι γεμάτος ή άδειος κ.ο.κ.
Οι έλεγχοι αυτοί γίνονται με βοήθεια της μεθόδου assert().
Παράδειγμα:
public void testThatDemonstratesAssertions() throws Throwable {
int a = 5;
int b = 3;
int c = 5;
int d = 10;
assertEquals("X should be equal", a, c);
assertTrue("Y should be true", d > a);
assertFalse("Z should be false", a == b);
if (b > d) {
fail("XX should never happen");
}
}
Διαβάστε περισσότερα για το Unit Testing στο Tutorials Point
Οι βασικές κλάσεις στις οποίες θα υλοποιήσουμε τα δικά μας Unit tests είναι οι κλάσεις: TestPractice και FullTestSuite
Hint:
- Στην κλάση TestDb βγάζουμε απο σχόλια τη μέθοδο: testCreateDb() και στην κλάση TestUtilities τις μεθόδους createNorthPoleLocationValues() insertNorthPoleLocationValues() *
Άσκηση 2. Εισαγωγή και ανάγνωση Location και weather data απο τους πίνακες Location και Weather αντιστοιχα
κλάση TestDb μέθοδος testLocationTable
public void testLocationTable() {
// First step: Get reference to writable database
// If there's an error in those massive SQL table creation Strings,
// errors will be thrown here when you try to get a writable database.
WeatherDbHelper dbHelper = new WeatherDbHelper(mContext);
SQLiteDatabase db = dbHelper.getWritableDatabase();
// Second Step: Create ContentValues of what you want to insert
// (you can use the createNorthPoleLocationValues if you wish)
ContentValues testValues = TestUtilities.createNorthPoleLocationValues();
// Third Step: Insert ContentValues into database and get a row ID back
long locationRowId;
locationRowId = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, testValues);
// Verify we got a row back.
assertTrue(locationRowId != -1);
// Data's inserted. IN THEORY. Now pull some out to stare at it and verify it made
// the round trip.
// Fourth Step: Query the database and receive a Cursor back
// A cursor is your primary interface to the query results.
Cursor cursor = db.query(
WeatherContract.LocationEntry.TABLE_NAME, // Table to Query
null, // all columns
null, // Columns for the "where" clause
null, // Values for the "where" clause
null, // columns to group by
null, // columns to filter by row groups
null // sort order
);
// Move the cursor to a valid database row and check to see if we got any records back
// from the query
assertTrue( "Error: No Records returned from location query", cursor.moveToFirst() );
// Fifth Step: Validate data in resulting Cursor with the original ContentValues
// (you can use the validateCurrentRecord function in TestUtilities to validate the
// query if you like)
TestUtilities.validateCurrentRecord("Error: Location Query Validation Failed",
cursor, testValues);
// Move the cursor to demonstrate that there is only one record in the database
assertFalse( "Error: More than one record returned from location query",
cursor.moveToNext() );
// Sixth Step: Close Cursor and Database
cursor.close();
db.close();
}
μέθοδος testWeatherTable() --->>> Ασκηση!!!
public void testWeatherTable() {
// First insert the location, and then use the locationRowId to insert
// the weather. Make sure to cover as many failure cases as you can.
// Instead of rewriting all of the code we've already written in testLocationTable
// we can move this code to insertLocation and then call insertLocation from both
// tests. Why move it? We need the code to return the ID of the inserted location
// and our testLocationTable can only return void because it's a test.
// First step: Get reference to writable database
// Create ContentValues of what you want to insert
// (you can use the createWeatherValues TestUtilities function if you wish)
// Insert ContentValues into database and get a row ID back
// Query the database and receive a Cursor back
// Move the cursor to a valid database row
// Validate data in resulting Cursor with the original ContentValues
// (you can use the validateCurrentRecord function in TestUtilities to validate the
// query if you like)
// Finally, close the cursor and database
}