DMT – Drie Minuten Toets

Een van de meest succesvolle apps van HappyWorx is nog steeds FlitsWoorden. Vanwege dat succes heb ik gewerkt aan een mooiere, modernere versie. DMT is een app die gebaseerd is op hetzelfde idee, met een paar verbeteringen. In DMT kan gekozen worden voor 2 methoden van woorden tonen. De eerste is – gelijk aan Flitswoorden – door te tikken op het getoonde woord. De tweede laat na een bepaalde periode het volgende woord zien. Met behulp van een slider rechts in het scherm is in te stellen hoe snel de woorden getoond worden.

Het bouwen van DMT was ook het uitgelezen moment om meer aandacht te besteden aan de GUI. Ik heb met behulp van PaintShop Pro een aantal houten “cut outs” gemaakt, die getoond worden als knoppen. Voor het database gedeelte heb ik een methode gebruikt waar ik inmiddels goed bekend mee ben; De SQLite database, die geïnstalleerd wordt op de telefoon.

Tot slot bleek er nog een heel leuke snack in de ontwikkeling van deze app te zitten. Ik ben gaan zoeken naar een manier om spraakherkenning te doen, zonder dat daarvoor een Internet Connectie nodig is. PocketSphinx bleek hiervoor uitstekend geschikt. Ik heb een Proof Of Concept gemaakt, dat ik binnenkort ook zal publiceren. In deze PoC is instelbaar hoeveel woorden er getoond worden. Deze woordenlijst vormt ook direct het “grammatical dictionary” voor de spraakherkenning. Andere onderdelen van de engine zijn een acoustisch model en een linguistisch model. Door een fonetisch woordenboek toe te voegen kan de volledige spraakherkenning worden afgehandeld binnen de telefoon, zonder gebruik te maken van externe API’s. De resultaten zijn nog niet heel erg goed (veel valse positieven), maar als PoC is het zeker bruikbaar.

Terug naar DMT. Een echt nieuw concept – nog niet eerder toegepast in HappyWorx apps – is de Interstitial advertentie. Dit is een full-screen advertentie, die getoond wordt nadat de ingestelde lees-duur is verstreken. Pas na het wegklikken van deze advertentie wordt de leessnelheid weergegeven. Ik werk aan een in-app purchasing model, waarmee de advertenties voor de duur van een jaar kunnen worden afgekocht. Genoeg tekst, laten we eens een blik werpen op de code.

Om een Interstitial advertentie te maken, moet je uiteraard beschikken over een AdMob account. Maak een nieuwe advertentie aan. Deze codeblokken zorgen voor het inlezen en tonen van de advertentie;

        mInterstitialAd = new InterstitialAd(this);
        mInterstitialAd.setAdUnitId("ca-app-pub-8205015694325849/8830088010");

        mInterstitialAd.setAdListener(new AdListener() {
            @Override
            public void onAdClosed() {
                requestNewInterstitial();
                endShowResult();
            }
        });
        requestNewInterstitial();
    }

Als er – om welke reden dan ook – nog geen advertentie geladen is, dan willen we direct doorstappen naar de methode die het lees-resultaat toont. Daarom:

    public void initializeStopTimerTask() {
        myStopTimerTask = new TimerTask() {
            @Override
            public void run() {
                myTimer.cancel();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        myStopwatch.cancel();
                        tv_Word.setText("");
                        if (mInterstitialAd.isLoaded()) {
                            mInterstitialAd.show();
                        } else {
                            endShowResult();
                        }
                    }
                });
            }
        };
    }

Als de lees-tijd voorbij is en de advertentie is getoond, dan kan de endActivity worden gestart:

    public void endShowResult(){
        Intent i = new Intent(context, EndActivity.class);
        i.putExtra("ReadingTimeMin", mm);
        i.putExtra("ReadingTimeSec", ss);
        i.putExtra("NumberWordsRead", numberOfWordsRead);
        startActivity(i);
    }

In de EndActivity class worden de Integers ReadingTimeMin, ReadingTimeSec en NumberWordsRead gebruikt om de TextView te vullen. Later meer 🙂

Flash Colors – Game using seperate Sound Class

fklAnother app in the Google Play Market. This time, I focussed on how to create a seperate class to handle Sounds. The app is quite simple. It shows colors. The user can choose one of 3 playing modes; simple, medium (remember growing series) or hard (remember random series). It was a challenge to work with a seperate Sounds class, that would handle sound playing.

For this app, I used SoundPool. There is one other method to play sounds, MediaPlayer. The latter is for large sounds, music and so on. For Flash Colors, a small soundbite is sufficient, so SoundPool will do.

First, we create a seperate class that we can invoke when sounds are played ;

 

import android.media.SoundPool;

public class DoSound {

	private static SoundPool soundPool;
	private static HashMap<Integer, Integer> soundsMap;
	static int SoundYeah = 1;
	static int SoundWrong = 2;

	public static void initSounds(Context context) {

	soundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 100);
	soundsMap = new HashMap<Integer, Integer>();
	soundsMap.put(SoundYeah, soundPool.load(context, R.raw.yeah, 1));
	soundsMap.put(SoundWrong, soundPool.load(context, R.raw.wrong, 1));
}
	 /** Play a given sound in the soundPool */
	 public static void playSound(Context context, int soundID) {
	if(soundPool == null || soundsMap == null){
	   initSounds(context);
	}
	    float volume = 1.0f;

	    soundPool.play(soundsMap.get(soundID), volume, volume, 1, 0, 1f);
	 }

}

Now, we can use this class in a different class. First, we instantiate the class in the OnCreate() portion of the Main class.

    private DoSound myDoSound;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.series_onthouden);

        myDoSound = new DoSound();

Then, we can call the sound-module like this :

myDoSound.playSound(this, DoSound.SoundWrong);

Please note that the context (this) is being passed to the Sound handling class.

 

We’ve been going steady for Two years

Well, it’s been 2 years now. Last year was way slower then the first, when it comes to developing. However, you won’t see me complaining. The original goal, my mortgage paid by app development is not reached yet. But.. we’re defenatily moving along. Right now, income is about 15% of what it should be. I keep calm, don’t panic and rely on the steady stream of installs apps like “FlitsWoorden” are showing. Currently, I’m working on the next app project. Ofcourse, I’ll keep you posted. Target date for release : Q1 2014.

FlashMath – You do the math

After some hours of coding, a new App was published in the Market. You can find it here. This app challenges kids to do math even faster. It requires some settings (max values, types of questions) and then it’s good to go. Coding-wise, not much new stuff in here. I used smart layouting for Landscape / Portrait optimalization.

CaptureJust define layouts for all the seperate elements and later, include them in a master layout (<include layout=”@layout/calculator_frame” />) in your Layout-Port folder. Now, define a different layout for your Landscape-mode and place it in the Layout-Land folder. That’s it!

As for the Settings (Preferences), I really should get down to studying the newly introduced, fragment-style settings. I just used the old (deprecated) methods for now.

There is one nice piece of could I would like to highligt. Each time the user answers a question correctly, the time (ms) is stored in an ArrayList. Later, this list is used to calculate a mean time. Now, for adding more fun while crunching the numbers, I wanted to introduce a motivating message when the user does better than he or she did before. But, obviously, the app could’ve just presented a series of very easy questions, rendering this new found ‘level of mathism’ quite useless. So, I decided to weigh the average time against the three most recent average time scores.. That way, the motivational quote would only appear if real progress was made. If that level is held or if the user falls back, the good old “speed up a bit” will be shown. I used this code :

	public void handleMeanTimes(int meantime) {
		if (ArrayMeanTimes.size() < 4) {
			ArrayMeanTimes.add(meantime);
			StatusMessage = getString(R.string.status_you_can_do_it);
		} else {
			Log.v(TAG, "Let's remove item at 0...");
			ArrayMeanTimes.remove(0);
			ArrayMeanTimes.add(meantime);
			int calc = 0;
			for (int i = 0; i < ArrayMeanTimes.size(); i++) {
				calc = calc + ArrayMeanTimes.get(i);
				Log.v(TAG, "Item at " + i + " = " + ArrayMeanTimes.get(i));
			}
			int toBeat = calc / 4;
			Log.v(TAG, "Item to Beat : " + toBeat);
			if (meantime < toBeat) {
				StatusMessage = getString(R.string.status_great_work);
			} else {
				StatusMessage = getString(R.string.status_speed_up);
			}
		}
	}

