How To Add Google AdMob To Your iOS App

Google AdMob is an advertisement network for mobile apps, offered by Google. Basically, if you want to show some ads in your app and make money, you can use AdMob for that. Of course there are other options, but Google is certainly a trusted name in the business. Today we’ll look at how to add AdMob to an iOS app that I created for practicing times tables. You can see how the app was created here:

iOS Times Tables App – Part 1

Let’s get started!

Create a new app in AdMob

If you haven’t signed up for AdMob yet, check out how I did that for the Android version of this app. Assuming you have already signed up, go to https://admob.google.com/home/. We’re going to create a new app, so from the home page click “Add App”:

AdMob Home Page

Then we’re going to give AdMob a few basic details about the app:

App successfully created!

The app now has its own page in AdMob. The plan is to create a basic banner on the practice page, so we’ll create a single “Ad Unit”. Click on “Add Ad Unit”:

Make sure to select “Banner” on this page:

Give it a name on the next page and click “Create ad unit”:

We’ve successfully created an app and a place to show ads within it! Now on to the next step, integrating the Google AdMob SDK.

Installing the AdMob SDK

We’re going to do this with CocoaPods, as it’s the easiest and quickest way (in my humble opinion) to get external stuff. If you don’t have CocoaPods, check out their installation guide. Assuming CocoaPods is installed, we’re going to add CocoaPods to the Times Tables Xcode project that we used to create the app by creating a Podfile. CocoaPods describes this file in their Adding Pods to an Xcode Project guide.

From the terminal, cd your way to root directory of the app. Create a file called Podfile that looks like this, according to the Google AdMob SDK quick start guide:

target 'TimesTables' do

pod 'Google-Mobile-Ads-SDK'

end

If you haven’t updated CocoaPods in a while, you might need to update:

sudo gem install cocoapods

Then run this command to install the SDK:

pod install --repo-update

At this point, the SDK should be installed! You’ll now need to abandon the project file and start using the newly created workspace file called TimesTables.xcworkspace.

The next step is to edit the project’s Info.plist file to include your AdMob app ID. Following the same Google AdMob SDK quick start guide at section “Update your Info.plist”, we’re going to copy and paste the long XML snippet they show there into Info.plist.

You can edit the plist file from Xcode, but I found it hard to copy and paste anything. I’d recommend just opening with an external editor like TextEdit and pasting it in that way. Info.plist is not in the app’s root directory, it’s in a directory that should have the name of your app that holds your swift files. The directory for this particular app is called TimesTables.

You’ll need to find your AdMob app ID from the AdMob console and replace the dummy one they show here. Your app ID can be found from the “All Apps” AdMob page I showed at the very top of this post.

Now that we have made the required plist changes, we’ll add this snippet to didFinishLaunchingWithOptions in AppDelegate.swift, or more likely, if you’re on iOS 13 or above it’ll be in the SceneDelegate.swift in willConnectTo:

GADMobileAds.sharedInstance().start(completionHandler: nil)

Make sure you import the SDK at the very top of the file to which you added the above snippet:

import GoogleMobileAds

We have successfully downloaded and installed the AdMob SDK in our app!

Showing ads in the ad unit

Our next step is to actually show some ads in the ad unit we created in AdMob. Following Google’s AdMob iOS guide, we’ll add a new global variable to hold the banner. Add this to the top of the TableViewController (see the app code if you need) like this:

import GoogleMobileAds //Import the SDK

class TimesTableViewController: UIViewController {

var bannerView: GADBannerView = GADBannerView(adSize: GADAdSizeBanner) //Create the banner

//class continues from here

Then we’ll write a function to set it up. I used programmatic constraints with autolayout here but you can do anything you want to position it:

func setupBanner() {
    view.addSubview(bannerView) // Add to the view
    bannerView.translatesAutoresizingMaskIntoConstraints = false //Turn on autolayout
    bannerView.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true // X: same as view center
    bannerView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true // Y: attach bottom to view bottom
    bannerView.adUnitID = "ca-app-pub-3940256099942544/6300978111" //Google test banner ad unit ID
    bannerView.rootViewController = self //Google says do this so we do it
    bannerView.load(GADRequest()) //load an ad
}

Make sure to call this function from viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
    setupBanner()
    //There's more stuff here in the actual app, but you get the idea.
}

Try it out!

Our banner is looking great!

The Times Tables App running in the iOS simulator

iOS Swift – Building a Times Tables App – Part 4

Today is a good day. We’re going to finish the iOS times tables practice app we started a while ago. The first three parts are here:

iOS Times Tables App – Part 1
iOS Times Tables App – Part 2
iOS Times Tables App – Part 3

The full code for this app is on my Github page:

https://github.com/jamesmcclay/swift_times_tables

Today we will be adding the actual times tables flashcard functionality to the app.

Add the UI elements

As it turns out, you don’t really need to use the init functions I used in part 1. Oh well, live and learn. You can just add them at the top of your class as global variables like this:

//Displays the question
let questionLabel:UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = ""
    label.numberOfLines = 2
    label.textAlignment = .center
    label.font = UIFont.preferredFont(forTextStyle: .title1)
    label.adjustsFontForContentSizeCategory = true
    return label
}()
//Displays the answwer
let answerLabel:UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.text = ""
    label.numberOfLines = 2
    label.textAlignment = .center
    label.font = UIFont.preferredFont(forTextStyle: .title2)
    label.adjustsFontForContentSizeCategory = true
    return label
}()
//Either shows the answer or goes to next question
let button:UIButton = {
    let button = UIButton(type: .system) as UIButton
    button.translatesAutoresizingMaskIntoConstraints = false
    button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .title2)
    button.titleLabel?.adjustsFontForContentSizeCategory = true
    button.setTitle("Show Answer", for: .normal)
    return button
}()

//These are variables we'll need to keep track of the app state
var mode:String = "normal"
var table:Int = 0
var multiplier:Int = 0
var selectedTable = 0
var maxMultiplier:Int = 0
var lastMultiplier = 0
var answerShowing = false

We add three UI elements – questionLabel, answerLabel, and button. Each one does what it sounds like, and button will either show the answer or move on to the next question. Here’s the non-UI variables and what they will do:

  • mode is either “normal” or “random”. Normal starts the multiplier at 2 and goes to until maxMultiplier, then goes back to 2. Random picks a random multiplier between 2 and maxMultiplier.
  • table holds the current table being practiced. In all tables mode, it will be incremented up to 9, then go back to 2.
  • multiplier is the second number in a multiplication question.
  • selectedTable holds the table the user originally selected. If it’s anything other than 0 (the user didn’t select a table), selectedTable will be the same as table since the table won’t change. If selectedTable is 0, table will be incremented or changed randomly, depending on the mode. selectedTable needs to stay the same so that the app knows whether a table was selected or to practice them all.
  • maxMultiplier indicates how far up the user wants to practice in the table or tables. Defaults to 12.
  • lastMultiplier holds the value of the last multiplier. This exists to solve a problem where the same multiplier is selected multiple times in a row in random mode. Using a while loop, we can check if the newly selected multiplier is different from the last one. No need to practice the same question twice in a row.
  • answerShowing tells the app whether the answer is showing or not. Lets us control what code to run when the button is pressed, and what text to put on the labels.

