Issuu on Google+

SurfBuddy Smartphone Applications - E15 - [24007PU042]

Group 5 Laurits Langberg - 20113625 Søren Lundtoft - 20114277 Søren Ditlev - 20116323

13/10/2015


Indholdsfortegnelse * Overordnet Koncept ............................................................... 1 * Design ........................................................................... 2 * Material ..................................................................... 3 * Appbar ................................................................... 3 * Tabs ..................................................................... 4 * Floating Action Button ................................................... 4 * Cards .................................................................... 5 * Elevation ................................................................ 6 * Animation .................................................................... 6 * Fragments .................................................................... 7 * Platform specifikke layouts .................................................. 8 * Opbygning ........................................................................ 9 * UML Diagram ..................................................................10 * Sekvens diagrammer ...........................................................11 * MainActivity .............................................................11 * BrowseActivity ...........................................................11 * Model ........................................................................12 * Services .....................................................................12 * IntentService ............................................................13 * Service ..................................................................13 * Adapters .....................................................................15 * ArrayAdapter .............................................................15 * RecycleView.Adapter ......................................................15 * Database .....................................................................16 * Database helper ..........................................................17 * Filereader ...................................................................17 * Open Street Maps .............................................................18 * Implementering ...........................................................18 * Overlay ..................................................................19 * Location Services ............................................................20 * Kommunikation pĂĽ tvĂŚrs i applikationen .......................................21 * Activities imellem .......................................................21 * Services til Activities ..................................................21 * Deployment ...................................................................22 * Konklustion ......................................................................23


Overordnet Koncept SurfBuddy er en context aware applikation der sammenligner aktuel vejr data fra openweathermap.org med kitesurf lokationer rundt omkring i Danmark. Ved åbning af

SurfBuddy bliver brugeren først præsenteret for en liste af favoriserede surf lokationer og deres aktuelle vejr data, disse informationer giver et hurtigt overblik over brugerens sædvanlige surf lokationer og om hvorvidt betingelser ved disse lokationer gør det muligt at surfe. Informationen inkluderer vindhastighed, vindrzetning, temperatur og distance fra hver lokation til brugerens aktuelle lokation. Disse aktuelle vejrdata er sammenlignet med information om surf lokationen, blandt andet hvilke vindretninger, der er optimale til surfing ved den pågældende lokation. Matcher den optimale vindretning med den aktuelle vindretning, vil vindpilen være farvet grøn, matcher vindretningerne ikke farves pilen rød. For at tilføje nye lokationer til den favoriserede liste, kan brugeren benytte en søge funktion som henter data fra alle surf lokationer i Danmark og placerer dem i en liste. Her er det muligt at benytte forskellige sorterings kriterier til sortering af denne liste, disse inkluderer; alfabetisk orden efter navn, distance fra brugerens aktuelle lokation til surf lokationen, aktuel vindstyrke og erfarings niveau som lokationen kræver. Trykker man på en lokation i listen, vises uddybende information om den valgte lokation, blandt andet en uddybende beskrivelse af lokationen og et kort over området med en marker ved lokationens position. En knap i bunden af skærmen med et hjerte i, gør det muligt med et enkelt tap at tilføje lokationen til favoritter og den vil dermed blive vist så snart man åbner applikationen.

1 of 23


Design Vi lagde ud med at skitsere de aktiviteter vi ønskede at applikationen skulle indeholde, samt diskutere overgange og services der skulle indgå. Som man ser på skitsen herunder har vi 3 aktiviteter, hvoraf både aktivitet 1 (Favorits) og aktivitet 2 (Browse) kan tilgå aktivitet 3 (Location). Vi har endvidere allerede på dette stadie overvejet over hvilke elementer fra Material design vi ønskede at gøre brug af, derfor ses "cards", "tabs" og "FAB" elementerne allerede under skitseringen.

Efterfølgende gav vi os til at bygge hele designet af applikationen op grafisk i programmet Sketch , som gav os mulighed for at benytte mange af standard elementerne fra Material

design, til hurtigt og effektivt at bygge applikationen op som vi ønskede den grafisk.

2 of 23


Disse billeder er ikke screenshots fra vores færdige applikation, men derimod grafik lavet inden vi overhovedet gik i gang med at udvikle. Dette har fungeret som et fælles udgangspunkt, så vi alle var enige om hvad vi forventede udfaldet ville blive.

