zemariamm's posterous http://zemariamm.posterous.com Most recent posts at zemariamm's posterous posterous.com Tue, 22 May 2012 07:54:21 -0700 AudioStreaming in the Background on iOS http://zemariamm.posterous.com/audiostreaming-in-the-background-on-ios http://zemariamm.posterous.com/audiostreaming-in-the-background-on-ios

Audio Playback must be one of the most used features in the iPhone and iPod touch (heck, I didn't know what to do on the iPod if audio wasn't available :) ), with the internet becoming more and more ubiquitous, audio streaming seems to be popping up on every music app. However, as far as I know, there isn't an easy way to add audio streaming to your app without resorting to third party libraries, and this I did. 

 

 

 

Recently while working on our latest iOS application, SongZApp, I was faced with the issue described above, making some searches I  stumbled upon the outstanding AudioStreamer Library from Matt Gallagher, working with it is pretty straight forward and is very well documented on his website:

 

http://cocoawithlove.com/2008/09/streaming-and-playing-live-mp3-stream.html

 

 

I recently reached Matt to add another feature to his library: Multi-Tasking support since there are a lot of people trying to make it work:

 

http://stackoverflow.com/questions/6132690/how-to-multi-task-on-iphone-sdk

http://www.iphonedevsdk.com/forum/iphone-sdk-development/54158-playing-radio-stream-background-ios-4-a.html

 

Although at first it seems complicated, it actually, isn't. First we have to declare that our app will run in the background, playing music:

 

1.  In you application's plist file in "Required background modes" add "App plays audio"

 

Next we need to tell iOS that our app never stops playing, the trick is to never tell that it stopped :) However you should carefully watch what interrupted your audio (another app, SMS arriving, incoming phone call, etc) for demonstration purposes I'll just tell it to never deactivate audio:

 

2.  Comment line 870 of Classes/AudioStreamer.m  ( https://github.com/zemariamm/AudioStreamer/blob/master/Classes/AudioStreamer.m#L870)

 

 

We're almost done although there's only a small issue, whenever the audio is buffering, or the app is changing streams, the audio playback will stop if that happens the operating system will assume that we finished our task and will put our app to sleep. To overcome this problem, we should, while we're buffering audio or waiting for some other task to finish, tell iOS that we're performing a long running task and shouldn't be put to sleep.

 

3. Make a call to beginBackgroundTaskWithExpirationHandler: from time to time to refresh our time to live.

 

A Task created with beginBackgroundTaskWithExpirationHandler: will last a maximum of 10 minutes, the approach we'll take is to install a handler that gets called every 5 minutes to refresh the task (creates a new one and deletes the other one)

 

 

I forked Matt Gallagher's AudioStreamer Library and adapted his example to iterate through 4 different soundcloud streams (warning: Very ugly and possibly buggy code):

 

https://github.com/zemariamm/AudioStreamer

 

Note: All the code is released in a "It works for me" state. :)

 

Good luck!

Zé Maria

 

 

 

 

 

 

 

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1114241/josenamibia2.png http://posterous.com/users/heNYu2H31Wrx0 Jose Moura zemariamm Jose Moura
Thu, 09 Jun 2011 17:14:00 -0700 Android Marketing: Using coupons to giveaway paid Android Apps http://zemariamm.posterous.com/using-coupons-to-giveaway-paid-android-apps http://zemariamm.posterous.com/using-coupons-to-giveaway-paid-android-apps

Need help testing your android app? Want to test it on-demand across several devices? Check http://www.testingandroid.com


