The dead of DotNetNuke

It was arround 2007 when DNN got my attention for the first time. I needed a CMS system which was not based in PHP since I really didn’t want to be involved in another technology and I was somehow kind of familiar with c# and asp.net tech. At that time DNN was, in my opinion, far better in every aspect than any other free CMS available, being the weakest point the lack of free modules (although there were a lot of awesome modules like forum or blog ones, but when you needed for example a photo gallery module, you would only find one free option instead of a bunch of them) and the strongest points the architecture, the user experience, the easy to develop skins and so on.

cdtudelafixedconfondosolofranjas.jpg
Custom skin for a sports club DNN website

It was a really good free CMS, well build, easily customizable and expandable… I built some websites using this CMS, one for a local small soccer club wich isn’t online anymore and another one for a local rc sports club wich I plan to shut down soon and replace for another CMS. I built some other small websites but mainly those two were the big ones.

So, if it was so good, why is it dying?

In my opinion, they wanted to make money from it way too early.  As the Dnn CMS was just another contender to the more extended and popular PHP CMS’s, they started to serve an enterprise version and a community version. They somehow stopped developing modules or maybe the people who developed the first modules stopped working on it. In version 4 to 7 of the CMS you would find the exact same free modules, technology and a dropping community support.

Don’t get me wrong, it’s good they make as much money as they can, but monetization has to be done at the proper time.

So in the need of upgrading a website using DNN from a very old version to a newer one, I find discouraging how little they improved the CMS in so many years. Dnn is very powerful but you need modules to leverage it, without modules you get only a fatter and slower software that need a more expensive hosting service. It’s even worse because some new contenders have arrived since then, now you have bigger and more numerous contenders, most of them just focus on one aspect, being shops or blogs. They offer huge variety of modules for their respective targets and they do their job excelent.

DNN-Google-Trends

Recently, less than a year ago, ESW Capital acquired DNN Corp, so, considering Dnn is practically dead right now, I’m really looking forward to ESW to make some big investment into the DNN CMS to make it shine again and return it from the grave where it has been resting for the last years.

Advertisements

Baby Wooden Games released!

My toy-project for the last months has finally come to an end. Baby Wooden Games is online in the android store. It’s intended for babies and kids up to 5 years. You can get more info here.

And you can also watch the official youtube intro video here:

In app purchases for Android with Unreal Engine 4.16 and Blueprints

You know, once you’ve almost finished a nice little game with such amazing engine as Unreal Engine, you have to deal with all the nasty and unconfortable details of publishing and monetizing it.

But, is Unreal Engine ready to make that final step as easy as developing the game itself?…well not that much.

First of all, this apply for version 4.16.3, but I think this hasn’t changed in the current beta version 4.19 and after all the searches I’ve done It doesn’t look like its gonna be fixed soon, but if your engine version is far newer than this, maybe this doesn’t apply for you.

When you have to monetize your game, you want ads and you want in app purchases. Its a posibility to have a free limited version and a paid full version instead, in wich case you can just use google play store and have 2 separate versions published and everything will be easier. But it looks like you will get more revenue with a free version that display some kind of ads and offer some in app purchases.

For my experiment and first UE game “Baby Wooden Games” I’ve decided to go with a free version with ads and offer just one IAP (in app purchase) product, the one that removes ads completely from the game; This would be a “non-consumable” in app purchase.

Now, you have to solve several case-uses:

  1. When the user don’t own the product, you have to show ads
  2. When the user buys the IAP, you must not show ads.
  3. You have to deal with the purchase workflow in order to let the user buy the products.
  4. If the user owns the product and makes a new installation in a new device, you have to detect that the product was already bought and just don’t show any ads.
  5. If the user returns the product (an Android user can get a return some purchases in a period of time if he is not happy with them), then you have to detect it and show ads again in every device he had the product installed on.
  6. What if the user loads your game and hasn’t got internet connection? Some ads get cached into devices and can be shown without connection, but, if you can’t check online if the product was bought, how to know wether to show ads or not?.

You will have even much more case-uses if you have more IAP products with some of them consumables, but let’s stick with this example.

The problem:

UE4 provides several BP functions to deal with IAP and to solve ALMOST all your needs :

online.png

