LeadBolt App Wall Code Sample - Activity with WebView for HTML Ads

Hi all,

I’ve seen a few people mention that the HTML LeadBolt App Wall is difficult to integrate. I definitely found it a bit confusing at first. I wrote my own Activity as a wrapper for the HTML ads. Since it ended up involving a few WebView quirks, I thought I’d share it with you guys in case it helps your integration.

Here’s the Activity code:

[java]
import java.util.Random;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class AdViewLBWall extends Activity {

protected static final String TAG = "AdViewLBWall";
private ProgressDialog progress;

//Whether the first page loaded successfully
private static boolean initialLoadFinished = false;

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

	//Get Webview
	WebView adWebView = (WebView) findViewById(R.id.adWebView);
	
	//Set callback on error
	adWebView.setWebViewClient(new WebViewClient() {
		@Override
	    public boolean shouldOverrideUrlLoading(WebView view, String url) {
			Log.d(TAG, "URL: "+url);
	        // If this is HTTP or HTTPS, load as normal
	        if(url.startsWith("http")) {
	        	//Don't override
	        	return false;
	        } else {
	            // Otherwise, this might be a market:// or mailto:// URL,
	        	// so launch another Activity that handles URLs
	        	try {
	        		//Safely launch the intent
	        		Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
		            startActivity(intent);
		            //Once we've left this application, no need to keep the ad visible
		            finish();
		            return true;
	        	} catch (Exception e) {
	        		//Couldn't launch intent on this device
	        		e.printStackTrace();
	        		//Let the user know
	        		Toast.makeText(AdViewLBWall.this, "Not supported on your device.", 1000).show();
					//If the URL's not working, just quit this ad display
					//No point annoying the user further by letting them try again
					finish();
					return true;
	        	}
	            
	        }

	    }
	    @Override
	    public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
	            Log.i(TAG, "WebView failed to load. Error code:" + errorCode);
	            //If this was the first page, and failed to load, we should die
	            //Because there's no way the user can do anything
	            if(initialLoadFinished==false) {
		            //Hide webview
		            view.setVisibility(View.INVISIBLE);
			    	hideProgress();
		            super.onReceivedError(view, errorCode, description, failingUrl);
		            
		            //Cancel this screen
		            finish();
	            }
	    }
	    @Override
	    public void onPageFinished(WebView view, String url) {
	    	Log.d(TAG, "WebView onPageFinished");
	    	hideProgress();
	    	//We've loaded the first page
	    	initialLoadFinished = true;
	    }
	 });
	
	//Select url to display
	String url = "INSERT_YOUR_APPWALL_URL_HERE";
	
	//Load data
	adWebView.getSettings().setJavaScriptEnabled(true);
	adWebView.setInitialScale(1);
	adWebView.setBackgroundColor(0);
	adWebView.loadUrl(url);
    
    //Show progress bar
    showProgress();
}

private void showProgress() {
	if(progress==null) {
		progress = new ProgressDialog(this);
		progress.setMessage("Loading...");
	}
	progress.show();
}

private void hideProgress() {
	if(progress!=null && progress.isShowing()) {
		progress.dismiss();
	}
}

@Override
public void onPause() {
	super.onPause();
	//Try to prevent context leaks
	hideProgress();
}

}
[/java]

And here’s the accompanying XML layout:

[xml]
<?xml version=“1.0” encoding=“utf-8”?>
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android
android:id="@+id/mainLayout"
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:gravity=“center”
android:orientation=“vertical” >

&lt;WebView
    android:id="@+id/adWebView"
    android:layout_width="match_parent"
    android:layout_height="match_parent" /&gt;

</LinearLayout>
[/xml]

