Fall 2019 HackPrinceton, Saturday November 9th 11am. Duration ~1.5 hours + Q&A.
Workshop lead by Jessica Zheng Princeton '19.
Foreword
This workshop will walk participants through building a simple Android app that showcases Android fundamentals and programming practices. This will provide you with a solid foundation to build off of when you develop more complex applications that go beyond the scope of this workshop. Open to all levels of programmers, experience with Java recommended.
Q&A following the workshop is open for deeper questions about Android, questions about our experience navigating the hiring process, and my day to day life as a SWE at Google :) or come find me when I'm mentoring.
Tech Stack: Android, Android Studio, Firebase
Setup: Please install Android Studio. Download here. Please do this ASAP, preferably before the workshop. If you are using a physical Android phone, make sure that USB debugging is enabled under settings and developer options and you follow the setup instructions here.
Demo App
We will be creating a bare-bones rendition of Tiger Confessions 3.0! (Although it is really just a glorified to-do list that highlights some Android fundamentals). Video demo can be found here.
https://developer.android.com/studio
If you have a physical Android phone to test with, follow the setup instructions here. Otherwise, briefly read on how to use a Android Virtual Device (AVD) here.
Open Android Studio, start a new project with min SDK version 26. Choose "Empty Activity".
Name the project a cool name like "TigerConfessions". Click finish, and wait for the Gradle Sync and build to complete. When building and running the app, this is what you should get:
An Activity is a single, focused thing that the user can do. Almost all activities interact with the user, so the Activity class takes care of creating a window for you in which you can place your UI. In most cases, activities are often presented to the user as full-screen windows, so for now you can think of activities as "pages" in an application.
A View is the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for widgets, which are used to create interactive UI components (Button, TextView, ScrollView, EditText, RecyclerView etc). You can even create custom views. Views can encapsualte one another.
A Layout is how you organize your views within an Activity. A layout defines the structure for a user interface in your app, such as in an activity. All elements in the layout are built using a hierarchy of View and ViewGroup objects.
A ConstraintLayout
allows you to create large and complex layouts with a flat view hierarchy (no nested view groups), but it is more complex than what we need. We will use a LinearLayout instead. A LinearLayout is a view group that aligns all children in a single direction, vertically or horizontally.
- Change the ConstraintLayout to LinearLayout
- Add android:orientation="vertical" attribute in the LinearLayout. For a LinearLayout it is good practice to explicitly specify the layout direction with the
android:orientation
attribute.
Resulting activity_xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
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" />
</LinearLayout>
Resulting activity_xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context=".MainActivity">
</LinearLayout>
It is the convention in Android to place drawable resources into res/drawable folder. Here is the image I used. Save it as "tigercat.png".
Note that we set the src attribute to @drawable/tigercat so the ImageView can reference tigercat.png
Resulting activity_xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="180dp"
android:src="@drawable/tigercat"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
Your application should now look like this:
Step 3. Create Layouts for User Input and Confession Posts [link]
An Android ScrollView is a ViewGroup that allows the view hierarchy placed within it to be scrolled. Scroll view may have only one direct child placed within it.
However, we want multiple views within the ScrollView, so we can solve this issue by making the single child a LinearLayout.
- Create a Linear Layout to hold the user input UI elements (EditText and Button). Note that we set the orientation attribute to be "horizontal" as the elements will be laid out horizontally.
- Add ScrollView containing a LinearLayout to hold the confession posts. Note that (1) the LinearLayout orientation attribute is set to "vertical." (2) we give this linear layout an id attribute: android:id="@+id/confession_list" -- this will allow us to reference this layout from our Java code later on.
Resulting activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:layout_width="wrap_content"
android:layout_height="180dp"
android:src="@drawable/tigercat"
android:layout_gravity="center_horizontal"/>
<!-- This linear layout will hold the EditText and Button.-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- Edit Text and Button will go here -->
</LinearLayout>
<!-- This ScrollView will hold a LinearLayout containing the confession posts. -->
<ScrollView
android:layout_height="match_parent"
android:layout_width="match_parent">
<!-- This LinearLayout will hold the confession posts. -->
<LinearLayout
android:id="@+id/confession_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
</LinearLayout>
</ScrollView>
</LinearLayout>
This should not result in any visual changes to the application.
Step 4. Add User Input UI Elements. [link]
An EditText is a user interface element for entering and modifying text.
The android:hint attribute is the greyed-out text displayed by default to the user that prompts them to input in the requested information. Here, we set it to "Enter your confession here..."
Note that we specify the inputType attribute to be "textMultiLine" -- this will allow us to enter in confessions spanning multiple lines.
Note we give this EditText an id attribute: android:id="@+id/confession_edit_text" -- this will allow us to reference this input field from our Java code later on.
<!-- This linear layout will hold the EditText and Button. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/confession_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:hint="Enter your confession here..."/>
</LinearLayout>
B. Download the "add" icon here and add it to the res/drawable resource folder.
The image should be named add.png
An ImageButton is a button with an image (instead of text) that can be pressed or clicked by the user.
Note we give this ImageButton an id attribute: android:id="@+id/confession_post_button" -- this will allow us to reference this input field from our Java code later on.
Note that we set the src attribute to @drawable/add so the ImageButton can reference add.png
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:orientation="horizontal">
<EditText
android:id="@+id/confession_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:hint="Enter your confession here..."/>
<ImageButton
android:id="@+id/confession_post_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add"/>
</LinearLayout>
Now the app should look like this:
In the screenshot above, we see that the EditText and ImageButton do not take up the entire width of the screen. And if they were to take up the entire screen, how much space should one element take up relative to the other?
We can solve both issues by using the layout_weight attribute that is available to the children inside of a LinearLayout -- this tells us how much space each element should take up relative to the others in the same LinearLayout.
Here, we give confession_edit_text a weight of 5, and confession_post_button a weight of 1. This means that on the screen, the confession_edit_text will take up 5 times as much horizontal width as confession_post_button.
<!-- This linear layout will hold the EditText and Button. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/confession_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:hint="Enter your confession here..."
android:layout_weight="5"/>
<ImageButton
android:id="@+id/confession_post_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add"
android:layout_weight="1"/>
</LinearLayout>
Step 5. Finishing touches on the UI. [link]
The file colors.xml defines the values of color variables used in the application. You'll notice that colorPrimary controls the background color of the App header (which currently is a Turquoise blue).
Believe it or not, there is officially a color called "Princeton Orange" with a hex value of #ff8f00.
Let's also add in a golden yellow called "Selective Yellow" to serve as the accent color.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#ff8f00</color> <!-- princeton orange -->
<color name="colorPrimaryDark">#00574B</color>
<color name="colorAccent">#ffba00</color> <!-- golden yellow -->
</resources>
<ImageButton
android:id="@+id/confession_post_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add"
android:background="@color/colorAccent"
android:layout_weight="1"/>
We set android:layout_marginHorizontal="10dp" and android:layout_marginTop="5dp". This will add a 10dp border to the left and right side of the layout, and 5dp at the top of the layout, which looks better visually.
<!-- This linear layout will hold the EditText and Button. -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<EditText
android:id="@+id/confession_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:hint="Enter your confession here..."
android:layout_weight="5"/>
<ImageButton
android:id="@+id/confession_post_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add"
android:background="@color/colorAccent"
android:layout_weight="1"/>
</LinearLayout>
This will add a 10dp border to the left and right side of the elements inside of the layout, which looks better visually.
<!-- This ScrollView will hold a LinearLayout containing the confession posts. -->
<ScrollView
android:layout_height="match_parent"
android:layout_width="match_parent">
<LinearLayout
android:id="@+id/confession_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="10dp"
android:orientation="vertical"
tools:context=".MainActivity">
</LinearLayout>
</ScrollView>
After these all changes, your application should look like this:
Step 6. Link UI Elements to Java Code and Allow for Confessions to be Posted [link]
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.util.TypedValue;
import android.view.View.OnClickListener;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
Create some instance variables for the EditText, ImageButton, and LinearLayout to hold the confessions.
In onCreate() within MainActivity -- this is called at the beginning when the Activity is initialized (read more about the Android Lifecycle here) -- initialize these variables. We use findViewById() and the android:id attribute we set above to link these UI elements in the XML to our Java instance variables in onCreate().
In MainActivity.java:
private EditText confessionEditText;
private ImageButton confessionPostButton;
private LinearLayout confessionList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.confessionEditText = findViewById(R.id.confession_edit_text);
this.confessionPostButton = findViewById(R.id.confession_post_button);
this.confessionList = findViewById(R.id.confession_list);
}
In activity_main.xml, we set the attribute android:onClick to "postConfession":
<ImageButton
android:id="@+id/confession_post_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/add"
android:layout_weight="1"
android:background="@color/colorAccent"
android:onClick="postConfession"/>
Create a method postConfession() that accepts a view parameter by convention, and is of type void. We do several things in postConfession():
- Retrieve the text string inside of confessionEditText via calling getText() and toString()
- Clearing the EditText by calling setText("")
- Creating a new TextView to hold our confession, via a method createNewConfession(String confessionText) that we will implement in the next step. A TextView is a user interface element that displays text to the user.
- Add this newly created TextView to the confessionsList so it will appear in our list of confessions.
public void postConfession(View view) {
String confessionText = confessionEditText.getText().toString();
confessionEditText.setText("");
TextView newConfession = createNewConfession(confessionText);
confessionList.addView(newConfession);
}
Add a new resource xml file under res/drawable called rectangle.xml, and place the following inside. This will create an orange border around every confession.
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@android:color/white" />
<stroke android:width="1dip" android:color="@color/colorPrimary"/>
</shape>
Create method createNewConfession() that accepts a String confessionText as an input parameter, and returns a TextView. We do several things in createNewConfession():
- Input validation -- confessionText should not be null. This is a good programming practice.
- Create a new TextView called confessionTextView.
- Set the text inside the confessionTextView to confessionText.
- Use a LinearLayout.LayoutParam object to set the width, height, and margins of the TextView.
- Give the TextView a 10dp padding on all sides/
- Set the TextView text size to 18dp.
- Set confessionTextView background to R.drawable.rectangle -- we added this resource in step D.
- Return the newly created confessionTextView
private TextView createNewConfession(String confessionText) {
if (confessionText == null) {
throw new NullPointerException("Confession should not be null!");
}
TextView confessionTextView = new TextView(this);
confessionTextView.setText(confessionText);
LinearLayout.LayoutParams params =
new LinearLayout.LayoutParams(
/* width= */ LayoutParams.MATCH_PARENT,
/* height= */ LayoutParams.WRAP_CONTENT);
params.setMargins(10,10,10,10);
confessionTextView.setLayoutParams(params);
confessionTextView.setPadding(10, 10, 10, 10);
confessionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
confessionTextView.setBackground(getResources().getDrawable(R.drawable.rectangle));
return confessionTextView;
}
Now if you run the application, you should be able to post confessions! :)
Step 7. Dialog and Share Intents [link]
As shown in the demo video, we want each confession to be clickable. When the user taps the confession, a Dialog will pop up with the confession text and give the user the option to (1) share the confession text, or (2) exit the dialog.
We create a click listener class for the confession text views that implements [View.OnClickListener]. (https://developer.android.com/reference/android/view/View.OnClickListener). We store the confessionText string as a private instance variable due to access permissions (if you keep it as a local variable, you will probably see that you run into access issues).
In the onClick() method we do the following:
- Check to make sure that the view is of type TextView, since we typecast it to a TextView. In the event that the view for some reason is NOT a TextView, onClick simply does nothing. This is an optional, but good form of input validation to have.
- Obtain and set the confessionText from the confessionTextView via getText() and toString()
- Create a new AlertDialog.Builder and set its message to confessionText.
- Obtain the dialog by calling builder.create();
- Show the dialog.
private class ConfessionsOnClickListener implements OnClickListener {
private AlertDialog.Builder builder;
private String confessionText;
@Override
public void onClick(View view) {
if (!(view instanceof TextView)) {
return;
}
TextView confessionTextView = (TextView) view;
confessionText = confessionTextView.getText().toString();
builder = new AlertDialog.Builder(view.getContext());
builder.setMessage(confessionText) .setTitle("Confession");
AlertDialog dialog = builder.create();
dialog.show();
}
}
Right now, the dialog will appear and show the confession, but there will not yet be buttons that give us a set of actions to choose from. Let's add those in. First, let's add in the capability to share messages. To do so, we'll need to use something called an Intent.
An Intent is an abstract description of an operation to be performed. An Intent provides a facility for performing late runtime binding between the code in different applications. Its most significant use is in the launching of activities, where it can be thought of as the glue between activities. It is basically a passive data structure holding an abstract description of an action to be performed.
In our onClick() before we create the dialog using the builder we do the following:
- We call builder.setPositiveButton(), where the first parameter is the label of the button "Share Confession", and the second parameter is a new DialogInterface.OnClickListener.
- We start to fill in the onClick() method of the new DialogInterface.OnClickListener by creating a new Intent object named something such as sharingIntent. This intent is of type ACTION_SEND. There are various Intent types such as Email, Uri Request, Location Request etc.
- Set the Intent type to "text/plain"
- By convention, we need to include a subject EXTRA_SUBJECT via putExtra(). Let's include "Subject Here"
- Next, we include confessionText via putExtra() under the EXTRA_TEXT category.
- Finally, we use startActivity() that will launch the Intent. The text prompt we include is the string "Share Using."
builder.setPositiveButton("Share Confession", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
sharingIntent.setType("text/plain");
sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Subject Here");
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, confessionText);
startActivity(Intent.createChooser(sharingIntent, "Share Using"));
}
});
We use builder.setNegativeButton() to create an onClick() listener that dismisses the dialog.
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
In createNewConfession() before returning confessionTextView, make the textView clickable and set the onClickListener to ConfessionsOnClickListener.
confessionTextView.setClickable(true);
confessionTextView.setOnClickListener(new ConfessionsOnClickListener());
The application should now look like this:
Finally, we are going to do some cleanup of the app.
In Android, it is convention to store all strings in the application as variables in a strings.xml file. This is good practice -- if you need to change a string, you can do so in this one place rather than everywhere that this string is used. Also, having the strings in strings.xml allows for additional features such as location translation and Talkback screenreader for accessibility features.
We create variables in strings.xml for each static string we used in the project. Afterwards, strings.xml should look like this:
<resources>
<string name="app_name">TigerConfessions</string>
<string name="enter_confession_text">Enter your confession here...</string>
<string name="confession_header">Confession</string>
<string name="share_confession">Share Confession</string>
<string name="subject_here">Subject Here</string>
<string name="share_using">Share Using</string>
<string name="cancel">Cancel</string>
</resources>
In the XML, remove direct mentions to the string itself and reference the variable instead. For instance, confession_edit_text should look like this:
<EditText
android:id="@+id/confession_edit_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:hint="@string/enter_confession_text"
android:layout_weight="5"/>
Similarly, in MainActivity.java remove direct mentions to the string itself and reference the variable instead. You will need to use getResources().getString(int resId), where resId is in the form of R.string.[id]. For example, instead of
builder.setMessage(confessionText).setTitle("Confession");
we write:
builder.setMessage(confessionText).setTitle(getResources().getString(R.string.confession_header));
For the complete set of changes, see the git commit.
While this doesn't result in visible changed to the end product, it helps to foster good Android programming habits :)
Step 1. Firebase in Android: Setting up the Database [link]
Go to Tools >> Firebase >> Realtime Database >> Save and Retrieve Data >> Follow Steps.
Create a new Firebase Project, and give it a name such as "TigerConfessions". Click on "Connect to Firebase" -- This will create a project on firebase.google.com).
Now go to firebase.google.com. In firebase.google.com click the blue "Go to Console" button at the top right corner. You'll see your application (whatever you named it as, such as ) under "All Firebase projects" from which you can access the developer console.
In the console under the Developer>>Database section on the left navigation sidebar, scroll and find "Or choose Realtime Database." The advantage of a realtime database is that it does not require you to use commits to explicitly save data.
Click on the blue "Create database" button. A menu will pop up. Choose "Start in test mode." In practice, this is a terrible idea since it would allow everyone who has access to your database reference will have read and write capabilities YIKES. For the ease of this tutorial, we will choose this option since a locked database requires authentication which involves some extra setup. Hit enable.
Now navigate back to Android Studio and click the button "Add the Realtime Database to your app", and click "Accept." This will add the necessary items to your build.gradle and app.gradle automatically. These are the libraries needed for Firebase.
This step should be already handled for you. Please signal me if you run into an error.
Step 2. Firebase in Android: Reading and Writing Data [link]
private DatabaseReference mDatabase;
and initialize it in onCreate() in MainActivity.java:
this.mDatabase = FirebaseDatabase.getInstance().getReference();
Let's say we want to store all of the confessions. In postConfession() we can create a section in our database called "posts" and assign confessionText to a child of the database "posts" section. For this, we need to have some sort of an identifying index. Here, I am using the number of Posts (numPosts) as the index.
Create an instance variable numPosts of type int, and initialize it in onCreate() to value 0.
// Use the numPosts as the identifying index in the database
mDatabase.child("posts").child(String.valueOf(numPosts)).setValue(confessionText);
// Increment numPosts so we have a unique identifier for each post
numPosts++;
The following is a manufactured scenario about how we would get updates from our realtime database.
Let's pretend you have a fully functioning app, and we want to update some EditText whenever there's an update on the database, in real time. This means that if the text field ref value changes from "Hello, World!" to "Hi Mom!", our EditText would pick up that change immediately and populate our EditText with that new value.
For this, we want to add some sort of an event listener to our database reference, this way we can be notified when the database value changes. Luckily, Firebase has a method for this (addValueEventListener). We'll also add a ValueEventListener which will give us some methods we can override to do our bidding 😈! The following is the change we want to add:
myRef.addValueEventListener(new ValueEventListener() {
...
});
Now we just need to use some methods (specifically onDataChange, and onCancelled) in order to specify what we do when the data changes, or we failed to read the data. Our code will finally look like the following:
myRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated. String value = dataSnapshot.getValue(String.class);
emailEditText.setText(value); // example of data flow
Log.d("MyRefDataChanged", "Value is: " + value);
}
@Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w("MyRefFailedToReadValue", "Failed to read value.", error.toException());
}
});
Inside of onDataChange, we get a snapshot of the data, and from which, we'll be getting the new string value that the reference contains.
If you run your app and check out the firebase, you can edit the message reference value and see the value change on your android app!
Here are a few ways you could test your development skills and take this app further...
- (Easy) Add a timestamp to when each confession was posted.
- (Easy) Right now the application looks fine in portrait mode, but not so great in landscape :( -- figure out how to do so! Hint: you will need to create some new layout resource files in a layout-land/ directory within res/ .
- (Medium) Allow persistant data via Shared Preferences!
- (Medium) Allow for the deletion of confessions.
- (Medium) Create a up/down voting system (like reddit?) and sort the list by popularity of votes.
- (Harder) Allow for replies and comments to confessions.
The codelab I recommend the most for learning about Android in-depth is the one provided by Google themselves: Android Developer Fundamentals. It is well-documented, maintained and kept-up-to-date, and even has a series of accompanying video lectures.
A select list of interesting tutorials:
- Using the Debugger
- Input Controls
- Menus and Pickers
- Clickable Images
- User Navigation
- Shared Preferences (Storing data for an app locally on a device)
- App Settings
- Notifications
- Cards and Colors
- Drawables, Styles, and Themes
- Implicit Intents (e.g. opening a link to a Webpage, Email, or Google Maps Location)
- Intents (passing data between screens within your application)