These all have dummy values, because their actual values will be set in viewWillAppear. The UINavigationController reuses old UIViewControllers, so this code only gets run once. viewWillAppear runs every time the view shows, so we’ll set values there.

Layout the UI elements

Like last time, we’ll run several functions to create autolayout constraints for the UI elements, to put them in their proper spots. The viewDidLoad function looks like this:

override func viewDidLoad() {
    super.viewDidLoad()
    setupQuestion()
    setupAnswer()
    setupButton()
}

Now we’ll create each of those functions:

func setupQuestion() {
    view.addSubview(questionLabel)
    questionLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -(view.frame.height * 0.1)).isActive = true
    questionLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
}

func setupAnswer() {
    view.addSubview(answerLabel)
    answerLabel.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: view.frame.height * 0.1).isActive = true
    answerLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    
}

func setupButton() {
    view.addSubview(button)
    button.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: view.frame.height * 0.2).isActive = true
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
}

Basically everything here is attached to the questionLabel, which we have constrained to 10% above the center Y of the view per -(view.frame.height * 0.1). answerLabel is 10% below that, while button is 20% below. The last line of the button function adds a function called buttonPressed as the function to run when the button is pressed.

Add the logic

As I mentioned earlier, we need to set the global variables’ values in viewDidAppear. It looks like this:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(true)

    //Show the nav bar
    self.navigationController!.navigationBar.isHidden = false

    //give the global variables some default values.
    table = 2
    multiplier = 1
    selectedTable = 0
    maxMultiplier = 12
    lastMultiplier = 0
    answerShowing = false

    //Get the user's specified table, if it exists. 0 means no selected table
    if let userTable = UserDefaults.standard.value(forKey: "table") as? Int {
        if userTable != 0 {
            table = userTable
        }
        self.selectedTable = userTable
    }
    //Get the user's specified max multiplier, if it exists. 0 means no selected multiplier
    if let multi = UserDefaults.standard.value(forKey: "multiplier") as? Int {
        if multi != 0 {
            maxMultiplier = multi
        }
    }
    //Get the mode
    if let practiceMode = UserDefaults.standard.value(forKey: "mode") as? String {
        mode = practiceMode
    }
    //Run nextQuestion once at the start.
    nextQuestion()
}

I added comments inline for this one. Please let me know in the comments if anything is not clear.

At this point, all of our values are set. We need to write out nextQuestion, as it is called in the last line. nextQuestion looks like this:

func nextQuestion() {
    answerLabel.text = "" //Clear the answer
    if mode == "normal" {
        multiplier += 1 //Increment the multiplier every time
        if multiplier > maxMultiplier { 
            multiplier = 2  //Go back to 2 if at max
            if selectedTable == 0 {
                table += 1 //Increment table if in all tables mode and current table is finished (multiplier at max)
                if  table == 10 {
                    table = 2 //Go back to table 2 if all tables finished.
                }
            }
        }
    } else {
        //This is random mode
        lastMultiplier = multiplier
        if selectedTable == 0 {
            table = Int.random(in: 2...9) //If no selected table, get a random one from all tables
        }
        //Get a new multiplier until it's different from the last one, avoids duplicate question.
        while multiplier == lastMultiplier {
            lastMultiplier = multiplier
            multiplier = Int.random(in: 2...maxMultiplier)
        }
    }
    questionLabel.text = "\(table) x \(multiplier) = " //Display question.
    answerShowing = false  //Answer no longer showing
    button.setTitle("Show Answer", for: .normal) //Change button text to "Show Answer"
}

I added comments inline for this one too. The logic here should take care of all use cases.

Now we’ll write the function to show the answer:

func showAnswer() {
    answerLabel.text = "\(table * multiplier)"
    answerShowing = true
    button.setTitle("Next Question", for: .normal)  
}

This one is short and sweet. Just show the answer, set answerShowing to true, and set the button text to “Next Question”.

Finally, the code that runs when the button is pressed:

@objc func buttonPressed() {
    if answerShowing {
        nextQuestion()
    }else{
        showAnswer()
    }
}

If the answer is showing, run nextQuestion. If not, run showAnswer.

We’re done!

Try it out!

Things are looking good! Be sure and try all modes and variables you can think of 🙂

iOS Simulator in Xcode

iOS Swift – Building a Times Tables App – Part 3

In two previous posts, we first added the UI elements and then added a bit of logic to make the initial view of the app work. You can find those posts here:

iOS Times Tables App – Part 1
iOS Times Tables App – Part 2

The full code for the app can be found on my Github page:

https://github.com/jamesmcclay/swift_times_tables

Today we’re going to add a UINavigationController as well as a second view controller for the actual times tables flash card view.

Adding a UINavigationController

UINavigationController is a handy object available in UIKit. It allows us to put a navigation bar that persists on the screen even as the user changes from one UIViewController to another. By default, it will come with a functioning “back” button.

Since we’re taking a programmatic approach for pretty much everything except adding UIViewControllers, we need to delete the storyboard entrypoint and add one from code. It shows up as a big arrow pointing to the initial UIViewController:

Storyboard Entry Point in Xcode

Once we have it selected (you can click on either the arrow on the canvas or on the one to the left), just hit delete and it’s gone. While we’re in the storyboard, make sure the main view controller has “Storyboard ID”, you can add it from the identity inspector. I called mine “homeVC”:

Identity Inspector for UIViewController

The next step is to add the UINavigationController to the app as well as an entry point from code. If you are developing for iOS 13 and onward, the place to do this is in SceneDelegate.swift. It used to be AppDelegate.swift, but no longer. We’ll add our code to SceneDelegate, in the function willConnectTo. The top of my SceneDelegate.swift file looks like this:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow? //Gotta add this reference to the app window

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }

        //Adding the UINavigationController here
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController (withIdentifier: "homeVC") as! ViewController
        let navigationController = UINavigationController(rootViewController: vc)
        window = UIWindow(windowScene: windowScene)
        window?.rootViewController = navigationController
        window?.makeKeyAndVisible()
        //End of custom code

        guard let _ = (scene as? UIWindowScene) else { return }
    }

In the first couple lines, we grab references to the storyboard and the home UIViewController. Then we create a UINavigationController, and add the home UIViewController as the root view controller. In the last three lines, we start up the window, make it visible and make the UINavigationController the root view controller for the window (yes, two roots). That should replace the storyboard entrypoint as well as add a navigation controller.

Add a second UIViewController

We’re going to use the storyboard to add a second UIViewController. Click the “+” button at the top left and a view controller:

Adding a UIViewController from the storyboard

Then we need to connect to it a swift file. Over on the left side where all the rest of the project files are, right click on the “view” folder and click “new file”. Create a “Cocoa Touch Class”:

Creating a Cocoa Touch Class

Let’s call it “TimesTableViewController”. Click through and create. The last step is to connect the newly created swift file to the newly created UIViewController on the storyboard. We can do that from the storyboard by clicking on the UIViewController and going to its identity inspector and changing the class to the new class we just created, and be sure to give it a storyboard ID:

Connecting a swift file to UIViewController

And our second UIViewController is complete. Now we just need to add code to jump to this view (or rather, push it on to the UINavigationController, to be precise) when the user taps the start button. Please check the github code or previous posts if you’re not sure what I’m talking about here. Let’s add this snippet to startButtonTapped:

