left-icon

Writing Native Mobile Apps in a Functional Language Succinctly®
by Vassili Kaplan

Previous
Chapter

of
A
A
A

CHAPTER 9

Adding Mobile Advertising and Advanced Topics

Adding Mobile Advertising and Advanced Topics


“Stopping advertising to save money is like stopping your watch to save time.”

Henry Ford

Adding Google AdMob to CSCS

Adding an advertisement to be displayed in your app is a good source of extra income—not only from the ads themselves, but also from the In-App Purchase items set up to remove ads if they annoy users too much. We saw how to do that in the previous chapter.

There are a few providers of the advertisement content for mobile devices. I chose Google AdMob because it is one of the most popular ones, and it also has good documentation and a support base.

To plug the AdMob Framework into the project, on iOS I used the Xamarin.Firebase.iOS.AdMob NuGet, and on Android, I used Xamarin.GooglePlayServices.Ads.Lite NuGet. You can check out the implementation details in the accompanying source code.

Code Listing 50 contains the CSCS code showing some of the AdMob banners. It also creates a button that when clicked, causes an interstitial ad to be displayed. Interstitial ads are the full-screen ads monopolizing the whole device screen (and are by far the most irritating ones).

Code Listing 50: Adding AdMob Banners in CSCS

AutoScale();
SetBackgroundColor("gainsboro");