And I can tell you all of them work correctly, but also that there’s ONE MISSING!

By the way the ones easier to use are the second ones, the ones that don’t start with “f”, and they can only be used in the main graph, they don’t work inside functions, the “f” functions have to be created with ad-hoc events wich can be difficult to use for some people.

  1. Make an In-App Purchase:
    This works, and allows the user to make an in app purchase.
  2. Restore in app purchase:
    I haven’t tested it but afaik it works correctly.
  3. Read In app purchase information:
    This one is the main reason of this post and of having lost several hours figuring how to finish all my IAP workflows.
    This function returns information on the AVAILABLE IAP products, it doesn’t return info on the ALREADY owned IAP products!!!So, if you have several IAP that can be created dinamically in the APP Store and must be offered dinamically to your user, this is what you need. But, what about the already purchased IAPS? how can I know if a “no-ads” product is owned by the user?
  4. The missing one: One to read the currently owned in app purchase products. And you need it. You can’t even publish a game in several stores if you can’t detect wether the user owns a product in a new installation. This is the reason for this post and …don’t worry! we have figured out couple of workarrounds and goona share with you!

The workarround, not that good one:

So, if a user buys the product, and you store a boolean value in preferences, then it doesn’t matter you can’t check online wether the user bought it, right?

Well, it matters a lot because if the user makes a new installation, you have no way to know if you must show ads or not. In fact, you have one way, if the user tries to buy the product again, you will receive a “fail” from the MakeInAppPurchase method with the message “Already Owned”, wich will allow you to store the correct boolean preference in the new device.

But even it’s working, this it’s not acceptable to me as it’s unintuitive and can annoy the users, and that’s why I keeped searching.

The workarround, third parties plugin:

There is at least one plugin to deal with IAP, from SDKBOX company, and it’s supposed to work, although it won’t be easy, at least in 4.16.3 engine as it won’t compile right off the box. You have too google a lot to solve the compilation issue, edit some .cs files, and voila, you have the plugin installed…but the documentation isn’t enought and you will see game crashes. In top of that you will have to convert your pure BP project into a C++ one and install Visual Studio… not an easy way to follow, that’s why I discarded it after some research.

The workarround, easy but hacky one:

I decided to see the java part of the engine wich is meant to deal with the google app store IAPs, the file is GooglePlayStoreHelper.java, whose methods are called by the previous BP methods using a C++  wrapper.

If you examine that file you will find that it has methods to deal with our issue:

online2.png

So, if there are a method in Java to recover the already owned IAPs, why can’t we get them from UE BP?

The answer can be embarrasing, cos it looks to me somebody messed up and/or forgot to implement the C++ Wrapper or the BP Function. Anyway, the method wich is called by the BP funcion in that java file is QueryInAppPurchases, wich returns just the details from the IAP’s like name, description, price, and so on, but wich won’t return the bought status, or receipt or transaction code.

So, what if that method would return not the name of the products but the name followed by and optional a token like “[OWNED]”?. That would solve our issue, we would just need to check the names returned by the BP ReadInAppPurchaseInformation in search for that token!.

It’s not very hard to do as you already have all the needed code in that java file.

	/**
	 * Query product details for the provided skus
	 */
	public boolean QueryInAppPurchases(String[] InProductIDs)
	{
		Log.debug("[GooglePlayStoreHelper] - GooglePlayStoreHelper::QueryInAppPurchases");
		ArrayList<String> skuList = new ArrayList<String> ();
		
		for (String productId : InProductIDs)
		{
			Log.debug("[GooglePlayStoreHelper] - GooglePlayStoreHelper::QueryInAppPurchases - Querying " + productId);
			skuList.add(productId);
		}

		Bundle querySkus = new Bundle();
		querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList);
		