let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let ttvc: TimesTableViewController = storyBoard.instantiateViewController(withIdentifier: "ttVC") as! TimesTableViewController
self.navigationController!.setViewControllers([self,ttvc], animated: true)

This looks pretty complicated, but it’s mostly just some magic that Apple wants you to do when you’re pushing a new view controller to the navigation controller. Lastly, I didn’t find that I wanted the navigation bar to show up on the home view controller, just the times tables one. So I added this to my code in the (home) ViewController:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(true)
    self.navigationController!.navigationBar.isHidden = true
}

This just tells the navigation bar to be hidden when the view appears.

Try it out!

The main view is unchanged, but when we click start, we head to the next view! And there’s a back button! And it works!

Hope you liked it. Next time we’ll code in the times tables view. Stay tuned!

iOS Swift – Building a Times Tables App – Part 2

Last post, we put together the UI layout for a times times tables app in Swift using Xcode. The UI elements all work and are in their correct positions, but there’s no logic to make them do anything. Today we’ll add a dash of logic to the home screen so that we can gather up all the information we need to show the user the times tables flash cards they want to see. You can find parts 1 and 3 of this series here:

iOS Times Tables App – Part 1
iOS Times Tables App – Part 3

To really understand what I’m talking about in this post, you’ll either want to look at the last post (link above) or take at the code for the ViewController in my github account:

https://github.com/jamesmcclay/swift_times_tables

Adding variables for selected values

There are three different values that the user could potentially provide to the app, and we need to account for all of them. The first is the table – for example, if the user wanted to practice 3×2, 3×3, 3×4 and so on, they would provide 3 in the “table” field. The second is the max multiplier, which is the second number. In the Android version, we set the default to be 12, we’ll keep with that. But the user can change the default by entering something in the max multiplier field. The last is the mode – normal or random. We’ll add some global variables at the top of the class, just below the UI elements variables:

var currentMode:String {
    get {
        if let mode = UserDefaults.standard.value(forKey: "mode") as? String {
            return mode
        } else {
            return "normal"
        }
    }
    set {
        UserDefaults.standard.setValue(newValue, forKey: "mode")
    }
}
var currentTable:Int {
    get {
        if let table = UserDefaults.standard.value(forKey: "table") as? Int {
            return table
        }else{
            return 0
        }
    }
    set {
        UserDefaults.standard.setValue(newValue, forKey: "table")
    }
}
var currentMultiplier:Int {
    get {
        if let multiplier = UserDefaults.standard.value(forKey: "multiplier") as? Int {
            return multiplier
        }else{
            return 0
        }
    }
    set {
        UserDefaults.standard.setValue(newValue, forKey: "multiplier")
    }
}

If you’re not familiar with “computed properties” in Swift, this probably looks pretty foreign. Basically, a computed property is a variable that doesn’t hold a value. Normally, variables hold values. Instead, you write code to get and set the value. The get and set code runs when elsewhere in your code you write something that either gets or sets the variable’s value. That’s why there’s a get and set part (technically set is optional).

The reason we’re doing this here is because we want the values to be stored in the UserDefaults file. UserDefaults is just a text file where you can store some key-value data that you can access even after the app or phone restarts. Because of that persistence, the app can “remember” values that were entered last time so the user can avoid re-entering their favorite table, max multiplier or mode. We’ll also be able access the values from the next view in the app with the actual flash cards without having to “send” it like in Android. For getting, it also lets us account for the possibility that there will be no value in UserDefaults, and return a default in that case. The get blocks return “normal” by default for the mode, and 0 by default for the table and multiplier.

Populate the table and max multiplier

We’ll use the code we just wrote to put values in the table and max multiplier text fields. In the setupInputs function we’ll add the following snippet:

if currentTable != 0 {
    tableInput.text = String(currentTable)
}
if currentMultiplier != 0 {
    multiplierInput.text = String(currentMultiplier)
}

Remember, since currentTable and currentMultiplier are computed, these lines simply run the get blocks that we wrote earlier for these variables. If the values stored in UserDefaults were not 0, we’re setting the the textfields to be whatever was stored. The reason why we avoid pre-populating 0 is because we’ll use 0 in the startTapped function to mean that the user accepted the defaults and entered nothing. In that case, we don’t need to pre-populate anything. We’ll add this snippet to startTapped:

if let tableText = tableInput.text {
    if let table = Int(tableText) {
        currentTable = table
    }else{
        currentTable = 0
    }
}else{
    currentTable = 0
}
if let multiText = multiplierInput.text {
    if let multiplier = Int(multiText) {
        currentMultiplier = multiplier
    }else{
        currentMultiplier = 0
    }
}else {
    currentMultiplier = 0
}

This code, which runs when the user taps the start button, will grab whatever the user entered in the text fields and runs the set blocks that we wrote earlier. We check first to see if there’s any text at all, then to see if it will properly change to an Integer. If both of those pass, we set the entered value to UserDefaults. If either fails, we set the UserDefault value to 0.

Setting the mode

In Android, we were able to create a radio group with two multually exclusive options, normal and random. In Swift and UIKit, no such thing exists. So what we’ll use is UISwitch, which is a little switcher button thingy. We already laid them out in the last post, but we need to make sure they work right. When one is selected, the other can’t also be selected. We’ll achieve that with the following code in modeTapped:

modes.forEach { $0.isOn = false }
sender.isOn = true
switch sender.tag {
case 0:
    currentMode = "normal"
case 1:
    currentMode = "random"
default:
    currentMode = "normal"
}

That first line simply unchecks both of the UISwitch elements in the modes array. Remember creating that array? It just holds the switches so we can loop through them. Then we turn the switch on for whichever switch fired the function, followed by setting currentMode to either “normal” or “random” based on whether the sender’s tag is 0 or 1. We set the tags when we created the switches, you might have forgotten 🙂

We’ll “remember” whatever setting the user selected last time they opened the app by adding this snippet to setupModes:

if currentMode == "random" {
    randomMode.isOn = true
}else{
    normalMode.isOn = true
}

This once again pulls the mode value from UserDefaults.

Dismiss keyboard

I ran into an issue when I realized I couldn’t dismiss the keyboard once it appeared for the text fields. I got it to go away by adding UITextFieldDelegate to my ViewController’s class declaration so it looks like this:

class ViewController: UIViewController, UITextFieldDelegate {

And for each text field, made the ViewController the delegate in the setupInputs function:

tableInput.delegate = self
multiplierInput.delegate = self

Finally, I added this function which is available if your ViewController has been made the UITextFieldDelegate:

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}

It basically just tells the keyboard to go away when return is tapped, if they keyboard appeared for a text field.

Try it out!

Our app in the Xcode iOS simulator

Not a whole lot about the look has changed since the last post, but one thing you can do is print the values of currentTable, currentMultiplier and currentMode in the console. You can also enter some, press start, and then close/restart the app. You should be able to see the values persist.

Stay tuned for the next post, where we’ll create the screen for the flash cards!

iOS Swift – Building a Times Tables App – Part 1

Previously, we built an Android times tables practice app in Kotlin, as well as a backend-only web times tables app in Python. Today we’re going to port this app to Swift so it can work on iOS for an iPhone as well. Let’s get started! You can find parts 2 and 3 of this series here:

iOS Times Tables App – Part 2
iOS Times Tables App – Part 3