There are a few tricks in there, especially the part which overrides the URL handler (this makes sure market:// URLs are handled correctly). I hope this code will help people who are starting out with LeadBolt to integrate the HTML App Wall more easily. All you have to do is start a new Activity with this class:

[java]
startActivity(this, AdViewLBWall.class);
[/java]

Of course, you could also use the LeadBolt SDK which includes a native App Wall. But I prefer using this method - it gives me more control over displaying a progress dialog while loading, and allows me to potentially pre-cache the HTML itself in future.

Any suggestions for improvement are welcome - I’m sure there are some parts which could be tidied up :slight_smile: Just thought I’d throw it out there anyway, in case somebody can use it.

Cheers,
David

Thanks David!

Out of the box, the HTML App Wall isn’t as nice as you had it in Fake iPhone 4S. Really appreciate you posting this.

I have few questions david:

  • In this AppWall is no “X” or “Close” button
  • Only on 320x480 looks ok, but on 480x800 to small, and on 240x320 are scrolls…
  • On AppWall I got prompt with questions about some game and two buttons on it, and in background list of apps to download. So first I have to click on this prompt and then on some app…
    Thanks for help in advance.
  • If there is no close button is because it is not needed when an user launches the appwall. If you put a “More games” button in your game that opens the app wall, you are not forcing the user to see the ad.
  • Not sure what kind of problem are you having with the resolution, but the layout is set to match_parent so it fits the whole screen.
  • That also happens with the SDK Appwall, there’s no way to get rid of that “Do you want to download an app?” question, it’s something Leadbolt has done by design. In fact the SDK Appwall is just a wrapper for a Webview, so I wouldn’t expect any difference in the content.

I just view the html AppWall as interstitial in a WebView. I set the width to 75% of the screen and add an X button myself that closes it. Might have to test it on smaller screens though, I totally forgot to test how it works on 480x320. And I have a problem that it shows a question “do you want to download a free app?” or sth like that and either answer (yes, no) does nothing but hides the question. Would be great to be able to get rid of that question or to bind “no” to closing of the AppWall…

Magnesus, when you click yes in the “do you want to download a free app ?” dialog, sometimes it takes you to the Play Store page for a given game/app. When you click “no”, it always closes the dialog. I think the former case is for CPA campaigns.

OK. Thanks. I’ll leave it like that then. :slight_smile:

Just wanted to say thank you David. You saved me some time. The HTML app wall also has the advantage of not having to include the in-app library if you are currently using notification and/or icon ads.

EDIT: A couple of missing things:

1- You need to add the Activity to the AndroidManifest.xml:

<activity
android:name=".AdViewLBWall"
android:theme="@android:style/Theme.Black.NoTitleBar" />

2- For sure a typo on your part, but you need to start the Activity through an Intent:

startActivity(new Intent(YourActivity.this, AdViewLBWall.class));

David:

Thanks again for the code.

I would like to point out a known issue for WebView with HTC devices (I have confirmed it on HTC One X and HTC Desire running 2.3.3).
It may be related to HTC WebView implementation (something to do with HTC’s Sense UI ?) not respecting the meta-tags or some such thing in the webpage HTML.

Background reading:


Android WebView not respecting scaling percentage - Stack Overflow
Some HTC devices have problems with WebView, try this snippet!

Issue 8137 - android - Issue with WebView on HTC Incredible - Android - An Open Handset Alliance Project - Google Project Hosting
Android :: WebView Seems To Ignore Viewport Information On Web Pages
html5 - Android WebView seems to ignore “viewport” information on web pages - Stack Overflow
android - How to set the initial zoom/width for a webview - Stack Overflow

To fix this issue - this change seemed to help so that HTC devices also were displaying the WebView at reasonable size.

Maybe possible to whittle down some of that stuff below, but it works on Samsung and these devices now.


// fixing the issue of tiny display of WebView in portrait mode on HTC devices
// for David's code for WebView display:
// http://forums.makingmoneywithandroid.com/advertising-networks/740-leadbolt-app-wall-code-sample-activity-webview-html-ads.html
// RE: LeadBolt App Wall Code Sample - Activity with WebView for HTML Ads
//
if (false) {
	// replace these lines with the ones below
	adWebView.getSettings().setJavaScriptEnabled(true);
	adWebView.setInitialScale(1);
	adWebView.setBackgroundColor(0);
} else {
	adWebView.getSettings().setLoadsImagesAutomatically(true);
	adWebView.getSettings().setJavaScriptEnabled(true);
	adWebView.getSettings().setBuiltInZoomControls(true);
	adWebView.setInitialScale(100);      


	// need this from above - else the screen flashes white ..
	adWebView.setBackgroundColor(0);


	// ok, this fixes it ..
	//
	adWebView.getSettings().setLoadWithOverviewMode(true);
	adWebView.getSettings().setUseWideViewPort(true);
	adWebView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
}

Hi everyone,

I wonder if I use WebView instead of SDK, do I have to add permissions required by SDK?

Thanks All

No extra permissions required (other than internet etc.).

The only app-specific thing is the url - which is specific for each app that you are using the HTML in etc.

Great discussion. Glad I found this place.

I tried Leadbolt’s alert ads in early December, and they didn’t perform well for me compared to AppBrain, AppLovin and Revmob. Actually, it looks like they’re not even offering that type of ad anymore under “Add Ad”, so I guess that says something.

I’m about to give their App Wall a try this week. It loads VERY slowly for me though. I’m going to try pre-fetching as well. Maybe have a webview with visibility=gone while it’s loading, then show it when it’s loaded or have a “More Apps/Free” button as David suggested in another thread.

Right now - I haven’t released this version yet though - I load the leadbolt appwall on an invisible webview at the beginning of the level and show it at the end - seems to be working.

etc. ? What do you mean ?

Also I read somewhere that the SDK version of the Appwall has better performance, but the support guy at Leadbolt didn’t know why… :S

Also I read somewhere that the SDK version of the Appwall has better performance, but the support guy at Leadbolt didn’t know why… :S

Maybe they have the prefetch thing builtin now ?

Can do prefetch to invisible webview - or can start a different activity (which will be cleaner).

That activity may have a “static” method “tryToPrefetchAd()” that sets up a layout. And the activity can have another “static” method “tryToShowAd()” which (if there IS an ad available) then tries to show that ad.

Maybe can put a time check so not do more often than every 3 minutes.


if (have ad) {
	// try to show ad

	long currentTime = System.currentTimeMillis();
	if (currentTime < lastFullScreenAdDisplayTime + WAIT_BETWEEN_DISPLAY_AD) {
		// still not waited long enough ..
		//
		// going to ignore this request ..

		return false;

	} else {
		display this ad

		reset variables to indicate that no longer have ad available
	
		can prefetch ad (in another thread) if want here


		// remember last time an ad was shown
		lastFullScreenAdDisplayTime = currentTime;

		// successfully set up ad to display
		return true;
	}

} else {
	prefetch ad (in another thread)

}

And then in your main activity can just use tryToShowAd() all over the place wherever you think the interstitial ad (Leadbolt AppWall) could be shown.

The ad will be shown if available and not shown if not enough time has elapsed between display of ad etc.

Since you will need to prefetch ads in a separate thread - can do everything else in the UI thread (the thread which calls tryToPrefetchAd()) and just do the adWebView.loadUrl() part in a separate thread.

Make the method tryToPrefetchAd() and tryToShowAd() “synchronized” and can set up variables to ensure that once fetch started, that don’t respond to another fetch request and similarly for show ad etc.

I would try prefetching the HTML into a String, and then loading this into a WebView when you’re actually displaying the app wall. Otherwise you might run into threading issues or memory leaks when carrying a WebView across Activities. The docs for WebView show how to load HTML from a String (see Basic Usage).

Loading into String may work - but from what I understand that means - you would be prefetching the HTML (only) into the String (not the allied graphics etc.).

This means when you THINK you have the ads all prefetched and display - then at THAT time the graphics would be downloaded.

In comparison if you use an “inflater” and do (in a separate thread):

adWebView.loadUrl(URL);

Then the HTML and all allied graphics would be prefetched ALSO - so when you display there would be NO more stuff to download.

Good point, I hadn’t considered images. That makes the preloaded WebView that you’ve described the better option. How would you handle making the WebView full-screen though? My approach in the past has been to start a completely new Activity for it. I don’t see how this would work with the layout already created in an existing Activity.

For within same activity - could define (using RelativeLayout to cover whole screen or overlay the other stuff - i.e. constrain it so) - and set it View.GONE - then when want to show - set all others View.INVISIBLE and set this View.VISIBLE etc.

But it maybe simpler to make into a separate activity - less clutter in your existing code - and can just have two calls (static) in the activity:

tryToFetchAd()
tryToDisplayAd()

I don’t know why Leadbolt does not provide this functionality to begin with - maybe their SDK does - but from your comments it suggested to me (I haven’t looked at the Leadbolt SDK) that that their SDK does NOT provide a prefetch mechanism ?

David:

This is using your (David) Leadbolt AppWall code, but then making that into a prefetch version (using a separate thread for the adWebView.loadUrl()).

In the interest of saving folks the SAME repeated effort, I am including working code below which seems to be working well.

That is, it loads ads in the background and when you want to show the appwall it shows it (in a separate activity).

If user presses back button they return back to your app.

The ad display is INSTANT.

Only caveat is that in landscape mode a thumbnail of the portrait mode seems to be shown momentarily before the landscape is shown.

That doesn’t happen if you use portrait mode.

The code also includes a workaround to ensure that WebView on HTC Devices in portrait mode is not TINY but appears at reasonable size - this seems to be a known issue with HTC devices and Webview (not sure if this is a general issue or only if use a separate thread to do adWebView.loadURL() etc.


Usage:

Whenever want to prefetch ads:


	LeadboltAppWallActivity.tryToPreLoadUrl(YOUR_ACTIVITY.this);

When want to show the appwall - no guarantee that it will show - but can call as many times as want WHENEVER there is a pause or opportunity in your game etc.:


	LeadboltAppWallActivity.tryToShowOccasionalAds(YOUR_ACTIVITY.this);

Can check first if it is available:


	if (isReadyToShowAds()) {
		...
	}

If use a “More Apps” button on screen - can put this code there:


if (moreApps_flag) {
	// show Leadbolt AppWall HTML ads ..

	//----------
	// try to show Leadbolt AppWall HTML ads ..
	if (LeadboltAppWallActivity.tryToShowAds(YOUR_ACTIVITY.this)) {
		// successfully showing ads ..

	} else {

		// not showed ads .. but started downloading them for next time etc. ..
		Toast.makeText(PrimaryActivity.this,
			"Loading apps list .. check back in 5 seconds."
			, Toast.LENGTH_LONG).show();

	}
	//----------
}


Add to res/layout/leadbolt_app_wall.xml:


	<?xml version="1.0" encoding="utf-8"?>
	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
		android:id="@+id/mainLayout"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:gravity="center"
		android:orientation="vertical"
		>

		<WebView
			android:id="@+id/adWebView"
			android:layout_width="match_parent"
			android:layout_height="match_parent"
			/>

	</LinearLayout>


Add to AndroidManifest.xml - choose “landscape” or “portrait” (the existing code does not respond to orientation changes mainly because that causes onPause() and we’ve chosen to do cleanup on onPause() i.e. at the slightest pretext):

Replace the YOUR.PACKAGE.NAME with your package name …


<!-- start of Leadbolt AppWall HTML -->
	<activity
		android:name="YOUR.PACKAGE.NAME.LeadboltAppWallActivity"
		android:screenOrientation="landscape"
		android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
		android:theme="@android:style/Theme.Black.NoTitleBar" />
<!-- end of Leadbolt AppWall HTML -->


Add to LeadboltAppWallActivity.java:



// ISSUE_ADD_APPWALL_URL_HERE
// ISSUE_CHANGE_PACKAGE_NAME
// ISSUE_SET_MINIMUM_INTERVAL_TIME





// ISSUE_WEBVIEW_WARNING_ON_ANDROID_ICS_ETC
	//
	// it seems doing loadUrl() in a separate (non-GUI) thread
	//	creates a warning ..
	//
	//	01-22 13:46:58.556: D/LeadboltAppWallActivity(18074): tryToPreLoadUrl() - loadUrl()
	//	01-22 13:46:58.561: W/webview(18074): java.lang.Throwable: Warning: A WebView method was called on thread 'Thread-1172'. All WebView methods must be called on the UI thread. Future versions of WebView may not support use on other threads.
	//
	// though can still use ..
	//
	//
	// see also:
	// http://stackoverflow.com/questions/8555749/getting-an-error-in-webview-on-ice-cream-sandwich
	//	You can call webview.loadUrl("javascript:myJavaMethod(" + itemArr + "," + telcoID + ");"); in a background thread. this can be a solution.
	//	
	//	it will warn: A webview method was called on thread xxxx All webview methods must be called on the UI thread.
	//	
	//	But it works.
	//	


// ISSUE_ENSURE_NOT_LOADURL_WHILE_ALREADY_DOING_SO

// ISSUE_ENSURE_NOTSTART_ACTIVITY_WHILE_ALREADY_STARTED




// ISSUE_SEQUENCE_OF_EVENTS_AFTER_USER_CLICKS_ON_AD_ALSO_SHOW_PROGRESS_DIALOG_AFTER_CLICK_ON_AD
	//
	// adding progress dialog .. that starts AFTER click on an ad ..
	//	since can take some time to load the different URLs ..
	//	and eventually get to market:// link that takes to Google Play ..
	//	(esp. on a GPRS connection)
	//
	//	therefore wanted to show a progress dialog ..



// ISSUE_LEADBOLTAPPWALLACTIVITY_NOT_WANT_ORIENTATION_CHANGES_TO_DESTROY_ACTIVITY
	//
	// to avoid orientation changes from causing a destroy of activity and then restart (onCreate() etc. ..)
	//	and onPause() etc.
	//
	//	(you are currently cleaning up stuff in onPause())
	//
	//
	// can simplify .. by choosing to NOT support rotation ..
	//
	//	this causes orientation changes to NOT cause activity destroy/restart etc. ..
	//
	//
	//
	// to not support rotation .. put this line in the AndroidManifest.xml file:
	//
	//	android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
	//

// ISSUE_LEADBOLTAPPWALLACTIVITY_ZOOM_LEVEL_BAD_ON_HTC_DESIRE_HTC_ONE_X
	//
	// seeing that when showing on HTC devices in "portrait" mode .. the layout is very small ..
	//
	//	i.e. not fills the screen ..
	//
	//	at least with the commands that given in original source code
	//	(from David at makingmoneywithandroid.com forum ..)
	//
	//
	// it seems HTC phones have an issue with WebView layout ..
	//	i.e. supposedly they ignore the meta-tag for HTML or etc. ..
	//
	//	i.e. for the browser .. it seems to ignore the tag
	//	and thus rendering screwed up or etc. .. !
	//
	//




// ISSUE_CHANGE_PACKAGE_NAME
// change this
package YOUR.PACKAGE.NAME.GOES.HERE



import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;


… continued in next post