Deprecated: Creation of dynamic property wpdb::$categories is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$post2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Creation of dynamic property wpdb::$link2cat is deprecated in /var/www/twodee/blog/wp-includes/wp-db.php on line 668

Deprecated: Return type of WP_Theme::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 554

Deprecated: Return type of WP_Theme::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 595

Deprecated: Return type of WP_Theme::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 535

Deprecated: Return type of WP_Theme::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-theme.php on line 544

Deprecated: Return type of WP_REST_Request::offsetExists($offset) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 960

Deprecated: Return type of WP_REST_Request::offsetGet($offset) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 980

Deprecated: Return type of WP_REST_Request::offsetSet($offset, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 992

Deprecated: Return type of WP_REST_Request::offsetUnset($offset) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/rest-api/class-wp-rest-request.php on line 1003

Deprecated: Return type of WP_Block_List::current() should either be compatible with Iterator::current(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 151

Deprecated: Return type of WP_Block_List::next() should either be compatible with Iterator::next(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 175

Deprecated: Return type of WP_Block_List::key() should either be compatible with Iterator::key(): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 164

Deprecated: Return type of WP_Block_List::valid() should either be compatible with Iterator::valid(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 186

Deprecated: Return type of WP_Block_List::rewind() should either be compatible with Iterator::rewind(): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 138

Deprecated: Return type of WP_Block_List::offsetExists($index) should either be compatible with ArrayAccess::offsetExists(mixed $offset): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 75

Deprecated: Return type of WP_Block_List::offsetGet($index) should either be compatible with ArrayAccess::offsetGet(mixed $offset): mixed, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 89

Deprecated: Return type of WP_Block_List::offsetSet($index, $value) should either be compatible with ArrayAccess::offsetSet(mixed $offset, mixed $value): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 110

Deprecated: Return type of WP_Block_List::offsetUnset($index) should either be compatible with ArrayAccess::offsetUnset(mixed $offset): void, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 127

Deprecated: Return type of WP_Block_List::count() should either be compatible with Countable::count(): int, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /var/www/twodee/blog/wp-includes/class-wp-block-list.php on line 199

Deprecated: Creation of dynamic property Automattic\Jetpack\Connection\Manager::$error_handler is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-connection/src/class-manager.php on line 91

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Instagram_Gallery::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-instagram-gallery.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Mailchimp::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-mailchimp.php on line 17

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 21

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Tweetstorm_Gather::$is_wpcom is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-tweetstorm-gather.php on line 22

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Gutenberg_Available_Extensions::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/gutenberg-available-extensions.php on line 20

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 23

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Memberships::$wpcom_is_site_specific_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/memberships.php on line 24

Deprecated: Creation of dynamic property WPCOM_REST_API_V2_Endpoint_Subscribers::$wpcom_is_wpcom_only_endpoint is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/_inc/lib/core-api/wpcom-endpoints/subscribers.php on line 15

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property Automattic\Jetpack\Sync\Queue::$random_int is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/vendor/automattic/jetpack-sync/src/class-queue.php on line 38

Deprecated: Creation of dynamic property WPCOM_Widget_Goodreads::$shelves is deprecated in /var/www/twodee/blog/wp-content/plugins/jetpack/modules/widgets/goodreads.php on line 33

Deprecated: DateTime::__construct(): Passing null to parameter #1 ($datetime) of type string is deprecated in /var/www/twodee/blog/wp-includes/script-loader.php on line 348

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_classic_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 347

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 349

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$toolbar_block_side is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 350

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$panels_block is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 351

Deprecated: Creation of dynamic property Advanced_Editor_Tools::$used_block_buttons is deprecated in /var/www/twodee/blog/wp-content/plugins/tinymce-advanced/tinymce-advanced.php on line 354

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Block_Type::$plan_check is deprecated in /var/www/twodee/blog/wp-includes/class-wp-block-type.php on line 333

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198

Deprecated: Creation of dynamic property WP_Term::$object_id is deprecated in /var/www/twodee/blog/wp-includes/class-wp-term.php on line 198
» SENG 440: Lecture 5 – List Activity and Implicit Intents

teaching machines

SENG 440: Lecture 5 – List Activity and Implicit Intents

March 4, 2019 by . Filed under
Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383

Deprecated: Creation of dynamic property WP_Term::$cat_ID is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 378

Deprecated: Creation of dynamic property WP_Term::$category_count is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 379

Deprecated: Creation of dynamic property WP_Term::$category_description is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 380

Deprecated: Creation of dynamic property WP_Term::$cat_name is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 381

Deprecated: Creation of dynamic property WP_Term::$category_nicename is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 382

Deprecated: Creation of dynamic property WP_Term::$category_parent is deprecated in /var/www/twodee/blog/wp-includes/category.php on line 383
lectures, semester1-2019, seng440.

Dear students,

A major theme of mobile development (and the web) is services. Our apps tend not to be monolithic beasts that do everything themselves. Rather, they offload some of their work to other apps that have intentionally exposed as service providers. Today we’ll see Android’s Intent model for invoking services from other apps. We’ll also see a giant among UI widgets: the list view. Our learning context will be an app that provides a directory of the people in this class.

Before we forget, here’s your TODO list for next time:

Let’s start making our app, which we’ll call Connect440.

ListActivity

The first screen of our app will be a list of our names. Lists are a big deal in the mobile world. We certainly see them in desktop computing, too, but I’d wager that they are much more prevalent on mobile. Why is that? We deal with collections on both kinds of systems, but on desktop computers we tend to have to have more screen space. That room makes it easier to lay out and organize the content across two dimensions as we do with file icons. We can also more easily show hierarchies. But on mobile, the UI mechanics have largely converged around two primary actions: vertical swiping and tapping.

There is a ListView widget that we can embed in our layouts, but because this widget is so common, there’s a pre-configured Activity subclass named ListActivity that already has one baked in. Let’s create one of those.

class MainActivity : ListActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
  }
}

We don’t need to call setContentView because the superclass takes care of that.

Now it’s time to populate our list. The software developers that have gone before us provided a framework to guide us whenever we do UI programming. That pattern is called model-view-controller. The name lists off the three buckets that our code should fall into. Code that manages the state with no regard to its presentation is the model. Code that manages the presentation is the view. Roughly, both model and view should be reusable in other contexts and not tightly coupled to each other or the controller. The specialized glue that binds them together is the controller.

The ListView has already been written for us, so let’s write a model. Each entry will represent a peer or friend from this class, and we will store the many possible ways we might contact them:

class Friend(val name: String, val slackId: String, val home: String, val email: String, val phone: String) {
}

We hardcore an array of our friends into our activity like so:

private val friends = arrayOf<Friend>(
  Friend("Chris Johnson", "Eau Claire, WI", "foo@canterbury.ac.nz", "#########")
)

To bridge between our model and view, we need a controller, but that has also been written for us. The ArrayAdapter class will wrap around an array. It has a method getView that yields a TextView for each visible item in the list. We create and register the adapter in onCreate:

listAdapter = ArrayAdapter<Friend>(this, android.R.layout.simple_list_item_1, friends)

The middle parameter references one of the many prefabricated layouts that Android provides. These layouts seem to drop magically out of the sky. If you encounter one, visit the Android source to figure out what’s going on.

We could just as easily have written our own layout resource. To get it to work with ArrayAdapter, we’d need to give it the ID text1. The advantage of using the stock item is that it will look like the user expects.

Our list is now populated, and it’s time to handle taps on the names. Because ListActivity was born for this moment, we need only override a superclass method:

override fun onListItemClick(l: ListView?, v: View?, friendId: Int, id: Long) {
  Log.d("FOO", "$friendId")
}

AlertDialog

When a person’s name is tapped, let’s display a dialog that prompts the user for how they want to contact the tapped individual: by email, by telephone, by going to their house, and so on. Android provides a comprehensive dialog system via the AlertDialog class. It comes with an inner Builder class to help you set up your dialog. You can make it have one button, or two, or three. You can have it show a title and a message. Or you can have it show a list of options, which is what we want:

override fun onListItemClick(l: ListView?, v: View?, friendId: Int, id: Long) {
  val options = arrayOf("Map", "Email", "Text", "Call", "Slack")
  val builder = AlertDialog.Builder(this)
  builder.setTitle("Connect how?")
  builder.setItems(options) { _, optionId ->
    dispatchAction(optionId, friends[friendId])
  }
  builder.show()
}

fun dispatchAction(optionId: Int, friend: Friend) {
  when (optionId) {
    0 -> TODO("Map")
    1 -> TODO("Email")
    2 -> TODO("Text")
    3 -> TODO("Call")
    4 -> TODO("Slack")
  }
}

The lambda that we feed to setItems is expected to have two parameters, one for the dialog and one for the index of the selected option. We don’t care about the dialog, so we use an underscore in its place.

Intents

We have reached our limits now. We don’t know how to send an email. We don’t know the cell protocols to call someone. We don’t how to draw a map. But other apps do. The last piece of our app is figuring out how to ask other apps to do things on our app’s behalf. We use Android’s Intent class to express our request. Before we look at the technical side of it, I’d like to discuss the nomenclature a bit.

Some years ago I read an article that claimed that many communication failures happen because we communicate our positions rather than our interests. Instead of saying our interest, “I want to spend more time together,” we say, “Let’s take dance lessons.” Instead of saying our interest, “I’m nervous that we won’t finish the project in time,” we say, “I need you here on the weekends.” Our positions are overconstrained, which means they are more likely to be rejected by the listener. There may be ways to spend more time together and the complete the project than the particular position that we, the speaker, have taken.

The intents that we consider today are like these interests. We will only make a very vague request for a service, and any app on the system is eligible to provide that service. Intents generally have this form:

val intent = Intent(REQUEST_DESCRIPTION, uri)
intent.putExtra(KEY1, VALUE1)
intent.putExtra(KEY2, VALUE2)
startActivity(intent)

There are a number of builtin requestion descriptions. We will see just a couple today. Let’s start with sending an email. We use the request Intent.ACTION_SEND with an email address as an extra parameter:

val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_EMAIL, friend.email)
startActivity(intent)

The operating system will present us with a list of all possible apps that can provide this service of sending a message to an email address for us. That’s what make this Intent more like an interest than a position. We only announce our intent, which can be satisfied in many ways.

To view a location on a map, we use the Intent.ACTION_VIEW request, and we send along a uniform resource identifier (URI) that specifies the location. The URI can take several forms. To query for a location based on its name, we’d write:

geo:0,0?q=Christchurch, New Zealand

But the pioneers of the internet like Tim Berners-Lee tell us (in section 8.2.2 of RFC 1866) that query parameters need to be encoded according to the application/x-www-form-urlencoded standard, like this:

geo:0,0?q=Christchurch%2C%20New%20Zealand

Thankfully the Java standard library has some methods for dealing with URIs and encoding. We issue our intent to map a friend’s home like so:

val uri = Uri.parse("geo:0,0?q=${URLEncoder.encode(friend.home, "UTF-8")}")
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)

To text someone, we use the Intent.ACTION_SENDTO request. The Android documentation says this of SEND:

Activity Action: Deliver some data to someone else. Who the data is being delivered to is not specified; it is up to the receiver of this action to ask the user where the data should be sent.

And it says this of SENDTO:

Activity Action: Send a message to someone specified by the data.

Of course, we just sent an email to a specific someone above using SEND. The difference here is that the recipient is identified in the SMS URI:

val uri = Uri.parse("smsto:${friend.phone}")
val intent = Intent(Intent.ACTION_SENDTO, uri)
startActivity(intent);

To call someone, we may use the Intent.ACTION_DIAL request:

val uri = Uri.parse("tel:${friend.phone}")
val intent = Intent(Intent.ACTION_DIAL, uri)
startActivity(intent);

This pops open the dialer. But there’s also Intent.ACTION_CALL. This takes the extra step of dialing the number. But the Android folks thought automatically dialing a number from an app might lead to abuse. To unlock that feature, we need to do two things. First, we announce that our app needs that permission in the manifest element of the manifest:

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

There are several classes of permissions. For the normal permissions, we need only declare them in the manifest. The user will see these permissions in the Play Store. For the dangerous permissions, we must declare them in the manifest and dynamically prompt the user to give them the okay. We’ll drop in a check for and request the CALL_PHONE permission in onCreate:

override fun onCreate(savedInstanceState: Bundle?) {
  ...

  val dangers = arrayOf(Manifest.permission.CALL_PHONE)
  val ungranted = dangers.filter { checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED }
  requestPermissions(ungranted, 1)
}

For our last service request, let’s open up a direct message on Slack. The Slack API describes how we can form URIs that the Slack app can respond to. For a direct message, we use:

slack://user?team={TEAM_ID}&id={USER_ID}

Finding the team ID and user ID are not as straightforward as they could be, but once we have them, we can use the ACTION_VIEW request to trigger the app:

val uri = Uri.parse("slack://user?team=$teamId&id=${friend.slackId}")
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent);

In all these cases, we farm out the real work to other apps. They don’t communicate any result back to us. Next time, we’ll see how to get the apps to communicate results back to the caller. That’s enough for today, however. See you next time!

Sincerelya

P.S. It’s time for a haiku!

Newton had giants
Who were just people themselves
Stacked up pretty high