At the time of writing, this was done in Xcode 12.5.1 and Swift 5.4.2. All the code for this app can be found on my github account:

https://github.com/jamesmcclay/swift_times_tables

Create a project in Xcode

We’re going to create an iOS app in Xcode, so make sure you select “iOS” in the Xcode new project window:

Xcode New Project Window

There are, in fact, many different ways to write an app in Xcode/iOS/swift. As a matter of complete personal preference, I like doing everything “programmatically”. In iOS development you have two choices: use Interface Builder or programmatically. Interface Builder uses the story board to create UI objects/elements, while programmatic creates the same elements from swift code without using Interface Builder. I happen to prefer the latter.

The only thing that is really difficult to create from code is a UIViewController, so I typically add those from the storyboard. Since Apple gives us the first UIViewController, we’ll jump right into creating the UI elements.

Create the UI objects

We’ll first create global variables to hold our UI elements so we can access them from anywhere in the class, like this:

let mainLabel:UILabel
let subLabel:UILabel
let tableInput:UITextField
let multiplierInput:UITextField
let normalMode: UISwitch
let normalLabel: UILabel
let randomMode: UISwitch
let randomLabel: UILabel
let startButton: UIButton
var modes: [UISwitch] = []

I’ll explain what each one does:

  • mainLabel: a title-like piece of text to show the name of the app.
  • subLabel: some text that tells the user to enter a table or leave it blank to practice all tables.
  • tableInput lets the user select a specific table to practice.
  • multiplierInput lets the user specify how far up they want to practice. The default is to practice to 12, but this input allows it to be configured.
  • normalMode: a switch for “normal mode” which practices in sequence.
  • normalLabel: a label attached to the switch saying “normal mode”.
  • randomMode: a switch for “random mode” which practices randomly.
  • randomLabel: a label attached to the switch saying “random mode”.
  • startButton: the button to begin practicing.
  • modes: an array to hold the two switches, more on that in the next post.

Now that we have these created, we’ll add a couple of functions that allow us to create objects to put in these variables, without ever touching Interface Builder. They look like this:

convenience init() {
    self.init()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

I won’t go into how these work, as it’s a bit distracting from the app building. Suffice to say they let us create UI objects programmatically.

Our objects can be created in the required init? function before the line with super.init is run. The way I typically create objects is using closures, which is just a function without a name. You can assign whatever the closure returns to a variable. So I just create a bunch of closures that create the objects and assign them to the global variables. This next bit is long, but it’s just a bunch of boilerplate creating these elements/objects the way that I wanted them. The required init? function now looks like this:

required init?(coder aDecoder: NSCoder) {
    mainLabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "James McClay's Times Tables!"
        label.numberOfLines = 2
        label.textAlignment = .center
        label.font = UIFont.preferredFont(forTextStyle: .largeTitle)
        label.adjustsFontForContentSizeCategory = true
        return label
    }()
    subLabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Enter a table to practice,\n or blank for all:"
        label.numberOfLines = 2
        label.textAlignment = .center
        label.font = UIFont.preferredFont(forTextStyle: .title2)
        label.adjustsFontForContentSizeCategory = true
        return label
    }()
    tableInput = {
        let input = UITextField()
        input.translatesAutoresizingMaskIntoConstraints = false
        input.placeholder = "All Tables"
        input.borderStyle = .bezel
        input.keyboardType = .numberPad
        return input
    }()
    multiplierInput = {
        let input = UITextField()
        input.translatesAutoresizingMaskIntoConstraints = false
        input.placeholder = "Max Multiplier (12)"
        input.borderStyle = .bezel
        input.keyboardType = .numberPad
        return input
    }()
    normalMode = {
        let mode = UISwitch()
        mode.translatesAutoresizingMaskIntoConstraints = false
        mode.tag = 0
        return mode
    }()
    normalLabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Normal Mode"
        label.font = UIFont.preferredFont(forTextStyle: .body)
        label.adjustsFontForContentSizeCategory = true
        return label
    }()
    randomMode = {
        let mode = UISwitch()
        mode.translatesAutoresizingMaskIntoConstraints = false
        mode.tag = 1
        return mode
    }()
    randomLabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Random Mode"
        label.font = UIFont.preferredFont(forTextStyle: .body)
        label.adjustsFontForContentSizeCategory = true
        return label
    }()
    startButton = {
        let button = UIButton(type: .system) as UIButton
        button.translatesAutoresizingMaskIntoConstraints = false
        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .title2)
        button.titleLabel?.adjustsFontForContentSizeCategory = true
        button.setTitle("Start!", for: .normal)
        return button
    }()
    super.init(coder: aDecoder)
    modes = [normalMode, randomMode]
}

modes = [normalMode, randomMode] is put after super.init because that’s where self is created. I’ll use that array to make sure only “normal” or “random” mode can be selected, both.

Now that we have all those elements created, let’s lay them out on the view.

Using autolayout to put objects on the view

All of the elements created above have translatesAutoresizingMaskIntoConstraints set to false, which means that Autolayout is turned on. Autolayout basically allows a swift developer to put UI elements on the view and have their position dynamically calculated based on the view size and their relative position to other elements.

The way I like to do this is to write a function to hold the code for each logical area, and call each function from viewDidLoad. So my viewDidLoad looks like this:

override func viewDidLoad() {
    super.viewDidLoad()
    self.navigationController!.navigationBar.isHidden = true
    setupLabels()
    setupInputs()
    setupModes()
    setupStartButton()
}

First we’ll set up the “labels”, the main one and the sub one. My setupLabels function looks like this:

func setupLabels() {
    view.addSubview(mainLabel)
    view.addSubview(subLabel)

    mainLabel.topAnchor.constraint(
        equalTo: view.safeAreaLayoutGuide.topAnchor,
        constant: view.frame.height * 0.1
    ).isActive = true
    mainLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    mainLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true

    subLabel.topAnchor.constraint(
        equalTo: mainLabel.bottomAnchor,
        constant: view.frame.height * 0.04
    ).isActive = true
    subLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    subLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.7).isActive = true
}

I really hate how complicated this looks, because it’s not complicated. The autolayout code here is doing just a few basic things:

  • Add the UI element to the view as a subview (it puts the element on the screen).
  • Anchor its y-axis, usually by putting a constraint on the topAnchor. I usually give a constant with that one as well to provide some dynamically-created spacing, with view.frame.height * 0.1.
  • Anchor its x-axis, a lot of times it can just be constrained to the view.centerAnchor.
  • Anchor the width, usually to a percentage of the main view.

Once those constraints are in place, the element will properly show on the screen. That’s really all that’s happening here. If you have any questions, please comment or contact me!

Now let’s create setupInputs:

func setupInputs() {
    view.addSubview(tableInput)
    view.addSubview(multiplierInput)

    tableInput.topAnchor.constraint(
        equalTo: subLabel.bottomAnchor,
        constant: view.frame.height * 0.04
    ).isActive = true
    tableInput.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    tableInput.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.4).isActive = true

    multiplierInput.topAnchor.constraint(
        equalTo: tableInput.bottomAnchor,
        constant: view.frame.height * 0.02
    ).isActive = true
    multiplierInput.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    multiplierInput.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.4).isActive = true
}

You might have noticed this looks very much like setupLabels. That’s because autolayout requires a great deal of boilerplate but necessary code. I haven’t found a good way to reduce the plates on my boiler for this kind of code, so I end up with a fair amount of it when laying things out programmatically.

