Skip to content

Summary of topics discussed during Grinnell AppDev Android training session

Notifications You must be signed in to change notification settings

GrinnellAppDev/Android-Training-Wiki

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 

Repository files navigation

AppDev Android Training Wiki

Sessions

# 11/23/16

This week's homework is more of a reading assignment than a programming assignment. We've been building upon all the major topics, and it will culminate in a final, useable product. Here's the big picture: you will create an app that users can sign up and login for, then view a list of "stories" retrieved over the network with JSON, a detailed view of the story, and finally logout. That seems like a lot, but it will all be built up in parts. This week is reading in greater depth about some of the topics we've discussed.

First, layouts. Recall that we use XML layouts to describe how we want our Activity to look. Here is an article that goes into further detail on the different types of layouts and the various attributes you can use: https://guides.codepath.com/android/Constructing-View-Layouts. You should try to use this to make your login and signup pages look prettier if you can -- this isn't homework you must complete for next class, but you will need to do this eventually.

Second, buttons. We talked briefly about onClick and how to use buttons, but there are many more things you can do with them: https://guides.codepath.com/android/Basic-Event-Listeners

You don't need to memorize every detail about the various types of layouts or know exactly how to use all the listeners, but you should know that they all exist in case you need to use them in the future.

Finally, ListViews. A common pattern in mobile apps is having a small scrollable list of content. For example, here is an example of a simple ListView with radio buttons, which has a static source of options. As a sidenote, we use a RecyclerView for larger datasets to decrease memory consumption, and we'll go into more detail on this in a later session. The visuals and data are broken down into three major components: the DataSource, Adapter, and Adapter View. The DataSource holds the "raw" data in the form of Java objects. For example, take this JSON file:

{  
   "status":0,
   "users":[  
      {  
         "id":1,
         "name":"Mattori"
      },
      {  
         "id":2,
         "name":"Larry"
      }
   ]
}

If you are unfamiliar with JSON you can read up on it here (although skip the Javascript part), but it's a very straightforward data format. Objects are contained in {}. Objects must use pairs of names or keys, which are strings, with values. A value can be a string, number, boolean, object, null, or an array. Arrays are contained in [] and use commas to separate values.

The above JSON object has a status of 0 and an array of users. Each user has an id and a name. Only two users are defined: Mattori with id 1 and Larry with id 2. Typically, we would parse the JSON and see that since the status is 0 then there were no issues retrieving the data. Then, we would take the users array and pass that as a DataSource, possibly as an ArrayList.

An Adapter is, generally, something that bridges between the data and the View. One major application is with lists of data since you can then define a View for each item in the list. For example, we might not want to actually show what each user's id is but we would probably want to show the name in big text.

The Adapter View is the way we want to represent this set of data. The basic variants are a ListView and a GridView.

Take a look through this article on ArrayAdapters and ListViews: https://guides.codepath.com/android/Using-an-ArrayAdapter-with-ListView. The important sections are Using a Custom ArrayAdapter, Attaching the Adapter to a ListView, Populating Data into ListView (although don't worry too much about parsing JSON yet), and Attaching Event Handlers Within Adapter. Everything else is useful but extra.

We will go into further detail on what exactly you are making and what the time frame is next class.

# 11/9/16

TOC

### Prerequisites

So far, we've learned about Activities and Intents. Let's tie it all up!

For this assignment, you will need to be able to produce new Activities with custom content, respond to buttons, read data from widgets, and start new Activities with extra data/parameters. If you are unclear on any of this, look at previous lessons and the Android API Reference.

### Homework