if (_IOS_) {
  appId           = "ca-app-pub-5365456490909700~2128563695";
  interstitialId  = "ca-app-pub-5365456490909700/7192496494";
  bannerId        = "ca-app-pub-5365456490909700/3326095292";
elif (_ANDROID_) {
  appId           = "ca-app-pub-5365456490909700~8197798895";
  interstitialId  = "ca-app-pub-5365456490909700/2151265291";
  bannerId        = "ca-app-pub-5365456490909700/8058198097";
}

InitAds(appId, interstitialId, bannerId);

locClickme = GetLocation("ROOT""CENTER""ROOT""CENTER"020);
AddButton(locClickme, "buttonClickme""Interstitial"20080);
AddAction(buttonClickme,  "clickme_click");

function clickme_click(sender, arg)
{
  ShowInterstitial();
}

locAd1 = GetLocation("ROOT""LEFT""ROOT""TOP"00);
AddAdMobBanner(locAd1, "adMob1""MediumRectangle", DisplayWidth, 500);

locAd2 = GetLocation("ROOT""LEFT", buttonClickme, "BOTTOM"020);
AddAdMobBanner(locAd2, "adMob2""FullBanner"1024160);

locAd3 = GetLocation("ROOT""LEFT", adMob2, "BOTTOM"010);
AddAdMobBanner(locAd3, "adMob3""LargeBanner", DisplayWidth, 200);

locAd4 = GetLocation("ROOT""CENTER""ROOT""BOTTOM"00);
AddAdMobBanner(locAd4, "adMob4""Banner", DisplayWidth, 100);

Code Listing 50 contains the production banner and interstitial IDs. You get these IDs when you register your app with Google AdMob. For testing, you can use the following IDs; they are the same for iOS and Android:

bannerId          = "ca-app-pub-3940256099942544/6300978111";
interstitialId    = "ca-app-pub-3940256099942544/1033173712";

The result of running Code Listing 50 with the testing IDs is shown in Figure 29.

Google AdMob with Different Banners (left) and an Interstitial Ad (right)

Figure 29: Google AdMob with Different Banners (left) and an Interstitial Ad (right)

Debugging and unit testing

To test and debug the CSCS code, you can use the Visual Studio Debugger for all of the CSCS functions implemented in C#.

For functions implemented in CSCS, the easiest is to use the WriteConsole() CSCS method to see where the problem occurs and print out the current values of the variables.

I have also implemented unit testing. The code is in the unitTest.cscs file. As soon as I add a new feature to CSCS, or change an existing feature, I add or modify a unit test in unitTest.cscs. To run unit tests, I just enable the following line in start.cscs:

ImportFile("unitTest.cscs");

The output from the unit tests is shown in a scrollable TextView. I create it as follows:

locView = GetLocation("ROOT""CENTER""ROOT""TOP");
AddTextView(locView, "textView""", DisplayWidth - 30, DisplayHeight - 60);

The main CSCS function for unit testing is test(). Its implementation is shown in Code Listing 51.

Code Listing 51: Implementation of the CSCS Test Function

function test(x, expected)
{
  if (x == expected) {
    AddText(textView, string(x) + " as expected. OK""green");
    return;
  }
  if (type(expected) != "NUMBER") {
    AddText(textView, "[" + x + "] but expected [" + expected +

                      "]. ERROR""red");
    return;
  }

  epsilon = 0.000001;
  if ((expected == 0 && abs(x) <= epsilon) ||
       abs((x - expected) / expected) <= epsilon) {
    AddText(textView, "[" + x + "] within epsilon to [" + expected +

                      "]. almost OK""gray");
  } else {
    diff = expected - x;
    AddText(textView, "[" + x + "] but expected [" + expected +

                      "]. diff=" + diff + ". ERROR""red");
  }
}

Note that the third argument to the AddText() function is the text color. Using the test() function, it’s easy to write various CSCS unit tests.

Code Listing 52 shows some of the unit tests, and the Figure 30 shows the result of running them on an iPhone simulator.

Code Listing 52: A Fragment of the CSCS Unit Tests

AddText(textView, "Testing strings...");
txt = "lu";
txt += txt + Substring(txt, 01) + "_" + 1;
test(txt, "lulul_1");
bb = "abc_blah;";
c = Substring(bb, 43);
test(c, "bla");
ind = StrIndexOf(bb, "bla");
test(ind, 4);
between = StrBetween(bb, "_"";");
test(between, "bla");
between = StrUpper(StrReplace(between, "a""aa"));
test(between, "BLAAH");

AddText(textView, "Testing numbers...");
a=(-3+2*9)-(10-15);
test(a, 20);
test((a++)-(--a)-a--, -20);
test(a, 19);
test(((16-3)-3)+15/2*547.5);
test(1-2-3-(4-(5-(6-7)))-pow(2,3*exp(14-7*2)), -10);
test(sin(pi/2), 1);

x = 2.0E+15 + 3e+15 - 1.0e15;
test(x, 4e+15);
a=1; c=0; b=5;
test(a||c, 1);
test(c&&b, 0);

AddText(textView, "Testing arrays and maps...");
a[1][2]=22;
a[5][3]=15;
a[1][2]-=100;
a[5][3]+=100;
test(a[1][2], -78);
test(a[5][3], 115);

arr[2] = 10; arr[1] = "str";
test(type(arr),    "ARRAY");
test(type(arr[0]), "NONE");
test(type(arr[1]), "STRING");
test(type(arr[2]), "NUMBER");

x["bla"]["blu"]=113;
test(contains (x["bla"], "blu"), 1);
test(contains (x["bla"], "bla"), 0);
x["blabla"]["blablu"]=125;
test(--x["bla"]["blu"] + x["blabla"]["blablu"]--, 237);

b[5][3][5][3]=15;
b[5][3][5][3]*=1000;
test(b[5][3][5][3], 15000);
test(size(b), 6);
test(size(b[5]), 4);
test(size(b[5][3]), 6);
test(size(b[5][3][5]), 4);
test(size(b[5][3][5][3]), 5);

Unit Testing TextView in iOS Simulator

Figure 30: Unit Testing TextView in iOS Simulator

Localization

We already saw an example of the CSCS Localize() function in Code Listing 49. It returns a string, localized in the device locale. A general signature of this function is:

Localize(text, languageCode = AppLocale);

You can specify any other language in the second Localize() argument as well.

Other localization-specific CSCS functions are:

GetDeviceLocale();

SetAppLocale(languageCode);

GetDeviceLocale() returns the device language, and SetAppLocale() sets the language. It does not change the global device language, just the language that is used by the app.

To make it work, the translations must be manually added to the resource files.

On iOS, the translations are added to the languageCode.lproj/Localizable.strings files (the folder names are, de.lproj, fr.lproj, etc.), and on Android, the translations are added to the values-languageCode/Strings.xml files.

There is no need to write files for both projects: there are a few tools that convert from one localization format to another. On the other hand, Xamarin.Forms uses the RESX resource files for multiple platforms.

Settings or user defaults

The interface to access and modify user preferences is NSUserDefaults on iOS, and ISharedPreferences on Android. You use it to cache the user preference data, keep best user scores, and so on, so that they are persistent between different program runs.

The CSCS functions shield the iOS and Android differences:

SetSetting(aKey, aVariable, type = "string");
GetSetting(aKey, type = "string", defaultValue = "" or 0);

The type can be any of the following: string, int, long, bool, float, or double. If no type is specified, a string is assumed. On Android, double is treated as float, and on iOS, long is treated as int.

Here’s an example of using settings in CSCS:

SetSetting("myKey", a, "int");
a = GetSetting("myKey""int");

Swipe, long click, and drag and drop

We saw earlier that the AddAction(widget, callback) CSCS function connects a widget to a function that is called when the user clicks on a widget.

There are also other types of user events that you can connect with some actions in CSCS.

You can connect an event of a user doing a swipe with an action as follows:

AddSwipe(widget, type, callback);

The type parameter can be any of the following: Left, Right, Up, or Down. For example:

AddSwipe(widget, "Left",  "swipe");
AddSwipe(widget, "Right""swipe");

The swipe function has the following signature:

function swipe(sender, eventName)

The eventName variable is a string equal to the type parameter that triggered the event. It can be Left, Right, Up, or Down so that you know what type of swipe occurred.

There are also other events that you can connect to an action. For example, the following events are used when a widget has been clicked and held or dragged and dropped:

AddLongClick(widget, callback);
AddDragAndDrop(widget, callback);

The callback of the AddDragAndDrop function has the following signature:

function callback(sender, receiving_widget);

The receiving_widget parameter specifies a widget (or a comma-separated list of widgets), on which the original widget was dropped. It will be empty if there is no such widget.

How is it implemented? Every time a widget is added (or moved), we store its location. We also have a mapping from every tab and a parent view to the list of widgets that were placed there. Then we just look for the widgets that are present in the location where the original widget was dropped.

Scheduling events

One handy feature is the ability to schedule an event to be executed in the future. The CSCS functions used to schedule and to cancel an event are the following:

Schedule(timeoutMs, callback, sender = "", timerId = "", autoreset = false);

CancelSchedule(timerId);

If the autoreset parameter is set to false, then the callback function will be invoked only once; otherwise it will be invoked periodically, unless the CancelSchedule() function is called.

The event will be executed on the main GUI thread. This is done by invoking UIViewController().InvokeOnMainThread() on iOS, and MainActivity.RunOnUiThread() on Android.

For instance, I used scheduling when implementing an example of a Syncfusion Digital Gauge:

Schedule(1000"timer_timeout""""timerId"true);
function timer_timeout(sender, arg)
{
  SetValue(DigitalGauge, "value", Now("HH:mm:ss"));
}

The timer_timeout function is invoked every second (every 1,000 milliseconds). It updates the time shown by DigitalGauge.

Asynchronous programming in CSCS

We saw some examples of asynchronous programming in CSCS in the previous chapter, when we asynchronously requested purchasing and restoring In-App Purchase items. The C# implementation there used the async and await keywords.

In CSCS, asynchronous programming is done by supplying a callback function. Note that the callback function will be scheduled on the main GUI thread (see what this means in the previous section). So the callback will not be triggered in the “middle” of something, but only after the current operation on the main GUI thread is complete.

Calling native code from CSCS

By “native,” I mean C#. You might have a lot of existing C# code that you do not want to rewrite in CSCS. There is no need—it is possible to call C# code from CSCS and get the results back.

Code Listing 53 contains code that is used to compile and cache native calls for subsequent access. Only the first call will take time—because of reflection, compilation, and caching. The time spent on the subsequent calls will be roughly the same as when calling C# code directly.

Code Listing 53: Implementation of Calling C# Code from CSCS

static Dictionary<stringFunc<stringstring>> m_compiledCode =
   new Dictionary<stringFunc<stringstring>>();

public static Variable InvokeCall(Type type, string methodName,

              string paramName, string paramValue, object master = null)
{
  string key = type + "_" + methodName + "_" + paramName;
  Func<stringstring> func = null;

  // Cache compiled function:
  if (!m_compiledCode.TryGetValue(key, out func)) {
    MethodInfo methodInfo = type.GetMethod(methodName,

                                 new Type[] { typeof(string) });
    ParameterExpression param = Expression.Parameter(typeof(string),

                                                     paramName);
    MethodCallExpression methodCall = master == null ?

          Expression.Call(methodInfo, param) :
          Expression.Call(Expression.Constant(master), methodInfo, param);
    Expression<Func<stringstring>> lambda =
        Expression.Lambda<Func<stringstring>>(methodCall,

                   new ParameterExpression[] { param });
    func = lambda.Compile();
    m_compiledCode[key] = func;
  }

  string result = func(paramValue);
  return new Variable(result);
}

To register a CSCS function to call the native code, we use the following statement:

ParserFunction.RegisterFunction("CallNative"new InvokeNativeFunction());

Here, InvokeNativeFunction is a thin wrapper over Code Listing 53. This is the main call it makes:

var result = Utils.InvokeCall(typeof(Statics),
                              methodName, paramName, paramValue);

The CSCS function signature is the following:

CallNative(methodName, argumentName, argumentValue);

Here is an example of calling a native function from CSCS and getting a result back:

clicks++;

title = CallNative("ProcessClick""arg", clicks);

All the native functions are implemented in the Statics class, and here is the implementation of the ProcessClick method, which returns back to CSCS a string, created in C# code:

public static string ProcessClick(string arg)
{
  var now = DateTime.Now.ToString("T");
  return "Clicks: " + arg + "\n" + now;
}

 

Using this feature, you can keep using any existing C# code, for instance, for database access.

Conclusion

CSCS is an evolving language. I am constantly adding new features to it, so check periodically the GitHub repository, where the source code lives.

As we have seen, you can add to CSCS anything that can be implemented in Xamarin.iOS and Xamarin.Android. Since they can both implement any iOS or Android feature, you can add any native platform feature to CSCS as well.

I am looking forward to your feedback and seeing how you have modified CSCS, what you have added, and what apps you have created.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.