Sponsored By

Common Interfaces for mobile development (same code for all platforms)

This article describes the problem of platform and different framework fragmentation and how you can solve it by creating common interfaces, that will allow you to use Ads and In-app purchases using the same code for every platform/framework.

Arturs Sosins, Blogger

November 21, 2013

6 Min Read

As one of the developers of Gideros - cross platform mobile game development tool, my everyday task consist of incorporate different features of different platforms into our development eco system.

As Gideros offers plugin system for extending available features, it is quite easy to add almost any native functionality of each supported platform, but.

  1. Developers do not come to cross platform tool to write platform specific plugins

  2. There such a huge varriaty of possible plugins, there are tons of possible Advertising platforms, not even talking about other plugin options

So the problem was, how to satisfy the needs of the most clients, by making it easier for them to integrate Ads, In-app purchases and other platform specific libraries and without having to write and bind to our system every one of them.

And the idea was to create one single plugin for each type of commonly needed library, which would function as an interface and could work with any library wrapped underneath.

What it accomplishes:

  • It speeds up plugin development - you can reuse the same binding code, only wrapping library's functions under same interface

  • It speeds up code integration for developers - they are getting familiar only with one single Ads Interface, for example, and integrate it in their every app

  • It allows to easily switch between libraries - developers can basically reuse same code for every Ads library which is under provided interface

Common Ads Interface

So the first one we approached was the Ads Interface. While it was quite easy to come up with the interface itself, the implementation proved quite difficult, due to huge difference of the native implementations. For example, some Ads behaved like standard Android Views, other held some internal objects and tried to get atteched to the ViewGroup somehow internally. Ones instantiated immediately, while others waited for a response from server, and only then could be added, positioned and displayed.

The resulting interface we have looks something like that, and covers most of the developer needs:

  • Ads.new("frameworkname") --contructor

  • Ads:setKey() --provide library specific keys

  • Ads:showAd(adType) --display specific ad

  • Ads:hideAd() --hide ad

  • Ads:enableTesting() --enable test ads if available

  • Event.AD_RECEIVED --ad received and is displayed

  • Event.AD_FAILED -- failed to display ad

  • Event.AD_ACTION_BEGIN --user began action on ad

  • Event.AD_ACTION_END --user action ended

  • Event.AD_DISMISSED --ad was removed from the screen

  • Event.AD_ERROR --there was an error (incorrect set up, etc)

Common In-App purchase interface

Implementing common In-App interface was much more difficult. Mostly because the workflows between different libraries, were completely different. But there were also other problems that should be considered. 

Product identifiers for different stores could be different, so you would have to match them up into one single set of internal product Ids.

Detecting which In-app purchases are available on the phone, where the app is installed. This really differs from market to market, but on Android it mostly end with checking if app with specific package is installed, to check if it would support a specific market.

Google Billing V3

Main difference for Google Billing was the fact, that you had to knew all your product identifiers before you could do anything with them. Additionally you would need to consume the consumable products, while leaving entitlements.

IOS StoreKit

Main difference for IOS StoreKit, was that every purchase had to be confirmed.

Ouya IAP

In ouya's worflow there are no confirmations of the purchase event received by the app, thus it brings to a situation, where the purchase was processed by the server, but the app was not notified of it. So you must remember pending purchases and recheck them by trying to restore them form time to time.

Amazon IAP

The most annoying thing with Amazon In-app purchases was the pagination of results, so when you call for restoring the purchases, they give you bunch of results, but that might not be all of them, you need to check if there is more and request new page with the current offset.

Additionally Amazon does not provide receipt ID, so you must implement it on your own (or use some 3rd party server) to verify restored purchases, etc. And don't forget this ID should be device independent and only link to user, so user could restore the purchase from his account on any device.

And it seems that upon restoring Amazon returns all purchases, not only entitlements, but also consumables.

Result

To cover all frameworks, we required the user to provide the array (well actually table, since it is Lua) of product Ids, by using keys as app internal product Ids, and also provide which of them are consumables. Of crouse we could leave consumption and confirmation to end developer, but it seemed too appealing to cover all that internally in the interface and provide a small clean API for in-app purchases.

So the end united workflow for all mentioned In-app libraries looks like this:


require "iab"
local iaps = IAB.detectStores()
iab = nil
if iaps[1] == "google" then
	iab = IAB.new(iaps[1])
	iab:setUp("google-key")
	--using google product identifiers
	iab:setProducts({p1 = "googleprod1", p2 = "googleprod2", p3 = "googleprod3"})
elseif iaps[1] == "amazon" then
	iab = IAB.new(iaps[1])
	--using amazon product identifiers
	iab:setProducts({p1 = "amazonprod1", p2 = "amazonprod2", p3 = "amazonprod3"})
elseif iaps[1] == "ios" then
	iab = IAB.new(iaps[1])
	--using ios product identifiers
	iab:setProducts({p1 = "iosprod1", p2 = "iosprod2", p3 = "iosprod3"})
end
 
--load previous purchases
purchases = dataSaver.loadValue("purchases")
--if there were no purchases
if not purchases then
	--create and store empty table
	purchases = {}
	dataSaver.saveValue("purchases", purchases)
end
 
--if we have a supported store
if iab then
	--set which products are consumables
	iab:setConsumables({"p1", "p2"})
 
	--if purchase complete
	iab:addEventListener(Event.PURCHASE_COMPLETE, function(e)
		--if it was not previousle purchases
		if not purchases[e.receiptId] then
			--save purchase
			purchases[e.receiptId] = true
			dataSaver.saveValue("purchases", purchases)
 
			if (e.productId == "p1") then
				--p1 was purchased
			elseif (e.productId == "p2") then
				--p2 was purchased
			elseif (e.productId == "p3") then
				--p3 was purchased
			end
			AlertDialog.new("Purchase Completed", "Purchase successfully completed", "Ok"):show()
		end
	end)
 
	--if there was an error
	iab:addEventListener(Event.PURCHASE_ERROR, function(e)
		AlertDialog.new("Purchase Canceled", e.error, "Ok"):show()
	end)
 
	--call restore on each app starts
	--or make a button to allow users to restore purchases
	iab:restore()
end
 
--some where in your code to make a purchase
stage:addEventListener(Event.MOUSE_UP, function()
	if iab then
		iab:purchase("p1")
	end
end)

WIP and future works

Other library types that we will work on in future would include Controller Interface (Work in progress), where the most difficult part will be to map different controller device buttons to one single common mapping across all platforms

Next idea is to create leader board and achievement interface, where the most difficult task could be synchronizing achievements and stats between different frameworks.

And master class of common interfaces would be to create multiplayer support for different frameworks on different platforms under the same interface.

Read more about:

Blogs
Daily news, dev blogs, and stories from Game Developer straight to your inbox

You May Also Like