Implement an application that features a login screen and subsequent greeting screen based off of their login. The login screen should ask for their username and password and then pass that information on to the greeting screen, which can simply greet the user by their username (here's a hint: look into EditText). Once you complete that, try to add a registration feature. When the user tries to login with an invalid combination they should get a response -- a Toast works.

I realize there is a login Activity template in Android Studio; do not use it. Not only does it defeat the purpose of learning, but it also contains a lot of junk and unnecessary content. It is only one of many different ways to make a login screen for Android.

This is going to be a bit different than what we've done in the past. This is a real project that you will be building upon in the future, and it will culminate in a complete, usable app. You don't need to worry about making it look pretty yet, but if you have extra time and want to play around feel free to add some polish or bonus features!

### Mockups

When you are developing an app for AppDev, you will be given mockups from designers of what the app should look like. This includes all screens and panels, including multiple mockups for side panels and settings. Here are a couple mockups for AppDev's Publications app for SPARC, which is still a work in progress. You can use them as a model for your own login screens. The only caveat is that you should not try to duplicate the logo.

Splash screen

Sign up screen


# 11/2/16

TOC

### Prerequisites

At this point, you should understand classes vs layouts, how to add and interact with widgets (like a Button), and how to respond to events (like on click events). If you are still confused about any of these, please refer to the 10/12 wiki page. Here are a few brief summaries:

Classes vs Layouts

All of your application logic should live in your Java classes, and all (most) of your application visuals should live in your XML layouts. Logic should not go in XML because it's very hard to express anything complex with XML, outside of some simple behavior like on click, which you should generally avoid in favor of Java anyway. Visuals should not go in Java because it's very hard to express hierarchies and attributes like positioning with Java.

Adding Widgets and Events

Widgets should be added via your layouts and given arguably the most important attribute: the id. You should put as much detail as you can in the XML representation of your widgets. Here is a concrete example of adding a Button widget:

<Button
    android:text="Hello, World!"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button" />

As a reminder, this Button does not actually do anything yet, other than the default animation for when you tap it. The way that you give it behavior is by referencing it in Java and then giving it on-something events. The easiest one to see is the on click event, so here's an example of listening to that and making a Toast:

// in your onCreate function
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Toast.makeText(getApplicationContext(), "Clicky clicky", Toast.LENGTH_LONG).show();
    }
});
### Intents

Now that we're caught up, we can move onto the meat of today: Intents! Intents are the way that we communicate between Activities in Android. You can think of it as our intent to go to something else; for now, it's just within our app with an explicit intent, but down the line we can start Intents that cause other apps to open with an implicit intent. An explicit intent looks something like this:

final Intent popupIntent = new Intent(this, PopupActivity.class);
startActivity(popupIntent);

Note that the first parameter, this, is supposed to be a Context. An Activity is a Context because it implements Context, but if you're writing an OnClickListener then you won't be able to just write this. The easiest way out of this dilemma is to write getApplicationContext() there instead; we'll get to Context at a later time. This code specifies a specific component, the PopupActivity, to be opened. That class is not defined yet, so let's throw together a quick little Activity to demonstrate that our Intent worked. Here's the Java code, which should go in PopupActivity.java:

public class PopupActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_popup);
    }
}

...and here's the corresponding XML layout, which should go in activity_popup.xml in res/layouts. Note that the only significant thing here is a TextView, just to indicate the Activity even appeared.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_popup"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="edu.appdev.android.androidtraining.PopupActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New activity!"
        android:id="@+id/text" />

</RelativeLayout>

The last step is to put the new Intent code in the correct place: in the on click method of the button in the MainActivity. Here's the full MainActivity.java code:

// whatever your package setup is

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final Intent popupIntent = new Intent(getApplicationContext(), PopupActivity.class);
                startActivity(popupIntent);
            }
        });
    }
}
### Sending Data

Sometimes you want to actually send some information to the new Activity. In this case, let's send an arbitrary constant String. This variable can be sourced from anywhere; in fact, you can put any type as long as it is a primitive (ie. int, boolean) or implements the Parcelable interface. But for simplicity, let's just send a name for the PopupActivity to display. Update the Intent creation code to look like this:

final Intent popupIntent = new Intent(getApplicationContext(), PopupActivity.class);
popupIntent.putExtra(PopupActivity.EXTRA_NAME, "Larry");
startActivity(popupIntent);

Notice that PopupActivity.EXTRA_NAME is referenced here. The first parameter of the putExtra function is always some String to identify the second parameter. It should typically be a constant since it never changes and is only used when making Intents for the PopupActivity. You get partial credit for thinking about the strings.xml XML file in res/values; yes, we generally want to put String literals there, but usually only Strings that are seen by the user. This String constant is just an internal identifier, much like a regular variable name, so it's sufficient to just make it a Java constant instead of an XML constant. You can make the PopupActivity.EXTRA_NAME constant by adding the following line:

// ...
public class PopupActivity extends AppCompatActivity {
    public static final String EXTRA_NAME = "extra_name"; // add this line inside PopupActivity but outside of any methods

