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.