Cake
  • Log In
  • Sign Up
    • In this panel, I'd like to walk you through the process of creating an Android app. This will not be a programming course or anything, but might give you an idea about what's possible. Because this has been talked about elsewhere, the app I'm going to create here will be something very simple at first - an app that allows opening and navigating the Cake website:

      I will likely skip over some things that I should talk about in more detail - so, if there are any questions, including further ideas for the app we're building here, feel free to ask.

    • 1. The development environment and setting up a project

      Android Studio is an integrated development environment (IDE) offered by Google for Android developers. It can be downloaded for free here:

      After installing and opening the IDE for the first time, we can Start a new Android Studio project to get going.

      After doing so, the first thing we're asked is to Select a Project Template. Every template comes with a small graphic that should look familiar to everyone who has used Android apps in the past. We can choose between templates that come with one or another navigation scheme (navigation drawer, tabbed, bottom navigation, some with special purposes (Google Maps, Login, Settings) - but we won't need any of that for the moment, so we choose Empty Activity for a clean slate.

      Next, we are asked to Configure [our] Project (image below):
      Our app needs a name, I'm going for Tortotrulo, which is Esperanto for "cake lifter". It also needs a package name, which is typically constructed from a domain name you own and the apps name. So, if you control the domain example.org, a potential package name for this app would be org.example.tortotrulo. If you don't plan to publish the app, you can enter whatever you like, here - but if an app gets published on the Google Play Store, this package name needs to be unique and stay the same throughout the lifetime of the app.

      Next, we can pick our programming language of choice. For years, programming for the Android platform was mostly done in Java - but somewhat recently, Google started offering Kotlin as another choice that is actually preferred by now. I'm going to pick Kotlin, because the language is very nice and concise - but it is fully interopable with Java, so we could add code in both languages later, if necessary.

      Last but not least, we need to pick a Minimum SDK for our app. This setting controls which devices are allowed to run the app. As you can see, there's some overlap between an API level (here, 21), an Android version number (here, 5.0) and a code name (here, Lollipop). All of those refer to more or less the same thing, so this is just a confusing mess we have to accept. ;) Choosing a higher API level here might make it a bit easier to implement things down the line - but increases the number of old phones that won't be able to run our app. This is a setting that can be changed later if necessary, so I'm just going to accept the default here.

      Clicking Finish will create and open our project for the first time.

    • 2. First view and project structure

      Once our project is set up, we will see something like shown in the image below.

      To the left is a structural view of our project. The java folder will contain all our app's code - for historical reasons, even if we selected Kotlin as our programming language. The res folder is where all of the app's resources are located. Resources are all image files (for example the icons or background images used by our app), but also text strings, numeric values used for formatting, or layout files for our app' UI.

      There's also a manifest folder, where we can declare how our app is structured and what permission it needs - and a folder for Gradle Scripts which is mostly concerned with eventually compiling our app's code into something that can run on a phone. More about that later.

      To the right is a text editor, currently open files are a layout file ("activity_main.xml") and a file containing some Kotlin code ("MainActivity.kt"). I'll talk a bit about what an Activity even is in the next post.

      Surrounding this area are a ton of icons, buttons and menus. Not sure what exactly we'll need in the future, so let's postpone that as well.

    • 3. Aside: About Android Activities

      So, what is an Activity in Android terminology? When we think about programs on a desktop PC, we typically think of them as running all the time: our browsers keep playing videos in the background, our word processors keep the text we wrote even if we leave the PC for a while. The same is not true for apps. They are stopped and resumed all the time to keep our batteries from draining - and they often aren't even a single coherent program that runs, more a group of loosely connected sub-programs for individual tasks.

      An Activity is basically "everything that happens on a single screen" of our app, and we are responsible for making sure that this screen can be destroyed and created anew at any time, including keeping track of the state of our app. In order to do so, when we create a new Activity we can put code in certain functions that run onCreate(), onResume(), onPause(), etc. as shown in this chart:

      For the simple app we're writing, we won't need anything but the onCreate() function at first, but it's always a good idea to keep this Activity lifecycle in mind.

    • 4. Our Empty Activity

      With that in mind, let's look at what was prepared for us. The code for our main activity (in "MainActivity.kt") currently consists of only eleven lines - ignoring package and import instructions, we see this:

      class MainActivity : AppCompatActivity() {
      override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)
      }
      }

      As outlined above, there's an onCreate() function, so whatever is in there runs as soon as we start our app. The first instruction is there for technical reasons, leaving only one other line as current functionality of our app:

      setContentView(R.layout.activity_main)

      What does this do? It applies a layout file to our Activity, so that something appears on the screen. This layout file is "activity_main.xml", so let's have a look at that as well

      <androidx.constraintlayout.widget.ConstraintLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      tools:context=".MainActivity">

      <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Hello World!"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

      </androidx.constraintlayout.widget.ConstraintLayout>

      Again, a lot of stuff that's there for technical reasons - but if you ever wrote HTML for a website directly, this should look familiar. What happens here is that we define a ConstraintLayout (first block, plus last line) that is as large as the screen (layout_width and layout_height parameters set to "match_parent").

      In that ConstraintLayout, we define a TextView that is just as large as necessary (layout_width and layout_height parameters set to "wrap_content"), and add the text "Hello World!" to that view. We are also setting some layout constraints, which will lead to the text being presented in the middle of the screen.

      Good thing the IDE comes with a Design view for layouts, so we can see the spectacular result in all its glory without having to install and run the app first:

    • 5. Finally, some coding

      Our goal is to have an app that, when we open it, displays the cake.co main page and allows us to navigate around without leaving the app. Starting with our layout file, all we need is a full screen WebView, so we simplify our layout as follows:

      Note that the WebView we defined got a name, "webview". We can use that name in our code to directly access and modify the view.

      Next, we're adding code to the onCreate() function of our Activity, following this guide:

      First, we're making sure that all cake.co URLs are opened in our app, while external URLs are not:

      webview.webViewClient = CakeWebViewClient()

      Next, we're enabling JavaScript, necessary on the cake.co website for signin and other functionality:

      webview.settings.javaScriptEnabled = true

      Finally, we're starting by loading the cake.co main page:

      webview.loadUrl("https://www.cake.co/")

      The last bit is something that we might want to revisit later. It doesn't make sense to always open the main page, for example if we click on a cake.co link elsewhere and want that link to be opened in our app. For the moment, it's good enough though.

      Last but not least, we need to declare that our app needs internet access. This is a one line change in the manifest file "AndroidManifest.xml", we're simply adding:

      <uses-permission android:name="android.permission.INTERNET"/>

      With these changes, our first version of an Android app that shows the cake.co website is done.

    • 6. Last but not least, running the app

      Now that we have a working app, we of course want to run it on our device. Nothing easier than that, all we need to do is enable developer options first by finding the Build Number hidden somewhere in the settings and tapping it seven times:

      Once we've done that, we can find a new entry Developer options in our settings - and somewhere in those options, find the one that's called USB debugging and enable it.

      As soon as we do that and connect our device to the PC, we can select Run>Run app from the menu, which will install the app and start it.

      That's it in a nutshell. Of course, making the app do more than just act as a limited internet browser means implementing much more than we did until now - but if you're interested, we can dive into some more details. One question is already waiting, add your own if you like! :)

    • Great questions! If we want to do that, we'd need to access the URL currently visible in our WebView. Let's have a look at what that class has to offer for us:

      We're good, there's a function getURL() which returns the current URL as a String. If we store that URL somewhere, for example before the app is paused or stopped, we can later use that string in the line that currently reads

      webview.loadUrl("https://www.cake.co/")

      to open a different page than simply the home page the next time we start our app.

      Similarly, if we add a small "bookmark" button to our layout, we can tap that to add the current URL to a list of bookmarked items somewhere. If we make use of the getTitle() function that is also available, we can even store a meaningful title that we can display later.

    • @StephenL Implementing "last page visited" was quite easy. First, I added a function that gets called whenever the app is paused:

      override fun onPause() {
      super.onPause()
      getSharedPreferences("default",0).edit()
      .putString(key_last_url,webview.url).apply()
      }

      The new thing here is something called SharedPreferences. As the name suggests, it is something that allows us to store and retrieve values that we might need across our app. These values can be numerical, or boolean values - or a string like in our URL example.

      In one line of code, I start editing these preferences, write a string value (with a certain key which allows me to retrieve the same value later), and finally apply my edit.

      Second, I changed some code in the onCreate() function. Instead of just loading the main page URL, I now load a string from preferences and use that one instead, if it is valid:

      val lastURL = getSharedPreferences("default",0).getString(key_last_url,null)
      if (lastURL != null && Uri.parse(lastURL).host=="www.cake.co") {
      //load last visited URL
      webview.loadUrl(lastURL)
      } else {
      //load main page
      webview.loadUrl("https://www.cake.co/")
      }

    • 7. Another day, another feature

      So far, we have an app that we can start ourselves to navigate around the cake.co website. However, if we receive a link to a page on Cake, for example via mail or messenger, clicking that link still opens a standard browser. Wouldn't it be nice if it started our app instead?

      In post #3, I described Activities and how they are the separate, basic building blocks that make an app. Typically, an app consists of more than one activity, and it is often the case that one Activity starts another for the user to perform a certain task, sometimes even across different apps. In that case, how can we pass necessary information from one Activity to the other? We can do that via Intents and intent filters:

      An Intent is a way for our app to let the operating system know that it should start a different Activity for us. This can be done explicitly, by naming the exact Activity that should be started - or it can be done by just declaring what that Activity should be able to do. In the latter case, the operating system checks what Activities are available across all apps, and either starts a default one or suggests more than one app to the user.

      Our app already can open all cake.co URLs, so let's just tell the operating system. In our manifest file, we add the following code to the section that describes our MainActivity:

      <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data
      android:scheme="https"
      android:host="www.cake.co" />
      </intent-filter>

      By doing so, we state that it is safe for a browser to call us, and that we actually want to be the default app for the data that is described below. We state that the data we're interested in is any URI that starts with "https://www.cake.co".

      After doing so, the next time we click a link that matches, we are asked whether we want to open it in Chrome or our own app, and whether we want to do this just this time, or always (image below).

      On the receiving end, we now just need to make sure that our MainActivity doesn't start with the main page or last page we had open, but with the link we were sent. To do that, we just check if there's intent data that matches, and load that page instead:

      if (intent.data != null && intent.data?.host=="www.cake.co") {
      //valid url in intent, load that
      webview.loadUrl(intent.data.toString())
      } else {
      //do what we previously did
      }