Recently I was faced with an issue: one of my android users had a problem and wasted 2 hours with me debugging it, he didn't ask me for anything but I though it would be nice if I gave him the full version of the free app he helped me fix.
However, there's no way of giving a user a copy of a priced app on the Android Market ( at least that I know of), so I ended up giving him the APK for that product. Unfortunately, this approach has 2 big drawbacks:

  1. The user has to turn on the settings for "Install from Unknown sources"
  2. The user will NOT receive automatic updates for the app (since it wasn't deployed through the market)

Normally when I design an app I usually follow this pattern:

  • Design the full app first (the paid one)
  • Design the free app by restricting access to some functionalities

As you can imagine the source code is exactly the same, only a small option in my AndroidManifest file is changed (I know that it isn't the best approach but works for me as is).

In the retail world if you want to give someone a product  for free you give him a coupon to trade for the product, so I decided to follow the exact same approach.

So the requirements I set to myself to fulfill were:

  1. be able to give away coupons for paid apps to anyone I wanted to
  2. be able to cancel any coupon at any give time
  3. be able to disable coupons for a specific app if required


As you imagine this project has 2 main components: the coupon controller on the free Android App and the backend to control which coupons are valid and which apps have coupons.

This is a very simple project so I ended up using AppEngine to deploy my backend code.

Android Coupon Controller Overview

Let's start by having a look at the coupon controller on the Android App, there're two ways for redeeming a coupon:

a) In your app have a "Redeem Coupon" option somewhere

Redeem
b) When the user tries to access priced app functionalities, fire a dialog for him to buy the app or use a coupon:

Forbidden

If the user presses "Redeem coupon" a new dialog will popup with the instruction to insert a coupon:

 

Insertcoupon