Material Det overordnede design bygger på principperne fra Googles Material Design. Dette har vi valgt at gøre, for at applikationen virker som en native del af Android systemet. Googles egne applikationer, samt flere nyere applikationer benytter sig af samme design principper, og giver derved en helhedsfornemmelse på tværs af applikationerne installeret på enheden.

AppBar Det øverste element man ser i vores applikation er AppBar'en, som informerer brugeren om hvilken aktivitet der er aktiv, samt giver mulighed for at udføre simple handlinger som fx opdater, søg eller sortering. Der er desuden tilføjet en pil, som altid vil returnere brugeren til "Favorites" aktiviteten. Dette er gjort ved at tilføje denne aktivitet som parrent i AndroidManifest.xml filen:

3 of 23


<activity android:parentActivityName=".MainActivity" />

Vi har valgt at den blå farve #4A90E2 skal være gennemgående i applikationen. Derfor har vi den mørke udgave af Material Design frameworket, da ikoner samt overskrifter som standard bliver sat til hvid, som er en god kontrast til den blå farve. For yderligere at holde designet konsistent med enheden, har vi valgt at statusbaren i toppen af skærmen også skal have en nuance af denne blå farve.

Tabs På den Activity der beskriver en surf lokation mere dybtgående, har vi valgt at dele informationerne op i 3 faner. Her har vi igen fulgt design retningslinjerne fra Google, og gjort fanerne til en del af AppBar'en, som derved er blevet højere.

Vi benytter en grøn streg, samt kraftigere tekst farve til at indikere hvilken fane der er valgt. Der er endvidere animationer i overensstemmelse med Material design når brugeren "tapper" på en fane.

Floating Action Button Bedre kendt som FAB, er den knap der "flyder" over applikationen og giver brugeren mulighed for at udføre en handling vi vurdere er vigtig for den nuværende aktivitet.

Vi benytter 3 forskellige ikoner på FAB'en, på favorit aktiviteten benytter vi + symbolet for at indikere at man kan tilføje en ny favorit. De 2 sidste symboler af et hjerte bliver brugt under

4 of 23


informations aktiviteten, for at indikere om brugeren har tilføjet den nuværende lokation til sine favoritter. For at tilføje denne knap, har vi været nødt til at benytte Googles design support bibliotek, da dette ikke er et standard element i Android frameworket.

<android.support.design.widget.FloatingActionButton android:id="@+id/myFAB" android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right|bottom" android:layout_marginRight="16dp" android:layout_marginBottom="16dp" android:src="@drawable/ic_add_white" app:elevation="12dp" app:backgroundTint="@color/fab_green" />

Cards For at vise alle favoritter, har vi valgt at benytte cards, igen fra Material design. Dette giver os mulighed for at vise en række informationer om hver lokation samt knapper til handlinger vedrørende den samme lokation nederst på "kortet".

Vores blå farve går igen her til den handling vi ønsker brugeren til at ligge mærke til først. Dette er gjort ud fra en vurdering af at brugeren ikke nødvendigvis ønsker at se mere information om et foretrukket sted, men nok snare ønsker at navigere til dette. Der er endvidere benyttet "flade" knapper, igen som beskrevet i Material design principperne.

5 of 23


Elevation Et centralt element af Material design, er niveau forskellen mellem elementerne på enheden. Idéen er, at man skaber dybde på en flad skræm ved at tilføje et niveau der kaster en skygge på elementer der er placeret hierarkisk bag dette element. Dette er bygget ind i XML layoutet på Android 5.0, og vi benytter denne samt retningslinjerne fra Google til at bestemme den korrekte højde.

<element app:elevation="12dp" />

Animation Et gennemgående element i applikationen er de vindpile som viser aktuel vindretning og de ringe som viser det optimale vindområde for lokationerne. Disse elementer er begge png filer placeret i res/drawable mappen, og er placeret i ImageViews i layout filen.

For at rotere billederne har vi benyttet os af klassen RotateAnimation . Denne klasse gør det muligt at rotere de ImageViews som indeholder billederne, om et defineret pivot punkt, hvilket i vores tilfælde er midten af det pågældende ImageView. Da dette skaleres efter den enhed som kører applikationen finder vi x og y værdierne for midten af et ImageView med følgende kald.

arrowHeight = viewHolder.favoriteWindDirectionArrow.getLayoutParams().height / 2; arrowWidth = viewHolder.favoriteWindDirectionArrow.getLayoutParams().width / 2;

Med pivot punktet defineret, roteres billedet fra en start position på 0 grader til en slut position givet ved den vindretning der ønskes vist. I nedenstående eksempel er den ønskede vindretning, den negative værdi af kaldet location.getWindDir() trukket fra 360, da rotationen skal animeres mod uret.

