name: inverse layout: true class: center, middle, inverse --- # Event Handling III - Reacting to Events in Android Lauren Bricker CSE 340 Winter 23 --- name: normal layout: true class: --- [//]: # (Outline Slide) # Today's goals .left-column60[ Agenda - So far we've learned... - Model View Controller - Events (theory) and Events (in Android) - Callbacks (theory) and - Today - Input dispatch process ([finish](/courses/cse340/23wi/slides/wk05/event-delivery.html#31)) - Listeners (in Android) - Maybe Today, Maybe Friday... - Other Android Callbacks (Menus) ] .right-column40[ Administrivia - Download [Spot the Heron](https://gitlab.cs.washington.edu/cse340/exercises/cse340-spot-the-heron) and switch to the `click` branch (`git checkout click` before tomorrow - Accessibility (code and report/reflection) due Thur 2-Feb - Assignment 4: Menus will be ready by Friday ] --- ## Recall: Event Dispatch .left-column-half[ ![:img Picture of interactor hierarchy connected to an interface and a dotted line indicating application interface with do_action() replaced with an actionListener, 100%, width](img/event-delivery/callbacks3.png)] .right-column-half[ Callbacks handle *application* response to events - Allows application to update model - Can be implemented using toolkit provided listeners - Can implment custom listeners for best flexibility ] --- ## Recall: Events can represent user actions... Example: [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent) which inherits from [InputEvent](https://developer.android.com/reference/android/view/InputEvent) abstract class. --- ## Recall: Events can represent abstract concepts... .left-column30[ ![:img Android Activity Lifecycle,100%, width](img/event-delivery/activity_lifecycle.png) ] .right-column70[ Android events registered with the system - **Create**: Android is done creating the object - **Active**: in foreground of screen, and receives input - **Paused**: activity lost focus (is no longer displayed on screen) - **Stopped**: - Another activity has focus - Original activity remains in memory -- perfect place to save data & state - Often can be *killed* by Android OS if not resumed relatively quickly - **Inactive**: after an activity has been killed; or before it is launched - **Timer**: Animation ] --- # Recall: Relating this back to the phone .left-column[
graph LR ap[Application Program] hlt[High Level Tools] t[Toolkit] w[Window System] o[OS] h[Hardware] class ap,w,o,h,hlt,t yellow
] .right-column[ Application Program: - Views - **(M) Stores local model (am I pressed, etc)** - **(V) Draws itself on screen** - (C) Implements callbacks for user events (such as onTouch) - Whole Application - **(M) Stores application model (data/state of app)** - **(V) Sets up the interactor hierarchy** - (C) Implements callbacks for interface events (such as buttonPressed) ] --- # Recall: Relating this back to the phone .left-column[
graph LR ap[Application Program] hlt[High Level Tools] t[Toolkit] w[Window System] o[OS] h[Hardware] class ap,w,o,h,hlt,t yellow
] .right-column[ Application Program: - Views - (M) Stores local model (am I pressed, etc) - (V) Draws itself on screen - **(C) Implements callbacks for user events (such as onTouch)** - Whole Application - (M) Stores application model (data/state of app) - (V) Sets up the interactor hierarchy - **(C) Implements callbacks for interface events (such as buttonPressed)** ] --- # Android Callbacks - `onCreate` - called when the activity is first created - we've seen this in our Doodle, Layout, and Spot The Heron apps: ```java @Override protected void onCreate(Bundle savedInstanceState) { // We want to do any view initialization work here super.onCreate(savedInstanceState); // Load the XML representation of our layout into the view setContentView(R.layout.activity_main); // Any other work we need to do like adding views or // registering click listeners! } ``` --- # Other Android Callbacks - `onStart` - Called when activity is about to be visible to the user - Always gets called after `onCreate` or `onRestart` -- - `onResume` - Called when the activity will start interacting with a use - Always gets called after `onStart` - ... --- template: inverse ## Event Listeners ### Putting theory into practice ??? - Implementation strategy - Basic goal: notify something of a change --- # Callbacks in Android - At the time Android was created the toolkit developers have *no* idea how every app may want to respond to events - Listeners are an interface that acts as a _callback_ method -- - Recall interfaces specify behavior (but no data) that can be inherited. -- - Must be *registered with* a particular `View` - The toolkit architecture (implemented in `View`) then delivers events that arrive at *that View* to *those listeners* - i.e. once the `View` is interacted with by the user -- - A `View` can have listen for different types of interactions ??? What is an interface? --- # Standard Listener Interfaces The toolkit has pre-defined interfaces so apps or components can respond to events such as clicks or touches. .left-column30[ - These interfaces are implemented inside `View` as *inner classes* - More listed in the [API Documentation](https://developer.android.com/guide/topics/ui/ui-events.html) ] .right-column70[ ```java // User tapped a view public static interface View.OnClickListener { ... } // User long pressed on a View public static interface View.OnLongClickListener { ... } // State of view has changed // (e.g. user clicked outside a EditText input box) public static interface View.OnFocusChangeListener { ... } // user typed something public static interface View.OnKeyListener { ... } ``` ] --- # Implementing clicking Create your button Register an event listener with the button --- # Pressing something! .left-column50[ In general to respond to a click/press/touch 1. Create your interactor 2. Register an event listener with the interactor Examples here - Buttons that respond to `onClick` events - `Counter` Interactor (we created) that responds to `onTouch` events ] .right-column50[
![:youtube Example counter app running with 5 buttons at the bottom and a touch target at the top,GlJq-kTdJOM] ] .footnote[ [Sample code](https://gitlab.cs.washington.edu/cse340/exercises/counter) ] --- # Registering an Event Listener ~~Three~~ five ways to register an event listener: - Creating a separate class/file or an inner class - Creating an anonymous inner class - Implementing an Interface in your custom view/application - Creating an anonymous class as a parameter - Lambdas Examples of all of these are in the [Counter](https://gitlab.cs.washington.edu/cse340/exercises/counter) exercise Take a moment to clone that repo and explore the starter code focusing on `activity_main.xml` and `EventExampleActivity`. What are some of the things you notice? --- # Exploring the Counter Starter code Code for the [Counter](https://gitlab.cs.washington.edu/cse340/exercises/counter) exercise uses a helper function ```java /** Method to increment the count field */ private void incrementCount(View v) { TextView counter = findViewById(R.id.count); String textCount = counter.getText().toString(); int count = Integer.parseInt(textCount); counter.setText("" + ++count); ((TextView)findViewById(R.id.whichButton)).setText(((Button)v).getText()); } ``` --- ## Part 1: Create an (Inner) Class and use it ```java public class EventExampleActivity extends AppCompatActivity { // Approach 1 implementation: An inner class that implements onClickListener private class MyButtonListener implements View.OnClickListener { @Override public void onClick(View v) { incrementCount(v); } } protected void onCreate(Bundle savedInstanceState) { Button b1 = findViewById(R.id.button_one); if (b1 != null) { // TODO: part 1: set the onClickListener for button_one by // creating a new object of type MyButtonListener and // calling the setOnClickListener method with that new object. } } ``` --- ## Part 1: Create an (Inner) Class and use it (solution) ```java public class EventExampleActivity extends AppCompatActivity { // Approach 1 implementation: An inner class that implements onClickListener private class MyButtonListener implements View.OnClickListener { @Override public void onClick(View v) { incrementCount(v); } } protected void onCreate(Bundle savedInstanceState) { Button b1 = findViewById(R.id.button_one); if (b1 != null) { b1.setOnClickListener(mButtonClickListener); } } ``` --- ## Part 2: Use a named anonymous class ```java public class EventExampleActivity extends AppCompatActivity { // Approach 2 implementation: An anonymous class that implements // OnClickListener is stored in a variable View.OnClickListener mButtonClickListener = new View.OnClickListener() { @Override public void onClick(View v) { incrementCount(v); } }; protected void onCreate(Bundle savedInstanceState) { Button b2 = findViewById(R.id.button_two); if (b2 != null) { // TODO: part 2: set the onClickListener for button_two by using the // mButtonClickListener variable defined above } } ``` --- ## Part 2: Use a named anonymous class (solution) ```java public class EventExampleActivity extends AppCompatActivity { // Approach 2 implementation: An anonymous class that implements // OnClickListener is stored in a variable View.OnClickListener mButtonClickListener = new View.OnClickListener() { @Override public void onClick(View v) { incrementCount(v); } }; protected void onCreate(Bundle savedInstanceState) { Button b2 = findViewById(R.id.button_two); if (b2 != null) { b2.setOnClickListener(mButtonClickListener); } } ``` --- ## Digging deeper: Creating an Named Anonymous Class Let's take some time to parse this... ```java View.OnClickListener mButtonClickListener = new View.OnClickListener() { ``` - `View.OnClickListener` is the variable type ([Documentation](https://developer.android.com/reference/android/view/View.OnClickListener.html)) - a nested class in `View` - `new View.OnClickListener()` is the the anonymous object - The on method that you MUST implement (in order to create a new object) is `OnClick` which overrides the abstract method - We're assigning that new object to a variable name (`mButtonClickListener`) ```java public void OnClick(View v) { /* stuff in here does the work when a click is received! */ } ``` --- # Part 3: Implement the interface .left-column30[ Make the class implement the `View.OnClickListener` interface Override the `onClick` method Set the onClickListener to `this` ] .right-column70[ ```java public class EventExampleActivity extends AppCompatActivity /* TODO */ { ... protected void onCreate(Bundle savedInstanceState) { // Registering the listener in Approach 3 (using this class) Button b3 = findViewById(R.id.button_three); if (b3 != null) { // TODO: part 3: After you have made this class implement View.OnClickListener // (see the class header and below this method), set the // onClickListener for button_three to this } ... // Approach 3 implementation: implement the listener // in *this* class // TODO: Part 3: Write the onClick(View v) // method to increment the count ``` ] --- # Part 3: Implement the interface (solution) .left-column30[ Make the class implement the `View.OnClickListener` interface Override the `onClick` method Set the onClickListener to `this` ] .right-column70[ ```java public class EventExampleActivity extends AppCompatActivity implements View.OnClickListener { ... protected void onCreate(Bundle savedInstanceState) { // Registering the listener in Approach 3 (using this class) Button b3 = findViewById(R.id.button_three); if (b3 != null) { b3.setOnClickListener(this); } ... // Approach 3 implementation: implement the listener // in *this* class public void onClick(View v) { incrementCount(v); } ``` ] --- ## Part 4: Use an anonymous class ```java public class EventExampleActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { // Registering the listener in Approach 4 (using an anonymous // class as a parameter) Button b4 = findViewById(R.id.button_four); if (b4 != null) { // TODO: part 4: set the onClickListener for button_four by creating an // anonymous View.OnClickListener Class right here! } } ``` --- ## Part 4: Use an anonymous class (solution) ```java public class EventExampleActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { // Registering the listener in Approach 4 (using an anonymous // class as a parameter) Button b4 = findViewById(R.id.button_four); if (b4 != null) { b4.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { incrementCount(v); } }); } } ``` --- # Part 5: Use a lambda For very simple listeners you can create the and register the listener all at once with lamba syntax for Java 8 or higher. ```java public class EventExampleActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { // Registering the listener in Approach 5 (using a lambda) Button b5 = findViewById(R.id.button_five); if (b5 != null) { // TODO: part 5: set the onClickListener for button_five by // using a lambda to call incrementCount(v) directly. } } ``` --- # Part 5: Use a lambda (solution) For very simple listeners you can create the and register the listener all at once with lamba syntax for Java 8 or higher. ```java public class EventExampleActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { // Registering the listener in Approach 5 (using a lambda) Button b5 = findViewById(R.id.button_five); if (b5 != null) { b5.setOnClickListener((View v) -> incrementCount(v)); } } ``` --- # Other Listeners The toolkit has pre-defined interfaces so apps or components can respond to events such as clicks or touches. More are listed in the [API Documentation](https://developer.android.com/guide/topics/ui/ui-events.html) ```java // User tapped a view public static interface View.OnClickListener { ... } // User long pressed on a View public static interface View.OnLongClickListener { ... } // State of view has changed // (e.g. user clicked outside a EditText input box) public static interface View.OnFocusChangeListener { ... } // user typed something public static interface View.OnKeyListener { ... } ``` --- # Registering Multiple Listeners Can register more than one listener For example: ```java View v = new View(); v.setOnClickListener(...); v.setOnLongClickListener(...); ``` --- # Many Views, Same Listener (1/3) Event callbacks are passed the `View` as a parameter -- count: false We can reuse a listener for views that handle the same action (e.g. all 5 buttons could use the same class/method for the action) --- # Many Views, Same Listener (2/3) And we can handle different actions by checking the `View` or its `id`: ```java protected void onCreate(Bundle savedState) { Button b1 = (Button) findViewById(R.id.button_one); if (b1 != null) { b1.setOnClickListener(mButtonClickListener); } Button b2 = (Button) findViewById(R.id.button_two); if (b2 != null) { b2.setOnClickListener(mButtonClickListener); } } ``` --- # Many Views, Same Listener (3/3) ```java View.OnClickListener mButtonClickListener = new View.OnClickListener({ public void onClick(View v) { if (v == null) { return; } // Use the ID of the view to determine where the event originated from int viewId = v.getId(); switch (viewId) { case R.id.button_one: // First Button Log.d("Button Press", "First Button"); break; case R.id.button_two: // Second Button Log.d("Button Press", "Second Button"); break; default: // Someone else is using the listener! Log.d("Button Press", "Invalid Button!"); } } }); ``` --- # Marking an event as handled This ensures only one view gets it (we talk about the algorithm behind this later) Return `boolean` value that indicate the event has been handled (`true`) ```java /** * onLongClick - triggered when a view is long clicked * @param v - the view long pressed * @return - true if the callback consumed the long click, false otherwise. **/ boolean onLongClick (View v) { ... }; ``` .footnote[[API Documentation for Long Click](https://developer.android.com/reference/android/view/View.OnLongClickListener.html) ] --- exclude: true ## Pair Programming Exercise: Spot the Heron Now its your turn. - We're going to work in pairs (or breakout rooms of 2 people) - Asynchronous folks can work independently - Start by introducing yourselves. - At least one person should download [Spot the Heron](https://gitlab.cs.washington.edu/cse340/exercises/cse340-spot-the-heron) (`events` branch) - Driver/Navigator model - Person with the code should open it in Android Studio and share their screen (driver) - Person who is not sharing their screen should open up this slide deck and the [Counter](https://gitlab.cs.washington.edu/cse340/exercises/counter) app for reference - Implement the next/prev/slideshow buttons in Spot the Heron. Your work will be turned in on the [Ed lesson](https://edstem.org/us/courses/32031/lessons/29232/slides/167332) for today. --- # Other types of interactors What other types of interactors have you used in a Graphical User Interface (GUI)? ??? - Menus - Drop down list boxes - Scroll bars - Text Edit boxes - Radio buttons/Check boxes -- count: false - Menus - Drop down list boxes - Scroll bars - Text Edit boxes - Radio buttons/Check boxes --- # Menus Create the menu resource - Click on the **Resource Manager** tab and click on the vertical ... - Select Menu - If you have a menu already, it will show up in this pane - If you don't have a menu already, click the **+** - Name your menu and add your menu items --- # Menus Example: Counter ![:img Full screen window of the menu item resource in a split view with the code and visuals,100%,width](img/event-handling/menu-resource.png) --- # Menus Handle the callback (from the **system**) with an `onCreateOptionsMenu(Menu menu)` listener ```java @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_main, menu); return true; } ``` --- # Menus But we still need to handle the events generated from the **user** ```java public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id != R.id.dropdown_menu) { // if not the parent hamburger menu if (id == R.id.reset_button_count) { ... } else if (id == R.id.reset_click_count) { ... } else { ... } return true; // return true if we handled the event! } // Otherwise pass it off to the parent class for handling return super.onOptionsItemSelected(item); } ``` --- # Summary: Relating this back to the phone .left-column[
graph LR ap[Application Program] hlt[High Level Tools] t[Toolkit] w[Window System] o[OS] h[Hardware] class ap,w,o,h,hlt,t yellow
] .right-column[ Hardware level: electronics to sense circuits closing or movement - Difference between hardware (Event vs sampled) - Sensor based input OS: "Interrupts" that tell the Window system something happened - Logical device abstraction and types of devices ] --- # Summary: Relating this back to the phone .left-column[
graph LR ap[Application Program] hlt[High Level Tools] t[Toolkit] w[Window System] o[OS] h[Hardware] class ap,w,o,h,hlt,t yellow
] .right-column[ Windows system: Tells which window received the input Toolkit Architecture: - Receives events from windowing system - Packages up Events and determines which `Views` should get them - Calls Callbacks - Handles other aspects of MVC (like redraw) ] --- # Summary: Relating this back to the phone .left-column[
graph LR ap[Application Program] hlt[High Level Tools] t[Toolkit] w[Window System] o[OS] h[Hardware] class ap,w,o,h,hlt,t yellow
] .right-column[ Application Program: - Views - (M) Stores local model (am I pressed, etc) - (V) Draws itself on screen - (C) Implements callbacks for user events (such as onTouch) - Whole Application - (M) Stores application model (data/state of app) - (V) Sets up the interactor hierarchy - (C) Implements callbacks for interface events (such as buttonPressed) ] --- # End of Deck Thing to think about... How would you handle input in a circular component?