For other parts of the application, I used technologies I already was familiar with. For instance, the sound is played by SoundPool. This is a technique you can review in this post.

All suggestions for this app are welcome! I can’t wait to implement them ;). As far as the Goal goes, I now have a steady income of about 1/24th of what I need. So… Not quite there yet 😉

Imagination Trainer – Developing for older Android versions

This month, I finished work on a brand-new app that jogs your Imagination. Einstein said : “Logic will get you from A to B. Imagination will take you Anywhere”. As a dad, I like to make up bed-time stories. I found it was very hard for the kids to dream up random characters, items and locations for these stories. Those factors combined made this app.

Imagination Trainer serves you with nine random images. Can you make up a story that uses all this pictures? Think outside the box. Try to stretch your story and introduce a plot, an intrige or maybe let your kids wonder what image will make its entrance next?

Programatically, this was not a very big deal. Until I found out that my code wouldn’t run on pre-Honeycomb Android versions! Don’t be mistaking, there are lots of people who won’t update their phones.

Android Versions

My MinSDK was 11, which maps to Android 3.0. This API level was needed, because I used the setAlpha property for ImageView :

 

view.setAlpha(1f);

I actually forgot to set the MinSDK in my AndroidManifest. Within a few hours, complaints started coming in :D. So, I shuffled around some code, and came up with this solution :

public void toggleButton(int i) {
if (indImageBtnEnabled[i]) {

int di = getDrawableId(findViewById(myImagebtns[i]));

Log.v("Button", "Drawable : ");
Drawable d = this.getResources().getDrawable(di);
d.setAlpha(25);

((ImageView) findViewById(myImagebtns[i])).setImageDrawable(d);

indImageBtnEnabled[i] = false;
} else {
// findViewById(myImagebtns[i]).setAlpha(1f);
int di = getDrawableId(findViewById(myImagebtns[i]));
Drawable d = this.getResources().getDrawable(di);
d.setAlpha(255);

((ImageView) findViewById(myImagebtns[i])).setImageDrawable(d);

indImageBtnEnabled[i] = true;
}
}

Also, instead of defining an OnClickListener for all buttons seperately, I decided to do some nice ArrayListing. If you have a lot of buttons (or want to make the number of buttons variable — Not sure, How would you do that?), this looks like a good solution :

	int[] myImagebtns = { R.id.ImageButton00, R.id.ImageButton01,
			R.id.ImageButton02, R.id.ImageButton03, R.id.ImageButton04,
			R.id.ImageButton05, R.id.ImageButton06, R.id.ImageButton07,
			R.id.ImageButton08 };

		for (int i = 0; i < myImagebtns.length; i++) {
			findViewById(myImagebtns[i]).setOnClickListener(this);
		}

Unfortunately, in the OnClick method, I see no possibility to cycle through these buttons (in the switch statement). If you have any suggestion on how to do this, please leave a comment. For now, I’m using :

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnRoll:
Log.v("Roll", " Do stuff here for loading new random images ");

usedImages.clear(); // Clear the arraylist containing the images
resetImageButtons(); // Re-enable all buttons
loadNewImages(); // Load random images
break;
case R.id.ImageButton00:
toggleButton(0);
break;
case R.id.ImageButton01:
toggleButton(1);
break;
// AND SO ON...
// AND SO FURTHER...
// UNTIL WE HIT BUTTON 08
case R.id.ImageButton08:
toggleButton(8);
break;
}
}

Feature Graphic

WieBenIk : Functionaliteit uitgebreid

In de Google-market maakte Chantal de opmerking dat WieBenIk te moeilijk is. Zij had het geinstalleerd, gespeeld, maar niet geraden dat ze “Google” was. Tja. Ik heb het zelf met 3 anderen 1,5 uur lang gespeeld. We hebben veel gelachen en eigenlijk vonden we het niet echt te moeilijk. Je moet wel je medespelers op het juiste moment een hintje geven … Juist daar dacht ik aan een stukje automatisering.

Dit weekend ben ik dus aan de slag gegaan met het maken van 3 hints per woord. Ook de code moest daarvoor een beetje worden aangepast. Op het moment dat we een woord uit de database halen, kunnen we ook meteen de hints erbij laden. De database heeft er dus 3 kolommen bijgekregen, die ik uitlees. De resultaten plaats ik in een String Array, zodat het later makkelijk is om de variabelen te vullen met de verschillende hints.

De DBHelper heeft dus een nieuwe methode gekregen :

	public String[] getRecord(long rowId) {
		SQLiteDatabase db = getReadableDatabase();

		Cursor c = db.query(TABLE_NAME, new String[] { WORD, "HINT1", "HINT2",
				"HINT3" }, _ID + "= " + rowId, null, null, null, null, null);
		if (c != null) {
			c.moveToFirst();
		}
		db.close();
		String[] result;
		result = new String[4];
		result[0] = c.getString(0);
		result[1] = c.getString(1);
		result[2] = c.getString(2);
		result[3] = c.getString(3);

		return result;

	}