Now let’s create setupModes:

func setupModes() {
    view.addSubview(normalMode)
    view.addSubview(normalLabel)
    view.addSubview(randomMode)
    view.addSubview(randomLabel)
    
    normalMode.topAnchor.constraint(
        equalTo: multiplierInput.bottomAnchor,
        constant: view.frame.height * 0.04
    ).isActive = true
    normalMode.leadingAnchor.constraint(equalTo: tableInput.leadingAnchor).isActive = true
    normalMode.addTarget(self, action: #selector(modeTapped), for: .touchUpInside)
    
    normalLabel.leadingAnchor.constraint(
        equalTo: normalMode.trailingAnchor,
        constant: view.frame.width * 0.01
    ).isActive = true
    normalLabel.centerYAnchor.constraint(equalTo: normalMode.centerYAnchor).isActive = true
    normalLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3).isActive = true
    
    randomMode.topAnchor.constraint(
        equalTo: normalMode.bottomAnchor,
        constant: view.frame.height * 0.02
    ).isActive = true
    randomMode.leadingAnchor.constraint(equalTo: tableInput.leadingAnchor).isActive = true
    randomMode.addTarget(self, action: #selector(modeTapped), for: .touchUpInside)
    
    randomLabel.leadingAnchor.constraint(
        equalTo: randomMode.trailingAnchor,
        constant: view.frame.width * 0.01
    ).isActive = true
    randomLabel.centerYAnchor.constraint(equalTo: randomMode.centerYAnchor).isActive = true
    randomLabel.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3).isActive = true
}

Once again, very similar to the previous ones with one difference. We’re attaching a label to the right edge of each switch. So normalMode has a corresponding normalLabel to explain to the user what the switch is for. The normalLabel.leadingAnchor is constrained to the normalMode.trailingAnchor with some spacing given to constant.

Finally, we’ll set up the start button with setupStartButton:

func setupStartButton() {
    view.addSubview(startButton)

    startButton.topAnchor.constraint(
        equalTo: randomMode.bottomAnchor,
        constant: view.frame.height * 0.04
    ).isActive = true
    startButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    startButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.3).isActive = true
}

Try it out!

Now that we have all the UI elements created and laid out and constrained, they should fall into place! On the simulator it’s looking good:

The app running on Xcode iPhone Simulator

In the next post, we’ll add some logic so our elements actually do things.

See you next time!

How To Publish An Android App On The Google Play Store

In a previous post, we created a simple app for practicing times tables. Then we added Google AdMob to it to display a basic ad banner at the bottom of the app. Today, we’re going to get the app published and available on the Google Play Store.

Create a developer account

Becoming an official developer with Google costs a one-time $25. It all starts here:

https://play.google.com/console

If you already have a google account, just follow the prompts to make your account a developer account.

Developer account creation page

Once your account is a developer account, you’ll need to upload a document that proves your identity.

Identity verification page

Verification usually takes a day or two to complete.

Create an app

Now we’re ready to create our first app on the store.

Play Store Console App Creation

The app creation process takes some time, and if you’re really serious about it, some planning. I won’t show all the screenshots, that would get a little long. The times tables app is very simple so we can move through it pretty quick.

Step 1 – basic details

We’ll need to give it a name, default language, whether it’s a game, and whether it’s paid or free. Be careful on that last one, once it’s set you can’t change it later.

Step 3 – App setup

App setup process

Google wants to know a fair amount about the app before it’s published. Some of it is for their own internal policies or data collection, others are to comply with various laws in the countries the app can be available in.

  • App access – whether your whole app is available immediately or if there is a paywall.
  • Ads – whether your app contains ads or not.
  • Content rating – a questionnaire about the content of your app.
  • Target audience – the age your app might be targeted to.
  • News apps – whether or not your app contains news.
  • COVID-19 – whether or not your app is for tracing COVID-19.
  • Data safety – you must disclose what kind of data your app collects. You also need to provide a link to a privacy policy for your app, so you’ll need to create both the policy and the page if you haven’t already.
  • Select an app category and provide contact details – a few questions about what categories your app falls under.
  • Set up your store listing – see below

Step 4 – Set up store listing

The store listing setup area will collect a bunch of information that is used to build your app’s Play Store page.

Store listing setup page

We need to upload the app icon, along with a feature graphic. A feature graphic is important if the app ever becomes popular and Google wants to feature it on the app store:

Then it’s time for screenshots:

Once we have all that stuff input and uploaded, we should be good on the store listing section. We should now be able to complete the final section to release the app on the Play Store.

Release the app on the Play Store

Now that we’ve done all the work to get the app ready, the “Publish your app on Google Play” section at the very bottom on the app’s main dashboard should be available. You’ll need to skip all the sections about testing and pre-releasing. It looks like this:

First check all countries in “Select countries and regions”, unless you have a reason not to select them all. Then click on the “Create a new release” button. On this page we’re actually going to upload a copy of the app code.

In Android Studio, you can create a bundle from Build –> Generate Signed Bundle:

Creating a Signed Bundle in Android Studio

If this is your first time creating a signed bundle, you’ll need to create a key store. Just fill in the information, it’s part of the X.509 certificate standard so it asks you for a bunch of identifying information:

Make a note of the build file destination, and create the bundle. Then upload the “.aab” file in on this page. After entering some quick details on the release at the bottom, we’re good to go.

We are now finally ready to roll it out to the world! On the Review Release page, we can click “Start rollout to Production”, which means we have completed all preparation steps!

Final release review page

Clicking the rollout button will send the app to Google, where it will be reviewed by human beings to ensure the app is safe/secure/compliant and otherwise OK to release to the world. If all goes well, the app should be available on the store within 3 days, sometimes taking up to 7, according to Google.

Congratulations!

Monetize Your App – Google AdMob

In the last couple of posts, we wrote a simple times tables flash card app. Let’s add some advertisements to it so we can make some money if anybody decides to actually use it.

Enter Google AdMob, one of the simplest and easiest ways to monetize an app.

The base code that we’ll be adding to is here on my Gihub account:

https://github.com/jamesmcclay/android_kotlin_times_tables

Let’s apply and get some ads popping up in our app!

Start the process

The application process starts here:

https://admob.google.com/home/

Clicking on “Get Started” will do what it promises.

If you’re already logged in with a Google Account, this part is easy. Your account will be created and you’ll be taken to the main AdMob page:

Google AdMob Dashboard

Click on “Add Your First App” to start adding an app.

Add your app

This part is pretty quick too. Just follow the prompts, answer the questions and create your app.

Now that we’ve created an app, we’ll need to add an ad to it. Once we’re done creating the app it leads us to the main page for the app. Clicking on “Add Ad Unit” will take us through the process to create an ad.

Create an ad unit

Ad units are just an ad within the app. There’s several different types to choose from. Some are simpler and less intrusive like the banner, and others take up the whole page and completely interrupt the flow of the app. Choose wisely. In this case, we’ll pick the simplest one, the banner.

Once the ad unit is created, we’ll get some identifiers we can use in our code to display the ad and connect it to our account to get paid.

Add the SDK

Now it’s time to write some code. We’re mostly going to be using the code given to us by Google, but since it’s in code we have a lot of control over how/when/if the ad is displayed. We’re following the quick start instructions on the Google documentation found here:

https://developers.google.com/admob/android/quick-start

We need to add some stuff to the build.gradle file located in the root directory (not the app-level one):

buildscript {
    repositories {
        google()
        mavenCentral()
    }
}

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

I also ran into an issue where I had to change a line in settings.gradle to be this:

repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)

The key was changing “FAIL_ON_PROJECT_REPOS” to “PREFER_SETTINGS”. After that, syncing gradle worked.

Now we need to add the dependency to the app-level build.gradle file (the other one) like this:

dependencies {
  implementation 'com.google.android.gms:play-services-ads:20.4.0'

  // For apps targeting Android 12, add WorkManager dependency.
  constraints {
    implementation('androidx.work:work-runtime:2.7.0') {
        because '''androidx.work:work-runtime:2.1.0 pulled from play-services-ads
                   has a bug using PendingIntent without FLAG_IMMUTABLE or
                   FLAG_MUTABLE and will fail in apps targeting S+.'''
    }
  }
}

Then we need to add the follow to AndroidManifest.xml. Make sure you put in the Application ID for your app that’s in the Google AdMob dashboard. It’s not the Ad ID that you created, the app ID. I got confused, so I thought I’d specifically call it out:

<manifest>
    <application>
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="ca-app-pub-xxxxxxxxxxxxxxxx~yyyyyyyyyy"/>
    </application>
</manifest>

With that, the SDK is added to the project. Next step is to initialize it.

Initialize the SDK

To initialize, we need to add the import at the top of the TableActivity.kt file (we’re going to put this on the flash card screen) and run the initializer inside onCreate. We’ll also add a global variable called mAdView to connect to adView (which we’ll create next):

import com.google.android.gms.ads.MobileAds

class TableActivity : AppCompatActivity() {

    lateinit var mAdView : AdView

