Tietokantaa käyttävä sovellus, osa 1
Learning objectives
- Tutustut tietokantaa käyttävän sovelluksen luomiseen askeleittaisia ohjeita noudattaen.
Olemme tähän mennessä tutustuneet tietoon liittyviin käsitteisiin. Olemme käsitelleet muuttujien tyyppejä ja harjoitelleet useita muuttujia sisältävien käsitteiden luomista luokkien avulla. Aloitimme edellisessä osassa matkan tietokantoihin tiedon lisäämistä ja listaamista harjoitellen. Tarkastellaan ja kerrataan tietokannan käyttöä lisäämiseen ja listaamiseen tarkoitetussa sovelluksessa.
Lähtökohtana sovellukselle on seuraava kuvaus: Ylläpidän listaa kotitöissä auttavista vapaaehtoisista. Jokaisesta vapaaehtoisesta tallennetaan nimi, numero, ja kotikaupunki. Kun minulle ilmoitetaan kotityöavun tarpeesta, haen apua tarvitsevan kotikaupungissa olevat vapaaehtoiset. Tämän jälkeen soitan vapaaehtoisille, ja kysyn ehtisikö joku heistä auttamaan.
Kehitetään seuraavaksi askeleittain tietokantaa käyttävä järjestelmä, joka mahdollistaa yllä kuvatun toiminnan.
Tekstikäyttöliittymä
Aloitetaan sovelluksen tekeminen tekstikäyttöliittymän luomisesta. Tekstikäyttöliittymä listaa saatavilla olevat komennot ja tarjoaa lähtökohdan komentojen käsittelyyn liittyvän logiikan toteuttamiseen. Yllä olevasta kuvauksesta tunnistetaan tarpeet vapaaehtoisten lisäämiseen ja vapaaehtoisten listaamiseen.
Luodaan käyttöliittymä siten, että käyttöliittymän käynnistämiselle on funktio, joka saa parametrinaan tietokannan. Tämä mahdollistaa käyttöliittymän käyttämisen eri tietokannoilla -- mikäli haluamme käyttää jotain muuta tietokantaa, muutamme vain sovelluksen käynnistämisestä vastuussa olevalle funktiolle parametrina annetun tietokannan.
Alla oleva ohjelma luo tietokannan ja käynnistää sovelluksen. Sovellus tarjoaa käyttäjälle tekstikäyttöliittymän.
import 'package:database/database.dart';
import 'dart:io';
main() {
var tietokanta = MemoryDatabaseAdapter().database();
kaynnista(tietokanta);
}
kaynnista(tietokanta) {
while(true) {
print('Komennot:');
print('1: Lisää vapaaehtoinen');
print('2: Listaa vapaaehtoiset');
print('3: Lopeta');
var komento = stdin.readLineSync();
if (komento == '1') {
lisaa(tietokanta);
} else if (komento == '2') {
listaa(tietokanta);
} else if (komento == '3') {
break;
} else {
print('Tuntematon komento!');
}
}
}
lisaa(tietokanta) {
print('Lisätään tietokantaan');
}
listaa(tietokanta) {
print('Listataan tietokannassa olevat tiedot');
}
program output
Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 1 Lisätään tietokantaan Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 2 Listataan tietokannassa olevat tiedot Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 3
Tiedon lisäämiseen ja listaamiseen liittyvä toiminnallisuus on tehty erillisinä funktioina luettavuuden lisäämiseksi. Mikäli lisäys ja listaustoiminnallisuus toteutetaan käyttöliittymän kaynnista
-funktion sisälle, tulee funktiosta ennen pitkää vaikeasti luettava. Vertauskuvana tälle pienempiin osiin jakamiselle voidaan ajatella lausahdusta hajota ja hallitse, joskin tavoitteemme eivät ole poliittisia vaan pyrkimyksenä on ymmärrettävyyden ja sitä kautta ylläpidettävyyden lisääminen.
Funktiot lisaa
ja listaa
sisältävät vain tulostuksen, joka kertoo mitä funktiossa pitäisi tehdä. Tällainen lähestymistapa, missä luomme väliaikaista toiminnallisuutta sisältävän käyttöliittymän, mahdollistaa ohjelman kokeilun jo varhaisessa vaiheessa.
Tietokannassa olevien tietojen listaaminen
Lisätään sovellukseen ensin toiminnallisuus, joka mahdollistaa tietokannassa olevien tietojen listaamisen. Tehdään tämä kahdessa vaiheessa. Luodaan ensin kaikki tiedot listaava toiminnallisuus, jonka jälkeen mahdollistetaan listattavien tietojen rajaaminen kaupungin perusteella.
Otetaan lähtökohdaksi edellisessä osassa käytetty henkilökokoelman sisältävien tietojen tulostamiseen käytetty lähdekoodi. Lähdekoodi näyttää seuraavalta.
var tulos = await henkilokokoelma.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
print(data);
}
Lisätään sama lähdekoodi funktioon listaa
.
listaa(tietokanta) {
print('Listataan tietokannassa olevat tiedot');
var tulos = await henkilokokoelma.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
print(data);
}
}
Funktio ei tällaisenaan toimi, sillä muuttujaa henkilokokoelma
ei ole määritelty eikä funktiota ole määritelty asynkroniseksi.
Tehdään tässä kohtaa päätös, että vapaaehtoisten tiedot tulevat olemaan vapaaehtoiset
-nimisessä tietokokoelmassa. Tällaisen kokoelman saa tietokannasta komennolla tietokanta.collection('vapaaehtoiset');
.
Muokataan funktiota listaa
siten, että käsittelemme vapaaehtoiset
-nimistä tietokokoelmaa ja määrittelemme funktion asynkroniseksi. Funktio näyttää nyt seuraavalta.
listaa(tietokanta) async {
print('Listataan tietokannassa olevat tiedot');
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
print(data);
}
}
Toiminnallisuuden toteuttaminen erillisiin funktioihin auttaa myös testaamisessa. Muokataan funktiota main
siten, että funktio kutsuu suoraan funktiota listaa
. Testauksen auttamiseksi main
-funktiossa lisätään vapaaehtoinen kokoelmaan.
import 'package:database/database.dart';
import 'dart:io';
main() async {
var tietokanta = MemoryDatabaseAdapter().database();
var kokoelma = tietokanta.collection('vapaaehtoiset');
await kokoelma.insert(data: {
'nimi': 'Ada Lovelace',
'puhelinnumero': '123-1234567',
'kotikaupunki': 'lontoo'
});
listaa(tietokanta);
}
// muita funktioita
listaa(tietokanta) async {
print('Listataan tietokannassa olevat tiedot');
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
print(data);
}
}
program output
Listataan tietokannassa olevat tiedot {nimi: Ada Lovelace, puhelinnumero: 123-1234567, kotikaupunki: lontoo}
Funktio listaa
tulostaa nyt kaikki tietokannassa olevat vapaaehtoiset.
Muokataan funktiota seuraavaksi siten, että funktiossa kysytään kaupunkia. Alla olevassa esimerkissä funktiossa kysytään kaupunkia, jonka jälkeen kaikki vapaaehtoiset listataan.
listaa(tietokanta) async {
print('Mitä kaupunkia haetaan?');
var kaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
print(data);
}
}
Haluamme rajata listauksen vain haettavaan kaupunkiin. Rajaus onistuu käymällä dokumentit yksi kerrallaan läpi ja vertaamalla jokaisen dokumentin kotikaupunkia käyttäjän syöttämään kaupunkiin. Mikäli syötetty kaupunki ei vastaa vapaaehtoisen kotikaupunkia, siirrytään käsittelemään seuraavaa dokumenttia continue
-komennolla. Muulloin tiedot tulostetaan.
listaa(tietokanta) async {
print('Mitä kaupunkia haetaan?');
var kaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
if (data['kotikaupunki'] != kaupunki) {
continue;
}
print(data);
}
}
Testidataa sisältävä ohjelmamme on tällä hetkellä seuraavanlainen. Funktiossa main
lisätään tietokantaan yksi vapaaehtoinen, jonka jälkeen kutsutaan listaa
-funktiota.
import 'package:database/database.dart';
import 'dart:io';
main() async {
var tietokanta = MemoryDatabaseAdapter().database();
await tietokanta.collection('vapaaehtoiset').insert(data: {
'nimi': 'Ada Lovelace',
'puhelinnumero': '123-1234567',
'kotikaupunki': 'lontoo'
});
listaa(tietokanta);
}
// muita funktioita
listaa(tietokanta) async {
print('Mitä kaupunkia haetaan?');
var kaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
if (data['kotikaupunki'] != kaupunki) {
continue;
}
print(data);
}
}
program output
Mitä kaupunkia haetaan? < lontoo {nimi: Ada Lovelace, puhelinnumero: 123-1234567, kotikaupunki: lontoo}
program output
Mitä kaupunkia haetaan? < helsinki
Muokataan sovellusta vielä siten, että vapaaehtoisten tulostus on hieman selkeämpi. Tulostetaan tiedot muodossa "nimi, puhelinnumero (kotikaupunki)".
// ...
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
if (data['kotikaupunki'] != kaupunki) {
continue;
}
var nimi = data['nimi'];
var puhelinnumero = data['puhelinnumero'];
var kotikaupunki = data['kotikaupunki'];
print("$nimi, $puhelinnumero ($kotikaupunki)");
}
// ...
program output
Mitä kaupunkia haetaan? < lontoo Ada Lovelace, 123-1234567 (lontoo)
Käyttöliittymää käyttävä ohjelma (ilman testidataa) näyttää kokonaisuudessaan tällä hetkellä seuraavalta.
import 'package:database/database.dart';
import 'dart:io';
main() {
var tietokanta = MemoryDatabaseAdapter().database();
kaynnista(tietokanta);
}
kaynnista(tietokanta) {
while(true) {
print('Komennot:');
print('1: Lisää vapaaehtoinen');
print('2: Listaa vapaaehtoiset');
print('3: Lopeta');
var komento = stdin.readLineSync();
if (komento == '1') {
lisaa(tietokanta);
} else if (komento == '2') {
listaa(tietokanta);
} else if (komento == '3') {
break;
} else {
print('Tuntematon komento!');
}
}
}
lisaa(tietokanta) {
print('Lisätään tietokantaan');
}
listaa(tietokanta) async {
print('Mitä kaupunkia haetaan?');
var kaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
if (data['kotikaupunki'] != kaupunki) {
continue;
}
var nimi = data['nimi'];
var puhelinnumero = data['puhelinnumero'];
var kotikaupunki = data['kotikaupunki'];
print("$nimi, $puhelinnumero ($kotikaupunki)");
}
}
Kun kokeilemme sovellusta, huomaamme, että jotain on pielessä. Eräs mahdollinen suoritus näyttää seuraavalta.
program output
Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 1 Lisätään tietokantaan Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 2 Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta Mitä kaupunkia haetaan? < kaupunki
Kun pyydämme ohjelmaa listaamaan vapaaehtoiset, ohjelman ei pitäisi tulostaa komentovaihtoehtoja vaan kysyä kaupunkia.
Tämä toiminta liittyy asynkronisiin funktioihin. Funktio listaa
on asynkroninen, jolloin funktio kaynnista
ei jää odottamaan funktion listaa
suorituksen valmistumista. Muokataan ohjelmaa siten, että kaynnista
-funktiossa odotetaan kunnes funktion listaa
suoritus on valmis. Lisäämme funktiolle käynnistä async
-avainsanan ja funktiokutsulle listaa
await
-avainsanan.
import 'package:database/database.dart';
import 'dart:io';
main() {
var tietokanta = MemoryDatabaseAdapter().database();
kaynnista(tietokanta);
}
kaynnista(tietokanta) async {
while(true) {
print('Komennot:');
print('1: Lisää vapaaehtoinen');
print('2: Listaa vapaaehtoiset');
print('3: Lopeta');
var komento = stdin.readLineSync();
if (komento == '1') {
lisaa(tietokanta);
} else if (komento == '2') {
await listaa(tietokanta);
} else if (komento == '3') {
break;
} else {
print('Tuntematon komento!');
}
}
}
lisaa(tietokanta) {
print('Lisätään tietokantaan');
}
listaa(tietokanta) async {
print('Mitä kaupunkia haetaan?');
var kaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
if (data['kotikaupunki'] != kaupunki) {
continue;
}
var nimi = data['nimi'];
var puhelinnumero = data['puhelinnumero'];
var kotikaupunki = data['kotikaupunki'];
print("$nimi, $puhelinnumero ($kotikaupunki)");
}
}
Tiedon lisääminen tietokantaan
Lisätään sovellukseen seuraavaksi toiminnallisuus tietojen lisäämiseen. Otetaan jälleen lähtökohdaksi edellisessä osassa käytetty tietojen lisäämiseen käytetty lähdekoodi. Lähdekoodi näyttää seuraavalta.
await henkilokokoelma.insert(data: {
'nimi': 'Ada Lovelace',
'syntymavuosi': 1815
});
Haluamme lisätä jokaisesta vapaaehtoisesta nimen, puhelinnumeron, ja kotikaupungin. Muokataan yllä olevaa esimerkkiä siten, että vapaaehtoisesta lisätään edellä mainitut tiedot.
await henkilokokoelma.insert(data: {
'nimi': 'Ada Lovelace',
'puhelinnumero': '123-1234567',
'kotikaupunki': 'lontoo'
});
Haluamme luonnollisesti, että lisättävät arvot voivat vaihdella. Muokataan esimerkkiä siten, että arvot asetetaan muuttujista.
var nimi = '';
var puhelinnumero = '';
var kotikaupunki = '';
await henkilokokoelma.insert(data: {
'nimi': nimi,
'puhelinnumero': puhelinnumero,
'kotikaupunki': kotikaupunki
});
Luonnollinen seuraava askel on arvojen kysyminen käyttäjältä. Muokataan esimerkkiä siten, että käyttäjältä kysytään tietokantaan lisättävät tiedot.
print('Syötä nimi.');
var nimi = stdin.readLineSync();
print('Syötä puhelinnumero.');
var puhelinnumero = stdin.readLineSync();
print('Syötä kotikaupunki.');
var kotikaupunki = stdin.readLineSync();
await henkilokokoelma.insert(data: {
'nimi': nimi,
'puhelinnumero': puhelinnumero,
'kotikaupunki': kotikaupunki
});
Lisätään muokattu toiminnallisuus seuraavaksi funktioon lisaa
.
lisaa(tietokanta) async {
print('Syötä nimi.');
var nimi = stdin.readLineSync();
print('Syötä puhelinnumero.');
var puhelinnumero = stdin.readLineSync();
print('Syötä kotikaupunki.');
var kotikaupunki = stdin.readLineSync();
await henkilokokoelma.insert(data: {
'nimi': nimi,
'puhelinnumero': puhelinnumero,
'kotikaupunki': kotikaupunki
});
}
Kuten aiemmin, muuttujaa henkilokokoelma
ei ole olemassa. Muokataan funktiota siten, että siinä käsitellään vapaaehtoiset
-kokoelmaa.
lisaa(tietokanta) async {
print('Syötä nimi.');
var nimi = stdin.readLineSync();
print('Syötä puhelinnumero.');
var puhelinnumero = stdin.readLineSync();
print('Syötä kotikaupunki.');
var kotikaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
await vapaaehtoiset.insert(data: {
'nimi': nimi,
'puhelinnumero': puhelinnumero,
'kotikaupunki': kotikaupunki
});
}
Funktiota voisi testata siten, että sitä kutsuttaisiin suoraan main
-funktiosta. Koska olemme jo toteuttaneet listaustoiminnallisuuden, voimme testata funktiota osana ohjelmaa.
Ohjelma kokonaisuudessaan on seuraava. Alla olevassa ohjelmassa myös kaynnista
-funktio jää odottamaan lisaa
-funktion suorituksen valmistumista.
import 'package:database/database.dart';
import 'dart:io';
main() {
var tietokanta = MemoryDatabaseAdapter().database();
kaynnista(tietokanta);
}
kaynnista(tietokanta) async {
while(true) {
print('Komennot:');
print('1: Lisää vapaaehtoinen');
print('2: Listaa vapaaehtoiset');
print('3: Lopeta');
var komento = stdin.readLineSync();
if (komento == '1') {
await lisaa(tietokanta);
} else if (komento == '2') {
await listaa(tietokanta);
} else if (komento == '3') {
break;
} else {
print('Tuntematon komento!');
}
}
}
lisaa(tietokanta) async {
print('Syötä nimi.');
var nimi = stdin.readLineSync();
print('Syötä puhelinnumero.');
var puhelinnumero = stdin.readLineSync();
print('Syötä kotikaupunki.');
var kotikaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
await vapaaehtoiset.insert(data: {
'nimi': nimi,
'puhelinnumero': puhelinnumero,
'kotikaupunki': kotikaupunki
});
}
listaa(tietokanta) async {
print('Mitä kaupunkia haetaan?');
var kaupunki = stdin.readLineSync();
var vapaaehtoiset = tietokanta.collection('vapaaehtoiset');
var tulos = await vapaaehtoiset.search();
var dokumentit = tulos.snapshots;
for (var i = 0; i < dokumentit.length; i++) {
var data = dokumentit[i].data;
if (data['kotikaupunki'] != kaupunki) {
continue;
}
var nimi = data['nimi'];
var puhelinnumero = data['puhelinnumero'];
var kotikaupunki = data['kotikaupunki'];
print("$nimi, $puhelinnumero ($kotikaupunki)");
}
}
program output
Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 1 Syötä nimi. < Ada Lovelace Syötä puhelinnumero. < 123-1234567 Syötä kotikaupunki. < lontoo Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 2 Mitä kaupunkia haetaan? < lontoo Ada Lovelace, 123-1234567 (lontoo) Komennot: 1: Lisää vapaaehtoinen 2: Listaa vapaaehtoiset 3: Lopeta < 3
Hi! Please help us improve the course!
Please consider the following statements and questions regarding this part of the course. We use your answers for improving the course.
I can see how the assignments and materials fit in with what I am supposed to learn.
I find most of what I learned so far interesting.
I am certain that I can learn the taught skills and knowledge.
I find that I would benefit from having explicit deadlines.
I feel overwhelmed by the amount of work.
I try out the examples outlined in the materials.
I feel that the assignments are too difficult.
I feel that I've been systematic and organized in my studying.
How many hours (estimated with a precision of half an hour) did you spend reading the material and completing the assignments for this part? (use a dot as the decimal separator, e.g 8.5)
How would you improve the material or assignments?