    @Override
    protected void onCreate(Bundle savedInstanceState) {
// ...

Now we just need to update the onCreate method to actually use this extra data. We can get the String extra with the following line:

String name = getIntent().getStringExtra(EXTRA_NAME);

If we were to send an extra of a non-String type, we need to use a slightly different method. There are a number of default methods defined for getting extras, such as getIntExtra and getStringArrayExtra. To get an extra that implements Parcelable -- again, you need to do this if you're getting a non-String non-primitive extra -- use the getParcelableExtra. The next line we need is to make the String we got appear on screen. We can do that by getting the existing TextView and updating its text, like so:

TextView txt = (TextView) findViewById(R.id.text);
txt.setText("Hi " + name);

If you put both of these lines at the bottom of the onCreate method of PopupActivity, run the app, and then tap the button you should get your new PopupActivity with some text that says "Hi Larry"!

Conclusion

As always, if any of this was confusing, please do not hesitate to contact Larry or Mattori. Intents can get somewhat complicated, but for now things are fairly simple and straightforward. You will use this content in the upcoming homework, so make sure you understand it.


# 10/12/16

Have any questions? Feel free to ask Larry Asante or Mattori Birnbaum, via email or in person.

TOC

### Android Studio

At this point, you should have Android Studio installed and configured. You will also need the necessary components from the Android SDK Manager, with preferably the latest version:

  • Tools
    • Android SDK Tools
    • Android SDK Platform-tools
    • Android SDK Build-tools (note that new revisions are listed as a separate download, so when you want to update you'll need to install the new one rather than simply patch the existing download)
  • Android 7.0 (API 24)
    • SDK Platform
  • Android 4.0.3 (API 15)
    • SDK Platform
  • Extras
    • Android Support Repository

We created new Android projects for our HelloWorld app last time, but here are the important options if you want to make a new one:

  • Application name can be whatever. In this case, it's HelloWorld.
  • Company Domain is like the URL for our "company." Typically, we use android.appdev.edu.
  • Your target Minimum SDK should be API 15. This is the minimum version of Android that can run our app. As of this writing, 97.4% of devices are on that API level or higher, which is good enough for us.
  • You can add an Empty Activity to your project. All these options just generate different amounts of "boilerplate" code, which is just the basic content required to get something running.
### Github

You should go to Github and create an account if you don't already have one. You probably won't need it for a bit for this class, but if you plan on doing any programming in the real world you will need a Github account, and you will definitely need one for AppDev. If you're not comfortable with git yet, you can review with Github's git tutorial.

We'll go through the functionality of Github when it's necessary, although you can feel free to look around or even take a look at AppDev's Github account.

### Emulator/Genymotion (optional)

If you don't have an Android device or want to be able to test purely on your computer, it is advisable to use Genymotion instead of the default Android emulator as it's significantly faster and a bit nicer to use. For some reason, Genymotion tries to imply that the only option is to pay for use of its emulator, but if you go to the "Fun Zone" you can download the personal edition. You need to create an account, and the emulator technically does not have all the features unlocked, but it's still very effective for development.

There is a convenient Genymotion plugin for Android Studio, which allows you to start and manage our emulator devices.

### HelloWorld App #### Classes vs Layouts

An important concept in Android, which also happens to transfer over to iOS and indeed many other GUI-related frameworks, is the separation of application logic and views. The idea is that visuals can be better represented in a more specialized language, which in the case of Android is XML with a variety of widgets already defined (and more can be added easily by developers), while more complicated tasks like reacting to buttons and doing network communication can be handled in code, which in the case of Android is Java. These are not 100% strict rules as you can dynamically create visual widgets in code (for example, if you wanted to create an image when a button is pressed) and can set up some larger components, like scrollable lists, in XML, but generally you want to keep things separated for clarity.

A good general rule, both in Android and in any at least moderately sized project, is to keep things clear, even if that means you have to write a bit more. For example, in Java the @Override annotation, used in polymorphism, is not actually required, but it's helpful to clarify to yourself and anyone else that wants to read your code that that method is overriding one of the parent type's methods (if this is confusing, look at the Polymorphism section below).

Just like how we use variables and give them arbitrary names in Java (ie. Button btn = ...), we give layout widgets ids in order to identify and do stuff with them. Take, for example, the following TextView:

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello, World!"
    android:id="@+id/text" />

This TextView has both a width and height of wrap_content, meaning it will only be as big as the text it contains; its text value is Hello, World!, so that's what it will show; and its id is text. The id attribute is arguably the most important attribute, more than the layout or text ones, because it's the only way we can access it from our code. Everything else about virtually any widget can be configured through code (it's a bit uglier, but it can be done), but it's only possible by using the id.

The way that we take a widget defined in a layout and use it in code is as follows:

TextView txt = (TextView) findViewById(R.id.text);

The important parts to note here are:

  • We want this variable to be a TextView
  • There needs to be a cast (TextView) to clarify what type of View we want (more on that in Polymorphism)
  • The findViewById method is how we get anything from a layout
  • The id attribute of the TextView we created is used in Java through R.id

The R class is a special class generated by the Android compiler that you should never try to mess with or risk breaking major parts of your project. It is a representation of any resource in your project that you give an id to. More on this in the next section, Resources Overview.

Now, let's create a widget that actually has some interesting functionality to it, like a Button! Here's the layout:

<Button
    android:text="Click me!"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:id="@+id/button" />

Don't worry about the layout-related attributes for now. The most important part is, again, the id. Right now, if you were to put this Button in your main activity's layout and run it there would be a corresponding button that allows you to click it... and that's it. Usually, when we use buttons we want them to actually do something, so let's add an on click event:

Button btn = (Button) findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Toast.makeText(getApplicationContext(), "hello", Toast.LENGTH_LONG).show();
    }
});

As before, this gets the Button we defined in the layout by its id, button. It then sets a listener for the click event, which triggers whenever the element is clicked. A bunch of that is boilerplate code that you don't need to worry about right now, so focus more on the fact that we are setting it so the code in the onClick function runs when the button is clicked. More on event-driven programming in Event-driven Programming Overview.

If this were a simple Java program, we would probably use System.out.println("hello"); to confirm that the onClick function was actually running, and then check the console to see if it actually appears. Unfortunately, since this is an Android application and the console tends to have a ton of informative but irrelevant output, we need something easier to spot. A typical replacement for sysout in Android is to use a Toast, which is just a little textual popup.

You can make a Toast by calling the makeText method, which takes a Context, CharSequence (or the id of a resource string, see Resources Overview), and an int. For now, just call getApplicationContext() for the first argument; you'll get to Contexts at a later time. A CharSequence is a more generic version of a String, so we can just put in a String literal for the second argument. The last argument is, importantly, not whatever time you want! It must be either Toast.LENGTH_LONG or Toast.LENGTH_SHORT! Do not make the same mistake I have made countless times.

Also take note of the show() method. If you do not call that method nothing will appear. Thankfully, Android Studio will complain to you if it's missing, but it's another extremely common mistake.

That's it for the meat of the training session. Everything else is abstract concepts that will certainly be helpful to know but aren't necessary to get the app running.

#### Resources Overview

Similar to how the visual layout of our Android app should be separate from the logic, certain values/constants should also be defined as resources rather than in the code. The main resource types are:

  • layouts
  • drawables/mipmaps (ie. images)
  • colors
  • dimensions
  • strings
  • styles

Dimension and string resources exist for a simple reason: it is very easy to lose track of your constants if you have them defined in every individual class/layout. For example, say you have a version string, 1.0, that you use in a couple places. Perhaps you need it for the splash screen, the about screen, and it's sent in bug reports. However, the bug report feature is not really updated all that much since it works and doesn't need anything new. If you make some changes and update the version string to 1.1, without a string resource you need to manually find every time you use the version string and update it yourself. In a large application, you might forget where everything is, especially if there are other variables that have similar purposes or names. A quick solution is to create a class that holds all your constants, but instead of doing that we just use another XML file. Here's an example strings.xml file:

<resources>
    <string name="app_name">AndroidTraining</string>
    <string name="button_txt">Click me!</string>
    <string name="toast_txt">Hello</string>
</resources>

Now that we have these string resources, if we want to change the text in our button we just need to hook it up and then look in the strings.xml file. To set up our button to use string resources we make the following change:

<Button
    android:text="@string/button_txt"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:id="@+id/button" />

Note how the android:text attribute's value now starts with @string/ and ends with the name of the string resource. Without the @string/ part, it interprets the text as how you literally want it to appear without looking up any variables. If we want to change the Toast text, we make this change:

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Toast.makeText(getApplicationContext(), R.string.toast_txt, Toast.LENGTH_LONG).show();
    }
});

Again, note the change of the second argument from hello to R.string.toast_txt. Just like when we were referencing the Button by its id in findViewById above, we use R.string and then the name of the string resource.

