Gå til hovedindhold
Version2 it for professionelle
Forsiden

Hovedmenu

  • It-nyheder
  • Blogs
  • It-job
  • It-firmaer
  • Whitepapers
  • Opret bruger
  • Log ind
Du kan logge ind med din e-mail-adresse
Der er forskel på store og små bogstaver i adgangskoden.
Glemt adgangskode?
Se kommentarer (5)
Emner Udviklingsværktøjer, Java, Android

Android-skolen del 2: Sådan skriver du din første applikation

Anden undervisningstime i Android-skolen tager dig gennem udviklingen af en simpel telefonbog-applikation. Fat tastaturet og hiv Eclipse frem på skærmen.

Af Mikkel Meister Fredag, 20. november 2009 - 6:59

Velkommen til anden del af Version2's skole for spirende Android-udviklere.

Anden lektion tager dig igennem kodningen af en simpel applikation, der henter oplysninger ud af telefonens adressebog og giver mulighed for at ringe op til kontaktpersonerne.

Der er lagt vægt på at introducere læseren for basale koncepter i Android-udvikling, mere end at fremstille verdens mest brugbare applikation fra begyndelsen. Husk, at du skal tale Java'sk i et vist omfang for at få det fulde udbytte af Android-skolen.

Gæsteunderviser er denne gang datalogistuderende ved Københavns Universitet Klaus Kartou. Han er en af udviklerne bag den danske Android-applikation Gigbox, der for tiden kæmper mod 199 andre Android-applikationer om 250.000 dollar i Google-konkurrencen Android Developer Challenge 2.

Vi lægger ud med at oprette et nyt Android-projekt, ganske som vi gjorde i første del af Android-skolen.

Her er det en god idé at udfylde Activity-feltet, selvom det ikke er strengt påkrævet. Det sikrer dig, at din applikation automatisk bliver afviklet på emulatoren, og at den installeres med et ikon i programmenuen. Vi vælger navnet AndroidSkolenDel2, som dermed bliver navnet på selve hovedklassen i applikationen.

Samtidig vælger vi at udvikle til Android SDK 1.6. Sørg derfor også for at den emulator, du oprettede i første lektion, har 1.6 som target.

1) Bliv fortrolig med ListViews

Et ListView i Android er en del af den grafiske brugergrænseflade, der viser elementer på en lodret liste med mulighed for scrolling, visning af tekst og billeder, klik for at aktivere handlingen bag elementet og meget mere.

ListViews bruges tit i Android-applikationer, fordi det ofte giver god mening af præsentere brugeren for informationer gennem en lodret liste af elementer. Det gælder for eksempel Gigbox-applikationen, hvor ListViews blandt andet bruges til at vise en liste over koncerter.

»ListViews er utroligt brugbare, så det er en god ting at lære tidligt,« siger Klaus Kartou.

Han peger på, at ListViews er en intuitiv måde for brugeren af interagere med telefonen på.

»Og så kan du proppe næsten hvad som helst ind i et ListView, fordi det æder alt, der nedarver fra View-klassen, som er den grundlæggende byggesten for brugerinterface-komponenter i Android. Vi bruger ListViews overalt i Gigbox, både til at visualisere store mængder koncertdata, men også som navigationsmenuer i applikationen,« siger Klaus Kartou.

Herunder er vist et af de mange ListViews i Gigbox-applikationen:

For at give dig en idé om ListViews funktionalitet begynder vi med at skrive en simpel applikation, der blot viser tre forskellige tekststrenge i et ListView.

Først sørger vi for, at vores nyoprettede klasse AndroidSkolenDel2 er en underklasse af ListActivity i stedet for blot Activity. Dernæst opretter vi en liste med de tekststrenge, vi gerne vil have vist i vores ListView.

Derefter sættes tekststrengene til visning i vores ListView med metoden setListAdapter.

Det hele sker indenfor rammerne af den autogenererede metode onCreate().

public class AndroidSkolenDel2 extends ListActivity {
 
  /** Called when the activity is first created. <em>/
  public void onCreate(Bundle savedInstanceState) {
    /</em> String array med elementer til visning i ListView <em>/
    final String[] listElements = { "Version2", "it",
      "for professionelle" };
 
    super.onCreate(savedInstanceState);
 
    /</em> Her sættes vores liste til visning */
    setListAdapter(new ArrayAdapter<String>(this,
        android.R.layout.simple_list_item_1, listElements));
  }
}

setListAdapter forbinder et ListView med de data, der skal vises i ListView'et. Et ListView kan i princippet vise alle slags data, så længe de er pakket ind i en ListAdapter. I dette tilfælde bruges underklassen ArrayAdapter som wrapper til vores liste af tekststrenge.

Bemærk argumentet android.R.layout.simple_list_item_1, som er et standardlayout af skærmbilledet på telefonen. Det modificerer vi senere til vores eget layout.

Oversæt og kør applikationen med ctrl+b efterfulgt af ctrl+F11.

Du vil nu se tekststrengene vist i et ListView, hvor du kan klikke på de forskellige elementer.

Indtil videre er underholdningsværdien af applikationen til at overse. Listens indhold er ubrugeligt, og vi får intet ud af at klikke på de enkelte entries.

Lad os derfor udvide applikationen med nye features.

2) Hent oplysninger fra telefonens adressebog

Begynd med at slette indmaden i onCreate()-metoden, på nær super.onCreate(savedInstanceState).

Hvis ikke du allerede har kontakter liggende i adressebogen i emulatoren, bør du oprette et par stykker, så der er lidt at arbejde med for applikationen. Hvis du ejer en Android-telefon, har du formentlig allerede en stak kontaktpersoner liggende i adressebogen.

Hver kontakt kan have en række oplysninger som privat-, mobil- og arbejdsnummer, e-mail og så videre knyttet til sig, men vi nøjes med at hente navn og det primære telefonnummer i denne omgang.

Det gøres ved gennem metodekaldet getContentResolver() at foretage en forespørgsel ? query - på alle kontakter fra telefonen og lægge dem over i en Cursor ? et interface, der giver læse-/skriveadgang til de data, der returneres fra en databaseforespørgsel.

Herefter beder vi Android om at stå for håndteringen af den oprettede Cursors 'livscyklus', så vi ikke selv skal bøvle med det, med metodekaldet startManagingCursor().

  /* Denne metode kaldes, når applikationen startes */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
    ...

Nu har vi fat i den rå klump data, der udgør kontaktpersonerne i telefonens adressebog. Da kontaktpersonerne som nævnt har en lang række informationer knyttet til sig, er det tid til at bede om de data, vi gerne vil vise i vores ListView ? navn og telefonnummer.

(NB: Bemærk, at klassen Contacts.People er deprecated fra og med Android SDK 2.0, som i stedet benytter klassen ContractsContact.CommonDataKinds)

  /* Denne metode kaldes, når applikationen startes */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
 
    // hent navn og tlf.nummer og map det over i vores layout
    String[] columns = new String[] { People.NAME, People.NUMBER };
    int[] contacts = new int[] { R.id.name, R.id.phone_number };
    ...

Læg her mærke til, at R.id.name og R.id.phone_number ikke automatisk findes på forhånd. Filen R.java (ligger under projektundermappen 'gen') autogenereres for hvert nyt Android-projekt, og den står for at forbinde vores Java-kildekode med det grafiske layout af brugergrænsefladen, der specificeres i XML-filen main.xml.

R.java kender lige nu ikke referencerne name og phone_number, som vi derfor skal tilføje i main.xml, hvorefter R.java vil blive autogenereret med de nye refererencer (NB: vi ændrer aldrig direkte i R.java!).

Åben main.xml fra projektundermappen res/layout (husk at vælge main.xml-fanen under editoren). Den ser således ud:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
</LinearLayout>

Fjern hele det eksisterende TextView-tag og indsæt i stedet to nye, så main.xml kommer til at tage sig således ud:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView android:id="@+id/name"
  android:textSize="18px" 
    android:textStyle="bold" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
/>
<TextView android:id="@+id/phone_number" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
/>
</LinearLayout>

Vi har dermed defineret vores eget layout, hvor teksten til navnet på kontaktpersonen har pixelstørrelsen 18 og er markedet med fed.

Gem og build projektet.

Herefter bør Eclipse holde op med at brokke sig over, at de to layout-referencer name og phone_number ikke findes i layout'et. (Bemærk, at vi har har valgt at beholde standardnavnet main.xml til layout-filen for nemheds skyld. I en større applikation med flere layoutfiler ville vi have valgt at omdøbe filen til contactView.xml eller et lignende, mere sigende navn.)

Nu mangler vi blot at 'mappe' vores data over i den grafiske brugergrænseflade. Det gøres med med en instans af klassen SimpleCursorAdapter, der mapper vores columns fra cursor'en over de to nye TextViews, vi netop har defineret i main.xml.

Hermed kommer den samlede onCreate()-metode til at se sådan ud:

private SimpleCursorAdapter cursorAdapter;
 
  /* Denne metode kaldes, når applikationen startes */
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
 
    // hent navn og tlf.nummer og map det over i vores layout
    String[] columns = new String[] { People.NAME, People.NUMBER };
    int[] contacts = new int[] { R.id.name, R.id.phone_number };
 
    cursorAdapter = new SimpleCursorAdapter(this, R.layout.main, c, columns,
        contacts);
    setListAdapter(cursorAdapter);
  }

Nu skal vi give applikationen lov til at læse kontakterne fra adressebogen.

Det sker i filen AndroidManifest.xml, som ligger i roden af projektbiblioteket. Den hentes frem i Eclipse-editoren ved at dobbeltklikke på filen og vælge fanebladet AndroidManifest.xml under editor-vinduet.

Her tilføjes linjen

<uses-permission android:name="android.permission.READ_CONTACTS"/>

under selve manifest-tag'et (se den komplette manifest-fil længere nede i artiklen).

Tast ctrl+b efterfulgt af ctrl+F11 for at build'e og køre projektet.

Når applikationen er startet op, kan du se en liste over kontaktpersoner med navn og telefonnummer.

Vi mangler dog stadig at give mulighed for at ringe til kontaktpersonen med et klik.

3) Tilføj mulighed for opkald

Tilbage i AndroidSkolenDel2.java skal vi nu oprette en metode, som lytter efter klik på kontaktpersonerne og sætter gang i et opkald ud fra det.

Det sker med metoden onListItemClick(), som vi finder en skabelon - eller stub - til med shift+alt+S -> Override/Implement methods... og et klik ind under ListActivity.

Bemærk, at onListItemClick() skal ligge i klassens globale scope, og ikke under metoden onCreate().

Vi tilføjer herefter en såkaldt Intent i onListItemClick(), som er en abstraktion over den operation, vi gerne vil gennemføre, når der klikkes på en kontaktperson - i dette tilfælde et opkald.

Herefter lægger vi positionen i ListView'et for den kontakt, vi har trykket på, over i en Cursor og henter id'et for kontaktens primære telefonnummer ud:

 /* Denne metode kaldes, når der klikkes på en kontaktperson */
  protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
 
    // vores intention er at foretage et opkald
    Intent i = new Intent(Intent.ACTION_CALL);
    // hvor i ListView'et har vi klikket?
    Cursor c = (Cursor) cursorAdapter.getItem(position);
    // hent id'et for kontaktens primære telefonnummer
    long phoneId = c.getLong(c.getColumnIndex(People.PRIMARY_PHONE_ID));
    ...

Når det er gjort, beder vi vores Intent om at arbejde på den samlede adresse - eller Uri - med metoden setData(), hvorefter vi sætter vores Intent i sving med metodekaldet startActivity():

...
    // Intent skal arbejde på telefon-URI'et + telefon-id'et
    i.setData(ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
    // sæt gang i vores Intent
    startActivity(i);
  }

Nu ser den færdige kildekode til applikationen i AndroidSkolenDel2.java således ud:

package dk.version2.as2;
 
import android.app.ListActivity;
import android.content.ContentUris;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.Contacts.People;
import android.provider.Contacts.Phones;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
 
public class AndroidSkolenDel2 extends ListActivity {
 
  private SimpleCursorAdapter cursorAdapter;
 
  /* Denne metode kaldes, når applikationen startes <em>/
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // hent alle kontakter på telefonen
    Cursor c = getContentResolver().query(People.CONTENT_URI, null, null,
        null, null);
    startManagingCursor(c);
 
    // hent navn og tlf.nummer og map det over i vores layout
    String[] columns = new String[] { People.NAME, People.NUMBER };
    int[] contacts = new int[] { R.id.name, R.id.phone_number };
 
    cursorAdapter = new SimpleCursorAdapter(this, R.layout.main, c, columns,
        contacts);
    setListAdapter(cursorAdapter);
  }
 
  /</em> Denne metode kaldes, når der klikkes på en kontaktperson */
  protected void onListItemClick(ListView l, View v, int position, long id) {
    super.onListItemClick(l, v, position, id);
 
    // vores intention er at foretage et opkald
    Intent i = new Intent(Intent.ACTION_CALL);
    // hvor i ListView'et har vi klikket?
    Cursor c = (Cursor) cursorAdapter.getItem(position);
    // hent id'et for kontaktens primære telefonnummer
    long phoneId = c.getLong(c.getColumnIndex(People.PRIMARY_PHONE_ID));
    // Intent skal arbejde på telefon-URI'et + telefon-id'et
    i.setData(ContentUris.withAppendedId(Phones.CONTENT_URI, phoneId));
    // sæt gang i vores Intent
    startActivity(i);
  }
}

Så mangler vi kun at give applikationen lov til at foretage et opkald.

Her tilføjer vi endnu et uses-permission-tag, CALL_PHONE, så manifest-filen samlet set kommer til at se ud som vist i koden herunder:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="dk.version2.as2"
      android:versionCode="1"
      android:versionName="1.0">
  <uses-permission android:name="android.permission.READ_CONTACTS"/>
  <uses-permission android:name="android.permission.CALL_PHONE"/>
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".AndroidSkolenDel2"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
    </application>
    <uses-sdk android:minSdkVersion="4" />
</manifest>

Oversæt og kør projektet med ctrl+b og ctrl+F11. Nu kan du ringe op til en kontakt ved at klikke en enkelt gang på kontakten i vores ListView:

Og hermed er du færdig med din første, simple Android-applikation.

Ifølge Klaus Kartou er en af styrkerne ved Android, at det kræver så forholdsvis få kodelinjer at få sendt en lille Android-applikation ud over stepperne.

»Jeg har oplevet, at djævlen ligger i detaljerne, og det kan for eksempel tage lang tid at implementere multitrådning, så tunge opgaver ikke blokerer UI-tråden. Og noget så simpelt som at håndtere, at brugeren skifter skærmbilledets orientering ved at vende på telefonen, er noget som mange udviklere kæmper med. Men fordelen er, at det er nemt at komme i gang og få sparket noget kode afsted,« siger Klaus Kartou.

Det var alt for anden omgang af Android-skolen. Næste gang tager vi fat i GPS, accelerometer og andre dele af telefonens mere eksotiske hardware.

Litteratur: [1] http://developer.android.com [2] http://www.anddev.org

Send Tweet
Udskriv

Mere om Android

Opret en konto eller log ind for at følge indhold på Version2 - og bliv opdateret via e-mail eller rss

Følg dette emne

Så splittet er Android: 3.997 forskellige enheder

Udgivet 16. maj 14.44Opdateret 16. maj 14.48

Nu er den her: Java-fri Android baseret på C#

Udgivet 10. maj 14.58Opdateret 10. maj 15.01

Advarsel: Batteri-app til Android er sms-svindel

Udgivet 9. maj 9.29Opdateret 9. maj 9.32

Anmeldelse af HTC One X: Teknobrag uden high-end finish

Udgivet 9. maj 6.59Opdateret 9. maj 9.01

IT-job & karriere

  • Se alle it-job
  • Importer din kompetenceprofil fra LinkedIn
Mobility arkitekt
Udgivet 7. dec 2011 13.13
Java udviklere – backend – gerne med Oracle erfaring
Udgivet 16. jun 2011 14.38
Nykredit søger javaudviklere
Udgivet 13. apr 13.55
Java udviklere – Web-frontend
Udgivet 16. jun 2011 14.21

Kommentarer (5)

Opret en konto eller log ind for at følge indhold på Version2 - og bliv opdateret via e-mail eller rss

Følg kommentarer
Thorvald Aagaard 1. dec. 2009 - 22.53
 
Permissions skal tilføjes tidligere

Allerede i forbindelse med visning af contacts er det nødvendigt at opdatere permission, idet applikationen ellers giver fejl i forbindelse med opstart.

Thorvald

  • Stem op 0
  • Stem ned 0
  • Log ind eller opret en konto for at skrive kommentarer
Mikkel Meister 2. dec. 2009 - 09.33
 
Re: Permissions skal tilføjes tidligere

Hej Thorvald -

Tak for de skarpe øjne på artiklen. Jeg har nu rettet teksten til, så det første af de to permissions sættes på det rette tidspunkt.

Mvh Mikkel, v2.dk

  • Stem op 0
  • Stem ned 0
  • Log ind eller opret en konto for at skrive kommentarer
kmm moller 6. jul. 2011 - 23.33
 
fatter ikke og en brik

bruger i ikke en anden version af eclipse???

  • Stem op 0
  • Stem ned 0
  • Log ind eller opret en konto for at skrive kommentarer
Jan G. Laursen 7. jul. 2011 - 08.52
 
Re: fatter ikke og en brik

Jo de gør. Men artiklen er altså også mere end 1½ år gammel.

  • Stem op 0
  • Stem ned 0
  • Log ind eller opret en konto for at skrive kommentarer
Palle Simonsen 7. jul. 2011 - 09.29
 
Re: fatter ikke og en brik

Hej,
Den officielle guide er opdateret og ok: http://developer.android.com/sdk/index.html

Hvis du alene skal lave noget til dig selv eller de nærmeste eller skal afprøve en rigtig god idé er App Inventor både sjov og nem: http://appinventor.googlelabs.com

  • Stem op 0
  • Stem ned 0
  • Log ind eller opret en konto for at skrive kommentarer

Tilføj kommentar

Opret en konto eller log ind for at følge indhold på Version2 - og bliv opdateret via e-mail eller rss

Følg kommentarer
Log ind herunder eller opret en bruger for at skrive kommentarer
Du kan logge ind med din e-mail-adresse
Der er forskel på store og små bogstaver i adgangskoden.
Glemt adgangskode?

Seneste nyt

GOTO Copenhagen dag 2 i billeder: Op med hænderne!

Udgivet 22. maj 16.02Opdateret 22. maj 17.02

Staten køber hardware for 1,2 milliarder - her er de syv heldige

Udgivet 22. maj 15.37Opdateret 22. maj 15.37

Firmaer leder efter ’ninjaer’ - men skriv det ikke på CV’et

Udgivet 22. maj 14.54Opdateret 22. maj 15.48

Ny Linux-kerne giver højere sikkerhed og bedre grafikkort-understøttelse

Udgivet 22. maj 14.13Opdateret 22. maj 14.13

Nu skal Google Chrome indtage iPhone og iPad

Udgivet 22. maj 13.20Opdateret 22. maj 13.20

Flere it-nyheder »

Tilmeld dig Version2's it-nyhedsbrev og vind den nye iPad.

Whitepapers

Kick-start your master data management initiative

Affecto Denmark

Affecto Data Quality Assessment: Er din indsigt og beslutning baseret på validt data?

Affecto Denmark

Framework til datamigrering i SAP miljøer - spar op til 50% på dine Data Migration udgifter

Affecto Denmark

Få et Data Warehouse (DW) review hos Affecto

Affecto Denmark

Ressourcehåndtering

Projectplace
  • Flere whitepapers

Branchenyheder

Konica Minoltas stand på drupa 2012 slog besøgsrekord

Konica Minolta Business Solutions Denmark

Komplex it er blevet Brocade Premier Partner

Komplex IT

Øg din effektivitet og produktivitet med bizhub C654/C754

Konica Minolta Business Solutions Denmark

Brugerfjendtlige it-løsninger gør brugerne til en sikkerhedstrussel

Projectplace

Athena IT-Group A/S med solid indtjening

Athena IT-Group

Seneste debat

  1. Staten køber hardware for 1,2 milliarder - her er de syv heldige

    4 comments.
    Last update 25 sekunder
    Skrevet af Thomas Hansen
  2. Finansminister afliver teori om NemID som spionsoftware

    20 comments.
    Last update 27 sekunder
    Skrevet af Peter Mogensen
  3. Dart: Dynamisk Statisk Programmering

    8 comments.
    Last update 24 minutter 17 sekunder
    Skrevet af Lars Bjerregaard
  4. Microsoft fjerner umoderne bling-effekter i Windows 8

    34 comments.
    Last update 1 time 49 sekunder
    Skrevet af Lars Bjerregaard
  5. Partner solgte Netgroups 'test-platform' med overskriften 'fuld redundans'

    14 comments.
    Last update 2 timer 1 minut
    Skrevet af Thomas Bundgaard
  6. Studerende taler ud om kæmpehul: Pærelet at hacke 100.000 danske routere

    12 comments.
    Last update 2 timer 43 minutter
    Skrevet af Thomas (bbb) Hansen
  7. Das NemID trojaner - paranoia eller rettidig omhu?

    24 comments.
    Last update 2 timer 46 minutter
    Skrevet af Mads Vanggaard
  8. To psykologiske årsager til at IT-projekter går galt

    14 comments.
    Last update 3 timer 29 minutter
    Skrevet af Finn Christensen

Mere debat »

It-virksomheder

Relation House
|
Ciklum
|
Mobile Advisor
|
Motus
|
ProData Consult
|
Magenta
|
SimCorp
|
IT Company
|
Tiger Media
|
Ricoh Danmark
|
Coolsms
|
Visma Sirius A/S
 

Information

  • Kontakt redaktionen
  • Job- og annoncesalg
  • Teknisk support
  • Om Version2
  • Brugerbetingelser
  • Privatlivspolitik

Aktuelle emner

  • Agil udvikling
  • Android
  • Bruttolønsordning
  • Business Intelligence
  • Cloud computing
  • Download Windows 8
  • HTML5
  • Harddisk-priser
  • IE9
  • Intranet
  • It-sikkerhed
  • Kindle Fire
  • Multimedieskat
  • NemID
  • OS X Mountain Lion
  • Open source CMS
  • Projektledelse
  • Scrum
  • Sharepoint intranet
  • Storage
  • Ubuntu 11.10
  • Virtualisering
  • Windows 8
  • Windows Phone 7
  • iOS 5
  • iPhone 4S

Tjenester

  • Android-app
  • iPhone-app
  • RSS-feeds
Følg @version2dk
Tilmeld dig Version2's it-nyhedsbrev og vind den nye iPad.

Version2 udgives af

  • Mediehuset Ingeniøren A/S work Skelbækgade 4 1717 København V
  • Tlf. work 33265300