6 of 23


RotateAnimation animArrow = new RotateAnimation(0, -(360-location.getWindDir()), arrowHeight, arrowWidth);

Som det ses i applikationen tager det 2 sekunder for animationen at færdiggøre rotationen, dette er en værdi sat inden animationen bliver eksekveret, sættes denne værdi til 0, vil rotationen være øjeblikkelig og pilen ville starte i den ønskede position.

animArrow.setDuration(2000);

Scroller man ned gennem den favoriserede liste på hovedskærmen ses det at animationerne eksekveres efterhånden som elementerne bliver synlige på skærmen. Dette skyldes at animationen er defineret i adapteren for listen, hvilket betyder at elementerne bliver vist og eksekveret når adapteren loader og instantierer dem på ny.

Fragments Aktiviteten der viser information omkring lokationen ( LocationInformationActivity.java ) benytter sig af fragments til til at vise forskellige typer af information i forskellige faner. Fanebladene styrer et ViewPager objekt, der er blevet udfyldt med fragments ved hjælp af vores implementation af en FragmentPagerAdapter .

private void setupViewPager(ViewPager viewPager) { Adapter adapter = new Adapter(getSupportFragmentManager()); adapter.addFragment(new DataFragment(), "Data"); adapter.addFragment(new InformationFragment(), "Information"); adapter.addFragment(new MapFragment(), "Map"); viewPager.setAdapter(adapter); }

Vi har tre fragmenter vi selv har lavet: DataFragment.java , InformationFragment.java og MapFragment.java . Hvert fragment har sin egen livscyklus og vi kan derfor køre vidt forskellige

ting i de forskellige tabs uden at bekymre os om hvordan man interagere med indholdet af disse fragments i LocationInformationActivity.java aktiviteten. Vi udnytter eksempelvis denne livscyklus i MapFragment.java da vi vil have en løbende lokations opdatering når man har fragmentet åben, men ønsker at stoppe denne når fragmentet er lukket / et andet er synligt.

7 of 23


@Override public void onPause() { super.onPause(); locationManager.removeUpdates(this); }

Et andet eksempel på et fragment i vores applikation er kortet vi implementerer i MapFragment.java . Dette er et fragment vi importerer fra et andet bibliotek, og vi skal derfor

ikke tage stilling til implantationen af gestures mm. på kortet, det er allerede givet til os når vi sætter fragmentet ind.

Platform specifikke layouts Vores applikation er bygget til håndholdte enheder, da informationen man bliver præsenteret for egner sig til at blive præsenteret når som helst. For at sikre os at flest muligt som har glæde af applikationen har vi valg at bygge den op omkring et layout til en telefon. Telefonen har dog nogle begrænsninger i visningen af nogle typer information blandt andet kortet kan være svært orientere sig på, især hvis man kigger på lokationer som ikke ligger i nærheden af sin egen. På en telefon viser vi kortet i et view for sig selv, som fylder næsten hele skærmen for bedst mulig interaktion på denne skærmtype. Dette går dog ud over oplevelsen, da man hele tiden skulle skifte til et andet view for at se informationerne om den pågældende lokationen. Vi valgte at løse dette problem ved at ændre designet, hvis man er på en anden platform end en telefon. Hvis du har en tablet (xlarge skærm type) og holder den i en vandret position, så vælger vi at give kortet et helt view, og have de andre information placeret oven på. Dermed giver vi det perfekte overblik over surf lokationen med vejrforhold, beskrivelse og faktiske lokation i et. Igen med fokus på Material designs principper.

8 of 23


Det er gjort ved at oprette en layout fil i samme navn som den aktiviteten bruger, dog med betingelserne som beskrevet oven over. Android frameworket sørger så selv for at skifte mellem de to forskellige layout filer når det er nødvendigt. activity_location_information.xml (2) activity_location_information.xml activity_location_information.xml (xlarge-land) I onCreate metoden på aktiviteten har vi sikret os at den sætter fragments og menuer op alt efter hvilket layout frameworket vælger. Dette gøres ved at lave en if sætning, som tjekker om et specifikt view (som kun er i landskab/x-large) layout filen eksistere.

if(findViewById(R.id.large_mapview) != null) { Setting up view1 } else { Setting up view2 }

Opbygning Dette er en gennemgang af de elementer, som vi har brugt gennem vores udvikling af SurfBuddy. Vi har siddet sammen og diskuteret løsningsmuligheder, og er derfor alle godt inde i hvordan applikationen er bygget op og hvordan det hænger sammen både internt mellem filer og i forhold til Android frameworket. Herunder ses en oversigt over filerne i vores projekt, samt hvem der har været ansvarlig:

9 of 23


BrowseActivity - Laurits Langberg BrowseListAdapter - Laurits Langberg BrowseService - Laurits Langberg CardviewAdapter - Søren Ditlev Database - Søren Lundtoft DatabaseHelper - Søren Lundtoft DataFragment - Søren Ditlev InformationFragment - Søren Ditlev LocationInformationActivity - Søren Lundtoft MainActivity - Søren Ditlev MapFragment - Laurits Langberg / Søren Lundtoft NonSwipeableViewPager - Søren Lundtoft SurfFileReader - Søren Lundtoft SurfLocation - Laurits Langberg WeatherService - Søren Lundtoft

UML Diagram Vores applikation er struktureret som vist på UML diagrammet her under. Klasser markeret med orange, er indbyggede klasser i frameworket, klasser markeret med blå er vores egne klasser, og til sidst er den grønne markering et interface i frameworket.

10 of 23


Sekvens diagrammer Vi har lavet 2 sekvens diagrammer der beskriver hvordan vi henter information fra database, filer og internettet.

MainActivity

BrowseActivity

11 of 23


Model Vores applikation er bygget op omkring modellen SurfLocation . Dette er en model der indeholder alle de variabler der er nødvendige for, at kunne beskrive en surf lokation. Værdierne har hver især en setter og getter metode, så man altid kan få den information man ønsker om en specifik lokation.

private private private Private

long id; double longitude, latitude, windSpeed, temperatur, waveHeight, distance; String name, describtion, locationDescription, updated; int windDir, surfDir, level;

Modellen har 2 konstruktører. En uden argumenter som indsætter tomme værdier og en som gør det muligt at sætte alle værdierne med det samme. Udover at have setter og getter metoder, har denne model også et par andre genbrugeligt metoder: getWindSpeed() : Returnerer vindhastighed i knob. isSurfable() : Returnerer en boolean, om lokationen er god at surfe i den vindretning. getWindDirString() : Giver vindretningen men som en String fx NW, N, E etc. getIdealWindString() : Giver den ideelle vindretning at surfe på som en String. getLevelString : Returnerer sværhedsgraden af lokationen som String getJsonString() : Returnerer en JSON string, som indeholder alle værdierne i en given

lokation. Bruges fx til deling af en lokation mellem aktiviteter. fillDataFromJson(String JSON) : Fylder data fra JSON String ind i en surf lokation.

Services Vi bruger services i vores applikation til at lave baggrundskald, som ofte kan være lidt tunge og tage længere tid end hvad man kan bør køre på UI tråden uden at ødelægge brugeroplevelsen (og Android frameworket tillader). Vi har to services, en der opdatere databasen med vejr information på favorit surf lokationerne, og en der henter alle surf lokationer i Danmark og vejrforholdet på disse steder. Fælles for begge service er den måde de bliver deklareret i manifestet, det gøres som følgende:

12 of 23


<service android:name="dk.iha.itsmap.e15.grp5.surfbuddy.WeatherService" android:exported="false" /> <service android:name="dk.iha.itsmap.e15.grp5.surfbuddy.BrowseService" android:exported="false" />

IntentService IntentService benyttes i klassen WeatherService til at hente og opdatere de favorit lokationer som er gemt på databasen. Dette gør den ved at holde en asynkron tråd kørende rekursivt, der med et delay på 1200000ms kalder fetchWeather() funtionen. Servicen vil derfor konstant køre i baggrunden og hente ny data så længe MainActivity() bliver brugt. fetchWeather() funktionen henter ny data fra openweathermap.org for hver lokation gemt i

databasen. Når ny data er hentet sendes et broadcastIntent til klassen MainActivity , som kalder getAction() på denne modtagne Intent, for at verificere at det er det korrekte Intent vi har modtaget. Er det en WEATHER_UPDATE action, hentes alle favorit lokationerne på ny og sendes til adapteren cardiewAdapter , som opdaterer hvert card view.

private BroadcastReceiver receiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent){ if(intent.getAction().equals(WeatherService.WEATHER_UPDATE)) { favorites = db.getAllLocations(); if(!favorites.isEmpty()) { mAdapter = new CardviewAdapter(favorites, R.layout.main_activity_card_view, getApplicationContext()); mRecyclerView.setAdapter(mAdapter); warningText.setText(""); } } } };

WeatherService startes gennem et Intent på onStart() i MainActivity , hvilket betyder at

servicen starter op så snart aktiviteten bliver synlig for brugeren. Er der ingen favoritter sender der stadigt et broadcast fra servicens side men intet sættes i gang så længe databasen er tom.

Service Vi bruger en bindService til at hente vejr data for alle surf lokationer i Danmark og udregner derefter vejrforhold og distance. Disse informationer præsenteres så for brugeren af applikationen der nu kan vælge at se mere information om en lokation. Alle surf lokationer i Danmark er gemt i en CSV fil.

13 of 23


For at lave denne service, extender vi klassen Service og overskriver onBind metoden. Metoden returnere en IBinder som ofte laves som en indre klasse, der extender Binder og returnere en service. Dette gøres så du fra den antivitet der binder servicen kan kalde metoder på servicen. I aktiviteten laver vi en connection variable, som sørger for at skabe en connection til selve servicen og kalde eventuelle metoder, som skal kaldes så snart servicen er bundet. Som det ses i eksemplet henter vi servicen og begynder at hente data, samtidigt sætte vi en variabel som viser os om forbindelsen til servicen er aktiv.

private ServiceConnection con = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { BrowseService.BrowseBinder binder = (BrowseService.BrowseBinder) service; mService = binder.getService(); mService.startFetcher(); mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { mBound = false; } };

Aktivitetens livscyklus metoder onStart og onStop sørger for at binde og unbinde servicen. Selve servicen BrowseService henter alle surf lokationer i Danmark, og henter vejr data for disse lokationer. Dette gøres ved at tilgå Openweathermaps REST API og behandle JSON resultatet herfra.

URL url = new URL("http://api.openweathermap.org/data/2.5/weather?units=metric" + "&lat=" + sf.getlatitude() + "&lon=" + sf.getLongitude()); HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();

Værdier fra resultatet tilføjes til surf lokationen. Derudover kaldes getLastKnownLocation() hvor afstanden til lokationen udregnes, samt notere timestamp for opdateringen. Mens den looper igennem denne liste af surf lokationer sender den broadcasts tilbage til den browse aktiviteten, som har bundet den. Når alle lokationerne er opdateret, kan aktiviteten anmode om hele listen af surf lokationer som den kan præsentere for brugeren.

14 of 23


Adapters For at lave et custom list view med den data vi har at tilbude brugeren, laver vi vores egen ListViewAdapter og RecuclerView adapter. I disse adaptere sætter vi designet for hver row

i listen og udfylder indholdet i dette design. Herefter tilføjer vi vores adapter til listen.

listview.setAdapter(browserAdapter);

ArrayAdapter Vi burger en custom adapter til visning af alle surf lokationer i Danmark, som vi får fra vores service. For at lave en custom adapter skal den extende ArrayAdapter<E> og man skal overskrive getView() metoden. For at gøre det nemt, har vi oprettet en indre klasse, som har variabler svarende til variablerne i vores custom layout for hver row.

static class ViewHolder{ TextView locationName, windSpeed, dist; ImageView windDirection; }

Denne klasse kan man så lave med en reference til en custom layout fil:

LayoutInflater inflater = ((Activity) context).getLayoutInflater(); row = inflater.inflate(layout, parent, false);

Når dette er gjort hentes de forskellige views i layoutet og udfyldes med dataen der hentes fra den ArrayList som sendes med. Når alt på hver række er sat op, returneres den og kan blive vist i listen.

RecyclerView.Adapter Til visning af favorit lokationerne benytter vi os af to nye widgets, introduceret sammen med Android Lollipop. Disse er henholdsvis CardView og Recyclerview . Begge widgets er lavet til at gøre det nemt at bygge lister op om de guidelines som det nye Material design bygger på. For at vise indhold i et RecyclerView Indsættes først et RecyclerView i main layout filen.

15 of 23


<android.support.v7.widget.RecyclerView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".CountryActivity" />

Det ovenstående view i main layout filen for aktiviteten, beskriver det område hvori listen af cards vil blive vist. I vores tilfælde er det hele skærmen så width og height er derfor sat til match_parent . Tilføjelse af indhold til et RecycleView foregår ved hjælp af en RecyclerView.Adapter hvilket er en klasse der søger for at lave et nyt view ud fra et givet

layout for hver entry i et givet dataset. Hver entry givet til adapteren er i vores tilfælde en SurfLocation , og layoutet for hvert card er givet i layout filen main_activity_card_view . I

vores CardView layout fil, er det layout vi ønsker for hvert card enkapsuleret i <android.support.v7.widget.CardView> . Når adapteren bliver kaldt i onCreate fra mainActivity gives main_activity_card_view med som argument, hvorefter adapteren

laver et nyt card for hver entry der er i den givne liste.

Database For at gemme favoritter, så de fortsat er på listen efter man lukker applikationen og genåbner den, har vi benyttet os af en SQLite database lokalt på enheden. For at gøre det nemt for fremtidig skalering, samt bedre overblik har vi placeret database styringen i en separat klasse Database.java . Denne klasse har metoder der åbner og lukker forbindelsen til databasen,

samt følgende metoder til at servicere vores applikation:

public long addLocation( String name, int idealDirection, int level, double lat, double lon); public void updateLocation( long id, int direction, double wind, double temp, double waveHeight, String desc); public void removeLocation(long id); public ArrayList<SurfLocation> getAllLocations();

16 of 23


Disse metoder gør det muligt for os at trække den data ud vi skal bruge, samt fjerne, opdatere og indsætte ny data. Hver metode benytter database hjælperen.

Database helper Database hjælperen er en implementation af SQLiteOpenHelper i filen DatabaseHelper.java . Vi definerer en række konstanter i starten af klassen, for at kunne referere til: database, tabel og kolonner. Denne data bruger vi til at opdatere vores database, samt oprette den, ved at overskrive de to metoder: onCreate() og onUpgrade() . Ud over dette har vi lavet database hjælperen til en singleton , da vi så sikre at der kun bliver instantieret et objekt af denne. Dette er gjort ved at kalde klassen via getInstance() metoden:

public static synchronized DatabaseHelper getInstance(Context context) { if (instance == null) { instance = new DatabaseHelper(context.getApplicationContext()); } return instance; }

Når vi kalder denne metode, sikre klassen sig at der kun bliver instansieret et objekt, da den vil returnere sin egen instans (gemt i feltvariablen instance ) hvis den er initialiseret, og ellers instansiere en ny hvis den ikke allerede eksisterer.

Filereader For at give applikationen nogle surf lokationer at kigge efter, har vi oprettet en CSV fil ud fra data hentet på http://www.kitemekka.net/ . Den indsamlede data er hentet via et crawler script skrevet i JavaScript, da siden ikke tilbyder en offentlig API. Det er værd at nævne, at i en ideel kontekst ville denne data være hentet dynamisk via et HTTP kald, som når vi henter vejr information. CSV filen der indeholder denne data er gemt i res/raw mappen. Filen surfspots.csv indeholder data om stedets navn, koordinat, ideelle vindretning, sværhedsgrad og beskrivelse. For at hente informationen fra filen benytter vi os af en BufferedReader .

InputStreamReader inputReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputReader);

Med denne reader går vi gennem hver linje i filen, og splitter dataen til et SurfLocation objekt

17 of 23


vi kan sende videre i en ArrayList til sidst i metoden.

String[] row = line.split(";"); SurfLocation sl = new SurfLocation();

Open Street Maps Til visning af kort i vores applikation har vi valgt at benytte os af Open Street Maps's API. Vi har valgt at bruge denne løsning til kort, da den har nogle fordele i forhold til blandt andet Google Services Google Maps API. For det første støtter det et Open Source projekt, og det er brugerne som selv udbyder dataen til systemet. Ud over dette, deler vi ikke dataen med Google, hvilket gør brugerne af systemt mere anonym end ellers. Alle kan benytte sig af Open Street Maps også på tværs af platforme. Det kræver ikke nogen API key, som fx ved Google maps hvilket gør det meget nemt at komme i gang med.

Implementering For at benytte denne API i vores applikation, benyttede vi os af osmdroid biblioteket, ved at tilføjede den i vores gradle fil ( osmdroid bruger endvidere også slf4j logging biblioteket).

compile 'org.osmdroid:osmdroid-android:4.3' compile 'org.slf4j:slf4j-android:1.7.12'

Når bibliotekerne er tilføjet, kan man i sine layout filer importere osmdroid's MapView og få et kort vist:

<org.osmdroid.views.MapView android:id="@+id/mapview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:clickable="true"/>

I vores applikation har vi sat nogle indstillinger på viewet, og de bliver sat i selve koden ved at kalde metoder på elementerne fx som disse:

18 of 23


map = (MapView) view.findViewById(R.id.mapview); map.setBuiltInZoomControls(false); map.setMultiTouchControls(true); map.getController().setZoom(12); map.getController().setCenter(surfPoint);

Overlay For at giver ekstra information til brugeren af applikationen om hvor surf lokationen er, samt hvor de befinder sig i forhold til denne, har vi benyttet os af et overlay på kortet. Dette overlay består af en rød pin til at vise surf lokationen, samt en blå cirkel til at vise brugerens lokation (som man kender det fra Google Maps). Der viste sig at være rigtig mange muligheder når det kommer overlays på osmdroid , hvilket gjorde det en smugle vanskeligt at konfigurere. Vi endte derfor med at implementere vores egen metode der tager imod et punkt, en drawable og et MapView, for at gøre det nemt for os at tilføje disse.

Vi starter med at oprette et nyt OverlayItem med nogle standard informationer, da vi ikke benytter os af dem:

OverlayItem myLocationOverlayItem = new OverlayItem("Title", "Desc", geoPoint);

Vi sikrer os derefter at ikonet har den rigtige størrelse ved at lave det til et Bitmap ændre størrelsen og konvertere det til et Drawable objekt igen. Vi tilføjer derefter elementet til en liste og laver et ItemizedIconOverlay objekt.

ItemizedIconOverlay<OverlayItem> ol = new ItemizedIconOverlay<>(items, new ItemizedIconOverlay.OnItemGestureListener<OverlayItem>() { public boolean onItemSingleTapUp(final int index, final OverlayItem item) { return true; } public boolean onItemLongPress(final int index, final OverlayItem item) { return true; } }, resourceProxy);

Vi lader de forskellige funktioner for overlayet stå tomme, da vi ikke har behov for nogen funktion når man trykker på et punkt. Til sidst tilføjer vi listen til kortet og det bliver derved vist på den korrekte lokation og skalerer

19 of 23


korrekt takket være osmdroid .

map.getOverlays().add(currentLocationOverlay);

Location Services Applikationen benytter brugerens lokation til at at udregne distance til surf lokationerne og til at vise brugerens placering på kortet. Da applikationen ikke er afhængig af en kontinuerligt opdateret lokation til brug i browse listen, benytter vi nedenstående kald for at hente den sidst kendte lokation som telefonen har udregnet. Denne metode sparer tid og energi da man ikke skal starte GPS'en op og vente på callbacks fra en locationManager . Opdateringer af GPS lokationen sker således når andre applikationer tilgår locationManageren, eller når android frameworket selv opdaterer den.

locationManager = (LocationManager)view.getContext() .getSystemService(Context.LOCATION_SERVICE); Criteria criteria = new Criteria(); provider = locationManager.getBestProvider(criteria, false); location = locationManager.getLastKnownLocation(provider);

Denne metode kan resultere i fejl hvis telefonen eksempelvis slukkes og flyttes til en ny lokation. Her er det muligt at den sidst kendte lokation er fra før telefonen blev slukket, og dermed mere eller mindre upræcis. Vi har valgt at tilgodese denne usikkerhed da det ikke er en fejl der vil ske ofte, og grundet at informationen skal være et approximat til sammenligning af lokationerne hvor en højere usikkerhed er acceptabel. For at vise lokationen på kortet, er det derimod nødvendigt at lokationen bliver opdateret kontinuerligt. Da man skal kunne have kortet fremme og se opdateringerne på kortet efterhånden som man flytter sig. Derfor benytter vi en LocationListener til kortet, der opdaterer markeren på kortet når onLocationChanged() bliver kaldt. For at få kontinuerlige opdateringer laver vi i onCreateView() 2 kald på location manageren, hvilket giver opdateringer fra netværks og GPS provideren. Vi ønsker opdateringer fra begge providers for at få så præcis en position som det er muligt, hvilket kan variere givet forholdene telefonen er brugt i. For eksempel vil netværks provideren give de mest præcise resultater inde midt i en by hvor det er tæt dækket af hot-spots.

locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this); locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1000, 1, this);

20 of 23


Ovenstående kald beder om opdateringer maks hvert sekund hvis forskellen på lokationen er mere end 1 meter. For at få adgang til netværk og GPS provideren skal nedenstående permisssions oprettet i android manifestet.

<uses-permission <uses-permission <uses-permission <uses-permission

android:name="android.permission.ACCESS_GPS" /> android:name="android.permission.ACCESS_COARSE_LOCATION" /> android:name="android.permission.ACCESS_FINE_LOCATION" /> android:name="android.permission.ACCESS_NETWORK_STATE" />

