Publishing My First App
I recently created an app called Punch Buggy.
Working on it has taught me a lot of things. It was a great first app because I was thrown head first into several important concepts:
- How to switch between different activities in Android and pass along primitive data
- How to pass objects between activities
- The life cycle of a view
- Considerations when organizing an app
- Saving data with Shared Preferences
- Working with ListViews
- How to publish an app on the Play Store
- How to build and generate an APK to upload and publish
- Things to setup when getting ready for a release
I've already written a blog post or two covering some of these topics individually, and I learned a lot more than what is covered here. This post just goes over the biggest things I learned (aka messed up on) when working on the app.
"The greatest teacher, failure is." -Yoda
Organizing an app
Having no prior experience with Android or apps in general, I made the mistake of diving into this head first without thinking about how the different views should affect my objects. I was so excited to see my work be displayed on a phone (even just an emulator) I didn't stop to consider how the backend logic should work with the front end.
When I started the app, Android and Kotlin were both new to me. To tackle things in more manageable, bite sized pieces, I first made a Kotlin repo to test the things I wanted to make. These were the things I thought I might need:
- Some overarching game management object to keep track of players (
Game
) - Player objects to keep track of people's individual score (
Player
) - A generic vehicle object that has a color associated with it. I knew early on I wanted to use Inheritance because growing up we would play the punch buggy game and when we went to Colorado, we added our own twist on it with Jeeps. I wanted to make adding Jeeps to the app easier in the future. (
Vehicle
)
Those were the basics. Activities and views weren't even on my radar. In what seemed like the blink of an eye, I was staring three activities dead in the face with no idea on how to organize them and my objects.
The three views I had were:
- Main Page (
MainActivity
) - User Management Page (
UserManagement
) - Player's Game Page (
UserView
)
After discussing some ideas with Beep, we came up with the idea to have the app organized with the Main Activity handling the Game
. When you want to add and remove users to and from the game via the User Management page, the game instance will be passed to that activity. Once all users are added or removed, the game instance will be updated then passed back to the Main Activity.
When a Player
gets updated because their score changes, the instance of the Player object should be passed to the User View. Once it has been updated accordingly, that same object should be passed back to the Main Activity and the game instance should be updated with the "new" player.
I'm proud of the end result, especially after a bit of a hiccup trying to adjust the already implemented logic to work with the front end technicalities. As I learn more about app development, I might have a different opinion, which I find exciting.
An old tech lead once told me something along the lines of "It's good to look at old code and think it's bad... it means you're improving" and it's stuck with me all these years.
Saving Data
Because we had very little information to save, Beep wrote some functions to write to a Shared Preference file. Reviewing the life cycle of the app, there were several key places we had to make sure the information would be saved. These methods are called when the app closes, quits, or is backgrounded for any reason.
- onPause
- onStop
- onDestroy
To do this we had to override these methods.
override fun onStop() {
super.onStop()
// your code here
}
The Android developer docs cover how to interact with the Shared Preferences file.
Our end product was writing and reading inside of private functions within the Main Activity. We wanted to save the instance of the running game which is a Game
object. Because of this, we used Gson, which was talked about in a previous post.
Below demonstrates how we got the context of our app so we could access its Shared Preference file.
private fun writePreferences() {
val context = this@MainActivity
val sharedPref = context.getPreferences(Context.MODE_PRIVATE)
with (sharedPref.edit()) {
val gson = Gson()
val gameData = gson.toJson(runningGame)
putString("game", gameData)
apply()
}
}
private fun readPreferences() {
val context = this@MainActivity
val sharedPref = context.getPreferences(Context.MODE_PRIVATE)
val gameData = sharedPref.getString("game", "")
if (gameData != "") {
val gson = Gson()
runningGame = gson.fromJson(gameData, Game::class.java)
} else {
runningGame = Game()
}
}
Working with ListViews
The main view of the app is a list of all users in the current game. I needed to populate the list view dynamically at runtime because it was to hold all of the players in the game which is constantly changing. From there, the user should be able to click on a player and open their scoreboard. To do this, I needed a ListView
that was listening for clicks.
Create the adapter
To make a list view dynamic, you must use an adapter. An adapter needs the instance of the list view you are working with which can be obtained with our good friend findViewById
.
Once you construct the adapter, you can set it to the list view's adapter. The ArrayAdapter
's constructor that I used took in three arguments:
- The current activity
- The resource for the adapter that should be used to display the data
- The list of data that should be displayed, which in my case was a call to
getPlayerNames
from therunningGame
instance.getPlayerNames
returns aMutableList
ofStrings
.
playerListView = findViewById(R.id.playerListView)
playerViewAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, runningGame.getPlayerNames())
playerListView.adapter = playerViewAdapter
Now the list view will reflect the current players in a game.
List View listener
For setting a listener, I only needed the position of the item that was clicked. This item was a player's name. Because each player has to have a unique name, this meant I could easily grab the instance of the player with the getPlayer
method that I created inside of the Game object.
After getting the instance of the player, I was able to convert it to Gson and load the user view for that player.
playerListView.setOnItemClickListener { _, _, position, _ ->
val clicked = playerViewAdapter.getItem(position).toString()
val player: Player? = runningGame.getPlayer(clicked)
val playerIntent = Intent(this, UserView::class.java)
val playerGson = Gson().toJson(player)
playerIntent.putExtra("player", playerGson)
startActivity(playerIntent)
}
Publishing the app
The app was done! I was ecstatic, but at the same time a little lost. It felt like sitting at the opening of a really long hallway; staring ahead trying to determine what was next. I never published an app before, and didn't really know what it would entail. I still don't know if there are things I missed or did wrong, but this is my experience and tips for you.
Creating a developer account and the developer console
First you need to make a developer account on play.google.com/console. From here you can upload and manage your app.
Cleaning up the app for release
There are a ton of resources online for setting up an app for release, but highlighted here are some of the big things you might run into.
Google of course has their full guide as well.
Remove log statements
If you are using Log.d
statements, it is recommended to remove all of these before you build your release APK.
Set the app icon
In the Manifest file, you need to set the icon you want to have for your app. There are two types of icons: normal and round.
Once you upload the images to your project, in the AndroidManifest.xml
file change the following lines inside of <application
to point to your images:
<application
...
android:icon="@drawable/icon"
android:roundIcon="@drawable/icon_circle"
Changing the package name from com.example
Why did I keep com.example
for so long??
Google will not allow an app to have a package named com.example
, which is the default package name Android Studio makes.
To change it click the gear icon when the Project tab is open. Deselect "Compact Middle Packages" and the folder java > com.example
should be expanded to a directory structure instead ( java/com/example
).
java > com.punchbuggy
before:
After:
Now, when it's in that expanded view, you can click on each directory and rename and refactor the packages. In this example I already renamed com.example
to com.punchbuggy
.
Building a signed APK
You need a signed APK to upload to the Play Store. The official Android developer guide already covers this really well.
When I was first inside of the developer console, I had no clue what to do. I only mention this to help point someone in the right direction (hopefully).
Follow that guide and then build your app!
Build > Generate Signed Bundle / APK > APK > choose the keystores made previously with the link above
Internal testing
I recommend setting up internal testing before releasing the app. This was the phase where I realized there were things I hadn't considered (app icon, locking orientation, etc) when publishing an app, so this was a great time to catch that and fix it.
Updating your app after publishing
Inside of build.gradle
there is a member called versionCode
.
defaultConfig {
applicationId "com.punchbuggy.game"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1"
If you make any changes to your app and want to upload a new APK to the developer console, this number has to be updated or the upload will fail with a message saying "You need to use a different version code for your APK or Android App Bundle because you already have one with version code X."
Conclusion
I had a great time creating this app. Android was totally new to me, as well as Kotlin, which I think gave me the necessary vigor to pursue such a tall task in a short timeframe (one that I made for myself).
I stumbled and fumbled in places, which I think makes learning something new special. It's important to fail as long as you learn from your failures. If the person reading this is considering making their first app, I say just jump into it. It might seem daunting at first, but I promise you will start to get the hang of it. It's a whole new world inside of our phones and there are a lot of things you just have to learn through experience. Just keep at it and remind yourself that it's okay to not know! How exciting, to not know something. That means you get to learn. Seeing your app for the first time on an actual phone and not just an emulator is insanely rewarding. Remember, you can do it!