Zoals je kunt zien, vul ik eerst een cursor met de juiste rij. Daarna vul ik het Array (String[]) met de verschillende waardes. Het is nu mogelijk geworden om de hints uit te lezen op het moment dat ze nodig zijn. Eerst laat ik een dialoog zien, waarin duidelijk gemaakt wordt dat er een hint beschikbaar is :

	private void ShowHintDialog(final int hintNumber) {
		PlaySound(R.raw.notify_bite);
		myVibrator.vibrate(250);
		AlertDialog.Builder screenDialog = new AlertDialog.Builder(this);
		screenDialog.setTitle("Hint " + hintNumber + " Beschikbaar");

		TextView hintText = new TextView(this);
		hintText.setText("\n\n  DRUK OP \"TOON\" OM DE HINT TE ZIEN  \n\n");
		LayoutParams textOutLayoutParams = new LayoutParams(
				LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
		hintText.setLayoutParams(textOutLayoutParams);

		LinearLayout dialogLayout = new LinearLayout(this);
		dialogLayout.setOrientation(LinearLayout.HORIZONTAL);
		dialogLayout.addView(hintText);
		screenDialog.setView(dialogLayout);

		screenDialog.setPositiveButton("Toon Hint",
				new DialogInterface.OnClickListener() {
					// do something when the button is clicked
					public void onClick(DialogInterface arg0, int arg1) {
						ShowHint(hintNumber);
					}
				});

		screenDialog.setNeutralButton("Overslaan",
				new DialogInterface.OnClickListener() {
					// do something when the button is clicked
					public void onClick(DialogInterface arg0, int arg1) {
						Log.d("DIALOG", "Cancel");
					}
				});
		screenDialog.show();
	}

Deze methode wordt dus aangeroepen vanuit de CountdownTimer. Een boolean zorgt ervoor dat de dialog maar één keer wordt weergegeven :

				if (millisUntilFinished / 1000 < 100 && !showHint2) {
					showHint2 = true;
					ShowHintDialog(2);
				}

 

Vervolgens wordt met deze methode de hint daadwerkelijk getoond :

	private void ShowHint(int hintNumber) {

		AlertDialog.Builder screenDialog = new AlertDialog.Builder(this);
		screenDialog.setTitle("HINT");

		TextView hintText = new TextView(this);

		String t = hintMap.get(hintNumber);
		hintText.setText(t);

		LayoutParams textOutLayoutParams = new LayoutParams(
				LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
		hintText.setLayoutParams(textOutLayoutParams);
		hintText.setPadding(10, 10, 10, 10);

		LinearLayout dialogLayout = new LinearLayout(this);
		dialogLayout.addView(hintText);
		screenDialog.setView(dialogLayout);

		screenDialog.setPositiveButton("OK",
				new DialogInterface.OnClickListener() {
					// do something when the button is clicked
					public void onClick(DialogInterface arg0, int arg1) {

					}
				});

		screenDialog.show();
	}

Je ziet dat ik een Map gebruik. Dat heb ik gedaan om het hintnumber in de methode variabel te kunnen houden. Eclipse gaf hier waarschuwingen over. HintNumber zou final moeten zijn, maar dan kon ik de inhoud weer niet veranderen.. Ik snap het nog niet helemaal, maar door een Map te gebruiken, kon ik dit probleem omzeilen. Dus :

public Map hintMap;

public void getRndRecord() {
Random rnd = new Random();
// TODO OUT FOR TESTING int rndrecord = rnd.nextInt(myDBHelper.getMaxRecord());
int rndrecord = rnd.nextInt(100);
String[] record = myDBHelper.getRecord(rndrecord);
objectToGuess = "--- " + record[0];
hintMap = new HashMap();
hintMap.put(1, "\n--- " + record[1] + " ---\n");
hintMap.put(2, "\n--- " + record[2] + " ---\n");
hintMap.put(3, "\n--- " + record[3] + " ---\n");
}

Je ziet dat ik in deze code de Randomizer even op 100 heb gezet. Dat heb ik gedaan, omdat pas 100 van de 600 woorden ook daadwerkelijk 3 hints hebben. Dat is nog het meeste werk, de hints verzinnen. Maar wel lachen, gieren, brullen 😉

Nieuwe app : Wie Ben Ik ?

Ik was op zoek naar een leuk spelletje, dat je met veel mensen kunt spelen. Bijvoorbeeld om elkaar iets beter te leren kennen, samen te lachen of gewoon als tijdverdrijf. Het moest een spelletje zijn dat je kunt spelen in een groep, maar ook met z’n 2en. Misschien zelfs wel in de trein, met mensen die je niet kent. Tijdens een weekend van Mensa speelden we “WelkSpel”. Een geweldig leuk spelletje met 3 rondes. In ronde één moet je een omschrijving geven van een persoon, je team moet raden “wie je bent”. Ik begon wat te fröbelen en, tadaa : Wie ben ik op je Telefoon!

Dus vandaag heb ik weer een app gepubliceerd in de Market. Ik ben nog bezig met de verfijning en ben dan ook erg nieuwsgierig naar jullie op- en aanmerkingen. Sowieso zit er nog wat werk in de vertaling. Daarnaast ben ik bezig met een andere GamePlay. We noemen het “WelkSpel” en het lijkt een beetje op de ronde ‘raadt de bekende Nederlander’ van Ik Hou van Holland. Het ontwikkelen van deze app ging behoorlijk goed! Inmiddels beginnen de JAVA tutorials hun vruchten af te werpen en begrijp ik een beetje hoe je Object Oriented moet programmeren. Een behoorlijke stapel nieuwe inzichten sinds ik begon (lees daarover hier). Ik zal proberen de gehele WieBenIk app hier toe te lichten. Daarom zal ik in dit bericht grote stukken code tonen, die ik aanvul met opmerkingen (gemarkeerd met “//”).

Ik was begonnen met het schrijven van een applicatie die nauwkeurig registreert hoe de telefoon wordt vastgehouden (de Orientation). Dat om te proberen het woord alleen te tonen als de speler naar de achterkant van de telefoon kijkt. Dat bleek echter heel lastig. Daarnaast heeft niet alle hardware de beschikking over nauwkeurige sensoren, waardoor er vaak flitsen van het woord toch nog zichtbaar waren. Uiteindelijk heb ik dit vervangen door een methode “instructPlayerToShowScreen()”.

De mainActivity ziet er zo uit :

package nl.happyworx.wiebenik;

import java.io.IOException;
import java.util.Random;

import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Color;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.os.Vibrator;
import android.util.Log;
import android.view.Gravity;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewFlipper;

public class WieBenIkActivity extends Activity {

	public TextView introTextView;
	public TextView hintTextView;
	public CountDownTimer guessingCountDownTimer;
	public boolean countDownFinished;

	public Button btn_start;
	public ViewFlipper flipper;
	public ScrollTextView toGuessText;
	public String objectToGuess;

	public View objectchallengeview;
	public int state;

	public ShakeListener myShakeListener;
	public DataBaseHelper myDBHelper;

	private ProgressBar pbar_showingToGuess;
	private ProgressBar pbar_guessTime;
	private Vibrator myVibrator;
	public MediaPlayer myMediaPlayer;

	public OrientationEventListener myOrientationEventListener;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		flipper = (ViewFlipper) findViewById(R.id.flipper);
		introTextView = (TextView) findViewById(R.id.introTextView1);
		hintTextView = (TextView) findViewById(R.id.hintTextView);
		pbar_showingToGuess = (ProgressBar) findViewById(R.id.progressBar1);
		pbar_guessTime = (ProgressBar) findViewById(R.id.pbar_guessTime);
		toGuessText = (ScrollTextView) findViewById(R.id.toGuessText);

		myVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
		myShakeListener = new ShakeListener(this);
		myDBHelper = new DataBaseHelper(this);

		// Kopieer de database met woorden naar de telefoon als deze er nog niet op staat
		// Dit stukje heb ik geleend uit één van mijn andere apps, FlitsWoorden.
		// Omdat ik nu begrijp hoe het OO model inelkaar zit, is de code wel veel schoner
		// geworden.

		try {
			myDBHelper.createDataBase();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		btn_start = (Button) findViewById(R.id.btn_Start);
		btn_start.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				getRndWord();
				instructPlayerToShowScreen();
			}
		});

	}

	// Dit is een methode om een willekeurig woord uit de database te halen
	public void getRndWord() {
		// Log.d("WieBenIkActivity", "Count : " + myDBHelper.getMaxRecord());
		// Log.d("WieBenIkActivity", "Word : " + myDBHelper.getWord(5));
		Random rnd = new Random();
		int rndrecord = rnd.nextInt(myDBHelper.getMaxRecord());
		Log.d("WieBenIk", "Random : " + rndrecord);
		objectToGuess = "--- " + myDBHelper.getWord(rndrecord);

	}

	// Dit is de code om de speler te instrueren het scherm aan de andere spelers
	// te laten zien.
	public void instructPlayerToShowScreen() {
		flipper.setDisplayedChild(0);
		btn_start.setClickable(false);
		introTextView.setText(R.string.show_screen_to_other_players_txt);
		introTextView.setGravity(Gravity.CENTER);
		introTextView.setTextSize(20);
		PlaySound(R.raw.danzegikdiekenik_bite_11s);
		startCountdownTimer(10000, pbar_showingToGuess);
	}

	// Hier een stuk code om te voorkomen dat de telefoon het scherm opnieuw gaat opbouwen
	// als je hem op z'n kop houdt
	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);

	}

	// De countdowntimer had ik al eerder gebouwd, voor Applez. Ook hier : doordat ik nu
	// beter begrijp wat de code precies doet, is het allemaal veel schoner geworden.
	// Het was mijn bedoeling om hier een universele CountDowntimer te bouwen, die ik
	// in de hele app kon gebruiken (vandaar de verwijzing naar de ProgressBar).
	// Het blijkt echter veel makkelijker om in een paar regels een nieuwe timer aan te
	// maken, daar waar je hem nodig hebt.
	private void startCountdownTimer(int ms_total, final ProgressBar progress) {
		countDownFinished = false;
		pbar_showingToGuess.setProgress(ms_total);

		final int totalMsecs = ms_total;
		int callInterval = 100;

		/** CountDownTimer */
		new CountDownTimer(totalMsecs, callInterval) {

			public void onTick(long millisUntilFinished) {

				float fraction = millisUntilFinished / (float) totalMsecs;

				// progress bar is based on scale of 1 to 100;
				progress.setProgress((int) (fraction * 100));
			}

			public void onFinish() {
				showToGuess();
			}
		}.start();
	}

	// Wel een aparte methode voor het afspelen van geluidjes. De MediaPlayer heeft best een
	// lastige lifecycle. Je moet zorgen dat je de onPrepared Override, zodat het geluid helemaal
	// geladen is voordat het wordt afgespeeld.
	public void PlaySound(int sound) {
		if (myMediaPlayer != null) {
			myMediaPlayer = null;
		}
		myMediaPlayer = MediaPlayer.create(getApplicationContext(), sound);

		myMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
			@Override
			public void onPrepared(MediaPlayer inPlayer) {
				myMediaPlayer.start();
			}
		});
		myMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
			@Override
			public void onCompletion(MediaPlayer player) {
				Log.d("Sound", "Sound Finito");
				player.release();
			}
		});

	}

	// Stukkie code om het woord te laten zien. Ik gebruik hier een ViewFlipper
	// Wat een uitvinding is dat! Je kunt, zonder dat je allerlei lastige intent-
	// calls moet doen, toch een ander schermpje tonen!
	private void showToGuess() {

		btn_start.setClickable(true);
		flipper.setInAnimation(inFromBottomAnimation(200));
		flipper.setOutAnimation(outToTopAnimation(200));
		flipper.setDisplayedChild(1);

		toGuessText.setText(objectToGuess);
		toGuessText.setTextColor(Color.BLACK);
		toGuessText.startScroll();
		myShakeListener.start();

		Button btn_Woops = (Button) findViewById(R.id.Woops);
		btn_Woops.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				myShakeListener.stop();
				guessingCountDownTimer.cancel();
				instructPlayerToShowScreen();

			}
		});

	}

	// En dan gaan we raden naar het woord. Ik ga dit nog een beetje 'variabeliseren'
	// zodat de speler kan instellen hoe lang de raad-tijd zou moeten zijn.
	// Hier zijn ook de buttons gedefinieerd. Uiteindelijk moeten die buttons een soort
	// scorelijstje gaan bijhouden, maar dat is wat minder belangrijk voor de GamePlay
	public void startGuess() {
		myShakeListener.stop();
		flipper.setDisplayedChild(2);
		myVibrator.vibrate(500);
		guessingCountDownTimer = new CountDownTimer(30000, 100) {
			public void onTick(long millisUntilFinished) {
				float fraction = millisUntilFinished / (float) 30000;
				pbar_guessTime.setProgress((int) (fraction * 100));
			}

			public void onFinish() {
				// Please note : alle texten verwijzen naar variabelen. Zo kan ik het geheel
				// makkelijk vertalen. De variabelen kun je terugvinden in je strings.xml
				// in de res\values map.
				hintTextView.setText(R.string.did_you_guess_correctly_txt);
			}
		}.start();
		findViewById(R.id.btn_correct).setOnClickListener(
				new OnClickListener() {

					@Override
					public void onClick(View v) {
						Toast.makeText(getApplicationContext(), "GOOD JOB!!",
								Toast.LENGTH_LONG).show();
						guessingCountDownTimer.cancel();
						onCreate(null);
					}
				});
		findViewById(R.id.btn_wrong).setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				Toast.makeText(getApplicationContext(), "Try Again",
						Toast.LENGTH_LONG).show();
				guessingCountDownTimer.cancel();
				onCreate(null);

			}
		});

	}

	// En helemaal onderaan definieer ik de animaties voor de ViewFlipper
	// Dat zou ook waarschijnlijk netjes in een aparte Interface kunnen
	// maar zo helemaal onderaan in de code doen ze niemand kwaad 🙂
	private Animation inFromBottomAnimation(int duration) {

		Animation infromBottom = new TranslateAnimation(
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, +1.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f);
		infromBottom.setDuration(duration);
		infromBottom.setInterpolator(new AccelerateInterpolator());
		return infromBottom;
	}

	private Animation outToTopAnimation(int duration) {
		Animation outToTop = new TranslateAnimation(
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, -1.0f);
		outToTop.setDuration(duration);
		outToTop.setInterpolator(new AccelerateInterpolator());
		return outToTop;
	}

	private Animation inFromTopAnimation() {
		Animation inFromTop = new TranslateAnimation(
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, -1.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f);
		inFromTop.setDuration(500);
		inFromTop.setInterpolator(new AccelerateInterpolator());
		return inFromTop;
	}

	private Animation outToBottomAnimation() {
		Animation outToBottom = new TranslateAnimation(
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, 0.0f,
				Animation.RELATIVE_TO_PARENT, +1.0f);
		outToBottom.setDuration(500);
		outToBottom.setInterpolator(new AccelerateInterpolator());
		return outToBottom;
	}

}

Voor de lastige bewerkingen (de DataBase, de scrolltextview en de shakelistener) heb ik aparte classes gebouwd. Ik zal ze toelichten in een andere post.

WieBenIk is nog LANG niet klaar.. zo moet er bijvoorbeeld nog in : Categorieën (zodat het ook met kinderen goed te spelen is), instellen van speeltijd, de nieuwe game-mode (WelkSpel), en als ik echt helemaal los ga : suggesties die je kunt doen voor aanvulling van de database (maar dan moet ik een stuk serverside programmeren.. en dat heb ik nog nooit gedaan 😉 ).

Oh ja.. wat wel een absolute must gaat worden : een check of je de meest recente database hebt. Zo niet, direct downloaden. Dat gaat dus één of andere spiffy FTP interface worden, met een check op een bestandsnaam ofzo..

Wat helemaal nice zou zijn : Na ieder woord kun je een aantal sterren geven. Op die manier kan ik nieuwe woorden introduceren en laat ik de rapportage bepalen welke woorden blijven..

 PS : I’m thinking about switching to English for this blog. Please leave a comment if you would like to read in English! — Make sure the comment doesn’t look like spam, though!  😉

Nota Bene : Voor de database heb ik aan m’n Mensa vriendjes gevraagd woordjes op te sturen. Dat werd massaal gedaan! Dus bij deze; dank aan Matthias Wagenaar, Luc Lacroix, Nathalie Bouts, Fanny Cattenstart, Yessica, Bas Warmenhoven, Vivien Kandou, Ineke Buitendijk, Laurence Steenbergen, Caroline Verdonk, Krister Horn, Hobbyfun.nl (check de site! :), Ton Bil, Wilma Voets

Failed to initialize Monitor Thread: Unable to establish loopback connection

Tijdens een Android training sessie kwamen we op sommige PC’s deze foutmelding weer tegen. Het heeft te maken met het feit dat Eclipse niet goed om kan gaan met IPv6 adressen voor de LoopBack adapter. Dit IPv6 adres wordt toegekend door de Toredo Interface. Je kunt deze interface makkelijk uitzetten, dan houdt je PC een IPv4 adres. Ik heb ook geprobeerd of het lukt als je de hosts file aanpast, maar omdat de meeste (zakelijke) systemen dat niet toestaan, lijkt me deze oplossing beter.

Mijn eerste tutorial met een filmpje, om te kijken hoe dat bevalt (bij mezelf, maar ook vooral bij de lezers van dit blog). En ook, eerlijk is eerlijk, omdat het publiceren van zo’n filmpje bijdraagt aan mijn doel.

Onderaan deze post zal ik het filmpje embedden. Ik denk dat je het wel full screen moet bekijken om alles goed te kunnen zien. Ik ben nog op zoek naar een goede ScreenCam voor dit soort doeleinden (tips, anyone?). BB Flashback lijkt ideaal.. en heeft als nadeel dat je uiteindelijk veel tijd gaat steken in het editten van het geheel. Het liefst zou ik iets hebben, waarmee je dmv een hotkey kunt in- en uitzoomen. Nog even doorzoeken, denk ik.

Ondertussen is het werk begonnen aan een nieuwe app. Wat precies blijft nog even een verrassing. Ik ga wel proberen om er stap-voor-stap videos van te maken, zodat je deze app makkelijk zelf kunt nabouwen om ervan te leren.

 

Failed to initialize monitor Thread: Unable to establish loopback connection

Cursus en tutorials online

De afgelopen week heb ik een een paar dagen cursus Android Development gevolgd (kostte me wat vrije dagen, maar was de moeite meer dan waard!). Tijdens de cursus werd mijn vermoeden bevestigd; Eigenlijk zijn het niet de specifieke Android-dingen die ik niet begrijp. Ik heb de JAVA-Basics gewoon niet onder de knie. This, Context, inner- en outer-classes en static operators. Al die termen zeggen mij eigenlijk (nog) helemaal niets. Zie ook deze blogpost. Ik heb de Apps die tot nu toe in de market staan, gebouwd door gewoon maar code kopiëren-en-plakken. Elke keer als ik een fout tegenkwam, heb ik Google geraadpleegd en met veel pleisters en extra code de boel maar weer opgelost. Dat heeft als resultaat dat mijn code niet overeenkomt met welke conventie dan ook (behalve misschien mijn eigen, haha).

Tijdens de cursus leerden we onder andere hoe je een XML pagina kunt ‘parsen’ en om kan zetten in FeedItems voor in een Listview. De Listview kun je dan weer tonen in een App. Interessante technologie! Ik dacht meteen aan mijn – vaak voorkomende – ergernis: Als Mensa-lid heb ik toegang tot de ledenlijst, waar ik soms een telefoonnummer of contactgegevens in wil opzoeken. Op dit moment hebben we daar alleen een web-pagina voor. Op een smartphone is het dan ook een heel gedoe om de persoon op te zoeken, het telefoonnummer te kopiëren en te bellen. Het leek me handig om – voor eigen gebruik – hiervoor een App te maken. Problemen stapelden zich op ; Cookies (opgelost), SSL Certificaten (opgelost), te kleine buffers (niet opgelost), etc. etc. Een heel weekend Googelen, spaghetti-code schrijven en uiteindelijk weinig resultaat. Ik kan met mijn app inmiddels inloggen op de leden-pagina (SSL, Cookie en authorisatie). Daarna doet mijn app niet veel meer. Ergernis, want als ik de JAVA basics zou kennen, zou ik misschien makkelijk verder kunnen coderen. Een extra ergernis is, dat deze App (die misschien in een paar uurtjes te schrijven zou moeten zijn) niet direct bijdraagt aan mijn doel.

Dus. Nu maar een paar JAVA tutorials doen. Online. Even toch die basis proberen op te pakken. Ik heb de volgende uitgekozen om mee te beginnen : Online JAVA Tutorial. Na een pagina of 10 te hebben gelezen, doorzag ik de eerste concepten van Object Oriented Programming. Hoe meer ik lees, hoe groter het kleine wonder van de door mij reeds geschreven Apps lijkt.

Ik besteed de komende dagen en weekends aan het goed doorgronden van (een stuk van) de basis. En hopen dat het helpt. De kriebel onderdrukken om mijn huidige Apps opnieuw te schrijven (refactoring). Dat is niet nodig, want ze doen wat ze moeten doen. Ondertussen houdt een ham-vraag mij bezig; Wat wordt de volgende App? Het VirtuaFly SoundBoard was leuk, maar ik wil eigenlijk toch iets maken dat connectie zoekt met een website ofzo. Misschien dan toch maar een App waarmee je de Thuistest kunt maken? 😉

Alle reclamekansen moet je pakken!

Toen ik een jaar of 17 was, werkte ik bij Domino’s Pizza. De Store Manager vertelde over de eigenaar, die in Amerika woont. Eén van zijn motto’s : “Never miss a marketing opportunity!”. Om deze reden had hij de tegeltjes op de bodem van zijn zwembad laten plaatsen in de vorm van het Domino’s Logo. Er zijn immers luchtvaartmaatschappijen die met hun vliegtuigen boven zijn tuin langsvliegen. Misschien vind je dit voorbeeld vergezocht, maar de grondgedachte houdt stand. Een evenement als het EK leent zich prima voor wat marketing. Daarom :

Wil jij ook dat Nederland het EK 2012 wint? Nu kun je de uitslag beïnvloeden!

Vuur jouw rocket of desire af voor het #EK 2012! Het is zo eenvoudig; Als we allemaal de krachten bundelen, zorgt het universum voor doelpunten!

Installeer NU deze app en voel de kracht van de 13e man. Vuur raketten van verlangen af. Rode kaarten voor de tegenstander? Extra kansen voor oranje? Een keeper die aan de grond gelijmd zit? Wat is het jou waard om dit EK te winnen?

Installeer de App en vuur direct je eerste Rocket of Desire af. Rockets zijn GRATIS. Je kunt er zoveel afvuren als je wilt. Met elke raket van verlangen vergroot je de kans voor Nederland in het #EK!!

Vertel het aan je vrienden, je buren, je collega’s en je oma. Zet je Raket op die manier nog meer kracht bij. De uitslag zal je verbazen! 5 – 0 voor Nederland is haalbaar!!

Download de app hier : http://bit.ly/JollyJotApp

Tech uitleg; De app heeft een koppeling met Twitter. De wensen van de eindgebruikers worden gepubliceerd op Twitter. Daarnaast plaatst JollyJot allerlei EK gerelateerde tweets door het uitluisteren van en reageren op de wereldwijde Twitterstream. Voor meer informatie over de App, zie de sectie “Tutorials”.

Volg hier de JollyJot Twitterstream : http://twitter.com/JollyJot

Nieuw in de market : VirtuaFly

Ik ben al een tijdje bezig met een SoundBoard. Het is VirtuaFly geworden, een vrij simpele app die de gebruiker in staat stelt een virtuele vlieg te laten horen. De vlieg kan allerlei avonturen beleven; Verliefd worden, in een shredder terechtkomen en zelfs een kamikaze-zelfmoord-missie uitvoeren. Ik heb geen idee hoe deze app wordt ontvangen door het publiek, maar het werken met geluidsbestanden gaf weer een heel nieuwe dimensie aan het ontwikkelen. Ik ben begonnen met ogg-bestanden. Deze worden het best door de verschillende delen van het Android besturingssysteem verwerkt. De SoundPool-methode die ik gebruikte, bleek niet toereikend. Een Heap Size Overflow was het gevolg.

ERROR/AudioCache(34): Heap size overflow! req size: 1052672, max size: 1048576

Dit komt, doordat het Android systeem het ogg geluidsbestand uitpakt in het werkgeheugen. Afhankelijk van het aantal kanalen, de samplerate en andere variabelen, kan dit een groot tot zeer groot bestand tot gevolg hebben. Het vreemde is, dat de applicatie wel gewoon opstart en blijft werken. De geluidsbestanden worden afgespeeld tot op het moment dat het geheugen vol was. In eerste instantie kortte ik de geluidsbestanden zo ver mogelijk in. Pas later vond ik een andere (makkelijker) methode om geluidsbestanden af te spelen ; MediaPlayer.
Vergeleken met Applez en Flitswoorden is de code voor deze app zeer eenvoudig.

Je maakt een Mediaplayer instantie aan met de volgende regel;

final MediaPlayer mp1 = MediaPlayer.create(this, R.raw.fly_start_01);

Daarna kun je dit geluidsbestand afspelen met ;

mp1.start();

Let op ; je kunt niet zomaar een geluid dat je hebt gestopt met mp1.stop(); opnieuw starten. Er is een prepare() voor nodig. Zie onderstaande state diagram :

Hieronder volgt de volledige code voor VirtuaFly (gereduceerd tot 2 knoppen). Als je behoefte hebt aan een complete tutorial voor een SoundBoard, laat dat dan even weten.. dan kan ik die maken ;).

Op mn werk heb ik ondertussen door toeval kennis gemaakt met MQTT (een message Queueing systeem). Met dit systeem zou ik vliegen die in een bepaalde straal gebruikt worden, met elkaar kunnen synchroniseren. Ik denk dat ik daar wat tijd ga besteden. Lijkt me cool om 40.000 vliegen in bijvoorbeeld een EK Stadion op hetzelfde moment door de shredder te halen..

package happyworx.nl.VirtuaFly;

import android.app.Activity;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.RelativeLayout;

import com.google.ads.AdView;

public class VirtuaFlyActivity extends Activity {
	private AdView adView;

	private SoundManager mSoundManager;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		// Create the adView
		// adView = new AdView(this, AdSize.BANNER, "CODE");

		// Lookup your LinearLayout assuming it’s been given
		// the attribute android:id="@+id/mainLayout"
		RelativeLayout layout = (RelativeLayout) findViewById(R.id.RelativeLayout1);

		// Add the adView to it
		// layout.addView(adView);

		// Initiate a generic request to load it with an ad
		// AdRequest adRequest = new AdRequest();
		// adRequest.addTestDevice(AdRequest.TEST_EMULATOR); // Emulator
		// adRequest.addTestDevice("DEVICECODE"); // Test
		// Android Device
		AdView adView = (AdView) this.findViewById(R.id.adView);

		mSoundManager = new SoundManager();
		mSoundManager.initSounds(getBaseContext());
		final MediaPlayer mp1 = MediaPlayer.create(this, R.raw.fly_start_01);
		final MediaPlayer mp2 = MediaPlayer.create(this, R.raw.fly_buzz_01);
		final MediaPlayer mp3 = MediaPlayer.create(this, R.raw.fly_end_01);
		final MediaPlayer mp4 = MediaPlayer.create(this, R.raw.fly_swatter_01);
		final MediaPlayer mp5 = MediaPlayer.create(this, R.raw.fly_drown_01);
		final MediaPlayer mp6 = MediaPlayer.create(this, R.raw.fly_love_01);
		final MediaPlayer mp7 = MediaPlayer.create(this, R.raw.fly_shredder_01);
		final MediaPlayer mp8 = MediaPlayer.create(this, R.raw.fly_frog_01);
		final MediaPlayer mp9 = MediaPlayer.create(this, R.raw.fly_kamikaze_01);

		ImageButton SoundButton1 = (ImageButton) findViewById(R.id.imageButton1);
		SoundButton1.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				if (mp2.isPlaying()) {
					mp2.pause();
				}
				mp1.start();
				while (mSoundManager.mAudioManager.isMusicActive()) {
				}
				findViewById(R.id.imageButton2).setPressed(true);
				mp2.setLooping(true);
				mp2.start();
			}
		});

		ImageButton SoundButton2 = (ImageButton) findViewById(R.id.imageButton2);
		SoundButton2.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				mp2.start();

			}
		});

		ImageButton SoundButton3 = (ImageButton) findViewById(R.id.imageButton3);
		SoundButton3.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				mp3.start();
				findViewById(R.id.imageButton2).setPressed(false);
				mp2.pause();
			}
		});

		ImageButton SoundButton4 = (ImageButton) findViewById(R.id.imageButton4);
		SoundButton4.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				mp4.start();
				findViewById(R.id.imageButton2).setPressed(false);
				mp2.pause();
			}
		});

		ImageButton SoundButton5 = (ImageButton) findViewById(R.id.imageButton5);
		SoundButton5.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				mp5.start();
				findViewById(R.id.imageButton2).setPressed(false);
				mp2.pause();
			}
		});

		ImageButton SoundButton6 = (ImageButton) findViewById(R.id.imageButton6);
		SoundButton6.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				findViewById(R.id.imageButton2).setPressed(false);
				mp2.pause();
				mp6.start();
				while (mSoundManager.mAudioManager.isMusicActive()) {
				}
				findViewById(R.id.imageButton2).setPressed(true);
				mp2.start();
			}
		});

		ImageButton SoundButton7 = (ImageButton) findViewById(R.id.imageButton7);
		SoundButton7.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				mp7.start();
				findViewById(R.id.imageButton2).setPressed(false);
				mp2.pause();
			}
		});

		ImageButton SoundButton8 = (ImageButton) findViewById(R.id.imageButton8);
		SoundButton8.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				mp2.pause();
				mp8.start();
				findViewById(R.id.imageButton2).setPressed(false);
			}
		});
		ImageButton SoundButton9 = (ImageButton) findViewById(R.id.imageButton9);
		SoundButton9.setOnClickListener(new OnClickListener() {

			public void onClick(View v) {
				mp2.pause();
				mp9.start();
				findViewById(R.id.imageButton2).setPressed(false);
			}
		});

	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.menu, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		Intent browserIntent = new Intent(Intent.ACTION_VIEW,
				Uri.parse("http://happyworx.nl/blog"));
		startActivity(browserIntent);

		return true;
	}

	@Override
	public void onDestroy() {
		if (adView != null) {
			adView.destroy();
		}
		super.onDestroy();
	}
}

ComputerIdee nummer 12 (2012)

In nummer 12 van ComputerIdee (jaargang 2012) sta ik, samen met mijn zoons, in de rubriek “Uitsmijter!”. Wat ontzettend leuk om op die manier met andere computer geïnteresseerden een stuk passie en nieuwsgierigheid te kunnen delen. Al eerder op dit blog, in de post doel, verwees ik naar mijn BBSje, dat ik draaide toen ik 13 was. Ik was lid van de Philips Computer Hobby Club (mijn vader werkte bij Philips). Daar zag ik op een maandagavond voor het eerst een modem. Het was een erg duur insteek-kaartje. 45 gulden als ik me het goed herinner. Toch, uiteindelijk, aangeschaft en ingebouwd in mijn P3200.

Een prachtige PC, voor die tijd echt een juweeltje. 286AT, 6Mhz processor en 26MB (!) harddisk. Ook een unieke EGA videokaart, die 16 kleuren tegelijk kon weergeven (uit een palet van 64). Die EGA kaart heb ik later vervangen door VGA. Mijn P3200 was een bedrijfssysteem geweest. Je moest hem dan ook aan- en uitzetten met een sleuteltje. Ik deed echt van alles met dat ding, soms stond ie 2 nachten te ratelen om een simpele donut te renderen. Na inbouwen van de modem (‘het’ modem mag ook) werd het helemaal feest. Ineens had ik toegang tot allerlei VideoTex diensten (het leek allemaal een beetje op Teletext). Bij mijn modem zat een klein softwarepakketje, Lync. Met Lync kon je zelf een inbel-punt creeren. Anderen konden zo naar je computer bellen en kleine bestanden overzenden.

Na een paar weken fröbelen met Lync, werd ik nieuwsgierig naar andere mogelijkheden. Ik installeerde RADMIN en zette een heus Bulletin Board op (DataWave). Omdat ik ook fungeerde als MailHub voor CachetNet en KroonNet, bouwde mijn board al snel wat bekendheid op. Ik weet nog dat ik in de eerste nachten dat mijn computer zou gaan uitbellen, expres wakker bleef. Rond 3 uur ‘s-nachts belde mijn computer (met FrontDoor) naar de MailHost, om alle post op te halen. In de uren daarna belden andere systemen naar mijn DataWave systeem om zo de post weer verder te verdelen. Gelukkig kon je met AT (ATtention) commando’s ook het speakertje van de modem uitzetten. Ik werd ‘s-nachts niet meer wakker van het connectie geluid.

Helaas hadden we één telefoonlijn in huis. Dat betekende dat we regelmatig ‘in gesprek’ waren (ik zat in verbinding met een BBS). Soms werden mijn gezinsgenoten, als ze even wilden bellen, verrast de luide piepen en kraken in de telefoonhoorn. Lachen was dat. Mijn ouders hebben me gestimuleerd en gemotiveerd om voor het BBS een eigen telefoonlijn aan te schaffen. En, uiteraard, die zelf ook te bekostigen. Ik startte een abonnement-service, waarmee betalende leden toegang hadden tot meer content en meer mochten downloaden per dag. Inmiddels had ik een programma gevonden (PC-Mix) waarmee het mogelijk was om tot 4 (!) DOS sessies tegelijk te draaien. Zo kon ik toch nog iets met mijn computer doen, terwijl op de achtergrond de BBS software z’n ding deed.

Ik werd lid van HackTic (XS4All waren toen nog Hackers ;)) en leerde steeds meer over computers. Toen ik een jaar of 15 was, begon het internet echt voet aan de grond te krijgen. XS4All werd (1993) de eerste Internet provider voor het grote publiek. Ikzelf was met andere dingen bezig. Vriendinnetjes, Scouting en mijn brommertje. Niet zo heel veel tijd voor de computers, toen. Ondertussen werd webdesign groot, groter en daarna weer kleiner en klein.

Nu, 2012, begint mobile voet aan de grond te krijgen. App ontwikkelaars van nu lijken erg op de website-brouwers van toen. Zonder de goede tekort te doen; Code wordt niet prijsgegeven, er worden verschillende technieken gebruikt en Content Management is nog geen common practice. Je kunt voor een paar honderd euro een App laten brouwen op de hoek van de straat. Die App voldoet voor nu misschien prima, maar zal snel tekort blijken te schieten.

NFC Chips maken hun opmars. Deze veranderen binnen een paar jaar onze telefoons in hét primaire betaalmiddel. PC’s zullen steeds minder gebruikt gaan worden, Mobile wordt het nieuwe platform. Voor bedrijven wordt het steeds belangrijker hun “Mobile Maturity” te onderzoeken en te vergroten. Niet alleen ; Hoe kunnen mijn klanten me vinden op hun mobile device? maar ook : Welke nieuwe businessmodellen onstaan er? Hoe kan ik de kracht van Mobility inzetten om mijn interne bedrijfsprocessen eenvoudiger, goedkoper of beter bestuurbaar te maken? Hoe zit het met de integratie van mijn Ketenpartners? Wat betekent Mobility voor mijn medewerkers en de “Wellbeing @ Work”?

Allemaal vragen die de komende maanden, jaren beantwoord gaan worden. Bedrijven beginnen positie te kiezen, sommige zijn al gestart. Het wordt een spannend decennium waarin mensen voor het eerst een Internet gaan beleven dat zich automagisch aanpast aan de context waarin zij zich op dat moment bevinden.


Lees hier het artikel : ComputerIdee 12, 2012, Uitsmijter

How To : Adverteren in je zelfgemaakte App

Monetizeren! Prachtig woord, volgens mij geen correct Nederlands. Engelstalige websites en blogs hebben het over “Monetizing” your App. Hoe moet dat nou precies?Als je wilt weten hoe je een App kan maken, kijk dan bij de Tutorials. Daar wordt onder andere uitgelegd hoe je een ontwikkelomgeving moet opzetten en hoe je je eerste App kunt bouwen. Er van uitgaande dat je een App gebouwd hebt; Hoe moet je die nu “Monetizeren”? ;)… Ten eertse kun je je App verkopen in de markt. Maar een eerdere post laat al zien, dat verkoop helemaal niet de meest winstgevende optie hoeft te zijn. Je kunt ook een willekeurig bedrijf bellen. Of een vakblad. En dan aanbieden dat je een bannertje in je app kan tonen (320×50 pixels). Je kunt dan een afspraak maken over het aantal keren dat de banner getoond wordt, en hoeveel dat blad daarvoor betaalt.

Er zitten nadelen aan dat idee. Het belangrijkste nadeel; de advertenties zijn voor iedereen hetzelfde. Ze worden niet ‘op maat’ aangeboden. Ten tweede; Je bent afhankelijk geworden van die ene adverteerder. Natuurlijk kun je er een tweede en een derde bijzoeken, maar dan heb je daar een dagtaak aan. Je was App developer, geen advertentie-verkoper. Andere nadelen? Facturatie, rapportage, het niet kunnen inspelen op acties, etc. etc.

Gelukkig is er een oplossing. Google is een bedrijf dat heel veel weet van Internet gebruikers. Voorkeuren worden gevolgd, zoekresultaten geindexeerd, etc. Sociale netwerken verzamelen data over jou (leeftijd, sexe, voorkeuren, teksten die je schrijft etc.) De kracht hiervan is ongekend groot. Als je een paar keer op facebook schrijft over je vakantieplannen, staan er binnen een paar dagen reisbureau-advertenties te lezen op je pagina. Hetzelfde geldt voor sportartikelen, sportartikelen en tapijttegels.

Er is dus een heel leger met slimme verkopers (M/V) dat advertentieruimte in de markt aanbiedt. Jij kunt dat leger voor je laten werken. Door lid te worden van bijvoorbeeld Google AdMob (maar er zijn ook anderen), kun je een banner in je App laten vullen met advertenties. De advertenties worden zoveel mogelijk toegesneden op de gebruiker (welke andere apps heeft deze gebruiker geïnstalleerd? En zelfs : Waar bevindt xij zich?). Geweldige technologie. En, eerlijk is eerlijk, je kunt met de juiste advertenties ook waarde toevoegen voor je gebruikers! Hier kwam ik eerder achter, tijdens het ontwikkelen van mijn App Flitswoorden.

Toepassen

1. Maak een account aan op AdMob.com. Op een gegeven moment moet je aangeven wat de URL naar je App in de market is. Als je die nog niet hebt (omdat je App nog niet gepubliceerd is), kun je er een intikken in het volgende format : https://play.google.com/store/apps/details?id=<full_app_name> . De Full App Name is bijvoorbeeld “happyworx.nl.Applez”. De “Publisher ID” die je krijgt heb je later nodig.

2. Download de nieuwste versie van de SDK (Software Development Kit) voor Android. https://developer.android.com/sdk/index.html

3. Zorg dat je project wordt gecompiled tegen Android versie 3.2 of hoger. Let op : Dit is aan verandering onderhevig. Check op admob.com wat de laatste versie is.

Toelichting : Je moet ook de laatste versie (tenminste versie 1.5 van de Android Runtime geinstalleerd hebben). Eén van de foutmeldingen die je tegen kunt komen als dit niet in orde is :

“String types not allowed (at ‘configChanges’ with value

‘keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize’).”.

Deze error kun je dus vermijden als je in je Project Properties de Target op versie 3.2 of hoger zet. Als je met Eclipse werkt, moet je na die wijziging een “Clean” draaien (Project > Clean)

De SDK Importeren in je project

4. In Eclipse, ga naar Project > Properties > Java Build Path > Libraries (TAB) > Add External JARs en voeg daar de Google AdMob jar toe.

NB : Verstandig om ook in je project te controleren of er een directory genaamd ‘libs’ is aangemaakt. Zo niet, zelf maken en het .jar bestand erin schleppen*.

In je AndroidManifest.xml moet je de AdActivity declareren;

    <activity android:name="com.google.ads.AdActivity"
              android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>

Hier kun je dus de foutmelding krijgen, waarvoor je de Project Target moet aanpassen (zie stap 3).

5. Omdat je App contact maakt met het Internet (om de advertenties op te halen), moet je ook die “permissions” toevoegen aan je AndroidManifest.xml :

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

Plaats deze regels direct na de </application> tag (vóór de </manifest> tag).

6. Rebuild (Clean) het project en controleer op fouten.

De AdView toevoegen.

In deze How-to ga ik er vanuit dat je werkt met XML layouts. Als je werkt met een code-gegenereerde layout, kijk dan naar de blogposts over Applez. Die app gebruikt een in JAVA gegenereerde view en laat zien hoe je de view toevoegt aan een in JAVA gegenereerde RelativeLayout.

Op hoofdlijnen;
– Import com.google.ads.*
– Declareer een AdView instantie
– Creeer de instantie, specificeer jouw unieke unit ID (AdMob publisher ID uit stap 1)
– Voeg de view toe aan je User Interface
– Laad een advertentie in de View.

Dit kun je bijvoorbeeld doen in de Activity van je app :

import com.google.ads.*;

public class HappyworxVoorbeeld extends Activity {
  private AdView adView;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Definieer de AdView
    // Let op AdSize.BANNER. Op deze plek kun je verschillende
    // variabelen kwijt, die verschillende typen advertenties
    // tonen. Bijvoorbeeld specifiek voor tablets.
    adView = new AdView(this, AdSize.BANNER, PUBLISHER_ID);

    // Zoek de Layout op, er vanuit gaande dat deze mainLayout heet
    // Als je Layout niet in XML gedefinieerd is, maar in de code
    // moet je dit op een andere manier doen. Zie de Applez blogposts
    // voor meer voorbeelden hiervan.
    LinearLayout layout = (LinearLayout)findViewById(R.id.mainLayout);

    // Voeg de adView toe aan de layout
    layout.addView(adView);

    // Doe een verzoek om de adview te vullen met een advertentie
    adView.loadAd(new AdRequest());
  }

  @Override
  public void onDestroy() {
    if (adView != null) {
      adView.destroy();
    }
    super.onDestroy();
  }
}

LET OP : Als je aan het ontwikkelen bent, zorg dan dat je in TEST mode draait. Dit voorkomt dat je Google AdMob account geblokkeerd wordt vanwege ongeldige clicks (lees de voorwaarden!).

Je kunt in Test mode door deze code toe te voegen voordat je een AdRequest doet :

AdRequest request = new AdRequest();

request.addTestDevice(AdRequest.TEST_EMULATOR);
request.addTestDevice("ID_VAN_JE_TELEFOON_ZIE_LOGCAT");

Om het allemaal nog mooier te maken; Je kunt gebruik maken van de Targetting informatie om specifieke Google Ads te laten zien. Gebruik hiervoor bijvoorbeeld de code :

request.setGender(AdRequest.Gender.MALE);
request.setLocation(location);
request.setBirthday("19850101");

In dit geval zal Google de ad proberen te vullen met specifieke advertenties voor mannen, geboren in 1985, rekening houdend met de huidige locatie. Zo kun je voor de gebruikers van je App ook in de advertenties echt waarde toevoegen!

Het kan eventjes duren voordat er een advertentie getoond wordt (minuut of 2). Elke keer als het Publisher ID 24 uur niet gebruikt is, duurt het 2 minuten voordat de advertentie getoond wordt.

* Dat betekent natuurlijk ‘slepen’.

Verdienrapport #1 – Estimated Earnings, Balance en Tap4Tap

Langzaam beginnen de contouren van het verdienen zich af te tekenen. De eerste stappen richting het doel. Nu wordt duidelijk hoe e.e.a. inelkaar zit en zijn de eerste rapporten beschikbaar. AdMob (adverteren in Apps) heeft al een keer $25 uitbetaald. De “Estimated Earnings” (de geschatte verdiensten per 1.000 indrukken) liggen op dit moment rond de $1,91. Als ik dat extrapoleer; Per maand moeten de Advertenties in de apps ca. 1.000.000 keer bekeken worden om een reëele verdiencapaciteit van iets minder dan 2.000 euro te bereiken. Ter vergelijking; FlitsWoorden heeft op dit moment ongeveer 100 indrukken per dag. FlitsWoorden is ca 4.000 keer geïnstalleerd, waarvan 2.000 actieve installaties.

Ik doe een hoop aannames EN… Als 50% van de installaties actief blijft moet FlitsWoorden dus ca. 1.400.000 keer geïnstalleerd worden om te komen tot de 35.000 indrukken per dag (= ca € 65). FlitsWoorden is een mooi model, want er blijven kinderen instromen in groepen 2 en 3 van de basisschool. Onderhouden, dus. Kijkend naar de revenue-grafieken van FlitsWoorden valt op, dat de app het meest wordt opgestart op woensdag. Waarschijnlijk vanwege de halve dag vrij. Ik verwacht tijdens vakanties een dip te zien. Daarnaast verwacht ik vlak voor de start van het schooljaar weer een lichte stijging. Die maanden (juli, augustus) zullen voor FlitsWoorden belangrijke marketing-maanden zijn. 1,4 miljoen installaties is prachtig, maar waarschijnlijk niet haalbaar (realistische doelen blijven stellen 😉 )

Gelukkig is FlitsWoorden niet de enige App die bijdraagt tot het doel. FlashWords en Applez zullen ook hun steentje bij moeten dragen. Van JollyJotXMas verwacht ik niet al te veel. Daarnaast zijn er inkomsten uit dit blog (nu nog niet veel, ca 3 eurocent per dag). In de toekomst zal ik ook wat gaan experimenteren met YouTube How-To’s, die ook te gelde gemaakt kunnen worden. Daarnaast zijn er nog de vrijwillige donaties die gedaan worden met de “Donate” button, rechts op deze site. Op dit moment valt het allemaal helemaal niet tegen. Omdat de inkomsten nog erg grillig zijn, blijft Google’s “Estimated Earnings” voor deze maand de barometer. Over een maand of 3 zijn betere statistieken zichtbaar en zal ik een poging doen om een eerste verdienrapport ‘per maand’ te publiceren.

Even over Tap4Tap. Een continue afweging voor app-advertising is; wanneer gebruik ik ruimte om reclame te maken voor de app (en dus mogelijk meer installaties) en wanneer gebruik ik ruimte om advertenties te laten zien (en mogelijk revenue). Tap4Tap kwam gisteren binnen in mijn mailbox. Het is een soort ‘uitwissel programma’ voor kleine in-app advertenties. Je verdient 1 credit voor het tonen van een banner (daar zitten 2 advertenties in). Je geeft 1 credit uit als jouw advertentie (in een halve banner) wordt getoond in de app van een ander. Als de gebruiker op die advertentie klikt, wordt xij naar de market geleid (om je app te installeren).

Installatie van de Tap4Tap code is eenvoudig. Je download en installeert de SDK, maakt een view aan en laad een Tap4Tap advertentie in die view. Ik zal dat in een aparte tutorial uitleggen. Je krijgt 1.000 credits als je een account opent. Die credits kun je direct gebruiken om een advertentiecampagne op te starten. In de tijd dat mijn advertentiecampagne (voor Applez) liep, heb ik 56 credits ‘verdiend’ door advertenties voor andere apps te laten zien. Voor zover ik nu kan bepalen heeft het tonen van advertenties voor Applez geleid tot het verbluffende aantal van NUL extra installaties :D. De Tap4Tap code blijft voorlopig in Applez aanwezig. Dat betekent dus dat Applez niet direct geld verdient met het tonen van advertenties, maar een 2-op-1 conversierate biedt; Elke keer dat iemand een Applez level afrondt, wordt er één keer een advertentie voor het installeren van Applez getoond (ter grootte van een halve banner) in een andere app, van een andere ontwikkelaar.

Als Applez voldoende vaak geïnstalleerd is, kan ik met een eenvoudige code wijziging de Tap4Tap advertenties veranderen in Google Admob advertenties. 😉

Concreet : nog geen 1% van het doel bereikt, maar Proof of Concept geaccepteerd ;). (De kosten van dit project zijn beperkt. De eenmalige entry-fee van $25,- voor het Google Development account en de jaarlijkse kosten voor webhosting zijn te verwaarlozen).

Ideeënstroom – So much to do…

Ik ben een beetje aan het fröbelen met Speed.java. Daarmee kan ik de fruitjes in aPpLeZ wat snelheid geven. Terwijl ik dat zo aan het doen ben schieten de ideeën door mijn hoofd. aPpLeZ moet beter, leuker en uitgebreider.

Ondertussen komt het eerste crashrapport van de Google Playmarket binnen. Een gebruiker is zo vriendelijk geweest om op Rapport te drukken (bedankt, Peter ;)).

java.lang.NullPointerException
at happyworx.nl.Applez.MainGamePanel.onDraw(MainGamePanel.java:271)
at happyworx.nl.Applez.MainThread.run(MainThread.java:58)

Voor apps die obscure code bevatten, bevatten stacktraces obscure symboolnamen. …

Wat er bedoeld wordt met “obscure symboolnamen” weet ik niet precies, maar wat er misgegaan is, gelukkig wel. Op regel 271 van de MainGamePanel worden de appeltjes getekend. Kennelijk heeft het spel geprobeerd een appel te tekenen die er niet meer was. Dat komt waarschijnlijk door de snelheid (traagheid) waarmee sommige arraylists worden leeggemaakt. Ik heb fluks één en ander veranderd in de code en een nieuwe versie in de Market gelanceerd. Nee, helaas geen team van testers om een uitgebreid test traject mee uit te voeren ;(.

Ondertussen bedenk ik allemaal leuke extraa’tjes. Appels die langzaam groter worden (dan zijn ze makkelijker te pakken), een levelcounter, na level 5 gaat dan alles draaien, na level 10 ook bewegen. Je kunt door punten te verzamelen (diamantjes?) een knop ‘verdienen’ waarmee je één groep fruit eventjes stil kunt zetten, zodat je ze beter kunt pakken. Ook : een nag-toeter, die om de 10 seconden (of misschien is dat wel te lang, moet het 7 zijn) één groep fruitjes onder een luide toet verandert van type (alle aardbeien worden bananen)… behalve natuurlijk de fruitjes die je al gevangen hebt (dank mijn lieve vriendin voor deze nuttige toevoeging! ;)). Oh ja.. een ‘extreme gravity event’ waarbij alle fruitjes in één keer naar beneden vallen. Hoe zat dat ook alweer.. 9,8m/secondekwadraat ?.. dat zal dus een functie moeten worden die Speed.java gebruikt. En als de appels de lijn raken, moeten ze veranderen in moes. Dat je weg moet vegen, met je vinger.. anders kun je niet bij de kassa.

Een ‘share-my-score’ voor FaceBook en Twitter mag natuurlijk ook niet ontbreken, want daar trek je de gebruikers mee.

… tja.

Zat te doen voor de komende dagen, weken, maanden. 😀