For ikke at modtage flere opdateringer når kortet ikke benyttes, kaldes locationManager.removeUpdates(this) under onPause() , hvilket betyder at vi ikke

modtager flere updates før vi igen kalder requestLocationUpdates() på onResume() .

Kommunikation på tværs i applikationen Activities imellem For at få information imellem aktiviteter bruger vi Intent og giver den ekstra værdier med via putExtra() metoden. Vi sender kun informationen omkring en SurfLocation videre, og det

gøres som følgende eksempel.

Intent intent = new Intent(context , LocationInformationActivity.class); intent.putExtra("surfLocation", surfLocation.getJsonString()); context.startActivity(intent);

I vores LocationInfromationActivity klasse kan vi så bruge denne streng til at bygge et SurfLocation objekt med de præcis samme informationer, uden at gennem den i fx en

database.

surfLocation = new SurfLocation(); surfLocation.fillDataFromJson((getIntent().getStringExtra("surfLocation")));

Services til Activities Til at modtage information fra vores services tilbage i vores aktiviteter, bruger vi BroadcastReceivers . Dette er en klasse der lytter på broadcasts og er klar til reagere på dem

hvis der kommer et den kender. Som et eksempel bruger vi en BroadcastReceiver til at lytte på hvor langt vores WeatherService er med sit arbejde.

21 of 23


Reciveren er instantieret som en variable i vores klasse, hvor vi overskriver onRecieve metoden. Vi tjekker så om det er et af de broadcasts vi lytter efter som kommer, og handler derefter. I dette eksempel opdatere den på en type broadcast der fortæller hvor langt vores WeatherService er kommet og på den anden opdatere den UI'et så snart WeatherService er

færdig med sit arbejde.

private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if(intent.getAction().compareTo(mService.UPDATE_IS_COMMING) == 0){ updateUI(); } else if(intent.getAction().compareTo(mService.UPDATE_PROGRESS) == 0){ progressText.setText(""+intent.getIntExtra("progress", 0)+"%"); } } };

For at starte reciveren så den lytter på det rigtige (i dette tilfælde UPDATE_IS_COMMING ), bruger vi følgende kode i vores onResume metode:

IntentFilter filter = new IntentFilter(mService.UPDATE_IS_COMMING); registerReceiver(mReceiver, filter);

Herefter burger vi unregisterReceiver(mReceiver) metoden i vores onPause livscyklus metode. I vores service bruger vi sendBroadcast(broadcastIntent) til at sende et broadcast, med et intent der er lavet med argumentet svarende til den streng reciveren lytter efter. For at få alt kommunikationen til at virker bruger vi intentFilter på den aktivitet der indeholder broadcast Receiveren. Dette sættes i manifestet som følgende:

<intent-filter> <action android:name="BrowseService.UPDATE_IS_COMMING" /> </intent-filter> <intent-filter> <action android:name="BrowseService.UPDATE_PROGRESS" /> </intent-filter>

Deployment Vi har udgivet applikationen til en .apk fil så den nem kan installeres på alle kompatible enheder. For at udføre en deployment til en .apk , skal man bruge en deployment key. Denne key genereres gennem Android Studio hvorefter den gemmes lokalt på computeren til brug ved alle fremtidige udgivelser af .apk filer.

22 of 23


Den nyeste .apk version af vores app kan findes under dette ink: http://cs.au.dk/~sditlev /itsmap/SurfBuddy.apk

Konklusion Vi havde en vision om at hjælpe surfere med at få et bedre overblik over surf lokationer i Danmark. Vi ville give dem nem adgang til at se om vind og vejr forholdene var ideelle til surf uden at manuelt skulle bladre gennem vejr udsigter for hvert enkelt surf lokation i nærheden. For at afhjælpe denne møjsommelige proces har vi brugt en kobling af information omkring surf lokationer i Danmark og vejrforholdene på den pågældende position. Vi har i vores applikation snakket sammen med en server, som feeder aktuel vejr information til applikationen. Denne information er samlet i et brugervenligt interface der hurtigt giver brugeren et overblik over surf lokationer i området. Overblikket indebærer et kort, vind og vejr forhold. I vores applikation har vi, for at udnytte Android frameworket bedst muligt, benyttet os af de nyeste widgets og design retningslinjer fra Google. Dette giver applikationen et nutidigt look som passer til designet benyttet i resten af android systemet. Dog betyder dette at applikationen i sin nuværende form ikke kører på enheder der benytter sig af ældre versioner af Android end Lollipop.

23 of 23


Surfbuddy