Visar inlägg med etikett Programming. Visa alla inlägg
Visar inlägg med etikett Programming. Visa alla inlägg

2013-11-11

parse.com-php-library - Contributed with a function in Github

We are using parse.com as the server backend in a project. The administration interface is written in PHP so we are using parse.com-php-library to easily integrate with parse.com.
parse.com-php-library is open source and available on GitHub.

Today we needed a function to filter our records on a specific date in the database.
The most obvious way to solve it was to combine a call to whereGreaterThanOrEqualTo() with a call to whereLessThanOrEqualTo(), but it will not work since it will handle it as an OR query.

The solution was to add a new function to parseQuery.php:
public function whereBetweenOrEqualTo($key, $startValue, $endValue){
   if(isset($key) && isset($startValue) && isset($endValue)){
      $this->_query[$key] = array(
         '$gte' => $startValue,
         '$lte' => $endValue
      );
   }        
   else{
      $this->throwError('the $key, $startValue and $endValue parameters must be set when setting a "where" query method');                
   }
}

We could just have added it to our own parseQuery.php and continued to work, but since it is open source and on GitHub it is very easy to contribute to the original project.

  1. Fork the project you would like to contribute to - Go to the project at the GitHub web page and click the Fork button.
  2. Clone the code to your computer. There is an application for GitHub that will handle repositories on GitHub for you which will make cloning, committing and other tasks very easy.
  3. Make changes to the code.
  4. Commit your changes to your local repository. Use the GitHub application to make it easier.
  5. Push your changes to the remote server. It is called Sync in the GitHub application.
Your changes are now in your the repository that you forked. Make a pull request to let the original repository now that you have made some great additions and changes to the code.

  1. Go to your project and click Pull requests and then New pull request.
  2. Follow the instructions to create a pull request for your changes and write an informative comment: "Fixes #117. Query for between or equal to start and end value."
    Hash + issue number will automatically be linked to the issue number.
  3. If the owner of the original repository likes the changes he/she will add them to the repository.

2012-11-13

Obfuskera Androidapp

Den Androidapp jag ska obfuskera är MantisDroid och den består av ett library (MantisDroidLibrary) och 2 appar: en gratisversion (MantisDroid Free) och en betalversion (MantisDroid).
Jag använder ProGuard för obfuskering och optimering. Det verktyget följer med Android SDK:n.

Först måste konfigurationsfilerna för ProGuard rättas till. I foldern <android-sdk>/tools/proguard/ ska filerna proguard-android.txt och proguard-android-optimize.txt editeras:
#-keep public class com.google.vending.licensing.ILicensingService
#-keep public class com.android.vending.licensing.ILicensingService
Dvs. kommentera bort de 2 raderna eftersom de annars ger fel i obfuskeringen.

Jag använder Ant för att bygga mina projekt så om du inte redan gjort det så kör följande kommando i din projektkatalog:
android update project -p .
Den skapar upp bland annat build.xml och lite andra filer som behövs.

Börja med projektet MantisDroidLibrary.
Den ska inte obfuskeras i sitt projekt utan gör det när apparna byggs och obfuskeras.
Se till att obfuskering inte är aktiverad genom att titta i project.properties och verifiera att direktivet proguard.config inte är aktiverad.

De två apparna har precis samma inställningar så gör samma sak i båda.
Öppna project.properties och kommentera fram följande för endast obfuskering:
proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
Om du vill ha både obfuskering och optimering så lägg till följande:
proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt

I filen proguard-project.txt i projekt så ska nu projektspecifika regler läggas till. Skapa filen om den inte redan finns.
I min skriver jag:
-dontwarn org.xmlpull.v1.**
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}
-keep public class com.google.android.vending.licensing.ILicensingService

Den första regeln döljer alla varningar för klasser i paketet org.xmlpull.v1. Det gör jag eftersom det blir krockar mellan klasser i android.jar och ett library (ksoap2-android) som jag använder. I mitt fall är det ingen fara utan helt ok.
Den andra regeln är en optimeringsregel som ser till att optimera bort alla anrop till Log.d(...) och Log.v(...). Anledningen till det är att de loggningarna inte ska komma ut i mina apk:er eftersom de avslöjar vad olika delar av min kod gör. Samtidigt så ska inte debug- och verbose-loggning finnas ute i kundernas installationer.
Den tredje regeln är för att jag använder Google Play Licensing och inte vill att den ska optimeras bort.

För att signeringen ska fungera så behöver man fylla i key.store och key.alias i filen ant.properties:
key.store=mantisdroid_release_key.keystore
key.alias=mantisdroid

key.alias.password=[your_password]
key.store.password=[your_password]
Library-projektet behöver inte signeras så skriv inte någon key.store eller key.alias där.

Kompilera, obfuskera, optimera och signera sedan appen med Ant genom att köra (i projektets folder):
ant release

2012-09-29

Gratis- och betalapp med samma kodbas

Jag har efter lanseringen av MantisDroid insett att jag även behöver publicera en gratisversion, så att man kan ladda hem appen och se om den är något att ha innan man betalar för fullversionen. Visst, man har 15 minuter på sig att testa en betalapp, men det är inte många som gör det eftersom 15 minuter är så väldigt kort tid.

Lösningen är alltså att skapa en gratisversion av min app med begränsad funktionalitet.

Hur gör man en gratisversion utan att duplicera all kod?

Det är faktiskt ganska enkelt. En app kan använda en annan app och referera till den som ett library (bibliotek). På så sätt kan den nya appen utnyttja allt i den andra. Dock kan inte en app som markerats som library längre vara en app som går att installera på en enhet. Därför behövs det 3 projekt: En betalapp, en gratisapp och en library-app. I mitt fall så ligger all kod i betalappen som redan är publicerad på Play Store.
  1. Skapa library-appen genom att skapa ett nytt projekt med lämpligt paketnamn, ex. se.nextsource.android.mantisdroid
    Det är samma paketnamn som i betalappen, men det gör ingenting.
  2. Öppna library-appen i Eclipse, ta fram properties för projektet, klicka på Android och markera checkboxen "Is Library".
  3. Flytta kod och resurser som ska vara gemensamma från betalappen till library-appen.
  4. Öppna properties för betalappen, klicka på Android och lägg till library-appen som ett Library.
  5. Skapa gratisappen som ett nytt projekt med nytt paketnamn, ex. se.nextsource.android.mantisdroidfree 
  6. Öppna properties för gratisappen, klicka på Android och lägg till library-appen som ett Library.
  7. Deklarera de komponententer (activity, service, receiver, provider etc. och även permissions, uses-library etc.) som gratis- och betalappen använder från library-appen.
  8. Nu har du 2 appar med exakt samma kodbas men med olika paketnamn.
  9. För att särskilja versionerna mellan varandra så jag lagt till en ny Application-klass i gratis- och betalapp-projekten som ärver från en Application-klassen som ligger i library-appen. Vid onCreate så sätts vilken version som körs i superklassen och sedan anropas super.onCreate(). I superklassens onCreate så slår jag av och på olika funktioner som ska skilja applikationerna åt.
Ref: http://developer.android.com/tools/projects/projects-eclipse.html

Problem jag stötte på
-Se till att ingen duplicering av kod eller res-filer finns för då bygger det inte!
-Från SDK 14 så går det inte att använda resId i switch-case i ett library eftersom de inte längre är final så ändra det med refactor-funktionen i Eclipse till if-else. Irriterande, men det finns en bra förklaring:
http://android-developers.blogspot.se/2011/10/changes-to-library-projects-in-android.html
-Proguard fick svårt att obfuskera koden i biblioteket så jag var tvungen att lägga till följande i proguard.cfg:
-keep public class com.google.android.vending.licensing.ILicensingService
-dontwarn org.xmlpull.v1.**
-dontwarn android.support.**
Obs! Tänk på att lägga till det i alla 3 proguard.cfg.

Resultatet
Nu när allt är klart så behöver jag bara göra följande för att införa kodändringar, bygga och publicera mina 2 appar:
  1. Gör ändringar och fixar i library-appen.
  2. Ändra versionsnumret i apparna så att de stegas upp.
  3. Kör kommandot: ant release på båda apparna så att de bygger, obfuskeras och signeras för release.
  4. Testkör båda apparna så att de fungerar och ändringarna är testade.
  5. Lägg upp de 2 nya apk:erna på Play Store.
  6. Klart!

2012-05-21

Problem med View Swiping och orientation changes


Jag har byggt en app med View Swiping enligt den här artikeln. Jag har gjort några ändringar för att det ska passa min app.

Min implementation:
En FragmentActivity som har en ViewPager.
ViewPager har en FragmentPagerAdapter.
I FragmentPagerAdapter så skapas det upp 6 stycken ListFragment.
Varje ListFragment har en ArrayAdapter som ska hålla sina Items.
Jag lägger till Items till varje adapter beroende på vilken status ett item har med:
fragmentPagerAdapter.getItem(listIndex).getAdapter ().add(item);
Det fungerar galant första gången jag visar mina listfragment.

Problemet:
När jag vrider på telefonen så skapas allt om via onDestroy() och onCreate() som det ska, men mina listfragment blir tomma. Jag har skapat om dem så det borde bli samma sak som första gången jag laddar allt.
Felet beror på att Android återanvänder listfragmenten som skapades första gången och de är inte samma som jag lägger till mina items till. Därför blir listorna tomma.
Jag använder getItem() för att få tag i mitt listfragment, men det gör inte Android. Den anropar inte ens getItem() andra gången utan letar reda på dem via instantiateItem().

Min lösning:
Lösningen känns lite som ett hack, men fungerar väldigt bra (det skapas dock upp några extra fragment, men det är nog inte jätteofta som användarna kommer att vrida telefonen fram och tillbaks just i listvyn där det här används):
Gör en override på: public Object instantiateItem(ViewGroup container, int position) i FragmentPagerAdapter:
Kod:
@Override
public Object instantiateItem(ViewGroup container, int position) {
ItemsFragment fragment = (ItemsFragment) super.instantiateItem(container, position);
if(fragment.getAdapter() == null) {
ItemsFragment myFragment = getItem(position);
fragment.setListAdapter(myFragment.getAdapter());
fragment.setTitle(myFragment.getTitle());
}
return fragment;
}
Notera: Mitt ItemsFragment har en ny metod getAdapter() som returnerar en typad adapter "ItemListAdapter" och nya metoder för set och getTitle() som används för att få rätt texter på PagerTitleStrip i ViewPagern.

Jag använder min metod getAdapter() för att kolla om det funna fragmentet har en adapter. Om den inte har det så tar jag en adapter via metoden getItem() som returnerar fragments som jag har kontroll på.

Ni kanske undrar varför jag helt enkelt inte bara lägger till:
android:configChanges= "orientation|keyboardHidden|screenSize"
i manifestet. För det första så ska den tekniken undvikas eftersom det gör hanteringen av resources onödigt komplicerad. Vidare så hanteras inte ActionBar och PagerTitleStrip korrekt.

Nästa steg nu är att använda:
onRetainCustomNonConfigurationInstance/getLastCustomNonConfigurationInstance
för att slippa ladda om all data mellan vridningarna av telefonen.

Ref: Mitt inlägg på Swedroid

2012-05-13

Automatiserad testning av Android-appar

Jag har den här veckan lagt in Robotium som testverktyg för den Android-app, MantisDroid, som jag håller på och utvecklar. Med hjälp av Robotium så kan jag sätta upp helt automatiserade tester av hela appen som kan köras både på emulator och riktig device. Jag har även byggt in stöd för att samtidigt testa mot flera olika versioner av MantisBT (en buggtracker som hanterar defekter för projekt) som är den servertjänst min app går mot.


Robotium är ett testverktyg som kör tester mot användargränssnittet på en app som kör på antingen en emulator eller riktig device. Den simulerar klick på knappar och andra GUI-kontroller, text-hantering i textboxar, den verifierar att olika kontrollera och texter hittas m.m. Kolla på videon ovan för att få en snabb introduktion i hur det fungerar.

Jag har byggt upp mina tester i en klass för varje typ av test, dvs. en klass som hanterar Login-hantering, en för Project och en för Issues.För att hantera flera olika versioner av MantisBT har jag skapat en Enum (MantisBTUrlEnum) som håller i URL:erna för de olika versionerna. Varje testfall loopar över alla värden i Enum:en och plockar ut den URL som ska testas och kör testet mot den. Det är alltså viktigt att återställa miljön efter varje körning så att nästa loop har samma förutsättningar som varvet innan. Om jag varit testpuritan så skulle jag kanske skapat ett testfall för varje version, men jag tycker att även bland testfallen så ska duplicering av kod undvikas. En idé som slog mig nu var att bryta ut själva testet i en egen metod och sedan låta varje testfall anropa den metoden med den URL som ska testas. Nu kommer jag dock att behålla min implementation för den fungerar bra och för tillfället har jag bara en enda URL att testa mot. När det blir fler URL:er så kanske jag bryter isär testfallen om det är fördelaktigt på något sätt.

Tips:

Använd inte hårdkodade strängar. Om ni gör det så kommer testerna att fallera om en sträng ändras eller om ni lägger till fler språk. Använd istället: solo.getString(id);
Ex.
För att hämta strängen "Login" så skriver ni:
solo.getString(se.nextsource.android.mantisdroid.R.string.login);
För att hämta "OK" som ligger inbyggt i Android så skriver ni:
solo.getString(android.R.string.ok);

Jag har stött på följande problem:

Problem - returnerar fel aktivitet vid uppstart:
I setUp() vill jag kontrollera att jag är på rätt aktivitet, men solo.getCurrentActivity() ger fel aktivitet som svar. Den returnerar den aktivitet som angetts vid uppstart. I min applikation anger jag LoginActivity som startaktivitet, men om användaren redan är inloggad så flyttas den automatiskt till MainActivity. Robotium har inte koll på det.
Lösning:
Lägg en sökning efter en textsträng som finns på den aktivitet du önskar starta med i en while-loop. I mitt fall är det en knapp med texten "Login". Loopa tills den strängen hittas. Inuti while-loopen så kontrollerar jag om jag är på MainActivity genom att söka efter en viss textsträng där och om den hittas så tar jag upp menyn och klickar på "Logout":
solo.clickOnMenuItem("Logout"));
I alla andra fall så trycker jag på back-knappen:
solo.goBack();

Problem - hittar inte knappar när soft keyboard är synligt:
När man klickar på en EditTextPreference i en PreferenceActivity så får man på en verklig device upp soft keyboard eftersom textfältet får fokus automatiskt. När soft keyboard är uppe så går det inte att klicka på "OK"-knappen. Knappen hittas med solo.searchButton("OK"); men är inte klickbar. På emulatorn inträffar inte det här eftersom soft keyboard inte kommer fram. Det saknas metoder i Robotium för att kontrollera om soft keyboard är framme eller inte.
Lösning:
Skapa en metod som stänger soft keyboard och använd den då soft keyboard automatiskt poppar upp:

@SuppressWarnings("static-access")
private void closeSoftKeyboard() {
InputMethodManager imm = (InputMethodManager)getActivity().
getSystemService(getActivity().INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(solo.getEditText(0).getWindowToken(), 0);
}

Problem - kan inte klicka på knappar i ActionBar:
Robotium saknar stöd för att klicka på knappar i ActionBar.
Lösning:
Använd Instrumentation och id:et för knappen för att klicka på den:
getInstrumentation().invokeMenuActionSync(solo.getCurrentActivity(), se.nextsource.android. mantisdroid.R.id.menu_settings, 0);

2012-02-02

Blockera besökare på Blogger/Blogspot

Googles bloggverktyg Blogger/Blogspot har inte någon blockeringsfunktion för enskilda användare. Det är allt eller inget genom inställningen under Inställningar - Basfunktioner - Tillstånd - Bloggläsare. Rättighetern som finns tillgängliga är Alla, Endast bloggskribenter och Enbart de här läsarna. Det fungerar bra för bloggar man vill hålla privata till ett begränsat antal läsare, men fungerar inte alls bra när man vill blockera några få.

Går det att blockera enskilda användare även fast det saknas stöd i Blogger?
Ja, med hjälp av lite javascript och klipp och klistra så löser man det genom att blockera vissa IP-adresser eller host-namn.
Det går hyffsat lätt att ta sig förbi skyddet genom att använda en proxy, men jag tror ändå att ett skydd byggt med javascript hindrar majoriteten av de man vill slippa. En proxy kräver ändå ett antal extrasteg och ofta att man betalar för tjänsten.

Hur gör man?
Jag har hämtat inspiration från följande sidor:
http://www.javascriptkit.com/script/script2/blockip.shtml
http://stackoverflow.com/questions/102605/can-i-lookup-the-ip-address-of-a-hostname-from-javascript

Gå till Blogger (kontrollpanelen för bloggarna på Blogspot) och klicka på din blogg.
Gå till menyvalet Layout och klicka på "Lägg till Gadget".
Rulla ner till "HTML/JavaScript" och tryck på +-knappen för att lägga till den.
Skriv ingen titel utan klistra endast in följande i rutan för Innehåll:
http://pastie.org/3301106 (Klicka på länken och hämta koden från Pastie)

Ändra följande rad och fyll i de IP-adresser du vill blockera (exempel på 2 olika adresser):
var bannedIps=["23.23.23.23", "11.11.11"]
Skriptet matchar början på en IP-adress så för att blockera alla IP-adresser som börjar med 23.23.23 skriver du följande:
var bannedIps=["23.23.23"]
Det går även att matcha hostname om du kör din egen tjänst för att slå upp IP-adresser och hostname. Blockera hostname genom att ändra följande rad:
var bannedHosts=["host1.se", "host2.se"];
Tryck slutligen på Spara
Det läggs nu till en gadget som heter "HTML/Javascript" och kommer skicka alla besökare med de inmatade IP-adresserna till http://www.google.com
Om du vill ändra adress till var de skickas så ändrar du adressen i följande rad:
window.location.replace("http://www.google.com");

Vad gör skriptet?
Den börjar med att se till att det garanterat finns en funktion för att hämta ut alla element via ett klassnamn. Vissa webbläsare har inte metoden getElementsByClassName.
Sedan kommer själva blockeringsfunktionen som matchar en lista med blockerade IP-adresser mot den IP-adress som skickades till funktionen. Om den får en träff så döljer den alla div:ar som har klassen "content" och skickar sedan vidare användaren till http://www.google.com
Därefter kommer en callback-funktion som används för att ta emot svaret från tjänsten som tar fram IP-adressen. Svaret är i json så den tar ut IP-adressen ur svaret och skickar den vidare till blockeringsfunktionen.
Allra sist kommer anropet till tjänsten som kollar upp IP-adressen på besökaren och genom att ange en callback-parameter så triggar den min callback-funktion som slutligen triggar blockeringsfunktionen.

Kända problem
Tjänsten för att kolla upp IP-adresser och sedan anropa min callback-funktion blir lätt överbelastad eftersom många använder den. Det som händer är att tjänsten ligger på Google App Engine och utvecklaren kör bara gratisvarianten vilket bara tillåter ett visst antal requester per dygn. När det inträffar så kommer blockeringsfunktionen att sluta fungera tills tjänsten får ny quota.

Min lösning på det är att tillhandahålla min egen tjänst skriven i PHP. Den är inte publik och om jag släpper URL:en dit så riskerar den att bli överbelastad. Det är enkelt att skapa det själv med följande PHP-kod:
http://pastie.org/3301306 (Klicka på länken och hämta koden från Pastie)

PHP-skriptet skickar först ut headers som anger att det är en response i JSON och att inget ska cachas. Sedan plockar det ut IP-adress, hostname och eventuell callback-parameter och skriver ut det i JSON-format. Om en callback-parameter är angiven så läggs JSON-svaret som ett argument till angiven funktion. Det gör att när man bäddar in skriptet i en webbsida så kommer den automatiskt anropa callback-funktionen.

2012-01-12

HTML5, apputveckling och programmering


Jag började skriva ett inlägg på Swedroid om HTML5 och apputveckling, men det svävade iväg och blev mer allmänna funderingar och åsikter kring programmering och systemutveckling, så det passade bättre här.

HTML5 ska man absolut satsa på och är ett naturligt steg när det gäller webbutveckling och kanske enklare apputveckling. När väl stödet från webbläsarna blir tillräckligt stort så kommer det bli riktigt användbart för att slippa skapa så många klienter. Det ständiga sorgebarnet Internet Explorer håller tillbaks takten. Inte ens IE9 har tillräckligt stort stöd för HTML5. Vad kommer i version 10? Förhoppningsvis gör de en stor satsning på HTML5 och kommer ikapp sina konkurrenter på det området i alla fall.
Jämförelse mellan olika webbläsares stöd av HTML5.


Det kommer dock aldrig ta bort behovet av att man kan programmera på "riktigt" också. Sen om koden hamnar i appen eller på servern är en annan fråga. En app består sällan bara av klienten på telefonen. Det tillkommer oftast webbklient i HTML, Javascript + js-ramverk (jQuery, Dojo...) och CSS tillsammans med alla tänkbara tekniker och ramverk både på klient och server (JSP, PHP, Velocity...). Sen kommer själva servern (ofta Java på JBoss, Tomcat... eller varför inte Python på App Engine) och databasen (oftast någon typ av relationsdatabas, men populärt är även olika typer av NoSQL-databaser).

Det är sällan företag bara vill ha en app och inget mer. Försök att kunna leverera hela kedjan så att man förstår alla delar av applikationen och inte begränsar sig till bara appen på klienten. Det betyder inte att man alltid ska leverera hela kedjan, men förståelsen skapar bättre produkter.

Grundläggande tekniker för mig när jag arbetar, som jag använder så gott som varje dag och som jag tycker är bra att behärska något så när i alla fall eftersom man stöter på dem hela tiden:
-HTML4 och 5, Javascript + jQuery, CSS, JSON
-Java (SE + EE), webservices (soap och rest), servlets
-SQL och design av relationsdatabaser

Kul och användbara tekniker jag tycker man bör kolla upp så man har vet på vad det handlar om:
-Git och SVN (subversion) - det finns säkert många företag som kör Clearcase, Mercurial och annat men med Git och SVN så får man koll på hur de flesta versionshanteringssystem fungerar
-Enhetstestning och mockramverk - jUnit + Mockito underlättar massor när man gör enhetstester
-Continuous build and integration - Jenkins
-NoSQL
-App Engine