Extending the Android Observer and Observable Classes for Auto Updates

The beauty of using an object oriented language such as Java is the ability to extend other classes on your object. Two awesome example of this are the Java Observer and Observable classes. By extending the Observable class on your data object (model), you are able to assign Observers which listen for changes in said data model. When the data changes, the Observer is notified automatically and fires its update() method.

Refresh your Views in the update() Method

The update() method is an obvious place to put your code which refreshes the views impacted by the changes in data, this is where the linkage between the views and the data model occurs (usually in the Android Activity). The beauty of using the Observer and Observable classes is that the data model, views and controller (the Activity that updates the views) are all separated. That is, you can use the data model for whatever you want, if you change the views it won’t break the data model and vice versa. This makes your app much simpler to understand and easier to update later down the road.

How this Example Works

Below I’ve created a very simple, but hopefully effective example which demonstrates using Observers and Observables to automatically update views upon changes in data. The Activity ObserverExample is registered as on Observer on the data model Score and acts as the controller in our application. When the Score changes, ObserverExample is notified and the method update() fires, within that method the app fires another method setScore() which refreshes the score to the new values.

Note this method of “wiring” up an app is called the Model-View-Controller paradigm. As you can see from the code, it simplifies the app by ensuring no parts of the app are tightly coupled to the other, this is important for creating simplistic and extensible code.

Take a look at the code below, also be sure to grab the .ZIP of the Eclipse Project and try it for yourself!

The main Activity ObserverExample.java

package com.ootpapps.observerexample; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import java.util.Observable; import java.util.Observer; public class ObserverExample extends Activity implements Observer { // Button variables for our 4 UI buttons which perform touchdowns and field // goals for the Home and Away teams. Button button1; Button button2; Button button3; Button button4; // Score variable for the Score object we'll be using to track and report // out both teams' scores. Score score; // TextView variables for our scoreboard display, #2 is Home score, #4 is // Vistors score. TextView textView2; TextView textView4; // Set some constants to make the code more human-readable. private static final int HOME = 1; private static final int VISITOR = 2; @Override public void update(Observable observable, Object data) { // Set the score to the current score each time there's a change. // Creating a helper method is a nice way to simplify the code here. setScore(); } // Grabs the score for the Home team and Away team and displays them in // the correct TextView's of the scoreboard. private void setScore() { textView2.setText(String.valueOf(score.getScore(HOME))); textView4.setText(String.valueOf(score.getScore(VISITOR))); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create a new score object, this will contain all of the logic to // track and report out both team's scores. score = new Score(); // Score is an Observable class, therefore, we can use the method // addObserver to add this Activity as an Observer using it's local // context. This is the magic sauce that enables instant updates // whenever the Score changes. When the Score reports a change, the // update() method runs in this Activity. score.addObserver(this); // This code ties in our TextView variables to the actual ID's of the UI // elements themselves. textView2 = (TextView) findViewById(R.id.textView2); textView4 = (TextView) findViewById(R.id.textView4); // Ties in the ID of the button to our variable set above. button1 = (Button) findViewById(R.id.button1); // Creates a new instance of OnClickListener which fires the onClick() // method upon, well, a click. button1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Update the score by running the touchDown() method for the // Home team. score.touchDown(HOME); } }); // Same idea as above repeated for buttons 2 - 4. button2 = (Button) findViewById(R.id.button2); button2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // Update the score by running the fieldGoal() method for the // Home team. score.fieldGoal(HOME); } }); button3 = (Button) findViewById(R.id.button3); button3.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { score.touchDown(VISITOR); } }); button4 = (Button) findViewById(R.id.button4); button4.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { score.fieldGoal(VISITOR); } }); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 package com . ootpapps . observerexample ; import android . app . Activity ; import android . os . Bundle ; import android . view . View ; import android . view . View . OnClickListener ; import android . widget . Button ; import android . widget . TextView ; import java . util . Observable ; import java . util . Observer ; public class ObserverExample extends Activity implements Observer { // Button variables for our 4 UI buttons which perform touchdowns and field // goals for the Home and Away teams. Button button1 ; Button button2 ; Button button3 ; Button button4 ; // Score variable for the Score object we'll be using to track and report // out both teams' scores. Score score ; // TextView variables for our scoreboard display, #2 is Home score, #4 is // Vistors score. TextView textView2 ; TextView textView4 ; // Set some constants to make the code more human-readable. private static final int HOME = 1 ; private static final int VISITOR = 2 ; @ Override public void update ( Observable observable , Object data ) { // Set the score to the current score each time there's a change. // Creating a helper method is a nice way to simplify the code here. setScore ( ) ; } // Grabs the score for the Home team and Away team and displays them in // the correct TextView's of the scoreboard. private void setScore ( ) { textView2 . setText ( String . valueOf ( score . getScore ( HOME ) ) ) ; textView4 . setText ( String . valueOf ( score . getScore ( VISITOR ) ) ) ; } @ Override protected void onCreate ( Bundle savedInstanceState ) { super . onCreate ( savedInstanceState ) ; setContentView ( R . layout . activity_main ) ; // Create a new score object, this will contain all of the logic to // track and report out both team's scores. score = new Score ( ) ; // Score is an Observable class, therefore, we can use the method // addObserver to add this Activity as an Observer using it's local // context. This is the magic sauce that enables instant updates // whenever the Score changes. When the Score reports a change, the // update() method runs in this Activity. score . addObserver ( this ) ; // This code ties in our TextView variables to the actual ID's of the UI // elements themselves. textView2 = ( TextView ) findViewById ( R . id . textView2 ) ; textView4 = ( TextView ) findViewById ( R . id . textView4 ) ; // Ties in the ID of the button to our variable set above. button1 = ( Button ) findViewById ( R . id . button1 ) ; // Creates a new instance of OnClickListener which fires the onClick() // method upon, well, a click. button1 . setOnClickListener ( new OnClickListener ( ) { @ Override public void onClick ( View v ) { // Update the score by running the touchDown() method for the // Home team. score . touchDown ( HOME ) ; } } ) ; // Same idea as above repeated for buttons 2 - 4. button2 = ( Button ) findViewById ( R . id . button2 ) ; button2 . setOnClickListener ( new OnClickListener ( ) { @ Override public void onClick ( View v ) { // Update the score by running the fieldGoal() method for the // Home team. score . fieldGoal ( HOME ) ; } } ) ; button3 = ( Button ) findViewById ( R . id . button3 ) ; button3 . setOnClickListener ( new OnClickListener ( ) { @ Override public void onClick ( View v ) { score . touchDown ( VISITOR ) ; } } ) ; button4 = ( Button ) findViewById ( R . id . button4 ) ; button4 . setOnClickListener ( new OnClickListener ( ) { @ Override public void onClick ( View v ) { score . fieldGoal ( VISITOR ) ; } } ) ; } }

The data model Score.java

package com.ootpapps.observerexample; import java.util.Observable; // In order to report changes to any interested objects, such as Activities, we // need to extend the Observable class. This enables other objects to register // themselves as an Observer by using the addObserver() method. public class Score extends Observable { // Initialize the score, scores always start at zero. private int home_score = 0; private int visitor_score = 0; public Score() { } // Grant 1 point to the team based on the variable fed in HOME == 1 // and VISITOR == 2; public void fieldGoal(int team) { switch (team) { case 1: this.home_score = this.home_score + 1; break; case 2: this.visitor_score = this.visitor_score + 1; break; } triggerObservers(); } // Returns the score of the Home or Visitor teams depending on which // variable is fed. public int getScore(int team) { switch (team) { case 1: return home_score; case 2: return visitor_score; } return 0; } // Adds six points to score of the team fed in as a variable, HOME == 1 and // VISITOR == 2 public void touchDown(int team) { switch (team) { case 1: this.home_score = this.home_score + 6; break; case 2: this.visitor_score = this.visitor_score + 6; break; } triggerObservers(); } // Create a method to update the Observerable's flag to true for changes and // notify the observers to check for a change. These are also a part of the // secret sauce that makes Observers and Observables communicate // predictably. private void triggerObservers() { setChanged(); notifyObservers(); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package com . ootpapps . observerexample ; import java . util . Observable ; // In order to report changes to any interested objects, such as Activities, we // need to extend the Observable class. This enables other objects to register // themselves as an Observer by using the addObserver() method. public class Score extends Observable { // Initialize the score, scores always start at zero. private int home_score = 0 ; private int visitor_score = 0 ; public Score ( ) { } // Grant 1 point to the team based on the variable fed in HOME == 1 // and VISITOR == 2; public void fieldGoal ( int team ) { switch ( team ) { case 1 : this . home_score = this . home_score + 1 ; break ; case 2 : this . visitor_score = this . visitor_score + 1 ; break ; } triggerObservers ( ) ; } // Returns the score of the Home or Visitor teams depending on which // variable is fed. public int getScore ( int team ) { switch ( team ) { case 1 : return home_score ; case 2 : return visitor_score ; } return 0 ; } // Adds six points to score of the team fed in as a variable, HOME == 1 and // VISITOR == 2 public void touchDown ( int team ) { switch ( team ) { case 1 : this . home_score = this . home_score + 6 ; break ; case 2 : this . visitor_score = this . visitor_score + 6 ; break ; } triggerObservers ( ) ; } // Create a method to update the Observerable's flag to true for changes and // notify the observers to check for a change. These are also a part of the // secret sauce that makes Observers and Observables communicate // predictably. private void triggerObservers ( ) { setChanged ( ) ; notifyObservers ( ) ; } }

Layout XML file activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Menu1" > <LinearLayout android:layout_width="match_parent" android:layout_height="48dp" android:layout_marginTop="80dp" android:background="#336699" android:gravity="center_vertical" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_weight="25" android:text="HOME" android:textColor="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="8dp" android:layout_weight="25" android:freezesText="false" android:text="-" android:textColor="#ffffff" android:textColorHint="#FFFFFF" android:textColorLink="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="25" android:text="VISITORS" android:textColor="#cccccc" android:textSize="22sp" android:textStyle="bold" /> <TextView android:id="@+id/textView4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="25" android:freezesText="false" android:text="-" android:textColor="#cccccc" android:textColorHint="#FFFFFF" android:textColorLink="#FFFFFF" android:textSize="22sp" android:textStyle="bold" /> </LinearLayout> <TextView android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginLeft="8dp" android:text="HOME TEAM" android:textSize="22sp" android:textStyle="bold" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/button2" android:layout_alignBottom="@+id/button2" android:layout_alignParentLeft="true" android:layout_margin="8dp" android:text="TOUCHDOWN" android:textSize="12sp" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView5" android:layout_toRightOf="@+id/button1" android:text="FIELD GOAL" android:textSize="12sp" /> <TextView android:id="@+id/textView6" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/button1" android:layout_marginLeft="8dp" android:layout_marginTop="48dp" android:text="VISITING TEAM" android:textSize="22sp" android:textStyle="bold" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/button3" android:layout_alignBottom="@+id/button3" android:layout_toRightOf="@+id/button3" android:text="FIELD GOAL" android:textSize="12sp" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/textView6" android:layout_margin="8dp" android:text="TOUCHDOWN" android:textSize="12sp" /> </RelativeLayout> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 < RelativeLayout xmlns : android = "http://schemas.android.com/apk/res/android" xmlns : tools = "http://schemas.android.com/tools" android : layout_width = "match_parent" android : layout_height = "match_parent" tools : context = ".Menu1" > < LinearLayout android : layout_width = "match_parent" android : layout_height = "48dp" android : layout_marginTop = "80dp" android : background = "#336699" android : gravity = "center_vertical" > < TextView android : id = "@+id/textView1" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_marginLeft = "8dp" android : layout_weight = "25" android : text = "HOME" android : textColor = "#FFFFFF" android : textSize = "22sp" android : textStyle = "bold" / > < TextView android : id = "@+id/textView2" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_marginRight = "8dp" android : layout_weight = "25" android : freezesText = "false" android : text = "-" android : textColor = "#ffffff" android : textColorHint = "#FFFFFF" android : textColorLink = "#FFFFFF" android : textSize = "22sp" android : textStyle = "bold" / > < TextView android : id = "@+id/textView3" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_weight = "25" android : text = "VISITORS" android : textColor = "#cccccc" android : textSize = "22sp" android : textStyle = "bold" / > < TextView android : id = "@+id/textView4" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_weight = "25" android : freezesText = "false" android : text = "-" android : textColor = "#cccccc" android : textColorHint = "#FFFFFF" android : textColorLink = "#FFFFFF" android : textSize = "22sp" android : textStyle = "bold" / > < / LinearLayout > < TextView android : id = "@+id/textView5" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_alignParentLeft = "true" android : layout_centerVertical = "true" android : layout_marginLeft = "8dp" android : text = "HOME TEAM" android : textSize = "22sp" android : textStyle = "bold" / > < Button android : id = "@+id/button1" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_alignBaseline = "@+id/button2" android : layout_alignBottom = "@+id/button2" android : layout_alignParentLeft = "true" android : layout_margin = "8dp" android : text = "TOUCHDOWN" android : textSize = "12sp" / > < Button android : id = "@+id/button2" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_below = "@+id/textView5" android : layout_toRightOf = "@+id/button1" android : text = "FIELD GOAL" android : textSize = "12sp" / > < TextView android : id = "@+id/textView6" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_alignParentLeft = "true" android : layout_below = "@+id/button1" android : layout_marginLeft = "8dp" android : layout_marginTop = "48dp" android : text = "VISITING TEAM" android : textSize = "22sp" android : textStyle = "bold" / > < Button android : id = "@+id/button4" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_alignBaseline = "@+id/button3" android : layout_alignBottom = "@+id/button3" android : layout_toRightOf = "@+id/button3" android : text = "FIELD GOAL" android : textSize = "12sp" / > < Button android : id = "@+id/button3" android : layout_width = "wrap_content" android : layout_height = "wrap_content" android : layout_alignParentLeft = "true" android : layout_below = "@+id/textView6" android : layout_margin = "8dp" android : text = "TOUCHDOWN" android : textSize = "12sp" / > < / RelativeLayout >

Download the .ZIP of the Eclipse Project here! Enjoy and please leave me feedback in the comments, I appreciate it!