    override fun onCreate(savedInstanceState: Bundle?) {

        MobileAds.initialize(this) {}

        mAdView = binding.adView
        val adRequest = AdRequest.Builder().build()
        mAdView.loadAd(adRequest)

Now all we need to do is add an AdView to the layout file so there’s an actual spot for ads. In the activity_table.xml file, we’ll stick this at the bottom, it came right from the Google documentation:

<com.google.android.gms.ads.AdView
    xmlns:ads="http://schemas.android.com/apk/res-auto"
    android:id="@+id/adView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_alignParentBottom="true"
    ads:adSize="BANNER"
    ads:adUnitId="ca-app-pub-3940256099942544/6300978111">
</com.google.android.gms.ads.AdView>

You’ll notice there’s a real adUnitId in there, that’s on purpose. That’s actually the Google AdMob official test ID, we’re supposed to use that for testing. You can get your account suspended if you use your actual AdMob production ad ID.

And that should do it! When we fire up the app, we should see a test banner at the bottom of the flash card screen

Happy coding!

Android Kotlin – Building a Times Tables App – Part 2

In a previous post, we built the first part of a times tables flash card app in Android Studio. So far, we have the main page of the app where a user can either select a specific table by entering a number, or leave it blank to practice all tables. In addition, we set up a radio group with two buttons, one for “Normal Mode” and the other for “Random Mode”.

Today, we’ll be implementing the page of the app that actually shows the user their table’s questions and answers. Let’s get right into it by creating a new activity.

When I wrote this, I was using Android Studio Arctic Fox | 2020.3.1 Patch 2.

The full code for this project is on my Github account:

https://github.com/jamesmcclay/android_kotlin_times_tables

Create a new activity

To create one, just go to File –> New –> Activity –> Empty Activity. I’m going to call my activity “TableActivity”:

New Android Activity Screen in Android Studio Arctic Fox

Now that we have a fresh activity, we can create the layout elements.

Creating layout elements

We can go ahead and just delete any auto-populated content in the layout. The first thing we’ll want to create is a toolbar, so that we can put a back button on it. My initial RelativeLayout looks like this:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:minHeight="?attr/actionBarSize"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:titleTextColor="@android:color/white"
        android:background="?attr/colorPrimary">
    </androidx.appcompat.widget.Toolbar>

</RelativeLayout>

This is some boilerplate that creates a toolbar with an ID of toolbard so we can grab it from our Kotlin code later.

As for the view itself – my idea for the layout of this page was to have just three elements. A TextView to hold the question, a TextView to hold the answer, and a Button to allow the user to show the answer when they’re ready. Between the RelativeLayout tags, I’ll write the XML for the elements:

<TextView
    android:id="@+id/textview_question"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_centerVertical="true"
    android:text=""
    android:textAlignment="center"
    android:textSize="32sp" />

<TextView
    android:id="@+id/textview_answer"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/textview_question"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="50dp"
    android:text=""
    android:textAlignment="center"
    android:textSize="32sp" />

<Button
    android:id="@+id/button_show"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/textview_answer"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="60dp"
    android:text="Show answer"
    android:width="150dp"/>

Nothing special here, just some text and a button on the screen. That’s all we need for a basic flashcard app.

TableActivity.kt

The main logic of the app is going in this file. Not that it’s anything complicated, but there’s a few calculations that need to be made.

Before we do any of that, some global variables need to be defined so we can store some (state) information there. At the top of the file right after the class declaration, we’ll put these in:

class TableActivity : AppCompatActivity() {

    var mode:String = "" //Normal or Random mode
    var tableMode = "normal" //A single table or all tables
    var table:Int = 0 //The number of the current table
    var multiplier = 1 //The number to multiply by the table
    var answerShowing = false //The state of whether answer is showing

    lateinit var appBarConfiguration: AppBarConfiguration //The Toolbar

The lateinit keyword just means that instead of giving the variable a value now, we promise to give it a value before we use it, or the app will crash. In this case, we’re going to do the toolbar code in onCreate, so I think we’re ok.

In addition, as was instructed to me by the Android documentation, we need to add this (overridden) function to our class to make the back button work:

override fun onSupportNavigateUp(): Boolean {
    onBackPressed()
    return true
}

onCreate function

Now let’s put some stuff in onCreate. First is the binding to the layout so we can access all the elements, it’s pretty quick:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding = ActivityTableBinding.inflate(layoutInflater)
    setContentView(binding.root)

Next is the toolbar code – I honestly struggled quite a bit with this part and ended up finding the solution on StackOverflow and can’t remember what page, sorry 🙁 But this magic incantation seemed to work:

val toolbar = binding.toolbar
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.title = "Practice!"

Then we put the layout elements (text fields and button) in some variables:

val question = binding.textviewQuestion
val answer = binding.textviewAnswer
val button = binding.buttonShow

Now let’s set the mode and table global variables to values sent from the previous activity. You’ll remember if the user didn’t put any value into the EditText for the table, we set the value to 0 so it would be a number. Let’s get the values now:

mode = intent.getStringExtra("mode").toString()
table = intent.getStringExtra("table").toString().toInt()

if (table == 0) {
    tableMode = "all"
    table = 2
}

The above is a bit confusing so let me explain – if the user selected a specific table, it will be a number and we can just work with that. But if it’s 0, they want to do all tables. We still need to use the table integer and increment it up as the user works through the tables. We need a way to know that we are in “all tables mode”, which is why I created the global variable tableMode. So if table is 0, we’ll set tableMode to all, and table we’ll set to 2. Why set to 2? Nobody wants to do tables 0 and 1, they’re too easy. We’ll start at 2.

We also need to run nextQuestion once on startup so the first question will show. All subsequent question will show when the user taps the button:

nextQuestion(question, answer, button)

The last chore in onCreate is to create an onClickListener for the showButton. This button is actually responsible for two things – if the answer isn’t showing and it’s clicked, it needs to show the answer. If the answer is showing, it should go to the next question. Hence the need for an answerShowing global variable. Here’s the listener:

button.setOnClickListener {
    if (answerShowing == false) {
        showAnswer(question, answer, button)
    }else {
        nextQuestion(question, answer, button)
    }
}

We’ll send the question, answer, and button layout items through as arguments so they can be updated by either the showAnswer or nextQuestion functions, whichever runs.

Done with onCreate. Let’s implement nextQuestion and showAnswer.

nextQuestion function

This function got a little more complicated than I would have liked, but it works. I’ll try to break it down so it’s clear what’s happening:

fun nextQuestion(question:TextView, answer:TextView, button: Button) {
    if (mode == "random") {
        multiplier = (2..12).random()
        if (tableMode == "all") {
            table = (2..9).random()
        }
    }else{
        multiplier += 1
        if (multiplier == 13) {
            multiplier = 2
            if (tableMode == "all") {
                table += 1
            }
        }
    }
    question.text = "$table X $multiplier = "
    answerShowing = false
    answer.text = ""
    button.text = "Show Answer"
}

This function starts with a check to see if random mode is on or not. If it’s random, we set the multiplier to something random between 2 and 12 (I assume people want to learn their tables up to 12). Then we check to see if we’re doing a specific table, or all of them. If it’s a specific table, we can use the existing fixed value of table. If we’re doing all tables, we’ll get a random table since this is random mode.

If the check for random is false, we’re incrementing the multiplier sequentially, so we increase it by 1. If multiplier is now 13, set it back to 2 (most people don’t learn tables past 12). In addition, when the multiplier is 13, increment the table by 1 so the user can move on to the next one.

That should do it for the logic. The lines after that are setting the text of the question, setting answerShowing to false (since it’s showing a new question now), then removing the answer text, and finally changing the button text to “Show Answer”.

showAnswer function

This function is much simpler – as the name suggests, it’s just showing the answer.

fun showAnswer(question:TextView, answer:TextView, button: Button) {
    val theAnswer = table * multiplier
    answer.text = "$theAnswer"
    answerShowing = true
    button.text = "Next"
}

Done with code!

Try it out

Everything is looking good!

Hope you liked this one. Let me know if you have any questions.

Android Kotlin – Building a Times Tables App – Part 1

Google’s Android Studio is a pretty awesome tool for building Android apps, but after building a times tables app in python and HTML, I’ll say doing an Android version was a bit more complicated. But with a little bit of knowledge about how the basics of Android Studio works along with some knowledge of Kotlin, you can have a basic app up and running in very little time. Today I’m building an app that is basically flash cards for times tables, with a couple different modes. I’ve split this up into two posts since there’s a fair amount of code.

Part 2 of this post is here.

When I wrote this, I was using Android Studio Arctic Fox | 2020.3.1 Patch 2.

The full code for this project is on my Github account:

https://github.com/jamesmcclay/android_kotlin_times_tables

Create a new project

We start by creating a new project, with an empty activity.

New project screen in Android Studio

Give your project a name, and make sure to set the language as Kotlin. Click finish, and you have a brand new project!

Write the layout code

Android Studio takes you to the MainActivity.kt file off the bat, but we’re going to jump into the layout file and create the interface elements. At the top of the screen, you should see a tab that says activity_main.xml, that’s where we can create the first page elements of app.

Opening up activity_main.xml, you’ll need to click on “split” near the top right so you can see the code as well as a rending of it on a mock phone. There’s a bunch of code already in there but you can delete all that.

First I’m going to create a RelativeLayout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

</RelativeLayout>

If you just type “Re”, RelativeLayout will probably autopopulate and you can press enter to get it and a bunch of pre-written code for you. No need to type all that stuff.

Text items

Inside the RelativeLayout (between the opening and closing <RelativeLayout> tags), I’ll create some text. These will just be banners introducing the app and instructing the user to enter a table to practice. This can be done with the element TextView:

    <TextView
        android:id="@+id/textview_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="100dp"
        android:text="James McClay's\nTimes Tables!"
        android:textAlignment="center"
        android:textSize="32sp" />

    <TextView
        android:id="@+id/textview_select"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textview_title"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:text="Practice all tables\n Or enter a table to practice:"
        android:textAlignment="center"
        android:textSize="24sp" />

I thought about commenting all these to explain what they’re doing, but it seems like they’re all pretty self-explanatory. It’s basically just putting some static text on the layout of the app, centering it, setting the font size, etc.

Next we’ll create a text entry field where the user can either leave it blank to practice all times tables or enter a specific one. This is done with the element EditText:

    <EditText
        android:id="@+id/edittext_timestable"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textview_select"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:hint="All tables"
        android:inputType="number"
        android:textAlignment="center"
        android:textSize="24sp" />

All elements have been given an "@+id", and using layout_below we can tell elements to go below another one.

Button and radio group

Next we’ll need a start button to send the user to the view that shows times tables questions. A button can be created with Button:

    <Button
        android:id="@+id/button_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/edittext_timestable"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp"
        android:text="Start!" />

Finally, we’ll create a RadioGroup to hold two radio button buttons. I thought having a normal, sequential mode as well as a random mode would be beneficial. The RadioGroup means that only one RadioButton can be selected in the group. Also, RadioButtons have a special property called onClick, where you can specify what function to run when one of the buttons is clicked and selected.

    <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button_start"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="20dp"
        >

    <RadioButton
        android:id="@+id/radio_normal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onRadioButtonClicked"
        android:text="Normal Mode"
        />

    <RadioButton
        android:id="@+id/radio_random"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onRadioButtonClicked"
        android:text="Random Mode" />
    </RadioGroup>

Remember, all of that stuff went in-between the RelativeLayout tags. Ok, now we’re ready for the Kotlin code.

onCreate function

You will probably need to add a snippet of code to your app’s build.gradle file so you can “bind” your Kotlin code to the layout file. Be careful – there are two of these files. You want the one that is inside “app” directory, not the one that’s in the root directory. In build.gradle, in the android block, paste this snippet and sync gradle:

buildFeatures {
    viewBinding = true
}

Next let’s write our code in the onCreate function that’s given to us by Android Studio:

class MainActivity : AppCompatActivity() {

    var mode: String = "normal" //represents what radio button user selected

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityMainBinding.inflate(layoutInflater) //bind to layout file
        setContentView(binding.root)

        val button = binding.buttonStart //get the start button
        button.setOnClickListener {
            val theIntent = Intent(this, TableActivity::class.java)
            var table = binding.edittextTimestable.text.toString() // get what table the user entered
            if (table == "") {
                table = "0" //if the user entered nothing, meaning all tables, set to 0 so toInt() won't fail.
            }
            theIntent.putExtra("mode", mode) // send mode to next screen
            theIntent.putExtra("table", table) // send table to next screen
            startActivity(theIntent)
        }
    }
}

I’ve created a global variable (I believe the Kotlin word is “property”) called mode to hold what radio button the user selected. The Intent stuff is just standard boilerplate code to send the user to the next screen. The next screen’s activity will be called TableActivity, which this bit Intent(this, TableActivity::class.java) has TableActivity in it.

You’ll notice we’ve attached mode and table to the intent so the next screen’s code can access it. You can see where table is retrieved and set, but you might be wondering how mode is set. We’ll do that next.

onRadioButtonClicked function

I took this code directly from the Android Studio documentation, and substituted my radio button id’s and mode property. We specified the onRadioButtonClicked function in the Radio Button XML code earlier.

fun onRadioButtonClicked(view: View) {
    if (view is RadioButton) {
        // Is the button now checked?
        val checked = view.isChecked

        // Check which radio button was clicked
        when (view.getId()) {
            R.id.radio_normal ->
                if (checked) {
                    this.mode = "normal"
                }
            R.id.radio_random ->
                if (checked) {
                    this.mode = "random"
                }
        }
    }
}

This basically just runs some logic to see which button is clicked, and then set the value of mode.

Try it out!

The app looks pretty good so far!

On the next post – I’ll build the screen where the user can actually practice times tables. Happy coding!

Check out part 2!

Python Flask Web App – Times Tables Quiz

Have you ever built a web app? Me neither! That’s why I’m going to build this app so we can both learn a thing or two. The components I will use are:

  • Python Flask Web Framework
  • HTML
  • No CSS!
  • No Bootstrap!
  • No React!
  • No Javascript!
  • No SQL!

So basically just python, Flask and HTML. I will also impose these limitations to make things simple and interesting:

  • No external HTML files – all HTML written within python files!
  • No http server in front of the Flask development http server – no NGINX, no Apache, no nothing! Just the Flask development http server.

This app will violate so many best practices, I can’t count. You have been warned.

You can find the entire code in one file on my Github account:

https://github.com/jamesmcclay/python_flask_times_tables

Install Flask

Make sure you install Flask, this is what we’re going to use for the main logic of the app. You might have to type “python3” or “python”, depending on what kind of system you have and how python is installed.

python3 -m pip install flask

Good to go!

Create the first page

I use vim, because using PyCharm or VisualStudio Code would probably be a lot more productive.

At the top of our file, I’ll do some imports to pull Flask in:

from flask import Flask
from flask import request

Then we’ll create the application object, and put it in a variable called app. I don’t actually know what this does other than fire up Flask. We’ll use the app object at the bottom of the file to actually start the http server and Flask app:

app = Flask(__name__)

I’m going to show the html in a separate block here so you can see it in html highlighting. Then I’ll show it embedded in the python code. It’s hard to look at in the python code because it’s all highlighted as a string.

My html code creates a form, asking the user to submit their requested times table (2*X table, 3*X table, etc). The selected table (it’s just a number) will be stored in the query string that is sent back to a different URL at /next.

<html>
<form action ="/next" method="get">
<label for="times_table">What times table do you want?</label><br>
<input type="text" id="times_table" name="times_table"><br> <!-- the variable "times_table" stores the requested table -->
<input type="submit" value="Submit">
</form>
</html>

Now here’s that html embedded into python code, in a multi-line string:

@app.route('/') #Flask provides this. This function will respond to requests to '/'
def index():
    #Three quotes makes a multi-line string
    form = '''
    <html>
    <form action ="/next" method="get">
    <label for="times_table">What times table do you want?</label><br>
    <input type="text" id="times_table" name="times_table"><br>
    <input type="submit" value="Submit">
    </form>
    </html>
    '''
    return form

Flask gives me a ‘decorator’, a type of python magic that allows you to add special functionality to an otherwise normal function. In this case @app.route('/') gives this function the ability to respond to http requests using return. Whatever return sends back will in fact be sent to the user’s browser. In this case, it’s sending back the html form.

At the bottom of the file, we add the command to run the app (it will listen on all network adapters, beware):

app.run(host='0.0.0.0', port=80)

The program should be runnable at this point, let’s give it a shot! This command runs the Flask development http server. I had to put the -E option on sudo to allow python to access all my modules.

sudo -E python3 times_tables_website.py 

Opening up a browser, we can just go to the loopback adapter at 127.0.0.1 and see the app! I zoomed in a bit:

Entering 6 and hitting submit, I will get a 404 because /next hasn’t been implemented yet, but you can see the query string has sent the data we want:

Implementing /next

Here is the html form I’m going to send from the /next route. It will also be embedded in my python string, so I’m showing it once here in html highlighting. I’m using a trick in order to store data in the form I send to the user. I send a couple of hidden <input> labels to store times_table and multiplier. When the user sends the form, my function will be able to extract those and know what question to ask the user next.

<html>
<form method="get">
{judgement} <!-- Not HTML!! Will use python to insert label showing answer correct/not -->
<input type="hidden" id="times_table" name="times_table" value="{times_table}"> <!--store times_table variable hidden-->
<input type="hidden" id="multiplier" name="multiplier" value="{multiplier}"> <!--store multiplier variable hidden-->
<label for="answer">What is {times_table} x {multiplier}</label><br> <!-- python string interpolation again-->
<input type="text" id="answer" name="answer"><br> <!-- answer variable stored here-->
<input type="submit" value="Submit">
</form>
</html>

Next we will implement the /next route. Web developers call these routes, they are relative to the top level of your site which is always /. In this code, we’ll use request.args to get data from the request that was sent. Remember the form put a times_table variable in the query string? We’ll need to get that to know what table the user requested. The way to grab one is with request.args.get('variable_in_query_string'). We’ll also grab the multiplier, but on the first page multiplier won’t exist in the query string because the user hasn’t started yet. Which means the multiplier should start at 1. So if multiplier is None, I set it to 1. Otherwise, grab it, turn it into an int, and do a check to see if the user’s answer is correct. The judgement label will dynamically change based on the answer, and be inserted before being sent back to the user.

@app.route('/next') #Answers requests to the "/next" route
def next_row():
    times_table = request.args.get('times_table') #"request" was brought in from a flask import, lets us get query string variables
    times_table = int(times_table) #turn "times_table" into a number so it can be multiplied
    multiplier = request.args.get('multiplier') #get the multiplier
    answer = request.args.get('answer') #get what user answered. I use int() later because this is None on first page
    if multiplier == None:
        multiplier = 1
        judgement = ''
    else:
        multiplier = int(multiplier) #turn multiplier into a number
        if int(answer) == (times_table * multiplier): #check if answer is correct. I use int() here because this is not None
            judgement = '<label>You got it right!</label><br><br>' #judgement inserted into html (interpolated) later
        else:
            judgement = '<label> You need to practice!!!!!</label><br><br>'
        multiplier +=1 #increment multiplier for the next question

    #This is string interpolation as of python 3.6, using "f" before initial string quote
    form = f''' 
    <html>
    <form method="get">
    {judgement}
    <input type="hidden" id="times_table" name="times_table" value="{times_table}">
    <input type="hidden" id="multiplier" name="multiplier" value="{multiplier}">
    <label for="answer">What is {times_table} x {multiplier}</label><br>
    <input type="text" id="answer" name="answer"><br>
    <input type="submit" value="Submit">
    </form>
    </html>
    '''

    return form

Let’s try and see if it works!

It works! Hope you enjoyed this one. I sure learned quite a bit.