It may not be obvious, but these values in R.string or R.id or anything else you use off of R are ints. This is related to the internals of Android and is a bit too low level to really worry about, but it's worth pointing out that they are ints and not some special String or View class.

You'll use resources more in the future, but the two most commonly used resources are layouts and strings, so make sure you understand how to create and reference them.

#### Event-driven Programming Overview

The basic idea of event-driven programming is that when certain events occur you want something to happen. Those events in Android are usually related to when something is clicked or some other user input, but there are many other cases where you want to pay attention to non-graphical events.

The Android terminology for something that listens for events is a Listener. In the examples above, we implemented a OnClickListener, which unsurprisingly listens for clicks. The first and only parameter for a Button's setOnClickListener method is a View.OnClickListener. The details of this are covered in Polymorphism, but the important part is the public void onClick(View view) method. This is how we signify that we want some code to run in response to the click event. When the Button is clicked, that method is run, and we can have it do anything from showing a Toast to running further complex code.

That's the gist of event-driven programming. It's best taught by example, so it'll be easier to understand once we use it more later on.

#### Polymorphism

Polymorphism is a fancy word for a really powerful but potentially confusing concept. Java's object-oriented programming design lends itself well to polymorphism, which is likely one of the main reasons why it was chosen to run Android.

There are two types of polymorphism: sub-type and parametric/generic. Generic polymorphism will be used later, but sub-type polymorphism is used right off the bat in Android. The idea is that a basic or "parent" class definition has sub- or child-types that extend the parent's functionality. Again, this is best described using examples:

public class Animal {
    String color;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void makeSound() {
        System.out.println("rawr");
    }
}

We have defined an Animal as a thing that has a color, which is a String, can tell your its color (getColor), can change its color (setColor), and can output its sound to the console (makeSound). Now, let's define a sub-type of Animal:

public class Dog extends Animal {
    String name;

    @Override
    public void makeSound() {
        System.out.println("woof");
    }
}

Now we have a Dog, which is a type of Animal. This is denoted by the extends keyword. If we create a Dog and use its color field, which is inheritted from its parent Animal, it works the same as its name field:

Dog rover = new Dog();
rover.color = "brown"; // could also be rover.setColor("brown");
System.out.println(rover.color); // => "brown"

rover.name = "Rover";
System.out.println(rover.name); // => "Rover"

As for the makeSound method, since Dog overrides the definition in Animal the functionality is completely replaced:

rover.makeSound(); // => "woof"
// this does not print "rawr"!

Note that the @Override attribute is not strictly necessary. It's simply there to remind us and anyone else that wants to read this code that the makeSound method does in fact override the version defined in Animal. On the other hand, if we create another type of Animal that does make a "rawr" sound:

public class Lion {
    // ...Fields

    // ...Methods
}

If we call the makeSound method on a Lion, it will use the version it inheritted from Animal:

Lion tiger = new Lion();
lion.makeSound(); // => "rawr"

Polymorphism is used a ton in Android, and sometimes it gets very complicated. We'll get into that later, but for now a simple example is looking at how the findViewById method works. This method actually returns a View, but in the examples above we needed to get a Button. The reason this works is because Button is a sub-type of View (more generally, every visual widget extends View). By adding a cast, (Button), we're telling the compiler that we know findViewById doesn't actually return a Button but to instead trust us when we say it's a Button.

A slightly more complex example is the use of View.OnClickListener. Here's the snippet where we give the button an on click listener:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // ...
    }
});

We create a new instance of the View.OnClickListener class, and then follow it with curly brackets ({}) and a method. This is called an anonymous class in Java, and it's equivalent to the following code:

//in ButtonToastClickListener.java

// ...imports

public class ButtonToastClickListener implements View.OnClickListener {
    @Override
    public void onClick(View view) {
        // ...
    }
}

// in MainActivity.java, after finding the button
button.setOnClickListener(new ButtonToastClickListener());

There is one new thing here: the implements keyword. In Java, in addition to classes you can make interfaces. They are exactly the same except none of the methods are defined (ie. public void onClick(View view);). Otherwise, these two code snippets have the same effect.

Conclusion

If any of this was confusing, please do not hesitate to contact Larry or Mattori. These are some rather big concepts, and you're likely to use them even if you don't end up programming in Android. Each section would probably take a couple full days in an actual class with much simpler Java.

About

Summary of topics discussed during Grinnell AppDev Android training session

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages