name: inverse layout: true class: center, middle, inverse --- # Event Handling III - Reacting to Events in Android Lauren Bricker CSE 340 Spring 2022 --- name: normal layout: true class: --- [//]: # (Outline Slide) # Today's goals .left-column60[ - So far we've learned... - Model View Controller - Events (theory) and Events (in Android) ([finish](/courses/cse340/22sp/slides/wk04/events.html#49)) - Today - Input dispatch process ([finish](/courses/cse340/22sp/slides/wk05/events-delivery.html#5)) - Callbacks (theory) and Listeners (in Android) - Listeners in Android - Maybe Today, Maybe Friday... - Other Android Callbacks (Menus) ] .right-column40[ Administrivia - Download [Spot the Heron v. 2.0](https://gitlab.cs.washington.edu/cse340/exercises/cse340-spot-the-heron-v-2.0) before tomorrow - Accessibility (code and report/reflection) due tomorrow - Assignment 4: Menus will be ready this evening (hopefully?) ] --- ## 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 in the [Counter](https://gitlab.cs.washington.edu/cse340/exercises/counter) exercise --- 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()); } ``` --- # Simplest approach: Implement the method .left-column[ - The current class implements the listener interface - Override the callback method ] .right-column[ ```java // Approach 3 implementation: implement the listener in // *this* class @Override public void onClick(View v) { incrementCount(v); } ``` ] --- # Then register the listener .left-column[ Registration typically done in onCreate() ] .right-column[ ```java // Approach 3 implementation: implement the listener in //*this* class @Override public void onClick(View v) { incrementCount(v); } // in onCreate... // Registering the listener in Approach 3 (using // this class) Button b3 = findViewById(R.id.button_three); if (b3 != null) { // Should always check for null b3.setOnClickListener(this); } ``` ] --- ## Preferred Approach: Create an Anonymous Inner Class ```java public class EventExampleActivity extends AppCompatActivity { // An anonymous inner class as a member variable in an Activity View.OnClickListener mButtonClickListener = new View.OnClickListener() { @Override public void onClick(View v) { // call the private method in EventExampleActivity incrementCount(v); } }; protected void onCreate(Bundle savedState) { Button b1 = findViewById(R.id.button_one); if (b1 != null) { b1.setOnClickListener(mButtonClickListener); } } } ``` ??? incrementCount is a private method Mention that mXxxx variables are private fields and this is a quick way to find all private fields when searching variables in code --- ## Digging deeper: Creating an Anonymous Inner 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 ```java public void OnClick(View v) { /* stuff in here does the work when a click is received! */ } ``` --- # For very simple listeners you can use a lambda .left-column[ Create the and register the listener all at once with lamba syntax ] .right-column[ ```java Button b5 = findViewById(R.id.button_five); if (b5 != null) { // Should always check for null b5.setOnClickListener((View v) -> incrementCount(v)); } ``` ] .right-column[ To use Lambdas you have to upgrade to Java 8. See these [instructions](https://developer.android.com/studio/write/java8-support) to do this. ] --- # Other Listeners .left-column30[ - The toolkit has pre-defined interfaces so apps or components can respond to events such as clicks or touches. - 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 { ... } ``` ] --- # 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) You can use the ID of the view to determine where the event originated from ```java View.OnClickListener mButtonClickListener = new View.OnClickListener({ public void onClick(View v) { if (v == null) { return; } 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 2.0](https://gitlab.cs.washington.edu/cse340/exercises/cse340-spottheheron) - 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/21053/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?