Obviously, a different dialog will be shown if the user inserts a valid or invalid coupon (or if there's no internet connection available):

Validcoupon
Invalidcoupon

Android Coupon Controller Installation and usage:

It's very easy to use the coupon controller on your Android App, just follow these steps:

  • Create a libs directory on your project (if it doesn't exist)
  • Copy coupon.jar to that folder
  • Right-Click on "libs", press "Refresh"
  • Go to Project -> Properties -> Java Build Path -> Libraries -> Add Jar

Addjar

After adding the library to your project is time to integrate your code with the coupon controller, here's a brief description of the controller's code:

  • interface ProcessCoupon - your activity should implements this
    • public void launchCoupon() - should call Coupon.createCouponDialog(..) to create the coupon dialog
    • public void processInvalidCoupon() - is called after an invalid coupon is inserted
    • public void processValidCoupon() - is called after an invalid coupon is inserted (should call CouponPreferences.markUnblocked(Context) )
  • CouponPreferences - utility class
    • public static boolean isUnblocked(Context context) - Returns true if a valid coupon was already inserted
    • public static void markUnblocked(Context context) - transforms the app in it's full version

Coupon class - UI for the coupon controller

 

  • public static void createForbiddenDialog(final ProcessCoupon parent, final String warning, final String packageNameFull ) - should be called when the user tries to access a feature that is only available in the full version: informs the user that he can't access that feature (described by the warning parameter) giving him the options to: Buy the App (with the pname assigned to ' packageNameFull') or to 'Redeem the coupon'
  • public static void createCouponDialog(final Context context,final ProcessCoupon parent,final String appname, final String host, final String deviceId) - This method shows the user a dialog to insert his coupon, it will then communicate with the backend and inform the user if the coupon is valid, invalid or if there's no internet connection. The parent param should handle those cases, the host param stands for the URL for your backend (more or this later) and the deviceId of the user's cellphone (which gets registered on server-side )


Android Coupon Backend Installation and usage:
The backend is where you create applications and coupons for them. To disable specific coupons or applications you will need to use the AppEngine dashboard and Datastore Viewer

To install your backend on AppEngine, you can start by cloning this git repo:

  1. https://github.com/zemariamm/DroidCoupons
  2. Go to appengine.google.com and create a new application
  3. Edit app.yaml and replace droidcoupons with your Application Identifier (you check get from the previous step)


Deploy it on AppEngine ( see: http://code.google.com/appengine/docs/python/gettingstarted/uploading.html )

Afterwards you will need to create coupons for your application, to do that just open (on your browser) the following link:
http://your-application-identifier.appspot.com/random/your-app-name -> replace your-app-name with the same string you on param "appname" when calling createCouponDialog on your java code, replace "your-application-identifier" with your application identifier
(If the application doesn't exist yet, it will be created, you don't have to worry about that)

After opening the bellow URL you will be asked for your google credentials and afterwards you will see something like this (if you open: http://droidcoupons.appspot.com/random/testcoupons ):

testcoupons - dkbm1ha5

Which means:appname - coupon

E.g. dkbm1ha5  is the string you will insert in your android app

NOTE: It's required that the "host" parameter mentioned on "createCouponDialog" matches: "http://your-application-identifier.appspot.com/check/"

To disable a specific coupon, open your appengine dashboard for the application identifier you just created, click Datastore Viewer and follow these steps:

Disablecoupon

To disable all coupons for a app you need to follow a similar proccess:
Disableapp

Example:
In order to explain better how the library works and to give a working sample, I'll walk you through a working app (available at: https://github.com/zemariamm/TestCoupon)
The goal is to start out with a limited app and having it's behavior changed upon inserting the coupon:

The app consist of one TextView and 2 Buttons: one button to redeem the coupon and the other one to perform a "paid" app action
Here's the screenshot:

Free

Where the user clicks the "Click Here" button he will see this message:

Clickfeatwhilefree

When he clicks Redeem coupon:

Redeem

And here're the printscreens after inserting a valid coupon:

Validcoupon
Full

After inserting the Coupon if the user presses the "Click Here" button he's now able to execute it's full functionality, which means, displaying the following dialog:
Featfull

The code for this test application is very simple (and available at: https://github.com/zemariamm/TestCoupon), the source code for the Main Activity follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.zemariamm.testcoupon;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

import com.zemariamm.coupons.Coupon;
import com.zemariamm.coupons.CouponPreferences;
import com.zemariamm.coupons.ProcessCoupon;

public class Main extends Activity implements ProcessCoupon{
    
TextView text;
    Button btn;
    Button btnFull;
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        text = (TextView) findViewById(R.id.text);
        btn = (Button) findViewById(R.id.btn);
        btnFull = (Button) findViewById(R.id.btnFull);
        if (CouponPreferences.isUnblocked(this))
         text.setText("Full Version");
        else
         text.setText("Limited Version");
        btn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
if (!CouponPreferences.isUnblocked(Main.this))
launchCoupon();
}
});
        
        btnFull.setOnClickListener(new OnClickListener() {
public void onClick(View v) {

if (CouponPreferences.isUnblocked(Main.this))
{
AlertDialog.Builder builder = new AlertDialog.Builder(Main.this);
builder.setMessage("You have access to the full version")
.setCancelable(false)
.setPositiveButton("Nice", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
text.setText("Full Version");
}
});
AlertDialog alert = builder.create();
alert.show();
}
else
{
Coupon.createForbiddenDialog(Main.this, "To access this feature you must redeem your coupon","com.zemariamm.wickedfull");
}

}
});
    }
public void launchCoupon() {
// working example - dkbm1ha5
Coupon.createCouponDialog(Main.this, Main.this, "testcoupons", "http://droidcoupons.appspot.com/check/");
}
public void processInvalidCoupon() {
Log.d("TestCoupon", "INvalid coupon!");
}
public void processValidCoupon() {
Log.d("TestCoupon", "Valid Coupon!");
CouponPreferences.markUnblocked(this);
}


}


The Main Activity (which handles coupons) implements ProcessCoupon, the behavior of each button is defined according to the value of CouponPreferences.isUnblocked (which will return true if a valid coupon was inserted previously), the Activity implements the required methods: launchCoupon (to show the Coupon dialog), processInvalidCoupon (which gets called when an invalid coupon is inserted) and processValidCoupon (which gets called when a valid coupon is inserted). It's also important to show that when the user tries to access paid app features that we call createForbiddenDialog giving him the option to buy the full app or redeem a coupon in order to have access to that feature.

Note: All the code is released in a "It works for me" state. :)


Source code:
Android Library - https://github.com/zemariamm/Coupons
AppEngine Backend - https://github.com/zemariamm/DroidCoupons

Example App - https://github.com/zemariamm/TestCoupon

Need help testing your android app? Want to test it on-demand across several devices? Check http://www.testingandroid.com
 


Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1114241/josenamibia2.png http://posterous.com/users/heNYu2H31Wrx0 Jose Moura zemariamm Jose Moura
Mon, 28 Mar 2011 11:18:00 -0700 Introducing Appirater for Android http://zemariamm.posterous.com/introducing-appirater-for-android http://zemariamm.posterous.com/introducing-appirater-for-android

Need help testing your android app? Want to test it on-demand across several devices? Check http://www.testingandroid.com

Nowadays with the rise of the appstores, every developer has been trying to game the stores’ algorithms to increase the relevance of their apps hence their revenue.

Being the app’s rating one of the variables in the equation, every developer is trying to get more ratings and comments from their users, Arash Payan created a class that you can drop into any iPhone app that will help remind your users to review your app on the App Store.

As Arash puts it:

" Like most developers, I’m not thrilled with the way the App Store presents my apps. There are several problems, but in particular, I really don’t like the user review system. It’s biased towards bad reviews, and that ends up hurting sales (there are odd exceptions to this). The only time a user is reminded or asked to rate an app is when you delete it, and you probably don’t care for the app if you’re deleting it. In comparison to the unhappy user, the satisfied user rarely takes the time to review your app. Which leaves you with crummy reviews from uninformed users hurting sales of your app "

So he created Appirater to encourage his users to rate his own apps. Appirater tracks the number of times your application has been started and after a reasonable amout it will display a dialog for your users reminding them to rate the app.

So I did the same for my Android applications and the Android Market. “Appirater for Android” tracks the number of times that an Activity is created and restarted (tracks “OnCreate” and “OnRestart - for more information check the docs: http://developer.android.com/guide/topics/fundamentals.html ), after a pre-defined number of times an Alert Dialog is shown to the users asking them to rate your app.

Here’s a screenshot of one of my apps running appirater:

Appirater

  • How to integrate Appirater for Android:

Assuming you’re using Eclipse and ADT:

  1. Copy appirater.jar into your project’s libs folder
  2. Select Project -> Properties -> Java Build Path -> Libraries -> Add JARs 

Eclipsebuildpath

  • How to use it:
  1. In your root Activity extend “com.zemariamm.appirater.AppiraterBase” (Ex: public class Main extends com.zemariamm.appirater.AppiraterBase )
  2. In your AndroidManifest.xml add the following between <application> and </application>: <meta-data android:name="appirater" android:value="8" />

This will make Appirater run every 8 times the Activity is created or reloaded.

  • Customizing

There are a couple of ways to customize Appirater's behaviour, for example, if you’re a control freak like myself, you can track the people that click on “Rate”, “Remind me Later” or “Never Rate”, to do that you just have to override the following Appirater’ methods:

  • void processNever(void)
  • void processRate(void)
  • void processRemindMe(void)

I usually combine mixpanel’ metrics with Appirater to have a feeling of the number of people that rate my app versus the people that don’t want to rate it.If you feel that there should be another condition to fire up Appirater, you can easily add it without having to dig on Appirater’s source code. To do so, just override:

  • boolean shoulAppiraterdRun(void)

The overriden method should return true if you want to Appirater to run, typical examples include:

  • only running appirater if there’s internet access
  • only run appirater if the app was installed at least x days ago

If you want to see Appirater in action just install the free versions of my apps:

The source code and the compiled jar are available at github: https://github.com/zemariamm/Appirater-for-Android

Thanks and good luck!

Need help testing your android app? Want to test it on-demand across several devices? Check http://www.testingandroid.com

Permalink | Leave a comment  »

]]>
http://files.posterous.com/user_profile_pics/1114241/josenamibia2.png http://posterous.com/users/heNYu2H31Wrx0 Jose Moura zemariamm Jose Moura