// -----------
		ArrayList<String> ownedSkus = new ArrayList<String>();
		
		try {
			
			ArrayList<String> purchaseDataList = new ArrayList<String>();
			ArrayList<String> signatureList = new ArrayList<String>();
		
			int responseCode = GatherOwnedPurchaseData(ownedSkus, purchaseDataList, signatureList, null);
			if (responseCode == BILLING_RESPONSE_RESULT_OK)
			{
				Log.debug("[GooglePlayStoreHelper] - AP GooglePlayStoreHelper::QueryExistingPurchases - User has previously purchased " + ownedSkus.size() + " inapp products" );

				ArrayList<String> productTokens = new ArrayList<String>();
				ArrayList<String> receipts = new ArrayList<String>();

				for (int Idx = 0; Idx < ownedSkus.size(); Idx++)
				{
					String purchaseData = purchaseDataList.get(Idx);
					String dataSignature = signatureList.get(Idx);

					try
					{
						Purchase purchase = new Purchase(ITEM_TYPE_INAPP, purchaseData, dataSignature);
						String token = purchase.getToken();
						String receipt = Base64.encode(purchase.getOriginalJson().getBytes());
					}			
					catch (JSONException e)
					{
						Log.debug("[GooglePlayStoreHelper] - AP GooglePlayStoreHelper::QueryExistingPurchases - Failed to parse receipt! " + e.getMessage());
					}
				}
				Log.debug("[GooglePlayStoreHelper] - AP GooglePlayStoreHelper::QueryExistingPurchases - Success!");
			}
		} catch (Exception ex) {
		}
// -----------
		try
		{
			Bundle skuDetails = mService.getSkuDetails(3, gameActivity.getPackageName(), ITEM_TYPE_INAPP, querySkus);

			int response = skuDetails.getInt(RESPONSE_CODE);
			Log.debug("[GooglePlayStoreHelper] - GooglePlayStoreHelper::QueryInAppPurchases - Response " + response + " Bundle:" + skuDetails.toString());
			if (response == BILLING_RESPONSE_RESULT_OK)
			{
				ArrayList<String> productIds = new ArrayList<String>();
				ArrayList<String> titles = new ArrayList<String>();
				ArrayList<String> descriptions = new ArrayList<String>();
				ArrayList<String> prices = new ArrayList<String>();
				ArrayList<Float> pricesRaw = new ArrayList<Float>();
				ArrayList<String> currencyCodes = new ArrayList<String>();

				ArrayList<String> responseList = skuDetails.getStringArrayList(RESPONSE_GET_SKU_DETAILS_LIST);
				for (String thisResponse : responseList)
				{
					JSONObject object = new JSONObject(thisResponse);
				
					String productId = object.getString("productId");
					for(String sku: ownedSkus) {
						if(sku.equals(productId)) {
							productId += "[OWNED]";
						}
					}
					
					productIds.add(productId);
					Log.debug("[GooglePlayStoreHelper] - GooglePlayStoreHelper::QueryInAppPurchases - Parsing details for: " + productId);
				
					String title = object.getString("title");
					titles.add(title);
					Log.debug("[GooglePlayStoreHelper] - title: " + title);

					String description = "[ownedSkus:";
					for(String sku: ownedSkus) {
						description += sku;
					}
					description += "]" + object.getString("description");
					
					descriptions.add(description);
					Log.debug("[GooglePlayStoreHelper] - description: " + description);

					String price = object.getString("price");
					prices.add(price);
					Log.debug("[GooglePlayStoreHelper] - price: " + price);

					double priceRaw = object.getDouble("price_amount_micros") / 1000000.0;
					pricesRaw.add((float)priceRaw);
					Log.debug("[GooglePlayStoreHelper] - price_amount_micros: " + priceRaw);

					String currencyCode = object.getString("price_currency_code");
					currencyCodes.add(currencyCode);
					Log.debug("[GooglePlayStoreHelper] - price_currency_code: " + currencyCode);
				}

				float[] pricesRawPrimitive = new float[pricesRaw.size()];
				for (int i = 0; i < pricesRaw.size(); i++)
				{
					pricesRawPrimitive[i] = pricesRaw.get(i);
				}

				Log.debug("[GooglePlayStoreHelper] - GooglePlayStoreHelper::QueryInAppPurchases " + productIds.size() + " items - Success!");
				nativeQueryComplete(response, productIds.toArray(new String[productIds.size()]), titles.toArray(new String[titles.size()]), descriptions.toArray(new String[descriptions.size()]), prices.toArray(new String[prices.size()]), pricesRawPrimitive, currencyCodes.toArray(new String[currencyCodes.size()]));
				Log.debug("[GooglePlayStoreHelper] - nativeQueryComplete done!");
			}
			else
			{
				Log.debug("[GooglePlayStoreHelper] - GooglePlayStoreHelper::QueryInAppPurchases - Failed!");
				nativeQueryComplete(response, null, null, null, null, null, null);
			}
		}
		catch(Exception e)
		{
			Log.debug("[GooglePlayStoreHelper] - GooglePlayStoreHelper::QueryInAppPurchases - Failed! " + e.getMessage());
			nativeQueryComplete(UndefinedFailureResponse, null, null, null, null, null, null);
		}

		return true;
	}

You just have to replace or edit your engine java file and it will be used the next time you package or deploy your game, and I encourage you to modify it to your particular needs.

The BP part would be this easy, just search for the tag in the strings.

online3.png

Android Architecture Components 1.0 Stable launched

Great news for Android developing and Android developers, architecture components are here, and this a really BIG one.

Developing Android apps always have had several design caveats, the design of activities with very complex lifecycles that can even turn crazy complex when you mess with several fragments (take for example an app with just 1 fragment visible in vertical position and 2 fragments in portrait mode) made it very difficult to develop, test, debug and maintain complex applications. Every developer had their own tricks to deal with this, in particular I can recall how Romain Guy (before working with google) told that they avoided using fragments in his company due to the lifecycle complexity.

This is not only an issue to manage the correct initialization and proper free up of the resources in the correct order but also to maintain the needed data for the activities and fragments, that can’t be stored in the activity due to configuration changes and have to live in another cache (and again every developer have to figure out how to do this the better possible way for a given app).

Another caveat was the database framework. Android provide a quite simple and effective (for the most apps) system to access and maintain local databases, but the price you had to pay was a lot (and you and me know it is a LOT) of boilerplate, runtime bugs and difficulty to test and maintain. However there are quite a bit of different ORM options designed or adapted for android, but the most part while solving a lot of complexity issues for large apps, were not suitable for small apps, also, not being an standard or “most used” ORM deals to a big variety of options wich can be bad in certain work environments.

The last thing that was remarkably hard was to update every GUI component whenever the data changed (as if weren’t hard enought to work with recycler views). Normally the data is always loaded in background to a local database or similar app-level cache, and normally you want the GUI to be updated with the data when its ready, not when all the data is ready but while is being ready, e.g. if you are downloading a huge list of recipes in an app like FamilyRecipe, you want to update the recipe list every time you download one of them, that way the user can see the ready ones asap. The coding to solve that kind of issues can be not that easy to deal with and as usual, a lot of boilerplate was involved.

Google had realized this were probably the main issues in every app and wanted to give us a wonderful solution, this is what the Architectural Components is, a MAYOR change in the way every Android app is going to be developed, and off course its a change for the better. The bad news are that you can’t easily refactor your apps to use every one of these new features, the architecture is so different that it just can’t be done in an easy way, but maybe you can refactor just some parts or leverage one or hopefully two of the new features in your beloved apps next version.

To sum up, Google have been trying for years to avoid “bad apps” in the store, changes in the framework, in the S.O. versions have always been oriented to avoid the apps to do nasty stuff, like asking too many permissions, blocking the UI thread, doing too much stuff while in background, abusing the system resource etc…, every change in the framework and S.O. had been encouraging developers to do the right thing the right way and this Architectural components launch is no different, it encourages good coding practices making more maintainable and testable apps (like Room), avoiding the typical errors when configuration changes happens (LifeCycles), help optimizing the app telling us to use a kind of app cache (ViewModel), making more responsive and interactive user experience (LiveData).

I’m not going to show you any code, since you know there is already a better place than anywhere else to check all the stuff, the google docs: Android Architecture Components

Ninety Six reached 10.000 downloads

Sin título2When some time ago I did the adaptation of the 20 years old pacman-style game, it was more like an experiment or something made for fun. How would it be to convert an old fashioned MS-DOS pure C&assembler game to a fresh new technology?. So it’s been a nice surprise seeing the game getting more and more downloads and eventually reaching up to 10.000 downloads with several thousands active installations!

It’s also funny what the people write in the comments, it looks like the millenials have some trouble with computer games from the past, they don’t seem to expect they were as hard and as mean as they actually were.